proj-9.8.1/CITATION000664 001750 001750 00000001010 15166171715 013600 0ustar00eveneven000000 000000 To cite PROJ in publications use: PROJ contributors (2026). PROJ coordinate transformation software library. Open Source Geospatial Foundation. URL https://proj.org/. DOI: 10.5281/zenodo.5884394 A BibTeX entry for LaTeX users is .. code-block:: latex @Manual{, title = {{PROJ} coordinate transformation software library}, author = {{PROJ contributors}}, organization = {Open Source Geospatial Foundation}, year = {2026}, url = {https://proj.org/}, doi = {10.5281/zenodo.5884394}, } proj-9.8.1/CMakeLists.txt000664 001750 001750 00000044502 15166171715 015220 0ustar00eveneven000000 000000 ################################################################################ # # This file is part of CMake configuration for PROJ library (inspired from SOCI # CMake, Copyright (C) 2009-2010 Mateusz Loskot ) # # Copyright (C) 2011 Nicolas David # Distributed under the MIT license # ################################################################################ # General settings ################################################################################ cmake_minimum_required(VERSION 3.16) project(PROJ DESCRIPTION "PROJ coordinate transformation software library" LANGUAGES C CXX ) # Only interpret if() arguments as variables or keywords when unquoted cmake_policy(SET CMP0054 NEW) include(CheckCXXCompilerFlag) # Set C++ version # Make CMAKE_CXX_STANDARD available as cache option overridable by user set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard version to use (default is 17)") message(STATUS "Requiring C++${CMAKE_CXX_STANDARD}") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) message(STATUS "Requiring C++${CMAKE_CXX_STANDARD} - done") # Set C99 version # Make CMAKE_C_STANDARD available as cache option overridable by user set(CMAKE_C_STANDARD 99 CACHE STRING "C standard version to use (default is 99)") message(STATUS "Requiring C${CMAKE_C_STANDARD}") set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) message(STATUS "Requiring C${CMAKE_C_STANDARD} - done") # Set global -fvisibility=hidden set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) # Set warnings as variables, then store as cache options set(PROJ_common_WARN_FLAGS # common only to GNU/Clang C/C++ -Wall -Wdate-time -Werror=format-security -Werror=vla -Wextra -Wformat -Wimplicit-fallthrough -Wmissing-declarations -Wshadow -Wswitch -Wunused-parameter ) if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") set(PROJ_common_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Wduplicated-cond -Wduplicated-branches -Wlogical-op ) set(PROJ_C_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Wmissing-prototypes ) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8) set(PROJ_CXX_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Wextra-semi) endif() set(PROJ_CXX_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Weffc++ # -Wold-style-cast -Woverloaded-virtual -Wzero-as-null-pointer-constant ) elseif("${CMAKE_C_COMPILER_ID}" MATCHES "Clang") set(PROJ_common_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Wcomma -Wdeprecated -Wdocumentation -Wno-documentation-deprecated-sync -Wfloat-conversion -Wlogical-op-parentheses ) # Not sure about the minimum version, but clang 12 complains about \file, @cond Doxygen_Suppress, etc. if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_GREATER_EQUAL 18.0.0) set(PROJ_common_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Wdocumentation-unknown-command) endif() set(PROJ_C_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Wmissing-prototypes -Wc11-extensions ) set(PROJ_CXX_WARN_FLAGS ${PROJ_common_WARN_FLAGS} -Weffc++ -Wextra-semi # -Wold-style-cast -Woverloaded-virtual -Wshorten-64-to-32 -Wunused-private-field -Wzero-as-null-pointer-constant -Wweak-vtables ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") add_definitions(/D_CRT_SECURE_NO_WARNINGS) # Eliminate deprecation warnings set(PROJ_C_WARN_FLAGS /W4 /wd4706 # Suppress warning about assignment within conditional expression /wd4996 # Suppress warning about sprintf, etc., being unsafe ) if("$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "arm64") # Suppress an inaccurate warning when compiling for an MSVC/ARM64 platform # It incorrectly assumes a division by zero is possible despite a check set(PROJ_C_WARN_FLAGS ${PROJ_C_WARN_FLAGS} /wd4723) endif() set(PROJ_CXX_WARN_FLAGS /EHsc ${PROJ_C_WARN_FLAGS}) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel") if(MSVC) set(PROJ_C_WARN_FLAGS /Wall) set(PROJ_CXX_WARN_FLAGS /Wall) else() set(PROJ_C_WARN_FLAGS -Wall) set(PROJ_CXX_WARN_FLAGS -Wall) endif() endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") # Intel CXX compiler based on clang defaults to -ffast-math, which # breaks std::isinf(), std::isnan(), etc. set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -fno-fast-math) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -fno-fast-math) endif () # Add other supported compiler flags if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) check_cxx_compiler_flag(-Wdeprecated-copy-dtor HAVE_DEP_COPY_DTOR_FLAG) if(HAVE_DEP_COPY_DTOR_FLAG) set(PROJ_CXX_WARN_FLAGS ${PROJ_CXX_WARN_FLAGS} -Wdeprecated-copy-dtor) endif() endif() set(PROJ_C_WARN_FLAGS "${PROJ_C_WARN_FLAGS}" CACHE STRING "C flags used to compile PROJ targets") set(PROJ_CXX_WARN_FLAGS "${PROJ_CXX_WARN_FLAGS}" CACHE STRING "C++ flags used to compile PROJ targets") ################################################################################ # PROJ CMake modules ################################################################################ # Path to additional CMake modules set(CMAKE_MODULE_PATH ${PROJ_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) include(ProjUtilities) message(STATUS "Configuring PROJ:") ################################################################################ #PROJ version information ################################################################################ include(ProjVersion) proj_version(MAJOR 9 MINOR 8 PATCH 1) set(PROJ_SOVERSION 25) set(PROJ_BUILD_VERSION "${PROJ_SOVERSION}.${PROJ_VERSION}") ################################################################################ # Build features and variants ################################################################################ include(Ccache) include(ProjConfig) include(ProjMac) include(policies) ############################################## ### SWITCH BETWEEN STATIC OR SHARED LIBRARY### ############################################## # default config is shared option(BUILD_SHARED_LIBS "Build PROJ library shared." ON) ################################################################################ # Check for nlohmann_json ################################################################################ set(NLOHMANN_JSON_ORIGIN "auto" CACHE STRING "nlohmann/json origin. The default auto will try to use external \ nlohmann/json if possible") set_property(CACHE NLOHMANN_JSON_ORIGIN PROPERTY STRINGS auto internal external) # Probably not the strictest minimum, but known to work with it set(MIN_NLOHMANN_JSON_VERSION 3.7.0) if(NLOHMANN_JSON_ORIGIN STREQUAL "external") find_package(nlohmann_json REQUIRED) set(NLOHMANN_JSON "external") elseif(NLOHMANN_JSON_ORIGIN STREQUAL "internal") set(NLOHMANN_JSON "internal") else() find_package(nlohmann_json QUIET) if(nlohmann_json_FOUND) set(NLOHMANN_JSON "external") else() set(NLOHMANN_JSON "internal") endif() endif() if(NLOHMANN_JSON STREQUAL "external") # Check minimum version if(nlohmann_json_VERSION VERSION_LESS MIN_NLOHMANN_JSON_VERSION) message(STATUS "external nlohmann/json version ${nlohmann_json_VERSION} " "is older than minimum requirement ${MIN_NLOHMANN_JSON_VERSION}") set(NLOHMANN_JSON "internal") else() message(STATUS "found nlohmann/json version ${nlohmann_json_VERSION}") endif() endif() message(STATUS "nlohmann/json: ${NLOHMANN_JSON}") ################################################################################ # Check for sqlite3 ################################################################################ find_program(EXE_SQLITE3 sqlite3) if(NOT EXE_SQLITE3) message(SEND_ERROR "sqlite3 binary not found!") endif() # Deprecated variables since PROJ 9.4.0 if(DEFINED SQLITE3_INCLUDE_DIR) message(DEPRECATION "Use SQLite3_INCLUDE_DIR instead of SQLITE3_INCLUDE_DIR") set(SQLite3_INCLUDE_DIR ${SQLITE3_INCLUDE_DIR}) endif() if(DEFINED SQLITE3_LIBRARY) message(DEPRECATION "Use SQLite3_LIBRARY instead of SQLITE3_LIBRARY") set(SQLite3_LIBRARY ${SQLITE3_LIBRARY}) endif() find_package(SQLite3 REQUIRED) if(NOT TARGET SQLite3::SQLite3) # CMake < 4.3 set_target_properties(SQLite::SQLite3 PROPERTIES IMPORTED_GLOBAL TRUE) add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) endif() # Would build and run with older versions, but with horrible performance # See https://github.com/OSGeo/PROJ/issues/1718 if(SQLite3_VERSION VERSION_LESS "3.11") message(SEND_ERROR "SQLite3 >= 3.11 required!") endif() ################################################################################ # Check for libtiff ################################################################################ option(ENABLE_TIFF "Enable TIFF support to read some grids" ON) mark_as_advanced(ENABLE_TIFF) set(TIFF_ENABLED FALSE) if(ENABLE_TIFF) find_package(TIFF REQUIRED) if(TIFF_FOUND) set(TIFF_ENABLED TRUE) else() message(SEND_ERROR "libtiff dependency not found! Use ENABLE_TIFF=OFF to force it off") endif() else() message(WARNING "TIFF support is not enabled and will result in the inability to read " "some grids") endif() ################################################################################ # Check for curl ################################################################################ option(ENABLE_CURL "Enable Curl support" ON) set(CURL_ENABLED FALSE) if(ENABLE_CURL) find_package(CURL REQUIRED) if(CURL_FOUND) set(CURL_ENABLED TRUE) # Curl SSL options are described in # https://curl.se/libcurl/c/CURLOPT_SSL_OPTIONS.html #set(CURLSSLOPT_NO_REVOKE 2) #set(SSL_OPTIONS ${CURLSSLOPT_NO_REVOKE}) #add_compile_definitions(SSL_OPTIONS=${SSL_OPTIONS}) else() message(SEND_ERROR "curl dependency not found!") endif() endif() ################################################################################ # Check for Emscripten fetch ################################################################################ option(ENABLE_EMSCRIPTEN_FETCH "Enable Emscripten fetch support" OFF) set(EMSCRIPTEN_FETCH_ENABLED FALSE) if(ENABLE_EMSCRIPTEN_FETCH) set(EMSCRIPTEN_FETCH_ENABLED TRUE) endif() ################################################################################ option(EMBED_PROJ_DATA_PATH "Whether the PROJ_DATA_PATH should be embedded" ON) if(DEFINED PROJ_LIB_ENV_VAR_TRIED_LAST) set(PROJ_DATA_ENV_VAR_TRIED_LAST ${PROJ_LIB_ENV_VAR_TRIED_LAST}) message(WARNING "PROJ_LIB_ENV_VAR_TRIED_LAST option has been renamed to PROJ_DATA_ENV_VAR_TRIED_LAST. PROJ_LIB_ENV_VAR_TRIED_LAST is still working for now, but may be completely replaced by PROJ_DATA_ENV_VAR_TRIED_LAST in a future release") else() option(PROJ_DATA_ENV_VAR_TRIED_LAST "Whether the PROJ_DATA environment variable should be tried after the hardcoded location" OFF) endif() if(PROJ_DATA_ENV_VAR_TRIED_LAST) add_definitions(-DPROJ_DATA_ENV_VAR_TRIED_LAST) endif() ################################################################################ # threading configuration ################################################################################ set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads) if(Threads_FOUND AND CMAKE_USE_PTHREADS_INIT) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}") endif() # Set a default build type for single-configuration cmake generators if # no build type is set. if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() if(MSVC OR CMAKE_CONFIGURATION_TYPES) # For multi-config systems and for Visual Studio, the debug version of # the library has _d appended. set(CMAKE_DEBUG_POSTFIX _d) endif() # Put the libraries and binaries that get built into directories at the # top of the build tree rather than in hard-to-find leaf # directories. This simplifies manual testing and the use of the build # tree rather than installed PROJ libraries. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJ_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJ_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJ_BINARY_DIR}/bin) link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) ################################################################################ # Installation ################################################################################ include(ProjInstallPath) # By default GNUInstallDirs will use the upper case project name # for CMAKE_INSTALL_DOCDIR, resulting in something like share/doc/PROJ # instead of share/doc/proj which historically have been the path used # by the project. # Here force the use of a lower case project name and reset after # GNUInstallDirs has done its thing set(PROJECT_NAME_ORIGINAL "${PROJECT_NAME}") string(TOLOWER "${PROJECT_NAME}" PROJECT_NAME) include(GNUInstallDirs) set(PROJECT_NAME "${PROJECT_NAME_ORIGINAL}") set(PROJ_DATA_PATH "${CMAKE_INSTALL_FULL_DATADIR}/proj") ################################################################################ # Tests ################################################################################ option(BUILD_TESTING "Build the testing tree." ON) if(BUILD_TESTING) enable_testing() include(ProjTest) else() message(STATUS "Testing disabled") endif() ################################################################################ # Whether we should embed resources ################################################################################ include(CheckCSourceCompiles) function (is_sharp_embed_available res) if (NOT EMSCRIPTEN AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.21 AND ((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0) OR (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 19.0))) # CMAKE_C_STANDARD=23 only supported since CMake 3.21 set(TEST_SHARP_EMBED "static const unsigned char embedded[] = {\n#embed __FILE__\n};\nint main() { (void)embedded; return 0;}" ) set(CMAKE_C_STANDARD_BACKUP "${CMAKE_C_STANDARD}") set(CMAKE_C_STANDARD "23") check_c_source_compiles("${TEST_SHARP_EMBED}" _TEST_SHARP_EMBED) set(CMAKE_C_STANDARD "${CMAKE_C_STANDARD_BACKUP}") if (_TEST_SHARP_EMBED) set(${res} ON PARENT_SCOPE) else() set(${res} OFF PARENT_SCOPE) endif() else() set(${res} OFF PARENT_SCOPE) endif() endfunction() is_sharp_embed_available(IS_SHARP_EMBED_AVAILABLE_RES) if (NOT DEFINED BUILD_SHARED_LIBS) message(FATAL_ERROR "BUILD_SHARED_LIBS should be set") endif() if (NOT BUILD_SHARED_LIBS) set(DEFAULT_EMBED_RESOURCE_FILES ON) else() set(DEFAULT_EMBED_RESOURCE_FILES OFF) endif() option(EMBED_RESOURCE_FILES "Whether resource files (limited to proj.db) should be embedded into the PROJ library" ${DEFAULT_EMBED_RESOURCE_FILES}) option(USE_ONLY_EMBEDDED_RESOURCE_FILES "Whether embedded resource files (limited to proj.db) should be used (should nominally be used together with EMBED_RESOURCE_FILES=ON, otherwise this will result in non-functional builds)" OFF) if (USE_ONLY_EMBEDDED_RESOURCE_FILES AND NOT EMBED_RESOURCE_FILES) message(WARNING "USE_ONLY_EMBEDDED_RESOURCE_FILES=ON set but EMBED_RESOURCE_FILES=OFF: some drivers will lack required resource files") endif() ################################################################################ # Build configured components ################################################################################ set(PROJ_DICTIONARY world other.extra nad27 GL27 nad83 nad.lst CH ITRF2000 ITRF2008 ITRF2014 ITRF2020 ) include_directories(${PROJ_SOURCE_DIR}/src) add_subdirectory(data) add_subdirectory(include) add_subdirectory(src) add_subdirectory(man) add_subdirectory(cmake) if(BUILD_TESTING) add_subdirectory(test) endif() option(BUILD_EXAMPLES "Whether to build example programs" OFF) if(BUILD_EXAMPLES) add_subdirectory(examples) endif() add_subdirectory(scripts) set(docfiles COPYING NEWS.md AUTHORS.md) install(FILES ${docfiles} DESTINATION ${CMAKE_INSTALL_DOCDIR}) ################################################################################ # Build debug symbols ################################################################################ option(EXPORT_PDB "Export PDB file" OFF) if (WIN32 AND EXPORT_PDB) target_compile_options(proj PRIVATE /Zi /Zf) target_link_options(proj PRIVATE /DEBUG /OPT:REF /OPT:ICF) if(BUILD_SHARED_LIBS) set(pdbFile $) else() set(pdbDirFullPath ${CMAKE_BINARY_DIR}/lib) set_target_properties(proj PROPERTIES COMPILE_PDB_OUTPUT_DIRECTORY ${pdbDirFullPath} COMPILE_PDB_NAME proj) set(pdbFile ${pdbDirFullPath}/proj.pdb) endif() install(FILES ${pdbFile} DESTINATION lib) endif() ################################################################################ # pkg-config support ################################################################################ configure_proj_pc() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/proj.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") ################################################################################ # "make dist" workalike ################################################################################ set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") set(CPACK_SOURCE_PACKAGE_FILE_NAME "proj-${PROJ_VERSION}") set(CPACK_PACKAGE_VENDOR "OSGeo") set(CPACK_PACKAGE_VERSION_MAJOR ${PROJ_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJ_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJ_VERSION_PATCH}) set(CPACK_VERBATIM_VARIABLES TRUE) set(CPACK_SOURCE_IGNORE_FILES /\\..* # any file/directory starting with . /.*\\.yml /.*\\.gz /.*\\.zip /.*build.*/ \\.deps /autogen\\.sh /autom4te\\.cache /CODE_OF_CONDUCT.md /CONTRIBUTING.md /disabled_workflows/ /Dockerfile /docs/ /Doxyfile /HOWTO-RELEASE.md /m4/lt* /m4/libtool* /media/ /schemas/ /test/fuzzers/ /test/gigs/.*gie\\.failing /test/postinstall/ /travis/ ${PROJECT_BINARY_DIR} ) include(CPack) get_property(_is_multi_config_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(NOT _is_multi_config_generator) add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source ) message(STATUS "PROJ: Configured 'dist' target") endif() configure_file(cmake/uninstall.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/proj_uninstall.cmake @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/proj_uninstall.cmake) message(STATUS "EMBED_RESOURCE_FILES=${EMBED_RESOURCE_FILES}") proj-9.8.1/include/000775 001750 001750 00000000000 15166171735 014100 5ustar00eveneven000000 000000 proj-9.8.1/include/CMakeLists.txt000664 001750 001750 00000000027 15166171715 016635 0ustar00eveneven000000 000000 add_subdirectory(proj) proj-9.8.1/include/proj/000775 001750 001750 00000000000 15166171735 015052 5ustar00eveneven000000 000000 proj-9.8.1/include/proj/coordinateoperation.hpp000664 001750 001750 00000254243 15166171715 021643 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef COORDINATEOPERATION_HH_INCLUDED #define COORDINATEOPERATION_HH_INCLUDED #include #include #include #include #include "common.hpp" #include "io.hpp" #include "metadata.hpp" #include "proj.h" NS_PROJ_START namespace crs { class CRS; using CRSPtr = std::shared_ptr; using CRSNNPtr = util::nn; class DerivedCRS; class ProjectedCRS; } // namespace crs namespace io { class JSONParser; } // namespace io namespace coordinates { class CoordinateMetadata; using CoordinateMetadataPtr = std::shared_ptr; using CoordinateMetadataNNPtr = util::nn; } // namespace coordinates /** osgeo.proj.operation namespace \brief Coordinate operations (relationship between any two coordinate reference systems). This covers Conversion, Transformation, PointMotionOperation or ConcatenatedOperation. */ namespace operation { // --------------------------------------------------------------------------- /** \brief Grid description */ struct GridDescription { std::string shortName; /**< Grid short filename */ std::string fullName; /**< Grid full path name (if found) */ std::string packageName; /**< Package name (or empty) */ std::string url; /**< Grid URL (if packageName is empty), or package URL (or empty) */ bool directDownload; /**< Whether url can be fetched directly. */ /** Whether the grid is released with an open license. */ bool openLicense; bool available; /**< Whether GRID is available. */ //! @cond Doxygen_Suppress bool operator<(const GridDescription &other) const { return shortName < other.shortName; } PROJ_DLL GridDescription(); PROJ_DLL ~GridDescription(); PROJ_DLL GridDescription(const GridDescription &); PROJ_DLL GridDescription(GridDescription &&) noexcept; //! @endcond }; // --------------------------------------------------------------------------- class CoordinateOperation; /** Shared pointer of CoordinateOperation */ using CoordinateOperationPtr = std::shared_ptr; /** Non-null shared pointer of CoordinateOperation */ using CoordinateOperationNNPtr = util::nn; // --------------------------------------------------------------------------- class CoordinateTransformer; /** Shared pointer of CoordinateTransformer */ using CoordinateTransformerPtr = std::unique_ptr; /** Non-null shared pointer of CoordinateTransformer */ using CoordinateTransformerNNPtr = util::nn; /** \brief Coordinate transformer. * * Performs coordinate transformation of coordinate tuplies. * * @since 9.3 */ class PROJ_GCC_DLL CoordinateTransformer { public: //! @cond Doxygen_Suppress PROJ_DLL ~CoordinateTransformer(); //! @endcond PROJ_DLL PJ_COORD transform(PJ_COORD coord); protected: PROJ_FRIEND(CoordinateOperation); PROJ_INTERNAL CoordinateTransformer(); PROJ_INTERNAL static CoordinateTransformerNNPtr create(const CoordinateOperationNNPtr &op, PJ_CONTEXT *ctx); private: PROJ_OPAQUE_PRIVATE_DATA INLINED_MAKE_UNIQUE CoordinateTransformer & operator=(const CoordinateTransformer &other) = delete; }; // --------------------------------------------------------------------------- class Transformation; /** Shared pointer of Transformation */ using TransformationPtr = std::shared_ptr; /** Non-null shared pointer of Transformation */ using TransformationNNPtr = util::nn; /** \brief Abstract class for a mathematical operation on coordinates. * * A mathematical operation: *
    *
  • on coordinates that transforms or converts them from one coordinate * reference system to another coordinate reference system
  • *
  • or that describes the change of coordinate values within one coordinate * reference system due to the motion of the point between one coordinate epoch * and another coordinate epoch.
  • *
* Many but not all coordinate operations (from CRS A to CRS B) also uniquely * define the inverse coordinate operation (from CRS B to CRS A). In some cases, * the coordinate operation method algorithm for the inverse coordinate * operation is the same as for the forward algorithm, but the signs of some * coordinate operation parameter values have to be reversed. In other cases, * different algorithms are required for the forward and inverse coordinate * operations, but the same coordinate operation parameter values are used. If * (some) entirely different parameter values are needed, a different coordinate * operation shall be defined. * * \remark Implements CoordinateOperation from \ref ISO_19111_2019 */ class PROJ_GCC_DLL CoordinateOperation : public common::ObjectUsage, public io::IPROJStringExportable, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~CoordinateOperation() override; //! @endcond PROJ_DLL const util::optional &operationVersion() const; PROJ_DLL const std::vector & coordinateOperationAccuracies() const; PROJ_DLL const crs::CRSPtr sourceCRS() const; PROJ_DLL const crs::CRSPtr targetCRS() const; PROJ_DLL const crs::CRSPtr &interpolationCRS() const; PROJ_DLL const util::optional & sourceCoordinateEpoch() const; PROJ_DLL const util::optional & targetCoordinateEpoch() const; PROJ_DLL CoordinateTransformerNNPtr coordinateTransformer(PJ_CONTEXT *ctx) const; /** \brief Return the inverse of the coordinate operation. * * \throw util::UnsupportedOperationException if inverse is not available */ PROJ_DLL virtual CoordinateOperationNNPtr inverse() const = 0; /** \brief Return grids needed by an operation. */ PROJ_DLL virtual std::set gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const = 0; PROJ_DLL bool isPROJInstantiable(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const; PROJ_DLL bool hasBallparkTransformation() const; PROJ_DLL bool requiresPerCoordinateInputTime() const; PROJ_DLL static const std::string OPERATION_VERSION_KEY; PROJ_DLL CoordinateOperationNNPtr normalizeForVisualization() const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_FOR_TEST CoordinateOperationNNPtr shallowClone() const; //! @endcond protected: PROJ_INTERNAL CoordinateOperation(); PROJ_INTERNAL CoordinateOperation(const CoordinateOperation &other); PROJ_FRIEND(crs::DerivedCRS); PROJ_FRIEND(io::AuthorityFactory); PROJ_FRIEND(CoordinateOperationFactory); PROJ_FRIEND(ConcatenatedOperation); PROJ_FRIEND(io::WKTParser); PROJ_FRIEND(io::JSONParser); PROJ_INTERNAL void setWeakSourceTargetCRS(std::weak_ptr sourceCRSIn, std::weak_ptr targetCRSIn); PROJ_INTERNAL void setCRSs(const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn); PROJ_INTERNAL void setCRSsUpdateInverse(const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn); PROJ_INTERNAL void setInterpolationCRS(const crs::CRSPtr &interpolationCRSIn); PROJ_INTERNAL void setCRSs(const CoordinateOperation *in, bool inverseSourceTarget); PROJ_INTERNAL void setAccuracies( const std::vector &accuracies); PROJ_INTERNAL void setHasBallparkTransformation(bool b); PROJ_INTERNAL void setRequiresPerCoordinateInputTime(bool b); PROJ_INTERNAL void setSourceCoordinateEpoch(const util::optional &epoch); PROJ_INTERNAL void setTargetCoordinateEpoch(const util::optional &epoch); PROJ_INTERNAL void setProperties(const util::PropertyMap &properties); // throw(InvalidValueTypeException) PROJ_INTERNAL virtual CoordinateOperationNNPtr _shallowClone() const = 0; private: PROJ_OPAQUE_PRIVATE_DATA CoordinateOperation &operator=(const CoordinateOperation &other) = delete; }; // --------------------------------------------------------------------------- /** \brief Abstract class modelling a parameter value (OperationParameter) * or group of parameters. * * \remark Implements GeneralOperationParameter from \ref ISO_19111_2019 */ class PROJ_GCC_DLL GeneralOperationParameter : public common::IdentifiedObject { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeneralOperationParameter() override; //! @endcond //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override = 0; //! @endcond protected: PROJ_INTERNAL GeneralOperationParameter(); PROJ_INTERNAL GeneralOperationParameter(const GeneralOperationParameter &other); private: PROJ_OPAQUE_PRIVATE_DATA GeneralOperationParameter & operator=(const GeneralOperationParameter &other) = delete; }; /** Shared pointer of GeneralOperationParameter */ using GeneralOperationParameterPtr = std::shared_ptr; /** Non-null shared pointer of GeneralOperationParameter */ using GeneralOperationParameterNNPtr = util::nn; // --------------------------------------------------------------------------- class OperationParameter; /** Shared pointer of OperationParameter */ using OperationParameterPtr = std::shared_ptr; /** Non-null shared pointer of OperationParameter */ using OperationParameterNNPtr = util::nn; /** \brief The definition of a parameter used by a coordinate operation method. * * Most parameter values are numeric, but other types of parameter values are * possible. * * \remark Implements OperationParameter from \ref ISO_19111_2019 */ class PROJ_GCC_DLL OperationParameter final : public GeneralOperationParameter { public: //! @cond Doxygen_Suppress PROJ_DLL ~OperationParameter() override; //! @endcond //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond // non-standard PROJ_DLL static OperationParameterNNPtr create(const util::PropertyMap &properties); PROJ_DLL int getEPSGCode() PROJ_PURE_DECL; PROJ_DLL static const char *getNameForEPSGCode(int epsg_code) noexcept; protected: PROJ_INTERNAL OperationParameter(); PROJ_INTERNAL OperationParameter(const OperationParameter &other); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA OperationParameter &operator=(const OperationParameter &other) = delete; // cppcheck-suppress functionStatic PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) }; // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct MethodMapping; //! @endcond /** \brief Abstract class modelling a parameter value (OperationParameterValue) * or group of parameter values. * * \remark Implements GeneralParameterValue from \ref ISO_19111_2019 */ class PROJ_GCC_DLL GeneralParameterValue : public util::BaseObject, public io::IWKTExportable, public io::IJSONExportable, public util::IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeneralParameterValue() override; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override = 0; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override = 0; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override = 0; //! @endcond protected: //! @cond Doxygen_Suppress PROJ_INTERNAL GeneralParameterValue(); PROJ_INTERNAL GeneralParameterValue(const GeneralParameterValue &other); friend class Conversion; friend class SingleOperation; friend class PointMotionOperation; PROJ_INTERNAL virtual void _exportToWKT(io::WKTFormatter *formatter, const MethodMapping *mapping) const = 0; // throw(io::FormattingException) //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA GeneralParameterValue & operator=(const GeneralParameterValue &other) = delete; }; /** Shared pointer of GeneralParameterValue */ using GeneralParameterValuePtr = std::shared_ptr; /** Non-null shared pointer of GeneralParameterValue */ using GeneralParameterValueNNPtr = util::nn; // --------------------------------------------------------------------------- class ParameterValue; /** Shared pointer of ParameterValue */ using ParameterValuePtr = std::shared_ptr; /** Non-null shared pointer of ParameterValue */ using ParameterValueNNPtr = util::nn; /** \brief The value of the coordinate operation parameter. * * Most parameter values are numeric, but other types of parameter values are * possible. * * \remark Implements ParameterValue from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ParameterValue final : public util::BaseObject, public io::IWKTExportable, public util::IComparable { public: /** Type of the value. */ enum class Type { /** Measure (i.e. value with a unit) */ MEASURE, /** String */ STRING, /** Integer */ INTEGER, /** Boolean */ BOOLEAN, /** Filename */ FILENAME }; //! @cond Doxygen_Suppress PROJ_DLL ~ParameterValue() override; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond PROJ_DLL static ParameterValueNNPtr create(const common::Measure &measureIn); PROJ_DLL static ParameterValueNNPtr create(const char *stringValueIn); PROJ_DLL static ParameterValueNNPtr create(const std::string &stringValueIn); PROJ_DLL static ParameterValueNNPtr create(int integerValueIn); PROJ_DLL static ParameterValueNNPtr create(bool booleanValueIn); PROJ_DLL static ParameterValueNNPtr createFilename(const std::string &stringValueIn); PROJ_DLL const Type &type() PROJ_PURE_DECL; PROJ_DLL const common::Measure &value() PROJ_PURE_DECL; PROJ_DLL const std::string &stringValue() PROJ_PURE_DECL; PROJ_DLL const std::string &valueFile() PROJ_PURE_DECL; PROJ_DLL int integerValue() PROJ_PURE_DECL; PROJ_DLL bool booleanValue() PROJ_PURE_DECL; //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL explicit ParameterValue(const common::Measure &measureIn); PROJ_INTERNAL explicit ParameterValue(const std::string &stringValueIn, Type typeIn); PROJ_INTERNAL explicit ParameterValue(int integerValueIn); PROJ_INTERNAL explicit ParameterValue(bool booleanValueIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA ParameterValue &operator=(const ParameterValue &other) = delete; }; // --------------------------------------------------------------------------- class OperationParameterValue; /** Shared pointer of OperationParameterValue */ using OperationParameterValuePtr = std::shared_ptr; /** Non-null shared pointer of OperationParameterValue */ using OperationParameterValueNNPtr = util::nn; /** \brief A parameter value, ordered sequence of values, or reference to a * file of parameter values. * * This combines a OperationParameter with the corresponding ParameterValue. * * \remark Implements OperationParameterValue from \ref ISO_19111_2019 */ class PROJ_GCC_DLL OperationParameterValue final : public GeneralParameterValue { public: //! @cond Doxygen_Suppress PROJ_DLL ~OperationParameterValue() override; //! @endcond PROJ_DLL const OperationParameterNNPtr ¶meter() PROJ_PURE_DECL; PROJ_DLL const ParameterValueNNPtr ¶meterValue() PROJ_PURE_DECL; PROJ_DLL static OperationParameterValueNNPtr create(const OperationParameterNNPtr ¶meterIn, const ParameterValueNNPtr &valueIn); PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL static bool convertFromAbridged(const std::string ¶mName, double &val, const common::UnitOfMeasure *&unit, int ¶mEPSGCode); PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL OperationParameterValue(const OperationParameterNNPtr ¶meterIn, const ParameterValueNNPtr &valueIn); PROJ_INTERNAL OperationParameterValue(const OperationParameterValue &other); INLINED_MAKE_SHARED PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter, const MethodMapping *mapping) const override; // throw(io::FormattingException) private: PROJ_OPAQUE_PRIVATE_DATA OperationParameterValue & operator=(const OperationParameterValue &other) = delete; }; // --------------------------------------------------------------------------- class OperationMethod; /** Shared pointer of OperationMethod */ using OperationMethodPtr = std::shared_ptr; /** Non-null shared pointer of OperationMethod */ using OperationMethodNNPtr = util::nn; /** \brief The method (algorithm or procedure) used to perform the * coordinate operation. * * For a projection method, this contains the name of the projection method * and the name of the projection parameters. * * \remark Implements OperationMethod from \ref ISO_19111_2019 */ class PROJ_GCC_DLL OperationMethod : public common::IdentifiedObject, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~OperationMethod() override; //! @endcond PROJ_DLL const util::optional &formula() PROJ_PURE_DECL; PROJ_DLL const util::optional & formulaCitation() PROJ_PURE_DECL; PROJ_DLL const std::vector & parameters() PROJ_PURE_DECL; PROJ_DLL static OperationMethodNNPtr create(const util::PropertyMap &properties, const std::vector ¶meters); PROJ_DLL static OperationMethodNNPtr create(const util::PropertyMap &properties, const std::vector ¶meters); PROJ_DLL int getEPSGCode() PROJ_PURE_DECL; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL OperationMethod(); PROJ_INTERNAL OperationMethod(const OperationMethod &other); INLINED_MAKE_SHARED friend class Conversion; private: PROJ_OPAQUE_PRIVATE_DATA OperationMethod &operator=(const OperationMethod &other) = delete; }; // --------------------------------------------------------------------------- /** \brief Exception that can be thrown when an invalid operation is attempted * to be constructed. */ class PROJ_GCC_DLL InvalidOperation : public util::Exception { public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit InvalidOperation(const char *message); PROJ_INTERNAL explicit InvalidOperation(const std::string &message); PROJ_DLL InvalidOperation(const InvalidOperation &other); PROJ_DLL ~InvalidOperation() override; //! @endcond }; // --------------------------------------------------------------------------- class SingleOperation; /** Shared pointer of SingleOperation */ using SingleOperationPtr = std::shared_ptr; /** Non-null shared pointer of SingleOperation */ using SingleOperationNNPtr = util::nn; /** \brief A single (not concatenated) coordinate operation * (CoordinateOperation) * * \remark Implements SingleOperation from \ref ISO_19111_2019 */ class PROJ_GCC_DLL SingleOperation : virtual public CoordinateOperation { public: //! @cond Doxygen_Suppress PROJ_DLL ~SingleOperation() override; //! @endcond PROJ_DLL const std::vector & parameterValues() PROJ_PURE_DECL; PROJ_DLL const OperationMethodNNPtr &method() PROJ_PURE_DECL; PROJ_DLL const ParameterValuePtr & parameterValue(const std::string ¶mName, int epsg_code = 0) const noexcept; PROJ_DLL const ParameterValuePtr & parameterValue(int epsg_code) const noexcept; PROJ_DLL const common::Measure & parameterValueMeasure(const std::string ¶mName, int epsg_code = 0) const noexcept; PROJ_DLL const common::Measure & parameterValueMeasure(int epsg_code) const noexcept; PROJ_DLL static SingleOperationNNPtr createPROJBased( const util::PropertyMap &properties, const std::string &PROJString, const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, const std::vector &accuracies = std::vector()); PROJ_DLL std::set gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const override; PROJ_DLL std::list validateParameters() const; PROJ_DLL TransformationNNPtr substitutePROJAlternativeGridNames( io::DatabaseContextNNPtr databaseContext) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_DLL double parameterValueNumeric( int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept; PROJ_INTERNAL double parameterValueNumeric( const char *param_name, const common::UnitOfMeasure &targetUnit) const noexcept; PROJ_INTERNAL double parameterValueNumericAsSI(int epsg_code) const noexcept; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL bool isLongitudeRotation() const; //! @endcond protected: PROJ_INTERNAL explicit SingleOperation( const OperationMethodNNPtr &methodIn); PROJ_INTERNAL SingleOperation(const SingleOperation &other); PROJ_INTERNAL void setParameterValues(const std::vector &values); PROJ_INTERNAL void exportTransformationToWKT(io::WKTFormatter *formatter) const; PROJ_INTERNAL bool exportToPROJStringGeneric(io::PROJStringFormatter *formatter) const; PROJ_INTERNAL bool _isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext, bool inOtherDirection) const; PROJ_INTERNAL static GeneralParameterValueNNPtr createOperationParameterValueFromInterpolationCRS(int methodEPSGCode, int crsEPSGCode); PROJ_INTERNAL static void exportToPROJStringChangeVerticalUnit(io::PROJStringFormatter *formatter, double convFactor); private: PROJ_OPAQUE_PRIVATE_DATA SingleOperation &operator=(const SingleOperation &other) = delete; }; // --------------------------------------------------------------------------- class Conversion; /** Shared pointer of Conversion */ using ConversionPtr = std::shared_ptr; /** Non-null shared pointer of Conversion */ using ConversionNNPtr = util::nn; /** \brief A mathematical operation on coordinates in which the parameter * values are defined rather than empirically derived. * * Application of the coordinate conversion introduces no error into output * coordinates. The best-known example of a coordinate conversion is a map * projection. For coordinate conversions the output coordinates are referenced * to the same datum as are the input coordinates. * * Coordinate conversions forming a component of a derived CRS have a source * crs::CRS and a target crs::CRS that are NOT specified through the source and * target * associations, but through associations from crs::DerivedCRS to * crs::SingleCRS. * * \remark Implements Conversion from \ref ISO_19111_2019 */ /*! \section projection_parameters Projection parameters \subsection colatitude_cone_axis Co-latitude of cone axis The rotation applied to spherical coordinates for the oblique projection, measured on the conformal sphere in the plane of the meridian of origin. EPSG:1036 \subsection center_latitude Latitude of natural origin/Center Latitude The latitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the latitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0). EPSG:8801 \subsection center_longitude Longitude of natural origin/Central Meridian The longitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the longitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0). Sometimes known as "central meridian (CM)". EPSG:8802 \subsection scale Scale Factor The factor by which the map grid is reduced or enlarged during the projection process, defined by its value at the natural origin. EPSG:8805 \subsection false_easting False Easting Since the natural origin may be at or near the centre of the projection and under normal coordinate circumstances would thus give rise to negative coordinates over parts of the mapped area, this origin is usually given false coordinates which are large enough to avoid this inconvenience. The False Easting, FE, is the value assigned to the abscissa (east or west) axis of the projection grid at the natural origin. EPSG:8806 \subsection false_northing False Northing Since the natural origin may be at or near the centre of the projection and under normal coordinate circumstances would thus give rise to negative coordinates over parts of the mapped area, this origin is usually given false coordinates which are large enough to avoid this inconvenience. The False Northing, FN, is the value assigned to the ordinate (north or south) axis of the projection grid at the natural origin. EPSG:8807 \subsection latitude_projection_centre Latitude of projection centre For an oblique projection, this is the latitude of the point at which the azimuth of the central line is defined. EPSG:8811 \subsection longitude_projection_centre Longitude of projection centre For an oblique projection, this is the longitude of the point at which the azimuth of the central line is defined. EPSG:8812 \subsection azimuth_initial_line Azimuth of initial line The azimuthal direction (north zero, east of north being positive) of the great circle which is the centre line of an oblique projection. The azimuth is given at the projection centre. EPSG:8813 \subsection angle_from_recitfied_to_skrew_grid Angle from Rectified to Skew Grid The angle at the natural origin of an oblique projection through which the natural coordinate reference system is rotated to make the projection north axis parallel with true north. EPSG:8814 \subsection scale_factor_initial_line Scale factor on initial line The factor by which the map grid is reduced or enlarged during the projection process, defined by its value at the projection center. EPSG:8815 \subsection easting_projection_centre Easting at projection centre The easting value assigned to the projection centre. EPSG:8816 \subsection northing_projection_centre Northing at projection centre The northing value assigned to the projection centre. EPSG:8817 \subsection latitude_pseudo_standard_parallel Latitude of pseudo standard parallel Latitude of the parallel on which the conic or cylindrical projection is based. This latitude is not geographic, but is defined on the conformal sphere AFTER its rotation to obtain the oblique aspect of the projection. EPSG:8818 \subsection scale_factor_pseudo_standard_parallel Scale factor on pseudo standard parallel The factor by which the map grid is reduced or enlarged during the projection process, defined by its value at the pseudo-standard parallel. EPSG:8819 \subsection latitude_false_origin Latitude of false origin The latitude of the point which is not the natural origin and at which grid coordinate values false easting and false northing are defined. EPSG:8821 \subsection longitude_false_origin Longitude of false origin The longitude of the point which is not the natural origin and at which grid coordinate values false easting and false northing are defined. EPSG:8822 \subsection latitude_first_std_parallel Latitude of 1st standard parallel For a conic projection with two standard parallels, this is the latitude of one of the parallels of intersection of the cone with the ellipsoid. It is normally but not necessarily that nearest to the pole. Scale is true along this parallel. EPSG:8823 \subsection latitude_second_std_parallel Latitude of 2nd standard parallel For a conic projection with two standard parallels, this is the latitude of one of the parallels at which the cone intersects with the ellipsoid. It is normally but not necessarily that nearest to the equator. Scale is true along this parallel. EPSG:8824 \subsection easting_false_origin Easting of false origin The easting value assigned to the false origin. EPSG:8826 \subsection northing_false_origin Northing of false origin The northing value assigned to the false origin. EPSG:8827 \subsection latitude_std_parallel Latitude of standard parallel For polar aspect azimuthal projections, the parallel on which the scale factor is defined to be unity. EPSG:8832 \subsection longitude_of_origin Longitude of origin For polar aspect azimuthal projections, the meridian along which the northing axis increments and also across which parallels of latitude increment towards the north pole. EPSG:8833 */ class PROJ_GCC_DLL Conversion : public SingleOperation { public: //! @cond Doxygen_Suppress PROJ_DLL ~Conversion() override; //! @endcond PROJ_DLL CoordinateOperationNNPtr inverse() const override; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond PROJ_DLL bool isUTM(int &zone, bool &north) const; PROJ_DLL ConversionNNPtr identify() const; PROJ_DLL static ConversionNNPtr create(const util::PropertyMap &properties, const OperationMethodNNPtr &methodIn, const std::vector &values); // throw InvalidOperation PROJ_DLL static ConversionNNPtr create(const util::PropertyMap &propertiesConversion, const util::PropertyMap &propertiesOperationMethod, const std::vector ¶meters, const std::vector &values); // throw InvalidOperation PROJ_DLL static ConversionNNPtr createUTM(const util::PropertyMap &properties, int zone, bool north); PROJ_DLL static ConversionNNPtr createTransverseMercator( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGaussSchreiberTransverseMercator( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createTransverseMercatorSouthOriented( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createTwoPointEquidistant(const util::PropertyMap &properties, const common::Angle &latitudeFirstPoint, const common::Angle &longitudeFirstPoint, const common::Angle &latitudeSecondPoint, const common::Angle &longitudeSeconPoint, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createTunisiaMappingGrid( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createTunisiaMiningGrid( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createAlbersEqualArea(const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin); PROJ_DLL static ConversionNNPtr createLambertConicConformal_1SP( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createLambertConicConformal_1SP_VariantB( const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Scale &scale, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin); PROJ_DLL static ConversionNNPtr createLambertConicConformal_2SP(const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin); PROJ_DLL static ConversionNNPtr createLambertConicConformal_2SP_Michigan( const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin, const common::Scale &ellipsoidScalingFactor); PROJ_DLL static ConversionNNPtr createLambertConicConformal_2SP_Belgium( const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin); PROJ_DLL static ConversionNNPtr createAzimuthalEquidistant(const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGuamProjection(const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createBonne(const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createLambertCylindricalEqualAreaSpherical( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createLambertCylindricalEqualArea( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createCassiniSoldner( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEquidistantConic(const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin); PROJ_DLL static ConversionNNPtr createEckertI(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEckertII(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEckertIII(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEckertIV(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEckertV(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEckertVI(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEquidistantCylindrical(const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createEquidistantCylindricalSpherical( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGall(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGoodeHomolosine(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createInterruptedGoodeHomolosine(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGeostationarySatelliteSweepX( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &height, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGeostationarySatelliteSweepY( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &height, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createGnomonic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createHotineObliqueMercatorVariantA( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeProjectionCentre, const common::Angle &azimuthInitialLine, const common::Angle &angleFromRectifiedToSkrewGrid, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createHotineObliqueMercatorVariantB( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeProjectionCentre, const common::Angle &azimuthInitialLine, const common::Angle &angleFromRectifiedToSkrewGrid, const common::Scale &scale, const common::Length &eastingProjectionCentre, const common::Length &northingProjectionCentre); PROJ_DLL static ConversionNNPtr createHotineObliqueMercatorTwoPointNaturalOrigin( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &latitudePoint1, const common::Angle &longitudePoint1, const common::Angle &latitudePoint2, const common::Angle &longitudePoint2, const common::Scale &scale, const common::Length &eastingProjectionCentre, const common::Length &northingProjectionCentre); PROJ_DLL static ConversionNNPtr createLabordeObliqueMercator(const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeProjectionCentre, const common::Angle &azimuthInitialLine, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createInternationalMapWorldPolyconic( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createKrovakNorthOriented( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeOfOrigin, const common::Angle &colatitudeConeAxis, const common::Angle &latitudePseudoStandardParallel, const common::Scale &scaleFactorPseudoStandardParallel, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createKrovak(const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeOfOrigin, const common::Angle &colatitudeConeAxis, const common::Angle &latitudePseudoStandardParallel, const common::Scale &scaleFactorPseudoStandardParallel, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createLambertAzimuthalEqualArea(const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createMillerCylindrical(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createMercatorVariantA( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createMercatorVariantB(const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createPopularVisualisationPseudoMercator( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createMercatorSpherical( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createMollweide(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createNewZealandMappingGrid( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createObliqueStereographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createOrthographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createLocalOrthographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Angle &azimuthInitialLine, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createAmericanPolyconic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createPolarStereographicVariantA( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createPolarStereographicVariantB( const util::PropertyMap &properties, const common::Angle &latitudeStandardParallel, const common::Angle &longitudeOfOrigin, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createRobinson(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createSinusoidal(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createStereographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createVanDerGrinten(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerI(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerII(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerIII(const util::PropertyMap &properties, const common::Angle &latitudeTrueScale, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerIV(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerV(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerVI(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createWagnerVII(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createQuadrilateralizedSphericalCube( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createSphericalCrossTrackHeight( const util::PropertyMap &properties, const common::Angle &pegPointLat, const common::Angle &pegPointLong, const common::Angle &pegPointHeading, const common::Length &pegPointHeight); PROJ_DLL static ConversionNNPtr createEqualEarth(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createVerticalPerspective(const util::PropertyMap &properties, const common::Angle &topoOriginLat, const common::Angle &topoOriginLong, const common::Length &topoOriginHeight, const common::Length &viewPointHeight, const common::Length &falseEasting, const common::Length &falseNorthing); PROJ_DLL static ConversionNNPtr createPoleRotationGRIBConvention( const util::PropertyMap &properties, const common::Angle &southPoleLatInUnrotatedCRS, const common::Angle &southPoleLongInUnrotatedCRS, const common::Angle &axisRotation); PROJ_DLL static ConversionNNPtr createPoleRotationNetCDFCFConvention( const util::PropertyMap &properties, const common::Angle &gridNorthPoleLatitude, const common::Angle &gridNorthPoleLongitude, const common::Angle &northPoleGridLongitude); PROJ_DLL static ConversionNNPtr createChangeVerticalUnit(const util::PropertyMap &properties, const common::Scale &factor); PROJ_DLL static ConversionNNPtr createChangeVerticalUnit(const util::PropertyMap &properties); PROJ_DLL static ConversionNNPtr createHeightDepthReversal(const util::PropertyMap &properties); PROJ_DLL static ConversionNNPtr createAxisOrderReversal(bool is3D); PROJ_DLL static ConversionNNPtr createGeographicGeocentric(const util::PropertyMap &properties); PROJ_DLL static ConversionNNPtr createGeographic2DOffsets(const util::PropertyMap &properties, const common::Angle &offsetLat, const common::Angle &offsetLong); PROJ_DLL static ConversionNNPtr createGeographic3DOffsets( const util::PropertyMap &properties, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight); PROJ_DLL static ConversionNNPtr createGeographic2DWithHeightOffsets( const util::PropertyMap &properties, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight); PROJ_DLL static ConversionNNPtr createVerticalOffset(const util::PropertyMap &properties, const common::Length &offsetHeight); PROJ_DLL static ConversionNNPtr createAffineParametric(const util::PropertyMap &properties, const common::Measure &A0, const common::Scale &A1, const common::Scale &A2, const common::Measure &B0, const common::Scale &B1, const common::Scale &B2); PROJ_DLL ConversionPtr convertToOtherMethod(int targetEPSGCode) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL const char *getESRIMethodName() const; PROJ_INTERNAL const char *getWKT1GDALMethodName() const; PROJ_INTERNAL ConversionNNPtr shallowClone() const; PROJ_INTERNAL ConversionNNPtr alterParametersLinearUnit( const common::UnitOfMeasure &unit, bool convertToNewUnit) const; PROJ_INTERNAL static ConversionNNPtr createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS); PROJ_INTERNAL static ConversionNNPtr createGeographicGeocentricLatitude(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS); //! @endcond protected: PROJ_INTERNAL Conversion(const OperationMethodNNPtr &methodIn, const std::vector &values); PROJ_INTERNAL Conversion(const Conversion &other); INLINED_MAKE_SHARED PROJ_FRIEND(crs::ProjectedCRS); PROJ_INTERNAL bool addWKTExtensionNode(io::WKTFormatter *formatter) const; PROJ_INTERNAL CoordinateOperationNNPtr _shallowClone() const override; private: PROJ_OPAQUE_PRIVATE_DATA Conversion &operator=(const Conversion &other) = delete; PROJ_INTERNAL static ConversionNNPtr create(const util::PropertyMap &properties, int method_epsg_code, const std::vector &values); PROJ_INTERNAL static ConversionNNPtr create(const util::PropertyMap &properties, const char *method_wkt2_name, const std::vector &values); }; // --------------------------------------------------------------------------- /** \brief A mathematical operation on coordinates in which parameters are * empirically derived from data containing the coordinates of a series of * points in both coordinate reference systems. * * This computational process is usually "over-determined", allowing derivation * of error (or accuracy) estimates for the coordinate transformation. Also, * the stochastic nature of the parameters may result in multiple (different) * versions of the same coordinate transformations between the same source and * target CRSs. Any single coordinate operation in which the input and output * coordinates are referenced to different datums (reference frames) will be a * coordinate transformation. * * \remark Implements Transformation from \ref ISO_19111_2019 */ class PROJ_GCC_DLL Transformation : public SingleOperation { public: //! @cond Doxygen_Suppress PROJ_DLL ~Transformation() override; //! @endcond PROJ_DLL const crs::CRSNNPtr &sourceCRS() PROJ_PURE_DECL; PROJ_DLL const crs::CRSNNPtr &targetCRS() PROJ_PURE_DECL; PROJ_DLL CoordinateOperationNNPtr inverse() const override; PROJ_DLL static TransformationNNPtr create(const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies); // throw InvalidOperation PROJ_DLL static TransformationNNPtr create(const util::PropertyMap &propertiesTransformation, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const util::PropertyMap &propertiesOperationMethod, const std::vector ¶meters, const std::vector &values, const std::vector &accuracies); // throw InvalidOperation PROJ_DLL static TransformationNNPtr createGeocentricTranslations( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createPositionVector( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createCoordinateFrameRotation( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createTimeDependentPositionVector( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, double rateTranslationX, double rateTranslationY, double rateTranslationZ, double rateRotationX, double rateRotationY, double rateRotationZ, double rateScaleDifference, double referenceEpochYear, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createTimeDependentCoordinateFrameRotation( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, double rateTranslationX, double rateTranslationY, double rateTranslationZ, double rateRotationX, double rateRotationY, double rateRotationZ, double rateScaleDifference, double referenceEpochYear, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createTOWGS84( const crs::CRSNNPtr &sourceCRSIn, const std::vector &TOWGS84Parameters); // throw InvalidOperation PROJ_DLL static TransformationNNPtr createNTv2( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const std::string &filename, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createMolodensky( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double semiMajorAxisDifferenceMetre, double flattingDifference, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createAbridgedMolodensky( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double semiMajorAxisDifferenceMetre, double flattingDifference, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createGravityRelatedHeightToGeographic3D( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const std::string &filename, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createVERTCON( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const std::string &filename, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createLongitudeRotation( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset); PROJ_DLL static TransformationNNPtr createGeographic2DOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, const common::Angle &offsetLong, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createGeographic3DOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createGeographic2DWithHeightOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createCartesianGridOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Length &eastingOffset, const common::Length &northingOffset, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createVerticalOffset( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight, const std::vector &accuracies); PROJ_DLL static TransformationNNPtr createChangeVerticalUnit( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor, const std::vector &accuracies); PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL const std::string & getPROJ4NadgridsCompatibleFilename() const; PROJ_FOR_TEST std::vector getTOWGS84Parameters( bool canThrowException) const; // throw(io::FormattingException) PROJ_INTERNAL const std::string &getHeightToGeographic3DFilename() const; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL TransformationNNPtr shallowClone() const; PROJ_INTERNAL TransformationNNPtr promoteTo3D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_INTERNAL TransformationNNPtr demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_INTERNAL static bool isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method, bool allowInverse); //! @endcond protected: PROJ_INTERNAL Transformation( const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies); PROJ_INTERNAL Transformation(const Transformation &other); INLINED_MAKE_SHARED PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_FRIEND(CoordinateOperation); PROJ_FRIEND(CoordinateOperationFactory); PROJ_FRIEND(SingleOperation); PROJ_INTERNAL TransformationNNPtr inverseAsTransformation() const; PROJ_INTERNAL CoordinateOperationNNPtr _shallowClone() const override; private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class PointMotionOperation; /** Shared pointer of PointMotionOperation */ using PointMotionOperationPtr = std::shared_ptr; /** Non-null shared pointer of PointMotionOperation */ using PointMotionOperationNNPtr = util::nn; /** \brief A mathematical operation that describes the change of coordinate * values within one coordinate reference system due to the motion of the * point between one coordinate epoch and another coordinate epoch. * * The motion is due to tectonic plate movement or deformation. * * \remark Implements PointMotionOperation from \ref ISO_19111_2019 */ class PROJ_GCC_DLL PointMotionOperation : public SingleOperation { public: // TODO //! @cond Doxygen_Suppress PROJ_DLL ~PointMotionOperation() override; //! @endcond PROJ_DLL const crs::CRSNNPtr &sourceCRS() PROJ_PURE_DECL; PROJ_DLL CoordinateOperationNNPtr inverse() const override; PROJ_DLL static PointMotionOperationNNPtr create(const util::PropertyMap &properties, const crs::CRSNNPtr &crsIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies); // throw InvalidOperation PROJ_DLL static PointMotionOperationNNPtr create(const util::PropertyMap &propertiesOperation, const crs::CRSNNPtr &crsIn, const util::PropertyMap &propertiesOperationMethod, const std::vector ¶meters, const std::vector &values, const std::vector &accuracies); // throw InvalidOperation PROJ_DLL PointMotionOperationNNPtr substitutePROJAlternativeGridNames( io::DatabaseContextNNPtr databaseContext) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL PointMotionOperationNNPtr shallowClone() const; PROJ_INTERNAL PointMotionOperationNNPtr cloneWithEpochs(const common::DataEpoch &sourceEpoch, const common::DataEpoch &targetEpoch) const; PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: PROJ_INTERNAL PointMotionOperation( const crs::CRSNNPtr &crsIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies); PROJ_INTERNAL PointMotionOperation(const PointMotionOperation &other); INLINED_MAKE_SHARED PROJ_INTERNAL CoordinateOperationNNPtr _shallowClone() const override; private: PointMotionOperation &operator=(const PointMotionOperation &) = delete; }; // --------------------------------------------------------------------------- class ConcatenatedOperation; /** Shared pointer of ConcatenatedOperation */ using ConcatenatedOperationPtr = std::shared_ptr; /** Non-null shared pointer of ConcatenatedOperation */ using ConcatenatedOperationNNPtr = util::nn; /** \brief An ordered sequence of two or more single coordinate operations * (SingleOperation). * * The sequence of coordinate operations is constrained by the requirement * that * the source coordinate reference system of step n+1 shall be the same as * the target coordinate reference system of step n. * * \remark Implements ConcatenatedOperation from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ConcatenatedOperation final : public CoordinateOperation { public: //! @cond Doxygen_Suppress PROJ_DLL ~ConcatenatedOperation() override; //! @endcond PROJ_DLL const std::vector &operations() const; PROJ_DLL CoordinateOperationNNPtr inverse() const override; PROJ_DLL static ConcatenatedOperationNNPtr create(const util::PropertyMap &properties, const std::vector &operationsIn, const std::vector &accuracies); // throw InvalidOperation PROJ_DLL static CoordinateOperationNNPtr createComputeMetadata( const std::vector &operationsIn, bool checkExtent); // throw InvalidOperation PROJ_DLL std::set gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const override; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL static void fixSteps(const crs::CRSNNPtr &concatOpSourceCRS, const crs::CRSNNPtr &concatOpTargetCRS, std::vector &operationsInOut, const io::DatabaseContextPtr &dbContext, bool fixDirectionAllowed); //! @endcond protected: PROJ_INTERNAL ConcatenatedOperation(const ConcatenatedOperation &other); PROJ_INTERNAL explicit ConcatenatedOperation( const std::vector &operationsIn); PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL CoordinateOperationNNPtr _shallowClone() const override; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA ConcatenatedOperation & operator=(const ConcatenatedOperation &other) = delete; PROJ_INTERNAL static void setCRSsUpdateInverse(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS); }; // --------------------------------------------------------------------------- class CoordinateOperationContext; /** Unique pointer of CoordinateOperationContext */ using CoordinateOperationContextPtr = std::unique_ptr; /** Non-null unique pointer of CoordinateOperationContext */ using CoordinateOperationContextNNPtr = util::nn; /** \brief Context in which a coordinate operation is to be used. * * \remark Implements [CoordinateOperationFactory * https://sis.apache.org/apidocs/org/apache/sis/referencing/operation/CoordinateOperationContext.html] * from * Apache SIS */ class PROJ_GCC_DLL CoordinateOperationContext { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~CoordinateOperationContext(); //! @endcond PROJ_DLL const io::AuthorityFactoryPtr &getAuthorityFactory() const; PROJ_DLL const metadata::ExtentPtr &getAreaOfInterest() const; PROJ_DLL void setAreaOfInterest(const metadata::ExtentPtr &extent); PROJ_DLL double getDesiredAccuracy() const; PROJ_DLL void setDesiredAccuracy(double accuracy); PROJ_DLL void setAllowBallparkTransformations(bool allow); PROJ_DLL bool getAllowBallparkTransformations() const; /** Specify how source and target CRS extent should be used to restrict * candidate operations (only taken into account if no explicit area of * interest is specified. */ enum class SourceTargetCRSExtentUse { /** Ignore CRS extent */ NONE, /** Test coordinate operation extent against both CRS extent. */ BOTH, /** Test coordinate operation extent against the intersection of both CRS extent. */ INTERSECTION, /** Test coordinate operation against the smallest of both CRS extent. */ SMALLEST, }; PROJ_DLL void setSourceAndTargetCRSExtentUse(SourceTargetCRSExtentUse use); PROJ_DLL SourceTargetCRSExtentUse getSourceAndTargetCRSExtentUse() const; /** Spatial criterion to restrict candidate operations. */ enum class SpatialCriterion { /** The area of validity of transforms should strictly contain the * are of interest. */ STRICT_CONTAINMENT, /** The area of validity of transforms should at least intersect the * area of interest. */ PARTIAL_INTERSECTION }; PROJ_DLL void setSpatialCriterion(SpatialCriterion criterion); PROJ_DLL SpatialCriterion getSpatialCriterion() const; PROJ_DLL void setUsePROJAlternativeGridNames(bool usePROJNames); PROJ_DLL bool getUsePROJAlternativeGridNames() const; PROJ_DLL void setDiscardSuperseded(bool discard); PROJ_DLL bool getDiscardSuperseded() const; /** Describe how grid availability is used. */ enum class GridAvailabilityUse { /** Grid availability is only used for sorting results. Operations * where some grids are missing will be sorted last. */ USE_FOR_SORTING, /** Completely discard an operation if a required grid is missing. */ DISCARD_OPERATION_IF_MISSING_GRID, /** Ignore grid availability at all. Results will be presented as if * all grids were available. */ IGNORE_GRID_AVAILABILITY, /** Results will be presented as if grids known to PROJ (that is * registered in the grid_alternatives table of its database) were * available. Used typically when networking is enabled. */ KNOWN_AVAILABLE, }; PROJ_DLL void setGridAvailabilityUse(GridAvailabilityUse use); PROJ_DLL GridAvailabilityUse getGridAvailabilityUse() const; /** Describe if and how intermediate CRS should be used */ enum class IntermediateCRSUse { /** Always search for intermediate CRS. */ ALWAYS, /** Only attempt looking for intermediate CRS if there is no direct * transformation available. */ IF_NO_DIRECT_TRANSFORMATION, /* Do not attempt looking for intermediate CRS. */ NEVER, }; PROJ_DLL void setAllowUseIntermediateCRS(IntermediateCRSUse use); PROJ_DLL IntermediateCRSUse getAllowUseIntermediateCRS() const; PROJ_DLL void setIntermediateCRS(const std::vector> &intermediateCRSAuthCodes); PROJ_DLL const std::vector> & getIntermediateCRS() const; PROJ_DLL void setSourceCoordinateEpoch(const util::optional &epoch); PROJ_DLL const util::optional & getSourceCoordinateEpoch() const; PROJ_DLL void setTargetCoordinateEpoch(const util::optional &epoch); PROJ_DLL const util::optional & getTargetCoordinateEpoch() const; PROJ_DLL static CoordinateOperationContextNNPtr create(const io::AuthorityFactoryPtr &authorityFactory, const metadata::ExtentPtr &extent, double accuracy); PROJ_DLL CoordinateOperationContextNNPtr clone() const; protected: PROJ_INTERNAL CoordinateOperationContext(); PROJ_INTERNAL CoordinateOperationContext(const CoordinateOperationContext &); INLINED_MAKE_UNIQUE private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class CoordinateOperationFactory; /** Unique pointer of CoordinateOperationFactory */ using CoordinateOperationFactoryPtr = std::unique_ptr; /** Non-null unique pointer of CoordinateOperationFactory */ using CoordinateOperationFactoryNNPtr = util::nn; /** \brief Creates coordinate operations. This factory is capable to find * coordinate transformations or conversions between two coordinate * reference * systems. * * \remark Implements (partially) CoordinateOperationFactory from \ref * GeoAPI */ class PROJ_GCC_DLL CoordinateOperationFactory { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~CoordinateOperationFactory(); //! @endcond PROJ_DLL CoordinateOperationPtr createOperation( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const; PROJ_DLL std::vector createOperations(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const CoordinateOperationContextNNPtr &context) const; PROJ_DLL std::vector createOperations( const coordinates::CoordinateMetadataNNPtr &sourceCoordinateMetadata, const crs::CRSNNPtr &targetCRS, const CoordinateOperationContextNNPtr &context) const; PROJ_DLL std::vector createOperations( const crs::CRSNNPtr &sourceCRS, const coordinates::CoordinateMetadataNNPtr &targetCoordinateMetadata, const CoordinateOperationContextNNPtr &context) const; PROJ_DLL std::vector createOperations( const coordinates::CoordinateMetadataNNPtr &sourceCoordinateMetadata, const coordinates::CoordinateMetadataNNPtr &targetCoordinateMetadata, const CoordinateOperationContextNNPtr &context) const; PROJ_DLL static CoordinateOperationFactoryNNPtr create(); protected: PROJ_INTERNAL CoordinateOperationFactory(); INLINED_MAKE_UNIQUE private: PROJ_OPAQUE_PRIVATE_DATA }; } // namespace operation NS_PROJ_END #endif // COORDINATEOPERATION_HH_INCLUDED proj-9.8.1/include/proj/CMakeLists.txt000664 001750 001750 00000000304 15166171715 017605 0ustar00eveneven000000 000000 install( FILES util.hpp metadata.hpp common.hpp coordinates.hpp crs.hpp datum.hpp coordinatesystem.hpp coordinateoperation.hpp io.hpp nn.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/proj ) proj-9.8.1/include/proj/coordinatesystem.hpp000664 001750 001750 00000072731 15166171715 021167 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef CS_HH_INCLUDED #define CS_HH_INCLUDED #include #include #include #include #include "common.hpp" #include "io.hpp" #include "util.hpp" NS_PROJ_START /** osgeo.proj.cs namespace \brief Coordinate systems and their axis. */ namespace cs { // --------------------------------------------------------------------------- /** \brief The direction of positive increase in the coordinate value for a * coordinate system axis. * * \remark Implements AxisDirection from \ref ISO_19111_2019 */ class AxisDirection : public util::CodeList { public: //! @cond Doxygen_Suppress PROJ_DLL static const AxisDirection * valueOf(const std::string &nameIn) noexcept; //! @endcond AxisDirection(const AxisDirection &) = delete; AxisDirection &operator=(const AxisDirection &) = delete; AxisDirection(AxisDirection &&) = delete; AxisDirection &operator=(AxisDirection &&) = delete; PROJ_DLL static const AxisDirection NORTH; PROJ_DLL static const AxisDirection NORTH_NORTH_EAST; PROJ_DLL static const AxisDirection NORTH_EAST; PROJ_DLL static const AxisDirection EAST_NORTH_EAST; PROJ_DLL static const AxisDirection EAST; PROJ_DLL static const AxisDirection EAST_SOUTH_EAST; PROJ_DLL static const AxisDirection SOUTH_EAST; PROJ_DLL static const AxisDirection SOUTH_SOUTH_EAST; PROJ_DLL static const AxisDirection SOUTH; PROJ_DLL static const AxisDirection SOUTH_SOUTH_WEST; PROJ_DLL static const AxisDirection SOUTH_WEST; PROJ_DLL static const AxisDirection WEST_SOUTH_WEST; // note: was forgotten in WKT2-2015 PROJ_DLL static const AxisDirection WEST; PROJ_DLL static const AxisDirection WEST_NORTH_WEST; PROJ_DLL static const AxisDirection NORTH_WEST; PROJ_DLL static const AxisDirection NORTH_NORTH_WEST; PROJ_DLL static const AxisDirection UP; PROJ_DLL static const AxisDirection DOWN; PROJ_DLL static const AxisDirection GEOCENTRIC_X; PROJ_DLL static const AxisDirection GEOCENTRIC_Y; PROJ_DLL static const AxisDirection GEOCENTRIC_Z; PROJ_DLL static const AxisDirection COLUMN_POSITIVE; PROJ_DLL static const AxisDirection COLUMN_NEGATIVE; PROJ_DLL static const AxisDirection ROW_POSITIVE; PROJ_DLL static const AxisDirection ROW_NEGATIVE; PROJ_DLL static const AxisDirection DISPLAY_RIGHT; PROJ_DLL static const AxisDirection DISPLAY_LEFT; PROJ_DLL static const AxisDirection DISPLAY_UP; PROJ_DLL static const AxisDirection DISPLAY_DOWN; PROJ_DLL static const AxisDirection FORWARD; PROJ_DLL static const AxisDirection AFT; PROJ_DLL static const AxisDirection PORT; PROJ_DLL static const AxisDirection STARBOARD; PROJ_DLL static const AxisDirection CLOCKWISE; PROJ_DLL static const AxisDirection COUNTER_CLOCKWISE; PROJ_DLL static const AxisDirection TOWARDS; PROJ_DLL static const AxisDirection AWAY_FROM; PROJ_DLL static const AxisDirection FUTURE; PROJ_DLL static const AxisDirection PAST; PROJ_DLL static const AxisDirection UNSPECIFIED; private: explicit AxisDirection(const std::string &nameIn); static std::map registry; }; // --------------------------------------------------------------------------- /** \brief Meaning of the axis value range specified through minimumValue and * maximumValue * * \remark Implements RangeMeaning from \ref ISO_19111_2019 * \since 9.2 */ class RangeMeaning : public util::CodeList { public: //! @cond Doxygen_Suppress PROJ_DLL static const RangeMeaning * valueOf(const std::string &nameIn) noexcept; //! @endcond PROJ_DLL static const RangeMeaning EXACT; PROJ_DLL static const RangeMeaning WRAPAROUND; protected: friend class util::optional; RangeMeaning(); private: explicit RangeMeaning(const std::string &nameIn); static std::map registry; }; // --------------------------------------------------------------------------- class Meridian; /** Shared pointer of Meridian. */ using MeridianPtr = std::shared_ptr; /** Non-null shared pointer of Meridian. */ using MeridianNNPtr = util::nn; /** \brief The meridian that the axis follows from the pole, for a coordinate * reference system centered on a pole. * * \note There is no modelling for this concept in \ref ISO_19111_2019 * * \remark Implements MERIDIAN from \ref WKT2 */ class PROJ_GCC_DLL Meridian : public common::IdentifiedObject, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~Meridian() override; //! @endcond PROJ_DLL const common::Angle &longitude() PROJ_PURE_DECL; // non-standard PROJ_DLL static MeridianNNPtr create(const common::Angle &longitudeIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: #ifdef DOXYGEN_ENABLED Angle angle_; #endif PROJ_INTERNAL explicit Meridian(const common::Angle &longitudeIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA Meridian(const Meridian &other) = delete; Meridian &operator=(const Meridian &other) = delete; }; // --------------------------------------------------------------------------- class CoordinateSystemAxis; /** Shared pointer of CoordinateSystemAxis. */ using CoordinateSystemAxisPtr = std::shared_ptr; /** Non-null shared pointer of CoordinateSystemAxis. */ using CoordinateSystemAxisNNPtr = util::nn; /** \brief The definition of a coordinate system axis. * * \remark Implements CoordinateSystemAxis from \ref ISO_19111_2019 */ class PROJ_GCC_DLL CoordinateSystemAxis final : public common::IdentifiedObject, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~CoordinateSystemAxis() override; //! @endcond PROJ_DLL const std::string &abbreviation() PROJ_PURE_DECL; PROJ_DLL const AxisDirection &direction() PROJ_PURE_DECL; PROJ_DLL const common::UnitOfMeasure &unit() PROJ_PURE_DECL; PROJ_DLL const util::optional &minimumValue() PROJ_PURE_DECL; PROJ_DLL const util::optional &maximumValue() PROJ_PURE_DECL; PROJ_DLL const util::optional &rangeMeaning() PROJ_PURE_DECL; PROJ_DLL const MeridianPtr &meridian() PROJ_PURE_DECL; // Non-standard PROJ_DLL static CoordinateSystemAxisNNPtr create(const util::PropertyMap &properties, const std::string &abbreviationIn, const AxisDirection &directionIn, const common::UnitOfMeasure &unitIn, const MeridianPtr &meridianIn = nullptr); PROJ_DLL static CoordinateSystemAxisNNPtr create(const util::PropertyMap &properties, const std::string &abbreviationIn, const AxisDirection &directionIn, const common::UnitOfMeasure &unitIn, const util::optional &minimumValueIn, const util::optional &maximumValueIn, const util::optional &rangeMeaningIn, const MeridianPtr &meridianIn = nullptr); PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter, int order, bool disableAbbrev) const; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL static std::string normalizeAxisName(const std::string &str); PROJ_INTERNAL static CoordinateSystemAxisNNPtr createLAT_NORTH(const common::UnitOfMeasure &unit); PROJ_INTERNAL static CoordinateSystemAxisNNPtr createLONG_EAST(const common::UnitOfMeasure &unit); PROJ_INTERNAL CoordinateSystemAxisNNPtr alterUnit(const common::UnitOfMeasure &newUnit) const; //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA CoordinateSystemAxis(const CoordinateSystemAxis &other) = delete; CoordinateSystemAxis &operator=(const CoordinateSystemAxis &other) = delete; PROJ_INTERNAL CoordinateSystemAxis(); /* cppcheck-suppress unusedPrivateFunction */ INLINED_MAKE_SHARED }; // --------------------------------------------------------------------------- /** \brief Abstract class modelling a coordinate system (CS) * * A CS is the non-repeating sequence of coordinate system axes that spans a * given coordinate space. A CS is derived from a set of mathematical rules for * specifying how coordinates in a given space are to be assigned to points. * The coordinate values in a coordinate tuple shall be recorded in the order * in which the coordinate system axes associations are recorded. * * \remark Implements CoordinateSystem from \ref ISO_19111_2019 */ class PROJ_GCC_DLL CoordinateSystem : public common::IdentifiedObject, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~CoordinateSystem() override; //! @endcond PROJ_DLL const std::vector & axisList() PROJ_PURE_DECL; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL virtual std::string getWKT2Type(bool) const = 0; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL explicit CoordinateSystem( const std::vector &axisIn); private: PROJ_OPAQUE_PRIVATE_DATA CoordinateSystem(const CoordinateSystem &other) = delete; CoordinateSystem &operator=(const CoordinateSystem &other) = delete; }; /** Shared pointer of CoordinateSystem. */ using CoordinateSystemPtr = std::shared_ptr; /** Non-null shared pointer of CoordinateSystem. */ using CoordinateSystemNNPtr = util::nn; // --------------------------------------------------------------------------- class SphericalCS; /** Shared pointer of SphericalCS. */ using SphericalCSPtr = std::shared_ptr; /** Non-null shared pointer of SphericalCS. */ using SphericalCSNNPtr = util::nn; /** \brief A three-dimensional coordinate system in Euclidean space with one * distance measured from the origin and two angular coordinates. * * Not to be confused with an ellipsoidal coordinate system based on an * ellipsoid "degenerated" into a sphere. A SphericalCS shall have three * axis associations. * * \remark Implements SphericalCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL SphericalCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~SphericalCS() override; //! @endcond // non-standard PROJ_DLL static SphericalCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3); PROJ_DLL static SphericalCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2); /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "spherical"; protected: PROJ_INTERNAL explicit SphericalCS( const std::vector &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } private: SphericalCS(const SphericalCS &other) = delete; }; // --------------------------------------------------------------------------- class EllipsoidalCS; /** Shared pointer of EllipsoidalCS. */ using EllipsoidalCSPtr = std::shared_ptr; /** Non-null shared pointer of EllipsoidalCS. */ using EllipsoidalCSNNPtr = util::nn; /** \brief A two- or three-dimensional coordinate system in which position is * specified by geodetic latitude, geodetic longitude, and (in the * three-dimensional case) ellipsoidal height. * * An EllipsoidalCS shall have two or three associations. * * \remark Implements EllipsoidalCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL EllipsoidalCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~EllipsoidalCS() override; //! @endcond // non-standard PROJ_DLL static EllipsoidalCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2); PROJ_DLL static EllipsoidalCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3); PROJ_DLL static EllipsoidalCSNNPtr createLatitudeLongitude(const common::UnitOfMeasure &unit); PROJ_DLL static EllipsoidalCSNNPtr createLatitudeLongitudeEllipsoidalHeight( const common::UnitOfMeasure &angularUnit, const common::UnitOfMeasure &linearUnit); PROJ_DLL static EllipsoidalCSNNPtr createLongitudeLatitude(const common::UnitOfMeasure &unit); PROJ_DLL static EllipsoidalCSNNPtr createLongitudeLatitudeEllipsoidalHeight( const common::UnitOfMeasure &angularUnit, const common::UnitOfMeasure &linearUnit); /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "ellipsoidal"; //! @cond Doxygen_Suppress /** \brief Typical axis order. */ enum class AxisOrder { /** Latitude(North), Longitude(East) */ LAT_NORTH_LONG_EAST, /** Latitude(North), Longitude(East), Height(up) */ LAT_NORTH_LONG_EAST_HEIGHT_UP, /** Longitude(East), Latitude(North) */ LONG_EAST_LAT_NORTH, /** Longitude(East), Latitude(North), Height(up) */ LONG_EAST_LAT_NORTH_HEIGHT_UP, /** Other axis order. */ OTHER }; PROJ_INTERNAL AxisOrder axisOrder() const; PROJ_INTERNAL EllipsoidalCSNNPtr alterAngularUnit(const common::UnitOfMeasure &angularUnit) const; PROJ_INTERNAL EllipsoidalCSNNPtr alterLinearUnit(const common::UnitOfMeasure &linearUnit) const; //! @endcond protected: PROJ_INTERNAL explicit EllipsoidalCS( const std::vector &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } protected: EllipsoidalCS(const EllipsoidalCS &other) = delete; }; // --------------------------------------------------------------------------- class VerticalCS; /** Shared pointer of VerticalCS. */ using VerticalCSPtr = std::shared_ptr; /** Non-null shared pointer of VerticalCS. */ using VerticalCSNNPtr = util::nn; /** \brief A one-dimensional coordinate system used to record the heights or * depths of points. * * Such a coordinate system is usually dependent on the Earth's gravity field. * A VerticalCS shall have one axis association. * * \remark Implements VerticalCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL VerticalCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~VerticalCS() override; //! @endcond PROJ_DLL static VerticalCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis); PROJ_DLL static VerticalCSNNPtr createGravityRelatedHeight(const common::UnitOfMeasure &unit); /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "vertical"; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL VerticalCSNNPtr alterUnit(const common::UnitOfMeasure &unit) const; //! @endcond protected: PROJ_INTERNAL explicit VerticalCS(const CoordinateSystemAxisNNPtr &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } private: VerticalCS(const VerticalCS &other) = delete; }; // --------------------------------------------------------------------------- class CartesianCS; /** Shared pointer of CartesianCS. */ using CartesianCSPtr = std::shared_ptr; /** Non-null shared pointer of CartesianCS. */ using CartesianCSNNPtr = util::nn; /** \brief A two- or three-dimensional coordinate system in Euclidean space * with orthogonal straight axes. * * All axes shall have the same length unit. A CartesianCS shall have two or * three axis associations; the number of associations shall equal the * dimension of the CS. * * \remark Implements CartesianCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL CartesianCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~CartesianCS() override; //! @endcond PROJ_DLL static CartesianCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2); PROJ_DLL static CartesianCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3); PROJ_DLL static CartesianCSNNPtr createEastingNorthing(const common::UnitOfMeasure &unit); PROJ_DLL static CartesianCSNNPtr createNorthingEasting(const common::UnitOfMeasure &unit); PROJ_DLL static CartesianCSNNPtr createNorthPoleEastingSouthNorthingSouth(const common::UnitOfMeasure &unit); PROJ_DLL static CartesianCSNNPtr createSouthPoleEastingNorthNorthingNorth(const common::UnitOfMeasure &unit); PROJ_DLL static CartesianCSNNPtr createWestingSouthing(const common::UnitOfMeasure &unit); PROJ_DLL static CartesianCSNNPtr createGeocentric(const common::UnitOfMeasure &unit); /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "Cartesian"; // uppercase is intended PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL CartesianCSNNPtr alterUnit(const common::UnitOfMeasure &unit) const; //! @endcond protected: PROJ_INTERNAL explicit CartesianCS( const std::vector &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } private: CartesianCS(const CartesianCS &other) = delete; }; // --------------------------------------------------------------------------- class AffineCS; /** Shared pointer of AffineCS. */ using AffineCSPtr = std::shared_ptr; /** Non-null shared pointer of AffineCS. */ using AffineCSNNPtr = util::nn; /** \brief A two- or three-dimensional coordinate system in Euclidean space * with straight axes that are not necessarily orthogonal. * * \remark Implements AffineCS from \ref ISO_19111_2019 * \since 9.2 */ class PROJ_GCC_DLL AffineCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~AffineCS() override; //! @endcond /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "affine"; PROJ_DLL static AffineCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2); PROJ_DLL static AffineCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3); PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL AffineCSNNPtr alterUnit(const common::UnitOfMeasure &unit) const; //! @endcond protected: PROJ_INTERNAL explicit AffineCS( const std::vector &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } private: AffineCS(const AffineCS &other) = delete; }; // --------------------------------------------------------------------------- class OrdinalCS; /** Shared pointer of OrdinalCS. */ using OrdinalCSPtr = std::shared_ptr; /** Non-null shared pointer of OrdinalCS. */ using OrdinalCSNNPtr = util::nn; /** \brief n-dimensional coordinate system in which every axis uses integers. * * The number of associations shall equal the * dimension of the CS. * * \remark Implements OrdinalCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL OrdinalCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~OrdinalCS() override; //! @endcond PROJ_DLL static OrdinalCSNNPtr create(const util::PropertyMap &properties, const std::vector &axisIn); /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "ordinal"; protected: PROJ_INTERNAL explicit OrdinalCS( const std::vector &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } private: OrdinalCS(const OrdinalCS &other) = delete; }; // --------------------------------------------------------------------------- class ParametricCS; /** Shared pointer of ParametricCS. */ using ParametricCSPtr = std::shared_ptr; /** Non-null shared pointer of ParametricCS. */ using ParametricCSNNPtr = util::nn; /** \brief one-dimensional coordinate reference system which uses parameter * values or functions that may vary monotonically with height. * * \remark Implements ParametricCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ParametricCS final : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~ParametricCS() override; //! @endcond PROJ_DLL static ParametricCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axisIn); /** Value of getWKT2Type() */ static constexpr const char *WKT2_TYPE = "parametric"; protected: PROJ_INTERNAL explicit ParametricCS( const std::vector &axisIn); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool) const override { return WKT2_TYPE; } private: ParametricCS(const ParametricCS &other) = delete; }; // --------------------------------------------------------------------------- class TemporalCS; /** Shared pointer of TemporalCS. */ using TemporalCSPtr = std::shared_ptr; /** Non-null shared pointer of TemporalCS. */ using TemporalCSNNPtr = util::nn; /** \brief (Abstract class) A one-dimensional coordinate system used to record * time. * * A TemporalCS shall have one axis association. * * \remark Implements TemporalCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL TemporalCS : public CoordinateSystem { public: //! @cond Doxygen_Suppress PROJ_DLL ~TemporalCS() override; //! @endcond /** WKT2:2015 type */ static constexpr const char *WKT2_2015_TYPE = "temporal"; protected: PROJ_INTERNAL explicit TemporalCS(const CoordinateSystemAxisNNPtr &axis); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool use2019Keywords) const override = 0; private: TemporalCS(const TemporalCS &other) = delete; }; // --------------------------------------------------------------------------- class DateTimeTemporalCS; /** Shared pointer of DateTimeTemporalCS. */ using DateTimeTemporalCSPtr = std::shared_ptr; /** Non-null shared pointer of DateTimeTemporalCS. */ using DateTimeTemporalCSNNPtr = util::nn; /** \brief A one-dimensional coordinate system used to record time in dateTime * representation as defined in ISO 8601. * * A DateTimeTemporalCS shall have one axis association. It does not use * axisUnitID; the temporal quantities are defined through the ISO 8601 * representation. * * \remark Implements DateTimeTemporalCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DateTimeTemporalCS final : public TemporalCS { public: //! @cond Doxygen_Suppress PROJ_DLL ~DateTimeTemporalCS() override; //! @endcond PROJ_DLL static DateTimeTemporalCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis); /** WKT2:2019 type */ static constexpr const char *WKT2_2019_TYPE = "TemporalDateTime"; protected: PROJ_INTERNAL explicit DateTimeTemporalCS( const CoordinateSystemAxisNNPtr &axis); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool use2019Keywords) const override; private: DateTimeTemporalCS(const DateTimeTemporalCS &other) = delete; }; // --------------------------------------------------------------------------- class TemporalCountCS; /** Shared pointer of TemporalCountCS. */ using TemporalCountCSPtr = std::shared_ptr; /** Non-null shared pointer of TemporalCountCS. */ using TemporalCountCSNNPtr = util::nn; /** \brief A one-dimensional coordinate system used to record time as an * integer count. * * A TemporalCountCS shall have one axis association. * * \remark Implements TemporalCountCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL TemporalCountCS final : public TemporalCS { public: //! @cond Doxygen_Suppress PROJ_DLL ~TemporalCountCS() override; //! @endcond PROJ_DLL static TemporalCountCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis); /** WKT2:2019 type */ static constexpr const char *WKT2_2019_TYPE = "TemporalCount"; protected: PROJ_INTERNAL explicit TemporalCountCS( const CoordinateSystemAxisNNPtr &axis); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool use2019Keywords) const override; private: TemporalCountCS(const TemporalCountCS &other) = delete; }; // --------------------------------------------------------------------------- class TemporalMeasureCS; /** Shared pointer of TemporalMeasureCS. */ using TemporalMeasureCSPtr = std::shared_ptr; /** Non-null shared pointer of TemporalMeasureCS. */ using TemporalMeasureCSNNPtr = util::nn; /** \brief A one-dimensional coordinate system used to record a time as a * real number. * * A TemporalMeasureCS shall have one axis association. * * \remark Implements TemporalMeasureCS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL TemporalMeasureCS final : public TemporalCS { public: //! @cond Doxygen_Suppress PROJ_DLL ~TemporalMeasureCS() override; //! @endcond PROJ_DLL static TemporalMeasureCSNNPtr create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis); /** WKT2:2019 type */ static constexpr const char *WKT2_2019_TYPE = "TemporalMeasure"; protected: PROJ_INTERNAL explicit TemporalMeasureCS( const CoordinateSystemAxisNNPtr &axis); INLINED_MAKE_SHARED PROJ_INTERNAL std::string getWKT2Type(bool use2019Keywords) const override; private: TemporalMeasureCS(const TemporalMeasureCS &other) = delete; }; } // namespace cs NS_PROJ_END #endif // CS_HH_INCLUDED proj-9.8.1/include/proj/common.hpp000664 001750 001750 00000037610 15166171715 017060 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef COMMON_HH_INCLUDED #define COMMON_HH_INCLUDED #include #include #include #include "io.hpp" #include "metadata.hpp" #include "util.hpp" NS_PROJ_START /** osgeo.proj.common namespace * * \brief Common classes. */ namespace common { // --------------------------------------------------------------------------- class UnitOfMeasure; /** Shared pointer of UnitOfMeasure. */ using UnitOfMeasurePtr = std::shared_ptr; /** Non-null shared pointer of UnitOfMeasure. */ using UnitOfMeasureNNPtr = util::nn; /** \brief Unit of measure. * * This is a mutable object. */ class PROJ_GCC_DLL UnitOfMeasure : public util::BaseObject { public: /** \brief Type of unit of measure. */ enum class PROJ_MSVC_DLL Type { /** Unknown unit of measure */ UNKNOWN, /** No unit of measure */ NONE, /** Angular unit of measure */ ANGULAR, /** Linear unit of measure */ LINEAR, /** Scale unit of measure */ SCALE, /** Time unit of measure */ TIME, /** Parametric unit of measure */ PARAMETRIC, }; PROJ_DLL explicit UnitOfMeasure( const std::string &nameIn = std::string(), double toSIIn = 1.0, Type typeIn = Type::UNKNOWN, const std::string &codeSpaceIn = std::string(), const std::string &codeIn = std::string()); //! @cond Doxygen_Suppress PROJ_DLL UnitOfMeasure(const UnitOfMeasure &other); PROJ_DLL ~UnitOfMeasure() override; PROJ_DLL UnitOfMeasure &operator=(const UnitOfMeasure &other); PROJ_DLL UnitOfMeasure &operator=(UnitOfMeasure &&other); PROJ_INTERNAL static UnitOfMeasureNNPtr create(const UnitOfMeasure &other); //! @endcond PROJ_DLL const std::string &name() PROJ_PURE_DECL; PROJ_DLL double conversionToSI() PROJ_PURE_DECL; PROJ_DLL Type type() PROJ_PURE_DECL; PROJ_DLL const std::string &codeSpace() PROJ_PURE_DECL; PROJ_DLL const std::string &code() PROJ_PURE_DECL; PROJ_DLL bool operator==(const UnitOfMeasure &other) PROJ_PURE_DECL; PROJ_DLL bool operator!=(const UnitOfMeasure &other) PROJ_PURE_DECL; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter, const std::string &unitType = std::string()) const; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON( io::JSONFormatter *formatter) const; // throw(io::FormattingException) PROJ_INTERNAL std::string exportToPROJString() const; PROJ_INTERNAL bool _isEquivalentTo(const UnitOfMeasure &other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT) const; //! @endcond PROJ_DLL static const UnitOfMeasure NONE; PROJ_DLL static const UnitOfMeasure SCALE_UNITY; PROJ_DLL static const UnitOfMeasure PARTS_PER_MILLION; PROJ_DLL static const UnitOfMeasure PPM_PER_YEAR; PROJ_DLL static const UnitOfMeasure METRE; PROJ_DLL static const UnitOfMeasure METRE_PER_YEAR; PROJ_DLL static const UnitOfMeasure FOOT; PROJ_DLL static const UnitOfMeasure US_FOOT; PROJ_DLL static const UnitOfMeasure RADIAN; PROJ_DLL static const UnitOfMeasure MICRORADIAN; PROJ_DLL static const UnitOfMeasure DEGREE; PROJ_DLL static const UnitOfMeasure ARC_SECOND; PROJ_DLL static const UnitOfMeasure GRAD; PROJ_DLL static const UnitOfMeasure ARC_SECOND_PER_YEAR; PROJ_DLL static const UnitOfMeasure SECOND; PROJ_DLL static const UnitOfMeasure YEAR; private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Numeric value associated with a UnitOfMeasure. */ class Measure : public util::BaseObject { public: // cppcheck-suppress noExplicitConstructor PROJ_DLL Measure(double valueIn = 0.0, const UnitOfMeasure &unitIn = UnitOfMeasure()); //! @cond Doxygen_Suppress PROJ_DLL Measure(const Measure &other); PROJ_DLL ~Measure() override; //! @endcond PROJ_DLL const UnitOfMeasure &unit() PROJ_PURE_DECL; PROJ_DLL double getSIValue() PROJ_PURE_DECL; PROJ_DLL double value() PROJ_PURE_DECL; PROJ_DLL double convertToUnit(const UnitOfMeasure &otherUnit) PROJ_PURE_DECL; PROJ_DLL bool operator==(const Measure &other) PROJ_PURE_DECL; /** Default maximum resulative error. */ static constexpr double DEFAULT_MAX_REL_ERROR = 1e-10; PROJ_INTERNAL bool _isEquivalentTo(const Measure &other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, double maxRelativeError = DEFAULT_MAX_REL_ERROR) const; private: PROJ_OPAQUE_PRIVATE_DATA Measure &operator=(const Measure &) = delete; }; // --------------------------------------------------------------------------- /** \brief Numeric value, without a physical unit of measure. */ class Scale : public Measure { public: PROJ_DLL explicit Scale(double valueIn = 0.0); PROJ_DLL explicit Scale(double valueIn, const UnitOfMeasure &unitIn); //! @cond Doxygen_Suppress explicit Scale(const Measure &other) : Scale(other.value(), other.unit()) {} PROJ_DLL Scale(const Scale &other); PROJ_DLL ~Scale() override; //! @endcond protected: PROJ_FRIEND_OPTIONAL(Scale); Scale &operator=(const Scale &) = delete; }; // --------------------------------------------------------------------------- /** \brief Numeric value, with a angular unit of measure. */ class Angle : public Measure { public: PROJ_DLL explicit Angle(double valueIn = 0.0); PROJ_DLL Angle(double valueIn, const UnitOfMeasure &unitIn); //! @cond Doxygen_Suppress explicit Angle(const Measure &other) : Angle(other.value(), other.unit()) {} PROJ_DLL Angle(const Angle &other); PROJ_DLL ~Angle() override; //! @endcond protected: PROJ_FRIEND_OPTIONAL(Angle); Angle &operator=(const Angle &) = delete; }; // --------------------------------------------------------------------------- /** \brief Numeric value, with a linear unit of measure. */ class Length : public Measure { public: PROJ_DLL explicit Length(double valueIn = 0.0); PROJ_DLL Length(double valueIn, const UnitOfMeasure &unitIn); //! @cond Doxygen_Suppress explicit Length(const Measure &other) : Length(other.value(), other.unit()) {} PROJ_DLL Length(const Length &other); PROJ_DLL ~Length() override; //! @endcond protected: PROJ_FRIEND_OPTIONAL(Length); Length &operator=(const Length &) = delete; }; // --------------------------------------------------------------------------- /** \brief Date-time value, as a ISO:8601 encoded string, or other string * encoding */ class DateTime { public: //! @cond Doxygen_Suppress PROJ_DLL DateTime(const DateTime &other); PROJ_DLL ~DateTime(); //! @endcond PROJ_DLL bool isISO_8601() const; PROJ_DLL std::string toString() const; PROJ_DLL static DateTime create(const std::string &str); // may throw Exception protected: DateTime(); PROJ_FRIEND_OPTIONAL(DateTime); DateTime &operator=(const DateTime &other); private: explicit DateTime(const std::string &str); PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Data epoch */ class DataEpoch { public: //! @cond Doxygen_Suppress PROJ_DLL explicit DataEpoch(const Measure &coordinateEpochIn); PROJ_DLL DataEpoch(const DataEpoch &other); PROJ_DLL ~DataEpoch(); //! @endcond PROJ_DLL const Measure &coordinateEpoch() const; protected: DataEpoch(); PROJ_FRIEND_OPTIONAL(DataEpoch); private: DataEpoch &operator=(const DataEpoch &other) = delete; PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class IdentifiedObject; /** Shared pointer of IdentifiedObject. */ using IdentifiedObjectPtr = std::shared_ptr; /** Non-null shared pointer of IdentifiedObject. */ using IdentifiedObjectNNPtr = util::nn; /** \brief Abstract class representing a CRS-related object that has an * identification. * * \remark Implements IdentifiedObject from \ref ISO_19111_2019 */ class PROJ_GCC_DLL IdentifiedObject : public util::BaseObject, public util::IComparable, public io::IWKTExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~IdentifiedObject() override; //! @endcond PROJ_DLL static const std::string NAME_KEY; PROJ_DLL static const std::string IDENTIFIERS_KEY; PROJ_DLL static const std::string ALIAS_KEY; PROJ_DLL static const std::string REMARKS_KEY; PROJ_DLL static const std::string DEPRECATED_KEY; // in practice only name().description() is used PROJ_DLL const metadata::IdentifierNNPtr &name() PROJ_PURE_DECL; PROJ_DLL const std::string &nameStr() PROJ_PURE_DECL; PROJ_DLL const std::vector & identifiers() PROJ_PURE_DECL; PROJ_DLL const std::vector & aliases() PROJ_PURE_DECL; PROJ_DLL const std::string &remarks() PROJ_PURE_DECL; // from Apache SIS AbstractIdentifiedObject PROJ_DLL bool isDeprecated() PROJ_PURE_DECL; // Non-standard PROJ_DLL std::string alias() PROJ_PURE_DECL; PROJ_DLL int getEPSGCode() PROJ_PURE_DECL; PROJ_PRIVATE : //! @cond Doxygen_Suppress void formatID(io::WKTFormatter *formatter) const; PROJ_INTERNAL void formatID(io::JSONFormatter *formatter) const; PROJ_INTERNAL void formatRemarks(io::WKTFormatter *formatter) const; PROJ_INTERNAL void formatRemarks(io::JSONFormatter *formatter) const; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL bool _isEquivalentTo( const IdentifiedObject *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) PROJ_PURE_DECL; //! @endcond protected: PROJ_FRIEND_OPTIONAL(IdentifiedObject); INLINED_MAKE_SHARED IdentifiedObject(); IdentifiedObject(const IdentifiedObject &other); void setProperties(const util::PropertyMap &properties); // throw(InvalidValueTypeException) virtual bool hasEquivalentNameToUsingAlias( const IdentifiedObject *other, const io::DatabaseContextPtr &dbContext) const; private: PROJ_OPAQUE_PRIVATE_DATA IdentifiedObject &operator=(const IdentifiedObject &other) = delete; }; // --------------------------------------------------------------------------- class ObjectDomain; /** Shared pointer of ObjectDomain. */ using ObjectDomainPtr = std::shared_ptr; /** Non-null shared pointer of ObjectDomain. */ using ObjectDomainNNPtr = util::nn; /** \brief The scope and validity of a CRS-related object. * * \remark Implements ObjectDomain from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ObjectDomain : public util::BaseObject, public util::IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL ~ObjectDomain() override; //! @endcond // In ISO_19111:2018, scope and domain are compulsory, but in WKT2:2015, // they // are not necessarily both specified PROJ_DLL const util::optional &scope() PROJ_PURE_DECL; PROJ_DLL const metadata::ExtentPtr &domainOfValidity() PROJ_PURE_DECL; PROJ_DLL static ObjectDomainNNPtr create(const util::optional &scopeIn, const metadata::ExtentPtr &extent); PROJ_PRIVATE : //! @cond Doxygen_Suppress void _exportToWKT(io::WKTFormatter *formatter) const; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON( io::JSONFormatter *formatter) const; // throw(FormattingException) bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: //! @cond Doxygen_Suppress ObjectDomain(const util::optional &scopeIn, const metadata::ExtentPtr &extent); //! @endcond ObjectDomain(const ObjectDomain &other); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA ObjectDomain &operator=(const ObjectDomain &other) = delete; }; // --------------------------------------------------------------------------- class ObjectUsage; /** Shared pointer of ObjectUsage. */ using ObjectUsagePtr = std::shared_ptr; /** Non-null shared pointer of ObjectUsage. */ using ObjectUsageNNPtr = util::nn; /** \brief Abstract class of a CRS-related object that has usages. * * \remark Implements ObjectUsage from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ObjectUsage : public IdentifiedObject { public: //! @cond Doxygen_Suppress PROJ_DLL ~ObjectUsage() override; //! @endcond PROJ_DLL const std::vector &domains() PROJ_PURE_DECL; PROJ_DLL static const std::string SCOPE_KEY; PROJ_DLL static const std::string DOMAIN_OF_VALIDITY_KEY; PROJ_DLL static const std::string OBJECT_DOMAIN_KEY; //! @cond Doxygen_Suppress bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: ObjectUsage(); ObjectUsage(const ObjectUsage &other); void setProperties(const util::PropertyMap &properties); // throw(InvalidValueTypeException) void baseExportToWKT( io::WKTFormatter *formatter) const; // throw(io::FormattingException) void baseExportToJSON( io::JSONFormatter *formatter) const; // throw(io::FormattingException) private: PROJ_OPAQUE_PRIVATE_DATA ObjectUsage &operator=(const ObjectUsage &other) = delete; }; } // namespace common NS_PROJ_END #endif // COMMON_HH_INCLUDED proj-9.8.1/include/proj/io.hpp000664 001750 001750 00000145415 15166171715 016202 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef IO_HH_INCLUDED #define IO_HH_INCLUDED #include #include #include #include #include #include #include "proj.h" #include "util.hpp" NS_PROJ_START class CPLJSonStreamingWriter; namespace common { class UnitOfMeasure; using UnitOfMeasurePtr = std::shared_ptr; using UnitOfMeasureNNPtr = util::nn; class IdentifiedObject; using IdentifiedObjectPtr = std::shared_ptr; using IdentifiedObjectNNPtr = util::nn; } // namespace common namespace cs { class CoordinateSystem; using CoordinateSystemPtr = std::shared_ptr; using CoordinateSystemNNPtr = util::nn; } // namespace cs namespace metadata { class Extent; using ExtentPtr = std::shared_ptr; using ExtentNNPtr = util::nn; } // namespace metadata namespace datum { class Datum; using DatumPtr = std::shared_ptr; using DatumNNPtr = util::nn; class DatumEnsemble; using DatumEnsemblePtr = std::shared_ptr; using DatumEnsembleNNPtr = util::nn; class Ellipsoid; using EllipsoidPtr = std::shared_ptr; using EllipsoidNNPtr = util::nn; class PrimeMeridian; using PrimeMeridianPtr = std::shared_ptr; using PrimeMeridianNNPtr = util::nn; class GeodeticReferenceFrame; using GeodeticReferenceFramePtr = std::shared_ptr; using GeodeticReferenceFrameNNPtr = util::nn; class VerticalReferenceFrame; using VerticalReferenceFramePtr = std::shared_ptr; using VerticalReferenceFrameNNPtr = util::nn; class EngineeringDatum; using EngineeringDatumPtr = std::shared_ptr; using EngineeringDatumNNPtr = util::nn; } // namespace datum namespace crs { class CRS; using CRSPtr = std::shared_ptr; using CRSNNPtr = util::nn; class GeodeticCRS; using GeodeticCRSPtr = std::shared_ptr; using GeodeticCRSNNPtr = util::nn; class GeographicCRS; using GeographicCRSPtr = std::shared_ptr; using GeographicCRSNNPtr = util::nn; class VerticalCRS; using VerticalCRSPtr = std::shared_ptr; using VerticalCRSNNPtr = util::nn; class ProjectedCRS; using ProjectedCRSPtr = std::shared_ptr; using ProjectedCRSNNPtr = util::nn; class CompoundCRS; using CompoundCRSPtr = std::shared_ptr; using CompoundCRSNNPtr = util::nn; class EngineeringCRS; using EngineeringCRSPtr = std::shared_ptr; using EngineeringCRSNNPtr = util::nn; } // namespace crs namespace coordinates { class CoordinateMetadata; /** Shared pointer of CoordinateMetadata */ using CoordinateMetadataPtr = std::shared_ptr; /** Non-null shared pointer of CoordinateMetadata */ using CoordinateMetadataNNPtr = util::nn; } // namespace coordinates namespace operation { class Conversion; using ConversionPtr = std::shared_ptr; using ConversionNNPtr = util::nn; class CoordinateOperation; using CoordinateOperationPtr = std::shared_ptr; using CoordinateOperationNNPtr = util::nn; class PointMotionOperation; using PointMotionOperationPtr = std::shared_ptr; using PointMotionOperationNNPtr = util::nn; } // namespace operation /** osgeo.proj.io namespace. * * \brief I/O classes */ namespace io { class DatabaseContext; /** Shared pointer of DatabaseContext. */ using DatabaseContextPtr = std::shared_ptr; /** Non-null shared pointer of DatabaseContext. */ using DatabaseContextNNPtr = util::nn; // --------------------------------------------------------------------------- class WKTNode; /** Unique pointer of WKTNode. */ using WKTNodePtr = std::unique_ptr; /** Non-null unique pointer of WKTNode. */ using WKTNodeNNPtr = util::nn; // --------------------------------------------------------------------------- class WKTFormatter; /** WKTFormatter unique pointer. */ using WKTFormatterPtr = std::unique_ptr; /** Non-null WKTFormatter unique pointer. */ using WKTFormatterNNPtr = util::nn; /** \brief Formatter to WKT strings. * * An instance of this class can only be used by a single * thread at a time. */ class PROJ_GCC_DLL WKTFormatter { public: /** WKT variant. */ enum class PROJ_MSVC_DLL Convention { /** Full WKT2 string, conforming to ISO 19162:2015(E) / OGC 12-063r5 * (\ref WKT2_2015) with all possible nodes and new keyword names. */ WKT2, WKT2_2015 = WKT2, /** Same as WKT2 with the following exceptions: *
    *
  • UNIT keyword used.
  • *
  • ID node only on top element.
  • *
  • No ORDER element in AXIS element.
  • *
  • PRIMEM node omitted if it is Greenwich.
  • *
  • ELLIPSOID.UNIT node omitted if it is * UnitOfMeasure::METRE.
  • *
  • PARAMETER.UNIT / PRIMEM.UNIT omitted if same as AXIS.
  • *
  • AXIS.UNIT omitted and replaced by a common GEODCRS.UNIT if * they are all the same on all axis.
  • *
*/ WKT2_SIMPLIFIED, WKT2_2015_SIMPLIFIED = WKT2_SIMPLIFIED, /** Full WKT2 string, conforming to ISO 19162:2019 / OGC 18-010, with * (\ref WKT2_2019) all possible nodes and new keyword names. * Non-normative list of differences: *
    *
  • WKT2_2019 uses GEOGCRS / BASEGEOGCRS keywords for * GeographicCRS.
  • *
*/ WKT2_2019, /** Deprecated alias for WKT2_2019 */ WKT2_2018 = WKT2_2019, /** WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED */ WKT2_2019_SIMPLIFIED, /** Deprecated alias for WKT2_2019_SIMPLIFIED */ WKT2_2018_SIMPLIFIED = WKT2_2019_SIMPLIFIED, /** WKT1 as traditionally output by GDAL, deriving from OGC 01-009. A notable departure from WKT1_GDAL with respect to OGC 01-009 is that in WKT1_GDAL, the unit of the PRIMEM value is always degrees. */ WKT1_GDAL, /** WKT1 as traditionally output by ESRI software, * deriving from OGC 99-049. */ WKT1_ESRI, }; PROJ_DLL static WKTFormatterNNPtr create(Convention convention = Convention::WKT2, DatabaseContextPtr dbContext = nullptr); PROJ_DLL static WKTFormatterNNPtr create(const WKTFormatterNNPtr &other); //! @cond Doxygen_Suppress PROJ_DLL ~WKTFormatter(); //! @endcond PROJ_DLL WKTFormatter &setMultiLine(bool multiLine) noexcept; PROJ_DLL WKTFormatter &setIndentationWidth(int width) noexcept; /** Rule for output AXIS nodes */ enum class OutputAxisRule { /** Always include AXIS nodes */ YES, /** Never include AXIS nodes */ NO, /** Includes them only on PROJCS node if it uses Easting/Northing *ordering. Typically used for WKT1_GDAL */ WKT1_GDAL_EPSG_STYLE, }; PROJ_DLL WKTFormatter &setOutputAxis(OutputAxisRule outputAxis) noexcept; PROJ_DLL WKTFormatter &setStrict(bool strict) noexcept; PROJ_DLL bool isStrict() const noexcept; PROJ_DLL WKTFormatter & setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept; PROJ_DLL bool isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept; PROJ_DLL WKTFormatter &setAllowLINUNITNode(bool allow) noexcept; PROJ_DLL bool isAllowedLINUNITNode() const noexcept; PROJ_DLL const std::string &toString() const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_DLL WKTFormatter & setOutputId(bool outputIdIn); PROJ_INTERNAL void enter(); PROJ_INTERNAL void leave(); PROJ_INTERNAL void startNode(const std::string &keyword, bool hasId); PROJ_INTERNAL void endNode(); PROJ_INTERNAL bool isAtTopLevel() const; PROJ_DLL WKTFormatter &simulCurNodeHasId(); PROJ_INTERNAL void addQuotedString(const char *str); PROJ_INTERNAL void addQuotedString(const std::string &str); PROJ_INTERNAL void add(const std::string &str); PROJ_INTERNAL void add(int number); PROJ_INTERNAL void add(size_t number) = delete; PROJ_INTERNAL void add(double number, int precision = 15); PROJ_INTERNAL void pushOutputUnit(bool outputUnitIn); PROJ_INTERNAL void popOutputUnit(); PROJ_INTERNAL bool outputUnit() const; PROJ_INTERNAL void pushOutputId(bool outputIdIn); PROJ_INTERNAL void popOutputId(); PROJ_INTERNAL bool outputId() const; PROJ_INTERNAL void pushHasId(bool hasId); PROJ_INTERNAL void popHasId(); PROJ_INTERNAL void pushDisableUsage(); PROJ_INTERNAL void popDisableUsage(); PROJ_INTERNAL bool outputUsage() const; PROJ_INTERNAL void pushAxisLinearUnit(const common::UnitOfMeasureNNPtr &unit); PROJ_INTERNAL void popAxisLinearUnit(); PROJ_INTERNAL const common::UnitOfMeasureNNPtr &axisLinearUnit() const; PROJ_INTERNAL void pushAxisAngularUnit(const common::UnitOfMeasureNNPtr &unit); PROJ_INTERNAL void popAxisAngularUnit(); PROJ_INTERNAL const common::UnitOfMeasureNNPtr &axisAngularUnit() const; PROJ_INTERNAL void setAbridgedTransformation(bool abriged); PROJ_INTERNAL bool abridgedTransformation() const; PROJ_INTERNAL void setUseDerivingConversion(bool useDerivingConversionIn); PROJ_INTERNAL bool useDerivingConversion() const; PROJ_INTERNAL void setTOWGS84Parameters(const std::vector ¶ms); PROJ_INTERNAL const std::vector &getTOWGS84Parameters() const; PROJ_INTERNAL void setVDatumExtension(const std::string &filename); PROJ_INTERNAL const std::string &getVDatumExtension() const; PROJ_INTERNAL void setHDatumExtension(const std::string &filename); PROJ_INTERNAL const std::string &getHDatumExtension() const; PROJ_INTERNAL void setGeogCRSOfCompoundCRS(const crs::GeographicCRSPtr &crs); PROJ_INTERNAL const crs::GeographicCRSPtr &getGeogCRSOfCompoundCRS() const; PROJ_INTERNAL static std::string morphNameToESRI(const std::string &name); #ifdef unused PROJ_INTERNAL void startInversion(); PROJ_INTERNAL void stopInversion(); PROJ_INTERNAL bool isInverted() const; #endif PROJ_INTERNAL OutputAxisRule outputAxis() const; PROJ_INTERNAL bool outputAxisOrder() const; PROJ_INTERNAL bool primeMeridianOmittedIfGreenwich() const; PROJ_INTERNAL bool ellipsoidUnitOmittedIfMetre() const; PROJ_INTERNAL bool forceUNITKeyword() const; PROJ_INTERNAL bool primeMeridianOrParameterUnitOmittedIfSameAsAxis() const; PROJ_INTERNAL bool primeMeridianInDegree() const; PROJ_INTERNAL bool outputCSUnitOnlyOnceIfSame() const; PROJ_INTERNAL bool idOnTopLevelOnly() const; PROJ_INTERNAL bool topLevelHasId() const; /** WKT version. */ enum class Version { /** WKT1 */ WKT1, /** WKT2 / ISO 19162 */ WKT2 }; PROJ_INTERNAL Version version() const; PROJ_INTERNAL bool use2019Keywords() const; PROJ_INTERNAL bool useESRIDialect() const; PROJ_INTERNAL const DatabaseContextPtr &databaseContext() const; PROJ_INTERNAL void ingestWKTNode(const WKTNodeNNPtr &node); //! @endcond protected: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit WKTFormatter(Convention convention); WKTFormatter(const WKTFormatter &other) = delete; INLINED_MAKE_UNIQUE //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class PROJStringFormatter; /** PROJStringFormatter unique pointer. */ using PROJStringFormatterPtr = std::unique_ptr; /** Non-null PROJStringFormatter unique pointer. */ using PROJStringFormatterNNPtr = util::nn; /** \brief Formatter to PROJ strings. * * An instance of this class can only be used by a single * thread at a time. */ class PROJ_GCC_DLL PROJStringFormatter { public: /** PROJ variant. */ enum class PROJ_MSVC_DLL Convention { /** PROJ v5 (or later versions) string. */ PROJ_5, /** PROJ v4 string as output by GDAL exportToProj4() */ PROJ_4 }; PROJ_DLL static PROJStringFormatterNNPtr create(Convention conventionIn = Convention::PROJ_5, DatabaseContextPtr dbContext = nullptr); //! @cond Doxygen_Suppress PROJ_DLL ~PROJStringFormatter(); //! @endcond PROJ_DLL PROJStringFormatter &setMultiLine(bool multiLine) noexcept; PROJ_DLL PROJStringFormatter &setIndentationWidth(int width) noexcept; PROJ_DLL PROJStringFormatter &setMaxLineLength(int maxLineLength) noexcept; PROJ_DLL void setUseApproxTMerc(bool flag); PROJ_DLL const std::string &toString() const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_DLL void setCRSExport(bool b); PROJ_INTERNAL bool getCRSExport() const; PROJ_DLL void startInversion(); PROJ_DLL void stopInversion(); PROJ_INTERNAL bool isInverted() const; PROJ_INTERNAL bool getUseApproxTMerc() const; PROJ_INTERNAL void setCoordinateOperationOptimizations(bool enable); PROJ_DLL void ingestPROJString(const std::string &str); // throw ParsingException PROJ_DLL void addStep(const char *step); PROJ_DLL void addStep(const std::string &step); PROJ_DLL void setCurrentStepInverted(bool inverted); PROJ_DLL void addParam(const std::string ¶mName); PROJ_DLL void addParam(const char *paramName, double val); PROJ_DLL void addParam(const std::string ¶mName, double val); PROJ_DLL void addParam(const char *paramName, int val); PROJ_DLL void addParam(const std::string ¶mName, int val); PROJ_DLL void addParam(const char *paramName, const char *val); PROJ_DLL void addParam(const char *paramName, const std::string &val); PROJ_DLL void addParam(const std::string ¶mName, const char *val); PROJ_DLL void addParam(const std::string ¶mName, const std::string &val); PROJ_DLL void addParam(const char *paramName, const std::vector &vals); PROJ_INTERNAL bool hasParam(const char *paramName) const; PROJ_INTERNAL void addNoDefs(bool b); PROJ_INTERNAL bool getAddNoDefs() const; PROJ_INTERNAL std::set getUsedGridNames() const; PROJ_INTERNAL bool requiresPerCoordinateInputTime() const; PROJ_INTERNAL void setTOWGS84Parameters(const std::vector ¶ms); PROJ_INTERNAL const std::vector &getTOWGS84Parameters() const; PROJ_INTERNAL void setVDatumExtension(const std::string &filename, const std::string &geoidCRSValue); PROJ_INTERNAL const std::string &getVDatumExtension() const; PROJ_INTERNAL const std::string &getGeoidCRSValue() const; PROJ_INTERNAL void setHDatumExtension(const std::string &filename); PROJ_INTERNAL const std::string &getHDatumExtension() const; PROJ_INTERNAL void setGeogCRSOfCompoundCRS(const crs::GeographicCRSPtr &crs); PROJ_INTERNAL const crs::GeographicCRSPtr &getGeogCRSOfCompoundCRS() const; PROJ_INTERNAL void setOmitProjLongLatIfPossible(bool omit); PROJ_INTERNAL bool omitProjLongLatIfPossible() const; PROJ_INTERNAL void pushOmitZUnitConversion(); PROJ_INTERNAL void popOmitZUnitConversion(); PROJ_INTERNAL bool omitZUnitConversion() const; PROJ_INTERNAL void pushOmitHorizontalConversionInVertTransformation(); PROJ_INTERNAL void popOmitHorizontalConversionInVertTransformation(); PROJ_INTERNAL bool omitHorizontalConversionInVertTransformation() const; PROJ_INTERNAL void setLegacyCRSToCRSContext(bool legacyContext); PROJ_INTERNAL bool getLegacyCRSToCRSContext() const; PROJ_INTERNAL PROJStringFormatter &setNormalizeOutput(); PROJ_INTERNAL const DatabaseContextPtr &databaseContext() const; PROJ_INTERNAL Convention convention() const; PROJ_INTERNAL size_t getStepCount() const; //! @endcond protected: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit PROJStringFormatter( Convention conventionIn, const DatabaseContextPtr &dbContext); PROJStringFormatter(const PROJStringFormatter &other) = delete; INLINED_MAKE_UNIQUE //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class JSONFormatter; /** JSONFormatter unique pointer. */ using JSONFormatterPtr = std::unique_ptr; /** Non-null JSONFormatter unique pointer. */ using JSONFormatterNNPtr = util::nn; /** \brief Formatter to JSON strings. * * An instance of this class can only be used by a single * thread at a time. */ class PROJ_GCC_DLL JSONFormatter { public: PROJ_DLL static JSONFormatterNNPtr create(DatabaseContextPtr dbContext = nullptr); //! @cond Doxygen_Suppress PROJ_DLL ~JSONFormatter(); //! @endcond PROJ_DLL JSONFormatter &setMultiLine(bool multiLine) noexcept; PROJ_DLL JSONFormatter &setIndentationWidth(int width) noexcept; PROJ_DLL JSONFormatter &setSchema(const std::string &schema) noexcept; PROJ_DLL const std::string &toString() const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL CPLJSonStreamingWriter * writer() const; PROJ_INTERNAL const DatabaseContextPtr &databaseContext() const; struct ObjectContext { JSONFormatter &m_formatter; ObjectContext(const ObjectContext &) = delete; ObjectContext(ObjectContext &&) = default; explicit ObjectContext(JSONFormatter &formatter, const char *objectType, bool hasId); ~ObjectContext(); }; PROJ_INTERNAL inline ObjectContext MakeObjectContext(const char *objectType, bool hasId) { return ObjectContext(*this, objectType, hasId); } PROJ_INTERNAL void setAllowIDInImmediateChild(); PROJ_INTERNAL void setOmitTypeInImmediateChild(); PROJ_INTERNAL void setAbridgedTransformation(bool abriged); PROJ_INTERNAL bool abridgedTransformation() const; PROJ_INTERNAL void setAbridgedTransformationWriteSourceCRS(bool writeCRS); PROJ_INTERNAL bool abridgedTransformationWriteSourceCRS() const; // cppcheck-suppress functionStatic PROJ_INTERNAL bool outputId() const; PROJ_INTERNAL bool outputUsage(bool calledBeforeObjectContext = false) const; PROJ_INTERNAL static const char *PROJJSON_v0_7; //! @endcond protected: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit JSONFormatter(); JSONFormatter(const JSONFormatter &other) = delete; INLINED_MAKE_UNIQUE //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Interface for an object that can be exported to JSON. */ class PROJ_GCC_DLL IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~IJSONExportable(); //! @endcond /** Builds a JSON representation. May throw a FormattingException */ PROJ_DLL std::string exportToJSON(JSONFormatter *formatter) const; // throw(FormattingException) PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL virtual void _exportToJSON( JSONFormatter *formatter) const = 0; // throw(FormattingException) //! @endcond }; // --------------------------------------------------------------------------- /** \brief Exception possibly thrown by IWKTExportable::exportToWKT() or * IPROJStringExportable::exportToPROJString(). */ class PROJ_GCC_DLL FormattingException : public util::Exception { public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit FormattingException(const char *message); PROJ_INTERNAL explicit FormattingException(const std::string &message); PROJ_DLL FormattingException(const FormattingException &other); PROJ_DLL virtual ~FormattingException() override; PROJ_INTERNAL static void Throw(const char *msg) PROJ_NO_RETURN; PROJ_INTERNAL static void Throw(const std::string &msg) PROJ_NO_RETURN; //! @endcond }; // --------------------------------------------------------------------------- /** \brief Exception possibly thrown by WKTNode::createFrom() or * WKTParser::createFromWKT(). */ class PROJ_GCC_DLL ParsingException : public util::Exception { public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit ParsingException(const char *message); PROJ_INTERNAL explicit ParsingException(const std::string &message); PROJ_DLL ParsingException(const ParsingException &other); PROJ_DLL virtual ~ParsingException() override; //! @endcond }; // --------------------------------------------------------------------------- /** \brief Interface for an object that can be exported to WKT. */ class PROJ_GCC_DLL IWKTExportable { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~IWKTExportable(); //! @endcond /** Builds a WKT representation. May throw a FormattingException */ PROJ_DLL std::string exportToWKT(WKTFormatter *formatter) const; // throw(FormattingException) PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL virtual void _exportToWKT( WKTFormatter *formatter) const = 0; // throw(FormattingException) //! @endcond }; // --------------------------------------------------------------------------- class IPROJStringExportable; /** Shared pointer of IPROJStringExportable. */ using IPROJStringExportablePtr = std::shared_ptr; /** Non-null shared pointer of IPROJStringExportable. */ using IPROJStringExportableNNPtr = util::nn; /** \brief Interface for an object that can be exported to a PROJ string. */ class PROJ_GCC_DLL IPROJStringExportable { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~IPROJStringExportable(); //! @endcond /** \brief Builds a PROJ string representation. * *
    *
  • For PROJStringFormatter::Convention::PROJ_5 (the default), *
      *
    • For a crs::CRS, returns the same as * PROJStringFormatter::Convention::PROJ_4. It should be noted that the * export of a CRS as a PROJ string may cause loss of many important aspects * of a CRS definition. Consequently it is discouraged to use it for * interoperability in newer projects. The choice of a WKT representation * will be a better option.
    • *
    • For operation::CoordinateOperation, returns a PROJ * pipeline.
    • *
    * *
  • For PROJStringFormatter::Convention::PROJ_4, format a string * compatible with the OGRSpatialReference::exportToProj4() of GDAL * <=2.3. It is only compatible of a few CRS objects. The PROJ string * will also contain a +type=crs parameter to disambiguate the nature of * the string from a CoordinateOperation. *
      *
    • For a crs::GeographicCRS, returns a proj=longlat string, with * ellipsoid / datum / prime meridian information, ignoring axis order * and unit information.
    • *
    • For a geocentric crs::GeodeticCRS, returns the transformation from * geographic coordinates into geocentric coordinates.
    • *
    • For a crs::ProjectedCRS, returns the projection method, ignoring * axis order.
    • *
    • For a crs::BoundCRS, returns the PROJ string of its source/base CRS, * amended with towgs84 / nadgrids parameter when the deriving conversion * can be expressed in that way.
    • *
    *
  • * *
* * @param formatter PROJ string formatter. * @return a PROJ string. * @throw FormattingException if cannot be exported as a PROJ string */ PROJ_DLL std::string exportToPROJString( PROJStringFormatter *formatter) const; // throw(FormattingException) PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL virtual void _exportToPROJString(PROJStringFormatter *formatter) const = 0; // throw(FormattingException) //! @endcond }; // --------------------------------------------------------------------------- /** \brief Node in the tree-splitted WKT representation. */ class PROJ_GCC_DLL WKTNode { public: PROJ_DLL explicit WKTNode(const std::string &valueIn); //! @cond Doxygen_Suppress PROJ_DLL ~WKTNode(); //! @endcond PROJ_DLL const std::string &value() const; PROJ_DLL const std::vector &children() const; PROJ_DLL void addChild(WKTNodeNNPtr &&child); PROJ_DLL const WKTNodePtr &lookForChild(const std::string &childName, int occurrence = 0) const noexcept; PROJ_DLL int countChildrenOfName(const std::string &childName) const noexcept; PROJ_DLL std::string toString() const; PROJ_DLL static WKTNodeNNPtr createFrom(const std::string &wkt, size_t indexStart = 0); protected: PROJ_INTERNAL static WKTNodeNNPtr createFrom(const std::string &wkt, size_t indexStart, int recLevel, size_t &indexEnd); // throw(ParsingException) private: friend class WKTParser; PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- PROJ_DLL util::BaseObjectNNPtr createFromUserInput(const std::string &text, const DatabaseContextPtr &dbContext, bool usePROJ4InitRules = false); PROJ_DLL util::BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx); // --------------------------------------------------------------------------- /** \brief Parse a WKT string into the appropriate subclass of util::BaseObject. */ class PROJ_GCC_DLL WKTParser { public: PROJ_DLL WKTParser(); //! @cond Doxygen_Suppress PROJ_DLL ~WKTParser(); //! @endcond PROJ_DLL WKTParser & attachDatabaseContext(const DatabaseContextPtr &dbContext); PROJ_DLL WKTParser &setStrict(bool strict); PROJ_DLL std::list warningList() const; PROJ_DLL std::list grammarErrorList() const; PROJ_DLL WKTParser &setUnsetIdentifiersIfIncompatibleDef(bool unset); PROJ_DLL util::BaseObjectNNPtr createFromWKT(const std::string &wkt); // throw(ParsingException) /** Guessed WKT "dialect" */ enum class PROJ_MSVC_DLL WKTGuessedDialect { /** \ref WKT2_2019 */ WKT2_2019, /** Deprecated alias for WKT2_2019 */ WKT2_2018 = WKT2_2019, /** \ref WKT2_2015 */ WKT2_2015, /** \ref WKT1 */ WKT1_GDAL, /** ESRI variant of WKT1 */ WKT1_ESRI, /** Not WKT / unrecognized */ NOT_WKT }; // cppcheck-suppress functionStatic PROJ_DLL WKTGuessedDialect guessDialect(const std::string &wkt) noexcept; private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Parse a PROJ string into the appropriate subclass of * util::BaseObject. */ class PROJ_GCC_DLL PROJStringParser { public: PROJ_DLL PROJStringParser(); //! @cond Doxygen_Suppress PROJ_DLL ~PROJStringParser(); //! @endcond PROJ_DLL PROJStringParser & attachDatabaseContext(const DatabaseContextPtr &dbContext); PROJ_DLL PROJStringParser &setUsePROJ4InitRules(bool enable); PROJ_DLL std::vector warningList() const; PROJ_DLL util::BaseObjectNNPtr createFromPROJString( const std::string &projString); // throw(ParsingException) PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJStringParser & attachContext(PJ_CONTEXT *ctx); //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Database context. * * A database context should be used only by one thread at a time. */ class PROJ_GCC_DLL DatabaseContext { public: //! @cond Doxygen_Suppress PROJ_DLL ~DatabaseContext(); //! @endcond PROJ_DLL static DatabaseContextNNPtr create(const std::string &databasePath = std::string(), const std::vector &auxiliaryDatabasePaths = std::vector(), PJ_CONTEXT *ctx = nullptr); PROJ_DLL const std::string &getPath() const; PROJ_DLL const char *getMetadata(const char *key) const; PROJ_DLL std::set getAuthorities() const; PROJ_DLL std::vector getDatabaseStructure() const; PROJ_DLL void startInsertStatementsSession(); PROJ_DLL std::string suggestsCodeFor(const common::IdentifiedObjectNNPtr &object, const std::string &authName, bool numericCode); PROJ_DLL std::vector getInsertStatementsFor( const common::IdentifiedObjectNNPtr &object, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities = {"EPSG", "PROJ"}); PROJ_DLL void stopInsertStatementsSession(); PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_DLL void * getSqliteHandle() const; PROJ_DLL static DatabaseContextNNPtr create(void *sqlite_handle); PROJ_INTERNAL bool lookForGridAlternative(const std::string &officialName, std::string &projFilename, std::string &projFormat, bool &inverse) const; PROJ_DLL bool lookForGridInfo(const std::string &projFilename, bool considerKnownGridsAsAvailable, std::string &fullFilename, std::string &packageName, std::string &url, bool &directDownload, bool &openLicense, bool &gridAvailable) const; PROJ_DLL unsigned int getQueryCounter() const; PROJ_INTERNAL std::string getProjGridName(const std::string &oldProjGridName); PROJ_INTERNAL void invalidateGridInfo(const std::string &projFilename); PROJ_INTERNAL std::string getOldProjGridName(const std::string &gridName); PROJ_INTERNAL std::string getAliasFromOfficialName(const std::string &officialName, const std::string &tableName, const std::string &source) const; PROJ_INTERNAL std::list getAliases(const std::string &authName, const std::string &code, const std::string &officialName, const std::string &tableName, const std::string &source) const; PROJ_INTERNAL bool isKnownName(const std::string &name, const std::string &tableName) const; PROJ_INTERNAL std::string getName(const std::string &tableName, const std::string &authName, const std::string &code) const; PROJ_INTERNAL std::string getTextDefinition(const std::string &tableName, const std::string &authName, const std::string &code) const; PROJ_INTERNAL std::vector getAllowedAuthorities(const std::string &sourceAuthName, const std::string &targetAuthName) const; PROJ_INTERNAL std::list> getNonDeprecated(const std::string &tableName, const std::string &authName, const std::string &code) const; PROJ_INTERNAL static std::vector getTransformationsForGridName(const DatabaseContextNNPtr &databaseContext, const std::string &gridName); PROJ_INTERNAL bool getAuthorityAndVersion(const std::string &versionedAuthName, std::string &authNameOut, std::string &versionOut); PROJ_INTERNAL bool getVersionedAuthority(const std::string &authName, const std::string &version, std::string &versionedAuthNameOut); PROJ_DLL std::vector getVersionedAuthoritiesFromName(const std::string &authName); PROJ_FOR_TEST bool toWGS84AutocorrectWrongValues(double &tx, double &ty, double &tz, double &rx, double &ry, double &rz, double &scale_difference) const; //! @endcond protected: PROJ_INTERNAL DatabaseContext(); INLINED_MAKE_SHARED PROJ_FRIEND(AuthorityFactory); private: PROJ_OPAQUE_PRIVATE_DATA DatabaseContext(const DatabaseContext &) = delete; DatabaseContext &operator=(const DatabaseContext &other) = delete; }; // --------------------------------------------------------------------------- class AuthorityFactory; /** Shared pointer of AuthorityFactory. */ using AuthorityFactoryPtr = std::shared_ptr; /** Non-null shared pointer of AuthorityFactory. */ using AuthorityFactoryNNPtr = util::nn; /** \brief Builds object from an authority database. * * A AuthorityFactory should be used only by one thread at a time. * * \remark Implements [AuthorityFactory] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/referencing/AuthorityFactory.html) * from \ref GeoAPI */ class PROJ_GCC_DLL AuthorityFactory { public: //! @cond Doxygen_Suppress PROJ_DLL ~AuthorityFactory(); //! @endcond PROJ_DLL util::BaseObjectNNPtr createObject(const std::string &code) const; PROJ_DLL common::UnitOfMeasureNNPtr createUnitOfMeasure(const std::string &code) const; PROJ_DLL metadata::ExtentNNPtr createExtent(const std::string &code) const; PROJ_DLL datum::PrimeMeridianNNPtr createPrimeMeridian(const std::string &code) const; PROJ_DLL std::string identifyBodyFromSemiMajorAxis(double a, double tolerance) const; PROJ_DLL datum::EllipsoidNNPtr createEllipsoid(const std::string &code) const; PROJ_DLL datum::DatumNNPtr createDatum(const std::string &code) const; PROJ_DLL datum::DatumEnsembleNNPtr createDatumEnsemble(const std::string &code, const std::string &type = std::string()) const; PROJ_DLL datum::GeodeticReferenceFrameNNPtr createGeodeticDatum(const std::string &code) const; PROJ_DLL datum::VerticalReferenceFrameNNPtr createVerticalDatum(const std::string &code) const; PROJ_DLL datum::EngineeringDatumNNPtr createEngineeringDatum(const std::string &code) const; PROJ_DLL cs::CoordinateSystemNNPtr createCoordinateSystem(const std::string &code) const; PROJ_DLL crs::GeodeticCRSNNPtr createGeodeticCRS(const std::string &code) const; PROJ_DLL crs::GeographicCRSNNPtr createGeographicCRS(const std::string &code) const; PROJ_DLL crs::VerticalCRSNNPtr createVerticalCRS(const std::string &code) const; PROJ_DLL crs::EngineeringCRSNNPtr createEngineeringCRS(const std::string &code) const; PROJ_DLL operation::ConversionNNPtr createConversion(const std::string &code) const; PROJ_DLL crs::ProjectedCRSNNPtr createProjectedCRS(const std::string &code) const; PROJ_DLL crs::CompoundCRSNNPtr createCompoundCRS(const std::string &code) const; PROJ_DLL crs::CRSNNPtr createCoordinateReferenceSystem(const std::string &code) const; PROJ_DLL coordinates::CoordinateMetadataNNPtr createCoordinateMetadata(const std::string &code) const; PROJ_DLL operation::CoordinateOperationNNPtr createCoordinateOperation(const std::string &code, bool usePROJAlternativeGridNames) const; PROJ_DLL std::vector createFromCoordinateReferenceSystemCodes( const std::string &sourceCRSCode, const std::string &targetCRSCode) const; PROJ_DLL std::list getGeoidModels(const std::string &code) const; PROJ_DLL const std::string &getAuthority() PROJ_PURE_DECL; /** Object type. */ enum class ObjectType { /** Object of type datum::PrimeMeridian */ PRIME_MERIDIAN, /** Object of type datum::Ellipsoid */ ELLIPSOID, /** Object of type datum::Datum (and derived classes) */ DATUM, /** Object of type datum::GeodeticReferenceFrame (and derived classes) */ GEODETIC_REFERENCE_FRAME, /** Object of type datum::VerticalReferenceFrame (and derived classes) */ VERTICAL_REFERENCE_FRAME, /** Object of type datum::EngineeringDatum */ ENGINEERING_DATUM, /** Object of type crs::CRS (and derived classes) */ CRS, /** Object of type crs::GeodeticCRS (and derived classes) */ GEODETIC_CRS, /** GEODETIC_CRS of type geocentric */ GEOCENTRIC_CRS, /** Object of type crs::GeographicCRS (and derived classes) */ GEOGRAPHIC_CRS, /** GEOGRAPHIC_CRS of type Geographic 2D */ GEOGRAPHIC_2D_CRS, /** GEOGRAPHIC_CRS of type Geographic 3D */ GEOGRAPHIC_3D_CRS, /** Object of type crs::ProjectedCRS (and derived classes) */ PROJECTED_CRS, /** Object of type crs::VerticalCRS (and derived classes) */ VERTICAL_CRS, /** Object of type crs::CompoundCRS (and derived classes) */ ENGINEERING_CRS, /** Object of type crs::EngineeringCRS */ COMPOUND_CRS, /** Object of type operation::CoordinateOperation (and derived classes) */ COORDINATE_OPERATION, /** Object of type operation::Conversion (and derived classes) */ CONVERSION, /** Object of type operation::Transformation (and derived classes) */ TRANSFORMATION, /** Object of type operation::ConcatenatedOperation (and derived classes) */ CONCATENATED_OPERATION, /** Object of type datum::DynamicGeodeticReferenceFrame */ DYNAMIC_GEODETIC_REFERENCE_FRAME, /** Object of type datum::DynamicVerticalReferenceFrame */ DYNAMIC_VERTICAL_REFERENCE_FRAME, /** Object of type datum::DatumEnsemble */ DATUM_ENSEMBLE, }; PROJ_DLL std::set getAuthorityCodes(const ObjectType &type, bool allowDeprecated = true) const; PROJ_DLL std::string getDescriptionText(const std::string &code) const; // non-standard /** CRS information */ struct CRSInfo { /** Authority name */ std::string authName; /** Code */ std::string code; /** Name */ std::string name; /** Type */ ObjectType type; /** Whether the object is deprecated */ bool deprecated; /** Whereas the west_lon_degree, south_lat_degree, east_lon_degree and * north_lat_degree fields are valid. */ bool bbox_valid; /** Western-most longitude of the area of use, in degrees. */ double west_lon_degree; /** Southern-most latitude of the area of use, in degrees. */ double south_lat_degree; /** Eastern-most longitude of the area of use, in degrees. */ double east_lon_degree; /** Northern-most latitude of the area of use, in degrees. */ double north_lat_degree; /** Name of the area of use. */ std::string areaName; /** Name of the projection method for a projected CRS. Might be empty * even for projected CRS in some cases. */ std::string projectionMethodName; /** Name of the celestial body of the CRS (e.g. "Earth") */ std::string celestialBodyName; //! @cond Doxygen_Suppress CRSInfo(); //! @endcond }; PROJ_DLL std::list getCRSInfoList() const; /** Unit information */ struct UnitInfo { /** Authority name */ std::string authName; /** Code */ std::string code; /** Name */ std::string name; /** Category: one of "linear", "linear_per_time", "angular", * "angular_per_time", "scale", "scale_per_time" or "time" */ std::string category; /** Conversion factor to the SI unit. * It might be 0 in some cases to indicate no known conversion factor. */ double convFactor; /** PROJ short name (may be empty) */ std::string projShortName; /** Whether the object is deprecated */ bool deprecated; //! @cond Doxygen_Suppress UnitInfo(); //! @endcond }; PROJ_DLL std::list getUnitList() const; /** Celestial Body information */ struct CelestialBodyInfo { /** Authority name */ std::string authName; /** Name */ std::string name; //! @cond Doxygen_Suppress CelestialBodyInfo(); //! @endcond }; PROJ_DLL std::list getCelestialBodyList() const; PROJ_DLL static AuthorityFactoryNNPtr create(const DatabaseContextNNPtr &context, const std::string &authorityName); PROJ_DLL const DatabaseContextNNPtr &databaseContext() const; PROJ_DLL std::vector createFromCoordinateReferenceSystemCodes( const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, bool tryReverseOrder = false, bool reportOnlyIntersectingTransformations = false, const metadata::ExtentPtr &intersectingExtent1 = nullptr, const metadata::ExtentPtr &intersectingExtent2 = nullptr) const; PROJ_DLL std::vector createFromCRSCodesWithIntermediates( const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, const std::vector> &intermediateCRSAuthCodes, ObjectType allowedIntermediateObjectType = ObjectType::CRS, const std::vector &allowedAuthorities = std::vector(), const metadata::ExtentPtr &intersectingExtent1 = nullptr, const metadata::ExtentPtr &intersectingExtent2 = nullptr) const; PROJ_DLL std::string getOfficialNameFromAlias( const std::string &aliasedName, const std::string &tableName, const std::string &source, bool tryEquivalentNameSpelling, std::string &outTableName, std::string &outAuthName, std::string &outCode) const; PROJ_DLL std::list createObjectsFromName(const std::string &name, const std::vector &allowedObjectTypes = std::vector(), bool approximateMatch = true, size_t limitResultCount = 0) const; PROJ_DLL std::list> listAreaOfUseFromName(const std::string &name, bool approximateMatch) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL std::list createEllipsoidFromExisting( const datum::EllipsoidNNPtr &ellipsoid) const; PROJ_INTERNAL std::list createGeodeticCRSFromDatum(const std::string &datum_auth_name, const std::string &datum_code, const std::string &geodetic_crs_type) const; PROJ_INTERNAL std::list createGeodeticCRSFromDatum(const datum::GeodeticReferenceFrameNNPtr &datum, const std::string &preferredAuthName, const std::string &geodetic_crs_type) const; PROJ_INTERNAL std::list createVerticalCRSFromDatum(const std::string &datum_auth_name, const std::string &datum_code) const; PROJ_INTERNAL std::list createGeodeticCRSFromEllipsoid(const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code, const std::string &geodetic_crs_type) const; PROJ_INTERNAL std::list createProjectedCRSFromExisting(const crs::ProjectedCRSNNPtr &crs) const; PROJ_INTERNAL std::list createCompoundCRSFromExisting(const crs::CompoundCRSNNPtr &crs) const; PROJ_INTERNAL crs::CRSNNPtr createCoordinateReferenceSystem(const std::string &code, bool allowCompound) const; PROJ_INTERNAL std::vector getTransformationsForGeoid(const std::string &geoidName, bool usePROJAlternativeGridNames) const; PROJ_INTERNAL std::vector createBetweenGeodeticCRSWithDatumBasedIntermediates( const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, const std::vector &allowedAuthorities, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const; typedef std::pair PairObjectName; PROJ_INTERNAL std::list createObjectsFromNameEx(const std::string &name, const std::vector &allowedObjectTypes = std::vector(), bool approximateMatch = true, size_t limitResultCount = 0, bool useAliases = true) const; PROJ_FOR_TEST std::vector getPointMotionOperationsFor(const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const; //! @endcond protected: PROJ_INTERNAL AuthorityFactory(const DatabaseContextNNPtr &context, const std::string &authorityName); PROJ_INTERNAL crs::GeodeticCRSNNPtr createGeodeticCRS(const std::string &code, bool geographicOnly) const; PROJ_INTERNAL operation::CoordinateOperationNNPtr createCoordinateOperation(const std::string &code, bool allowConcatenated, bool usePROJAlternativeGridNames, const std::string &type) const; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA PROJ_INTERNAL void createGeodeticDatumOrEnsemble(const std::string &code, datum::GeodeticReferenceFramePtr &outDatum, datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const; PROJ_INTERNAL void createVerticalDatumOrEnsemble(const std::string &code, datum::VerticalReferenceFramePtr &outDatum, datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const; }; // --------------------------------------------------------------------------- /** \brief Exception thrown when a factory can't create an instance of the * requested object. */ class PROJ_GCC_DLL FactoryException : public util::Exception { public: //! @cond Doxygen_Suppress PROJ_DLL explicit FactoryException(const char *message); PROJ_DLL explicit FactoryException(const std::string &message); PROJ_DLL FactoryException(const FactoryException &other); PROJ_DLL ~FactoryException() override; //! @endcond }; // --------------------------------------------------------------------------- /** \brief Exception thrown when an authority factory can't find the requested * authority code. */ class PROJ_GCC_DLL NoSuchAuthorityCodeException : public FactoryException { public: //! @cond Doxygen_Suppress PROJ_DLL explicit NoSuchAuthorityCodeException(const std::string &message, const std::string &authority, const std::string &code); PROJ_DLL NoSuchAuthorityCodeException(const NoSuchAuthorityCodeException &other); PROJ_DLL ~NoSuchAuthorityCodeException() override; //! @endcond PROJ_DLL const std::string &getAuthority() const; PROJ_DLL const std::string &getAuthorityCode() const; private: PROJ_OPAQUE_PRIVATE_DATA }; } // namespace io NS_PROJ_END #endif // IO_HH_INCLUDED proj-9.8.1/include/proj/nn.hpp000664 001750 001750 00000035521 15166171715 016202 0ustar00eveneven000000 000000 #pragma once /* * Copyright (c) 2015 Dropbox, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include namespace dropbox { namespace oxygen { // Marker type and value for use by nn below. struct i_promise_i_checked_for_null_t {}; static constexpr i_promise_i_checked_for_null_t i_promise_i_checked_for_null{}; // Helper to get the type pointed to by a raw or smart pointer. This can be // explicitly // specialized if need be to provide compatibility with user-defined smart // pointers. namespace nn_detail { template struct element_type { using type = typename T::element_type; }; template struct element_type { using type = Pointee; }; } template class nn; // Trait to check whether a given type is a non-nullable pointer template struct is_nn : public std::false_type {}; template struct is_nn> : public std::true_type {}; /* nn * * Wrapper around a pointer that is guaranteed to not be null. This works with * raw pointers * as well as any smart pointer: nn, nn>, * nn>, * etc. An nn can be used just like a PtrType. * * An nn can be constructed from another nn, if the underlying * type would * allow such construction. For example, nn> can be copied * and moved, but * nn> can only be moved; an nn> can be * explicitly * (but not implicitly) created from an nn; implicit upcasts are * allowed; and so on. * * Similarly, non-nullable pointers can be compared with regular or other * non-nullable * pointers, using the same rules as the underlying pointer types. * * This module also provides helpers for creating an nn from operations * that would * always return a non-null pointer: nn_make_unique, nn_make_shared, * nn_shared_from_this, and * nn_addr (a replacement for operator&). * * We abbreviate nn as nn_unique_ptr - it's a little more readable. * Likewise, * nn can be written as nn_shared_ptr. * * Finally, we define macros NN_CHECK_ASSERT and NN_CHECK_THROW, to convert a * nullable pointer * to a non-nullable pointer. At Dropbox, these use customized error-handling * infrastructure * and are in a separate file. We've included sample implementations here. */ template class nn { public: static_assert(!is_nn::value, "nn> is disallowed"); using element_type = typename nn_detail::element_type::type; // Pass through calls to operator* and operator-> transparently element_type &operator*() const { return *ptr; } element_type *operator->() const { return &*ptr; } // Expose the underlying PtrType operator const PtrType &() const & { return ptr; } operator PtrType &&() && { return std::move(ptr); } // Trying to use the assignment operator to assign a nn to a PtrType // using the // above conversion functions hits an ambiguous resolution bug in clang: // http://llvm.org/bugs/show_bug.cgi?id=18359 // While that exists, we can use these as simple ways of accessing the // underlying type // (instead of workarounds calling the operators explicitly or adding a // constructor call). const PtrType &as_nullable() const & { return ptr; } PtrType &&as_nullable() && { return std::move(ptr); } // Can't convert to bool (that would be silly). The explicit delete results in // "value of type 'nn<...>' is not contextually convertible to 'bool'", rather // than // "no viable conversion", which is a bit more clear. operator bool() const = delete; // Explicitly deleted constructors. These help produce clearer error messages, // as trying // to use them will result in clang printing the whole line, including the // comment. nn(std::nullptr_t) = delete; // nullptr is not allowed here nn &operator=(std::nullptr_t) = delete; // nullptr is not allowed here nn(PtrType) = delete; // must use NN_CHECK_ASSERT or NN_CHECK_THROW nn &operator=(PtrType) = delete; // must use NN_CHECK_ASSERT or NN_CHECK_THROW //PROJ_DLL ~nn(); // Semi-private constructor for use by NN_CHECK_ macros. explicit nn(i_promise_i_checked_for_null_t, const PtrType &arg) noexcept : ptr(arg) { } explicit nn(i_promise_i_checked_for_null_t, PtrType &&arg) noexcept : ptr(std::move(arg)) { } // Type-converting move and copy constructor. We have four separate cases // here, for // implicit and explicit move and copy. template ::value && !std::is_convertible::value, int>::type = 0> explicit nn(const nn &other) : ptr(other.operator const OtherType &()) {} template ::value && !std::is_convertible::value && !std::is_pointer::value, int>::type = 0> explicit nn(nn &&other) : ptr(std::move(other).operator OtherType &&()) {} template ::value, int>::type = 0> nn(const nn &other) : ptr(other.operator const OtherType &()) {} template < typename OtherType, typename std::enable_if::value && !std::is_pointer::value, int>::type = 0> nn(nn &&other) : ptr(std::move(other).operator OtherType &&()) {} // A type-converting move and copy assignment operator aren't necessary; // writing // "base_ptr = derived_ptr;" will run the type-converting constructor followed // by the // implicit move assignment operator. // Two-argument constructor, designed for use with the shared_ptr aliasing // constructor. // This will not be instantiated if PtrType doesn't have a suitable // constructor. template < typename OtherType, typename std::enable_if< std::is_constructible::value, int>::type = 0> nn(const nn &ownership_ptr, nn target_ptr) : ptr(ownership_ptr.operator const OtherType &(), target_ptr) {} // Comparisons. Other comparisons are implemented in terms of these. template friend bool operator==(const nn &, const R &); template friend bool operator==(const L &, const nn &); template friend bool operator==(const nn &, const nn &); template friend bool operator<(const nn &, const R &); template friend bool operator<(const L &, const nn &); template friend bool operator<(const nn &, const nn &); // ostream operator template friend std::ostream &operator<<(std::ostream &, const nn &); template element_type *get() const { return ptr.get(); } private: // Backing pointer PtrType ptr; }; // Base comparisons - these are friends of nn, so they can access .ptr // directly. template bool operator==(const nn &l, const R &r) { return l.ptr == r; } template bool operator==(const L &l, const nn &r) { return l == r.ptr; } template bool operator==(const nn &l, const nn &r) { return l.ptr == r.ptr; } template bool operator<(const nn &l, const R &r) { return l.ptr < r; } template bool operator<(const L &l, const nn &r) { return l < r.ptr; } template bool operator<(const nn &l, const nn &r) { return l.ptr < r.ptr; } template std::ostream &operator<<(std::ostream &os, const nn &p) { return os << p.ptr; } #define NN_DERIVED_OPERATORS(op, base) \ template \ bool operator op(const nn &l, const R &r) { \ return base; \ } \ template \ bool operator op(const L &l, const nn &r) { \ return base; \ } \ template \ bool operator op(const nn &l, const nn &r) { \ return base; \ } NN_DERIVED_OPERATORS(>, r < l) NN_DERIVED_OPERATORS(<=, !(l > r)) NN_DERIVED_OPERATORS(>=, !(l < r)) NN_DERIVED_OPERATORS(!=, !(l == r)) #undef NN_DERIVED_OPERATORS // Convenience typedefs template using nn_unique_ptr = nn>; template using nn_shared_ptr = nn>; template nn_unique_ptr nn_make_unique(Args &&... args) { return nn_unique_ptr( i_promise_i_checked_for_null, std::unique_ptr(new T(std::forward(args)...))); } template nn_shared_ptr nn_make_shared(Args &&... args) { return nn_shared_ptr(i_promise_i_checked_for_null, std::make_shared(std::forward(args)...)); } template class nn_enable_shared_from_this : public std::enable_shared_from_this { public: using std::enable_shared_from_this::enable_shared_from_this; nn_shared_ptr nn_shared_from_this() { return nn_shared_ptr(i_promise_i_checked_for_null, this->shared_from_this()); } nn_shared_ptr nn_shared_from_this() const { return nn_shared_ptr(i_promise_i_checked_for_null, this->shared_from_this()); } }; template nn nn_addr(T &object) { return nn(i_promise_i_checked_for_null, &object); } template nn nn_addr(const T &object) { return nn(i_promise_i_checked_for_null, &object); } /* Non-nullable equivalents of shared_ptr's specialized casting functions. * These convert through a shared_ptr since nn> lacks the * ref-count-sharing cast * constructor, but thanks to moves there shouldn't be any significant extra * cost. */ template nn_shared_ptr nn_static_pointer_cast(const nn_shared_ptr &org_ptr) { auto raw_ptr = static_cast::element_type *>(org_ptr.get()); std::shared_ptr nullable_ptr(org_ptr.as_nullable(), raw_ptr); return nn_shared_ptr(i_promise_i_checked_for_null, std::move(nullable_ptr)); } template std::shared_ptr nn_dynamic_pointer_cast(const nn_shared_ptr &org_ptr) { auto raw_ptr = dynamic_cast::element_type *>(org_ptr.get()); if (!raw_ptr) { return nullptr; } else { return std::shared_ptr(org_ptr.as_nullable(), raw_ptr); } } template nn_shared_ptr nn_const_pointer_cast(const nn_shared_ptr &org_ptr) { auto raw_ptr = const_cast::element_type *>(org_ptr.get()); std::shared_ptr nullable_ptr(org_ptr.as_nullable(), raw_ptr); return nn_shared_ptr(i_promise_i_checked_for_null, std::move(nullable_ptr)); } } } /* end namespace dropbox::oxygen */ namespace std { template struct hash<::dropbox::oxygen::nn> { using argument_type = ::dropbox::oxygen::nn; using result_type = size_t; result_type operator()(const argument_type &obj) const { return std::hash{}(obj.as_nullable()); } }; } /* These have to be macros because our internal versions invoke other macros * that use * __FILE__ and __LINE__, which we want to correctly point to the call site. * We're looking * forward to std::source_location :) * * The lambdas ensure that we only evaluate _e once. */ #include // NN_CHECK_ASSERT takes a pointer of type PT (e.g. raw pointer, std::shared_ptr // or std::unique_ptr) // and returns a non-nullable pointer of type nn. // Triggers an assertion if expression evaluates to null. #define NN_CHECK_ASSERT(_e) \ (([&](typename std::remove_reference::type p) { \ /* note: assert() alone is not sufficient here, because it might be \ * compiled out. */ \ assert(p &&#_e " must not be null"); \ if (!p) \ std::abort(); \ return dropbox::oxygen::nn< \ typename std::remove_reference::type>( \ dropbox::oxygen::i_promise_i_checked_for_null, std::move(p)); \ })(_e)) // NN_CHECK_THROW takes a pointer of type PT (e.g. raw pointer, std::shared_ptr // or std::unique_ptr) // and returns a non-nullable pointer of type nn. // Throws if expression evaluates to null. #define NN_CHECK_THROW(_e) \ (([&](typename std::remove_reference::type p) { \ if (!p) \ throw std::runtime_error(#_e " must not be null"); \ return dropbox::oxygen::nn< \ typename std::remove_reference::type>( \ dropbox::oxygen::i_promise_i_checked_for_null, std::move(p)); \ })(_e)) proj-9.8.1/include/proj/datum.hpp000664 001750 001750 00000076121 15166171715 016702 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef DATUM_HH_INCLUDED #define DATUM_HH_INCLUDED #include #include #include #include "common.hpp" #include "io.hpp" #include "util.hpp" NS_PROJ_START /** osgeo.proj.datum namespace \brief Datum (the relationship of a coordinate system to the body). */ namespace datum { // --------------------------------------------------------------------------- /** \brief Abstract class of the relationship of a coordinate system to an * object, thus creating a coordinate reference system. * * For geodetic and vertical coordinate reference systems, it relates a * coordinate system to the Earth (or the celestial body considered). With * other types of coordinate reference systems, the datum may relate the * coordinate system to another physical or * virtual object. A datum uses a parameter or set of parameters that determine * the location of the origin of the coordinate reference system. Each datum * subtype can be associated with only specific types of coordinate reference * systems. * * \remark Implements Datum from \ref ISO_19111_2019 */ class PROJ_GCC_DLL Datum : public common::ObjectUsage, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~Datum() override; //! @endcond PROJ_DLL const util::optional &anchorDefinition() const; PROJ_DLL const util::optional &anchorEpoch() const; PROJ_DLL const util::optional &publicationDate() const; PROJ_DLL const common::IdentifiedObjectPtr &conventionalRS() const; //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL Datum(); #ifdef DOXYGEN_ENABLED std::string *anchorDefinition_; Date *publicationDate_; common::IdentifiedObject *conventionalRS_; #endif protected: PROJ_INTERNAL void setAnchor(const util::optional &anchor); PROJ_INTERNAL void setAnchorEpoch(const util::optional &anchorEpoch); PROJ_INTERNAL void setProperties(const util::PropertyMap &properties); // throw(InvalidValueTypeException) private: PROJ_OPAQUE_PRIVATE_DATA Datum &operator=(const Datum &other) = delete; Datum(const Datum &other) = delete; }; /** Shared pointer of Datum */ using DatumPtr = std::shared_ptr; /** Non-null shared pointer of Datum */ using DatumNNPtr = util::nn; // --------------------------------------------------------------------------- class DatumEnsemble; /** Shared pointer of DatumEnsemble */ using DatumEnsemblePtr = std::shared_ptr; /** Non-null shared pointer of DatumEnsemble */ using DatumEnsembleNNPtr = util::nn; /** \brief A collection of two or more geodetic or vertical reference frames * (or if not geodetic or vertical reference frame, a collection of two or more * datums) which for all but the highest accuracy requirements may be * considered to be insignificantly different from each other. * * Every frame within the datum ensemble must be a realizations of the same * Terrestrial Reference System or Vertical Reference System. * * \remark Implements DatumEnsemble from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DatumEnsemble final : public common::ObjectUsage, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~DatumEnsemble() override; //! @endcond PROJ_DLL const std::vector &datums() const; PROJ_DLL const metadata::PositionalAccuracyNNPtr & positionalAccuracy() const; PROJ_DLL static DatumEnsembleNNPtr create( const util::PropertyMap &properties, const std::vector &datumsIn, const metadata::PositionalAccuracyNNPtr &accuracy); // throw(Exception) //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL static std::string ensembleNameToNonEnsembleName(const std::string &s); PROJ_FOR_TEST DatumNNPtr asDatum(const io::DatabaseContextPtr &dbContext) const; //! @endcond protected: #ifdef DOXYGEN_ENABLED Datum datums_[]; PositionalAccuracy positionalAccuracy_; #endif PROJ_INTERNAL DatumEnsemble(const std::vector &datumsIn, const metadata::PositionalAccuracyNNPtr &accuracy); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DatumEnsemble(const DatumEnsemble &other) = delete; DatumEnsemble &operator=(const DatumEnsemble &other) = delete; }; // --------------------------------------------------------------------------- class PrimeMeridian; /** Shared pointer of PrimeMeridian */ using PrimeMeridianPtr = std::shared_ptr; /** Non-null shared pointer of PrimeMeridian */ using PrimeMeridianNNPtr = util::nn; /** \brief The origin meridian from which longitude values are determined. * * \note The default value for prime meridian name is "Greenwich". When the * default applies, the value for the longitude shall be 0 (degrees). * * \remark Implements PrimeMeridian from \ref ISO_19111_2019 */ class PROJ_GCC_DLL PrimeMeridian final : public common::IdentifiedObject, public io::IPROJStringExportable, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~PrimeMeridian() override; //! @endcond PROJ_DLL const common::Angle &longitude() PROJ_PURE_DECL; // non-standard PROJ_DLL static PrimeMeridianNNPtr create(const util::PropertyMap &properties, const common::Angle &longitudeIn); PROJ_DLL static const PrimeMeridianNNPtr GREENWICH; PROJ_DLL static const PrimeMeridianNNPtr REFERENCE_MERIDIAN; PROJ_DLL static const PrimeMeridianNNPtr PARIS; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL static std::string getPROJStringWellKnownName(const common::Angle &angle); //! @endcond protected: #ifdef DOXYGEN_ENABLED Angle greenwichLongitude_; #endif PROJ_INTERNAL explicit PrimeMeridian( const common::Angle &angle = common::Angle()); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA PrimeMeridian(const PrimeMeridian &other) = delete; PrimeMeridian &operator=(const PrimeMeridian &other) = delete; PROJ_INTERNAL static const PrimeMeridianNNPtr createGREENWICH(); PROJ_INTERNAL static const PrimeMeridianNNPtr createREFERENCE_MERIDIAN(); PROJ_INTERNAL static const PrimeMeridianNNPtr createPARIS(); }; // --------------------------------------------------------------------------- class Ellipsoid; /** Shared pointer of Ellipsoid */ using EllipsoidPtr = std::shared_ptr; /** Non-null shared pointer of Ellipsoid */ using EllipsoidNNPtr = util::nn; /** \brief A geometric figure that can be used to describe the approximate * shape of an object. * * For the Earth an oblate biaxial ellipsoid is used: in mathematical terms, * it is a surface formed by the rotation of an ellipse about its minor axis. * * \remark Implements Ellipsoid from \ref ISO_19111_2019 */ class PROJ_GCC_DLL Ellipsoid final : public common::IdentifiedObject, public io::IPROJStringExportable, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~Ellipsoid() override; //! @endcond PROJ_DLL const common::Length &semiMajorAxis() PROJ_PURE_DECL; // Inlined from SecondDefiningParameter union PROJ_DLL const util::optional & inverseFlattening() PROJ_PURE_DECL; PROJ_DLL const util::optional & semiMinorAxis() PROJ_PURE_DECL; PROJ_DLL bool isSphere() PROJ_PURE_DECL; PROJ_DLL const util::optional & semiMedianAxis() PROJ_PURE_DECL; // non-standard PROJ_DLL double computedInverseFlattening() PROJ_PURE_DECL; PROJ_DLL double squaredEccentricity() PROJ_PURE_DECL; PROJ_DLL common::Length computeSemiMinorAxis() const; PROJ_DLL const std::string &celestialBody() PROJ_PURE_DECL; PROJ_DLL static const std::string EARTH; PROJ_DLL static EllipsoidNNPtr createSphere(const util::PropertyMap &properties, const common::Length &radius, const std::string &celestialBody = EARTH); PROJ_DLL static EllipsoidNNPtr createFlattenedSphere(const util::PropertyMap &properties, const common::Length &semiMajorAxisIn, const common::Scale &invFlattening, const std::string &celestialBody = EARTH); PROJ_DLL static EllipsoidNNPtr createTwoAxis(const util::PropertyMap &properties, const common::Length &semiMajorAxisIn, const common::Length &semiMinorAxisIn, const std::string &celestialBody = EARTH); PROJ_DLL EllipsoidNNPtr identify() const; PROJ_DLL static const EllipsoidNNPtr CLARKE_1866; PROJ_DLL static const EllipsoidNNPtr WGS84; PROJ_DLL static const EllipsoidNNPtr GRS1980; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) //! @endcond PROJ_INTERNAL static std::string guessBodyName(const io::DatabaseContextPtr &dbContext, double a, const std::string &ellpsName = std::string()); PROJ_INTERNAL bool lookForProjWellKnownEllps(std::string &projEllpsName, std::string &ellpsName) const; protected: #ifdef DOXYGEN_ENABLED common::Length semiMajorAxis_; common::Scale *inverseFlattening_; common::Length *semiMinorAxis_; bool isSphere_; common::Length *semiMedianAxis_; #endif PROJ_INTERNAL explicit Ellipsoid(const common::Length &radius, const std::string &celestialBody); PROJ_INTERNAL Ellipsoid(const common::Length &semiMajorAxisIn, const common::Scale &invFlattening, const std::string &celestialBody); PROJ_INTERNAL Ellipsoid(const common::Length &semiMajorAxisIn, const common::Length &semiMinorAxisIn, const std::string &celestialBody); PROJ_INTERNAL Ellipsoid(const Ellipsoid &other); INLINED_MAKE_SHARED PROJ_INTERNAL static const EllipsoidNNPtr createCLARKE_1866(); PROJ_INTERNAL static const EllipsoidNNPtr createWGS84(); PROJ_INTERNAL static const EllipsoidNNPtr createGRS1980(); private: PROJ_OPAQUE_PRIVATE_DATA Ellipsoid &operator=(const Ellipsoid &other) = delete; }; // --------------------------------------------------------------------------- class GeodeticReferenceFrame; /** Shared pointer of GeodeticReferenceFrame */ using GeodeticReferenceFramePtr = std::shared_ptr; /** Non-null shared pointer of GeodeticReferenceFrame */ using GeodeticReferenceFrameNNPtr = util::nn; /** \brief The definition of the position, scale and orientation of a geocentric * Cartesian 3D coordinate system relative to the Earth. * * It may also identify a defined ellipsoid (or sphere) that approximates * the shape of the Earth and which is centred on and aligned to this * geocentric coordinate system. Older geodetic datums define the location and * orientation of a defined ellipsoid (or sphere) that approximates the shape * of the earth. * * \note The terminology "Datum" is often used to mean a GeodeticReferenceFrame. * * \note In \ref ISO_19111_2007, this class was called GeodeticDatum. * * \remark Implements GeodeticReferenceFrame from \ref ISO_19111_2019 */ class PROJ_GCC_DLL GeodeticReferenceFrame : public Datum { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeodeticReferenceFrame() override; //! @endcond PROJ_DLL const PrimeMeridianNNPtr &primeMeridian() PROJ_PURE_DECL; // We constraint more than the standard into which the ellipsoid might // be omitted for a CRS with a non-ellipsoidal CS PROJ_DLL const EllipsoidNNPtr &ellipsoid() PROJ_PURE_DECL; // non-standard PROJ_DLL static GeodeticReferenceFrameNNPtr create(const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, const util::optional &anchor, const PrimeMeridianNNPtr &primeMeridian); PROJ_DLL static GeodeticReferenceFrameNNPtr create(const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, const util::optional &anchor, const util::optional &anchorEpoch, const PrimeMeridianNNPtr &primeMeridian); PROJ_DLL static const GeodeticReferenceFrameNNPtr EPSG_6267; // North American Datum 1927 PROJ_DLL static const GeodeticReferenceFrameNNPtr EPSG_6269; // North American Datum 1983 PROJ_DLL static const GeodeticReferenceFrameNNPtr EPSG_6326; // WGS 84 //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL bool isEquivalentToNoExactTypeCheck( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const; //! @endcond protected: #ifdef DOXYGEN_ENABLED PrimeMeridian primeMeridian_; Ellipsoid *ellipsoid_; #endif PROJ_INTERNAL GeodeticReferenceFrame(const EllipsoidNNPtr &ellipsoidIn, const PrimeMeridianNNPtr &primeMeridianIn); INLINED_MAKE_SHARED PROJ_INTERNAL static const GeodeticReferenceFrameNNPtr createEPSG_6267(); PROJ_INTERNAL static const GeodeticReferenceFrameNNPtr createEPSG_6269(); PROJ_INTERNAL static const GeodeticReferenceFrameNNPtr createEPSG_6326(); bool hasEquivalentNameToUsingAlias( const IdentifiedObject *other, const io::DatabaseContextPtr &dbContext) const override; private: PROJ_OPAQUE_PRIVATE_DATA GeodeticReferenceFrame(const GeodeticReferenceFrame &other) = delete; GeodeticReferenceFrame & operator=(const GeodeticReferenceFrame &other) = delete; }; // --------------------------------------------------------------------------- class DynamicGeodeticReferenceFrame; /** Shared pointer of DynamicGeodeticReferenceFrame */ using DynamicGeodeticReferenceFramePtr = std::shared_ptr; /** Non-null shared pointer of DynamicGeodeticReferenceFrame */ using DynamicGeodeticReferenceFrameNNPtr = util::nn; /** \brief A geodetic reference frame in which some of the parameters describe * time evolution of defining station coordinates. * * For example defining station coordinates having linear velocities to account * for crustal motion. * * \remark Implements DynamicGeodeticReferenceFrame from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DynamicGeodeticReferenceFrame final : public GeodeticReferenceFrame { public: //! @cond Doxygen_Suppress PROJ_DLL ~DynamicGeodeticReferenceFrame() override; //! @endcond PROJ_DLL const common::Measure &frameReferenceEpoch() const; PROJ_DLL const util::optional &deformationModelName() const; // non-standard PROJ_DLL static DynamicGeodeticReferenceFrameNNPtr create(const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, const util::optional &anchor, const PrimeMeridianNNPtr &primeMeridian, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond protected: #ifdef DOXYGEN_ENABLED Measure frameReferenceEpoch_; #endif PROJ_INTERNAL DynamicGeodeticReferenceFrame( const EllipsoidNNPtr &ellipsoidIn, const PrimeMeridianNNPtr &primeMeridianIn, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DynamicGeodeticReferenceFrame(const DynamicGeodeticReferenceFrame &other) = delete; DynamicGeodeticReferenceFrame & operator=(const DynamicGeodeticReferenceFrame &other) = delete; }; // --------------------------------------------------------------------------- /** \brief The specification of the method by which the vertical reference frame * is realized. * * \remark Implements RealizationMethod from \ref ISO_19111_2019 */ class PROJ_GCC_DLL RealizationMethod : public util::CodeList { public: PROJ_DLL static const RealizationMethod LEVELLING; PROJ_DLL static const RealizationMethod GEOID; PROJ_DLL static const RealizationMethod TIDAL; private: PROJ_FRIEND_OPTIONAL(RealizationMethod); PROJ_DLL explicit RealizationMethod( const std::string &nameIn = std::string()); PROJ_DLL RealizationMethod(const RealizationMethod &other) = default; PROJ_DLL RealizationMethod &operator=(const RealizationMethod &other); }; // --------------------------------------------------------------------------- class VerticalReferenceFrame; /** Shared pointer of VerticalReferenceFrame */ using VerticalReferenceFramePtr = std::shared_ptr; /** Non-null shared pointer of VerticalReferenceFrame */ using VerticalReferenceFrameNNPtr = util::nn; /** \brief A textual description and/or a set of parameters identifying a * particular reference level surface used as a zero-height or zero-depth * surface, including its position with respect to the Earth. * * \note In \ref ISO_19111_2007, this class was called VerticalDatum. * \remark Implements VerticalReferenceFrame from \ref ISO_19111_2019 */ class PROJ_GCC_DLL VerticalReferenceFrame : public Datum { public: //! @cond Doxygen_Suppress PROJ_DLL ~VerticalReferenceFrame() override; //! @endcond PROJ_DLL const util::optional &realizationMethod() const; // non-standard PROJ_DLL static VerticalReferenceFrameNNPtr create(const util::PropertyMap &properties, const util::optional &anchor = util::optional(), const util::optional &realizationMethodIn = util::optional()); PROJ_DLL static VerticalReferenceFrameNNPtr create(const util::PropertyMap &properties, const util::optional &anchor, const util::optional &anchorEpoch, const util::optional &realizationMethodIn = util::optional()); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL bool isEquivalentToNoExactTypeCheck( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL const std::string &getWKT1DatumType() const; //! @endcond protected: #ifdef DOXYGEN_ENABLED RealizationMethod realizationMethod_; #endif PROJ_INTERNAL explicit VerticalReferenceFrame( const util::optional &realizationMethodIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class DynamicVerticalReferenceFrame; /** Shared pointer of DynamicVerticalReferenceFrame */ using DynamicVerticalReferenceFramePtr = std::shared_ptr; /** Non-null shared pointer of DynamicVerticalReferenceFrame */ using DynamicVerticalReferenceFrameNNPtr = util::nn; /** \brief A vertical reference frame in which some of the defining parameters * have time dependency. * * For example defining station heights have velocity to account for * post-glacial isostatic rebound motion. * * \remark Implements DynamicVerticalReferenceFrame from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DynamicVerticalReferenceFrame final : public VerticalReferenceFrame { public: //! @cond Doxygen_Suppress PROJ_DLL ~DynamicVerticalReferenceFrame() override; //! @endcond PROJ_DLL const common::Measure &frameReferenceEpoch() const; PROJ_DLL const util::optional &deformationModelName() const; // non-standard PROJ_DLL static DynamicVerticalReferenceFrameNNPtr create(const util::PropertyMap &properties, const util::optional &anchor, const util::optional &realizationMethodIn, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond protected: #ifdef DOXYGEN_ENABLED Measure frameReferenceEpoch_; #endif PROJ_INTERNAL DynamicVerticalReferenceFrame( const util::optional &realizationMethodIn, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DynamicVerticalReferenceFrame(const DynamicVerticalReferenceFrame &other) = delete; DynamicVerticalReferenceFrame & operator=(const DynamicVerticalReferenceFrame &other) = delete; }; // --------------------------------------------------------------------------- class TemporalDatum; /** Shared pointer of TemporalDatum */ using TemporalDatumPtr = std::shared_ptr; /** Non-null shared pointer of TemporalDatum */ using TemporalDatumNNPtr = util::nn; /** \brief The definition of the relationship of a temporal coordinate system * to an object. The object is normally time on the Earth. * * \remark Implements TemporalDatum from \ref ISO_19111_2019 */ class PROJ_GCC_DLL TemporalDatum final : public Datum { public: //! @cond Doxygen_Suppress PROJ_DLL ~TemporalDatum() override; //! @endcond PROJ_DLL const common::DateTime &temporalOrigin() const; PROJ_DLL const std::string &calendar() const; PROJ_DLL static const std::string CALENDAR_PROLEPTIC_GREGORIAN; // non-standard PROJ_DLL static TemporalDatumNNPtr create(const util::PropertyMap &properties, const common::DateTime &temporalOriginIn, const std::string &calendarIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL TemporalDatum(const common::DateTime &temporalOriginIn, const std::string &calendarIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class EngineeringDatum; /** Shared pointer of EngineeringDatum */ using EngineeringDatumPtr = std::shared_ptr; /** Non-null shared pointer of EngineeringDatum */ using EngineeringDatumNNPtr = util::nn; /** \brief The definition of the origin and orientation of an engineering * coordinate reference system. * * \note The origin can be fixed with respect to the Earth (such as a defined * point at a construction site), or be a defined point on a moving vehicle * (such as on a ship or satellite), or a defined point of an image. * * \remark Implements EngineeringDatum from \ref ISO_19111_2019 */ class PROJ_GCC_DLL EngineeringDatum final : public Datum { public: //! @cond Doxygen_Suppress PROJ_DLL ~EngineeringDatum() override; //! @endcond // non-standard PROJ_DLL static EngineeringDatumNNPtr create(const util::PropertyMap &properties, const util::optional &anchor = util::optional()); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL EngineeringDatum(); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class ParametricDatum; /** Shared pointer of ParametricDatum */ using ParametricDatumPtr = std::shared_ptr; /** Non-null shared pointer of ParametricDatum */ using ParametricDatumNNPtr = util::nn; /** \brief Textual description and/or a set of parameters identifying a * particular reference surface used as the origin of a parametric coordinate * system, including its position with respect to the Earth. * * \remark Implements ParametricDatum from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ParametricDatum final : public Datum { public: //! @cond Doxygen_Suppress PROJ_DLL ~ParametricDatum() override; //! @endcond // non-standard PROJ_DLL static ParametricDatumNNPtr create(const util::PropertyMap &properties, const util::optional &anchor = util::optional()); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL ParametricDatum(); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; } // namespace datum NS_PROJ_END #endif // DATUM_HH_INCLUDED proj-9.8.1/include/proj/crs.hpp000664 001750 001750 00000163446 15166171715 016366 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef CRS_HH_INCLUDED #define CRS_HH_INCLUDED #include #include #include #include "common.hpp" #include "coordinateoperation.hpp" #include "coordinatesystem.hpp" #include "datum.hpp" #include "io.hpp" #include "util.hpp" NS_PROJ_START /** osgeo.proj.crs namespace \brief CRS (coordinate reference system = coordinate system with a datum). */ namespace crs { // --------------------------------------------------------------------------- class GeographicCRS; /** Shared pointer of GeographicCRS */ using GeographicCRSPtr = std::shared_ptr; /** Non-null shared pointer of GeographicCRS */ using GeographicCRSNNPtr = util::nn; class VerticalCRS; /** Shared pointer of VerticalCRS */ using VerticalCRSPtr = std::shared_ptr; /** Non-null shared pointer of VerticalCRS */ using VerticalCRSNNPtr = util::nn; class BoundCRS; /** Shared pointer of BoundCRS */ using BoundCRSPtr = std::shared_ptr; /** Non-null shared pointer of BoundCRS */ using BoundCRSNNPtr = util::nn; class CompoundCRS; /** Shared pointer of CompoundCRS */ using CompoundCRSPtr = std::shared_ptr; /** Non-null shared pointer of CompoundCRS */ using CompoundCRSNNPtr = util::nn; // --------------------------------------------------------------------------- class CRS; /** Shared pointer of CRS */ using CRSPtr = std::shared_ptr; /** Non-null shared pointer of CRS */ using CRSNNPtr = util::nn; /** \brief Abstract class modelling a coordinate reference system which is * usually single but may be compound. * * \remark Implements CRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL CRS : public common::ObjectUsage, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~CRS() override; //! @endcond // Non-standard PROJ_DLL bool isDynamic(bool considerWGS84AsDynamic = false) const; PROJ_DLL GeodeticCRSPtr extractGeodeticCRS() const; PROJ_DLL GeographicCRSPtr extractGeographicCRS() const; PROJ_DLL VerticalCRSPtr extractVerticalCRS() const; PROJ_DLL CRSNNPtr createBoundCRSToWGS84IfPossible( const io::DatabaseContextPtr &dbContext, operation::CoordinateOperationContext::IntermediateCRSUse allowIntermediateCRSUse) const; PROJ_DLL CRSNNPtr stripVerticalComponent() const; PROJ_DLL const BoundCRSPtr &canonicalBoundCRS() PROJ_PURE_DECL; PROJ_DLL std::list> identify(const io::AuthorityFactoryPtr &authorityFactory) const; PROJ_DLL std::list getNonDeprecated(const io::DatabaseContextNNPtr &dbContext) const; PROJ_DLL CRSNNPtr promoteTo3D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_DLL CRSNNPtr demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL const GeodeticCRS * extractGeodeticCRSRaw() const; PROJ_FOR_TEST CRSNNPtr shallowClone() const; PROJ_FOR_TEST CRSNNPtr alterName(const std::string &newName) const; PROJ_FOR_TEST CRSNNPtr alterId(const std::string &authName, const std::string &code) const; PROJ_INTERNAL const std::string &getExtensionProj4() const noexcept; PROJ_FOR_TEST CRSNNPtr alterGeodeticCRS(const GeodeticCRSNNPtr &newGeodCRS) const; PROJ_FOR_TEST CRSNNPtr alterCSLinearUnit(const common::UnitOfMeasure &unit) const; PROJ_INTERNAL bool mustAxisOrderBeSwitchedForVisualization() const; PROJ_INTERNAL CRSNNPtr applyAxisOrderReversal(const char *nameSuffix) const; PROJ_FOR_TEST CRSNNPtr normalizeForVisualization() const; PROJ_INTERNAL CRSNNPtr allowNonConformantWKT1Export() const; PROJ_INTERNAL CRSNNPtr attachOriginalCompoundCRS(const CompoundCRSNNPtr &compoundCRS) const; PROJ_INTERNAL CRSNNPtr promoteTo3D( const std::string &newName, const io::DatabaseContextPtr &dbContext, const cs::CoordinateSystemAxisNNPtr &verticalAxisIfNotAlreadyPresent) const; PROJ_INTERNAL bool hasImplicitCS() const; PROJ_INTERNAL bool hasOver() const; PROJ_INTERNAL static CRSNNPtr getResolvedCRS(const CRSNNPtr &crs, const io::AuthorityFactoryPtr &authFactory, metadata::ExtentPtr &extentOut); PROJ_INTERNAL std::string getOriginatingAuthName() const; //! @endcond protected: PROJ_INTERNAL CRS(); PROJ_INTERNAL CRS(const CRS &other); friend class BoundCRS; PROJ_INTERNAL void setCanonicalBoundCRS(const BoundCRSNNPtr &boundCRS); PROJ_INTERNAL virtual CRSNNPtr _shallowClone() const = 0; PROJ_INTERNAL virtual std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const; PROJ_INTERNAL void setProperties(const util::PropertyMap &properties); // throw(InvalidValueTypeException) private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Abstract class modelling a coordinate reference system consisting of * one Coordinate System and either one datum::Datum or one * datum::DatumEnsemble. * * \remark Implements SingleCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL SingleCRS : public CRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~SingleCRS() override; //! @endcond PROJ_DLL const datum::DatumPtr &datum() PROJ_PURE_DECL; PROJ_DLL const datum::DatumEnsemblePtr &datumEnsemble() PROJ_PURE_DECL; PROJ_DLL const cs::CoordinateSystemNNPtr &coordinateSystem() PROJ_PURE_DECL; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void exportDatumOrDatumEnsembleToWkt(io::WKTFormatter *formatter) const; // throw(io::FormattingException) PROJ_INTERNAL const datum::DatumNNPtr datumNonNull(const io::DatabaseContextPtr &dbContext) const; //! @endcond protected: PROJ_INTERNAL SingleCRS(const datum::DatumPtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::CoordinateSystemNNPtr &csIn); PROJ_INTERNAL SingleCRS(const SingleCRS &other); PROJ_INTERNAL bool baseIsEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const; private: PROJ_OPAQUE_PRIVATE_DATA SingleCRS &operator=(const SingleCRS &other) = delete; }; /** Shared pointer of SingleCRS */ using SingleCRSPtr = std::shared_ptr; /** Non-null shared pointer of SingleCRS */ using SingleCRSNNPtr = util::nn; // --------------------------------------------------------------------------- class GeodeticCRS; /** Shared pointer of GeodeticCRS */ using GeodeticCRSPtr = std::shared_ptr; /** Non-null shared pointer of GeodeticCRS */ using GeodeticCRSNNPtr = util::nn; /** \brief A coordinate reference system associated with a geodetic reference * frame and a three-dimensional Cartesian or spherical coordinate system. * * If the geodetic reference frame is dynamic or if the geodetic CRS has an * association to a velocity model then the geodetic CRS is dynamic, else it * is static. * * \remark Implements GeodeticCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL GeodeticCRS : virtual public SingleCRS, public io::IPROJStringExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeodeticCRS() override; //! @endcond PROJ_DLL const datum::GeodeticReferenceFramePtr &datum() PROJ_PURE_DECL; PROJ_DLL const datum::PrimeMeridianNNPtr &primeMeridian() PROJ_PURE_DECL; PROJ_DLL const datum::EllipsoidNNPtr &ellipsoid() PROJ_PURE_DECL; // coordinateSystem() returns either a EllipsoidalCS, SphericalCS or // CartesianCS PROJ_DLL const std::vector & velocityModel() PROJ_PURE_DECL; // Non-standard PROJ_DLL bool isGeocentric() PROJ_PURE_DECL; PROJ_DLL bool isSphericalPlanetocentric() PROJ_PURE_DECL; PROJ_DLL static GeodeticCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, const cs::SphericalCSNNPtr &cs); PROJ_DLL static GeodeticCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, const cs::CartesianCSNNPtr &cs); PROJ_DLL static GeodeticCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFramePtr &datum, const datum::DatumEnsemblePtr &datumEnsemble, const cs::SphericalCSNNPtr &cs); PROJ_DLL static GeodeticCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFramePtr &datum, const datum::DatumEnsemblePtr &datumEnsemble, const cs::CartesianCSNNPtr &cs); PROJ_DLL static const GeodeticCRSNNPtr EPSG_4978; // WGS 84 Geocentric PROJ_DLL std::list> identify(const io::AuthorityFactoryPtr &authorityFactory) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void addDatumInfoToPROJString(io::PROJStringFormatter *formatter) const; PROJ_INTERNAL const datum::GeodeticReferenceFrameNNPtr datumNonNull(const io::DatabaseContextPtr &dbContext) const; PROJ_INTERNAL void addGeocentricUnitConversionIntoPROJString( io::PROJStringFormatter *formatter) const; PROJ_INTERNAL void addAxisSwap(io::PROJStringFormatter *formatter) const; PROJ_INTERNAL void addAngularUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter) const; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::EllipsoidalCSNNPtr &csIn); PROJ_INTERNAL GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::SphericalCSNNPtr &csIn); PROJ_INTERNAL GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::CartesianCSNNPtr &csIn); PROJ_INTERNAL GeodeticCRS(const GeodeticCRS &other); PROJ_INTERNAL static GeodeticCRSNNPtr createEPSG_4978(); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL void _exportToJSONInternal( io::JSONFormatter *formatter, const char *objectName) const; // throw(FormattingException) PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; PROJ_INTERNAL bool _isEquivalentToNoTypeCheck(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA GeodeticCRS &operator=(const GeodeticCRS &other) = delete; }; // --------------------------------------------------------------------------- /** \brief A coordinate reference system associated with a geodetic reference * frame and a two- or three-dimensional ellipsoidal coordinate system. * * If the geodetic reference frame is dynamic or if the geographic CRS has an * association to a velocity model then the geodetic CRS is dynamic, else it is * static. * * \remark Implements GeographicCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL GeographicCRS : public GeodeticCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeographicCRS() override; //! @endcond PROJ_DLL const cs::EllipsoidalCSNNPtr &coordinateSystem() PROJ_PURE_DECL; // Non-standard PROJ_DLL static GeographicCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, const cs::EllipsoidalCSNNPtr &cs); PROJ_DLL static GeographicCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFramePtr &datum, const datum::DatumEnsemblePtr &datumEnsemble, const cs::EllipsoidalCSNNPtr &cs); PROJ_DLL GeographicCRSNNPtr demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_DLL static const GeographicCRSNNPtr EPSG_4267; // NAD27 PROJ_DLL static const GeographicCRSNNPtr EPSG_4269; // NAD83 PROJ_DLL static const GeographicCRSNNPtr EPSG_4326; // WGS 84 2D PROJ_DLL static const GeographicCRSNNPtr OGC_CRS84; // CRS84 (Long, Lat) PROJ_DLL static const GeographicCRSNNPtr EPSG_4807; // NTF Paris PROJ_DLL static const GeographicCRSNNPtr EPSG_4979; // WGS 84 3D PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_DLL bool is2DPartOf3D( util::nn other, const io::DatabaseContextPtr &dbContext = nullptr) PROJ_PURE_DECL; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL GeographicCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::EllipsoidalCSNNPtr &csIn); PROJ_INTERNAL GeographicCRS(const GeographicCRS &other); PROJ_INTERNAL static GeographicCRSNNPtr createEPSG_4267(); PROJ_INTERNAL static GeographicCRSNNPtr createEPSG_4269(); PROJ_INTERNAL static GeographicCRSNNPtr createEPSG_4326(); PROJ_INTERNAL static GeographicCRSNNPtr createOGC_CRS84(); PROJ_INTERNAL static GeographicCRSNNPtr createEPSG_4807(); PROJ_INTERNAL static GeographicCRSNNPtr createEPSG_4979(); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA GeographicCRS &operator=(const GeographicCRS &other) = delete; }; // --------------------------------------------------------------------------- /** \brief A coordinate reference system having a vertical reference frame and * a one-dimensional vertical coordinate system used for recording * gravity-related heights or depths. * * Vertical CRSs make use of the direction of gravity to define the concept of * height or depth, but the relationship with gravity may not be * straightforward. If the vertical reference frame is dynamic or if the * vertical CRS has an association to a velocity model then the CRS is dynamic, * else it is static. * * \note Ellipsoidal heights cannot be captured in a vertical coordinate * reference system. They exist only as an inseparable part of a 3D coordinate * tuple defined in a geographic 3D coordinate reference system. * * \remark Implements VerticalCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL VerticalCRS : virtual public SingleCRS, public io::IPROJStringExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~VerticalCRS() override; //! @endcond PROJ_DLL const datum::VerticalReferenceFramePtr datum() const; PROJ_DLL const cs::VerticalCSNNPtr coordinateSystem() const; PROJ_DLL const std::vector & geoidModel() PROJ_PURE_DECL; PROJ_DLL const std::vector & velocityModel() PROJ_PURE_DECL; PROJ_DLL static VerticalCRSNNPtr create(const util::PropertyMap &properties, const datum::VerticalReferenceFrameNNPtr &datumIn, const cs::VerticalCSNNPtr &csIn); PROJ_DLL static VerticalCRSNNPtr create(const util::PropertyMap &properties, const datum::VerticalReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::VerticalCSNNPtr &csIn); PROJ_DLL std::list> identify(const io::AuthorityFactoryPtr &authorityFactory) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void addLinearUnitConvert(io::PROJStringFormatter *formatter) const; PROJ_INTERNAL const datum::VerticalReferenceFrameNNPtr datumNonNull(const io::DatabaseContextPtr &dbContext) const; PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond protected: PROJ_INTERNAL VerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::VerticalCSNNPtr &csIn); PROJ_INTERNAL VerticalCRS(const VerticalCRS &other); PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; INLINED_MAKE_SHARED PROJ_INTERNAL CRSNNPtr _shallowClone() const override; private: PROJ_OPAQUE_PRIVATE_DATA VerticalCRS &operator=(const VerticalCRS &other) = delete; }; // --------------------------------------------------------------------------- /** \brief Abstract class modelling a single coordinate reference system that * is defined through the application of a specified coordinate conversion to * the definition of a previously established single coordinate reference * system referred to as the base CRS. * * A derived coordinate reference system inherits its datum (or datum ensemble) * from its base CRS. The coordinate conversion between the base and derived * coordinate reference system is implemented using the parameters and * formula(s) specified in the definition of the coordinate conversion. * * \remark Implements DerivedCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DerivedCRS : virtual public SingleCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~DerivedCRS() override; //! @endcond PROJ_DLL const SingleCRSNNPtr &baseCRS() PROJ_PURE_DECL; PROJ_DLL const operation::ConversionNNPtr derivingConversion() const; PROJ_PRIVATE : //! @cond Doxygen_Suppress // Use this method with extreme care ! It should never be used // to recreate a new Derived/ProjectedCRS ! PROJ_INTERNAL const operation::ConversionNNPtr & derivingConversionRef() PROJ_PURE_DECL; PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: PROJ_INTERNAL DerivedCRS(const SingleCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr &cs); PROJ_INTERNAL DerivedCRS(const DerivedCRS &other); PROJ_INTERNAL void setDerivingConversionCRS(); PROJ_INTERNAL void baseExportToWKT( io::WKTFormatter *formatter, const std::string &keyword, const std::string &baseKeyword) const; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL virtual const char *className() const = 0; private: PROJ_OPAQUE_PRIVATE_DATA DerivedCRS &operator=(const DerivedCRS &other) = delete; }; /** Shared pointer of DerivedCRS */ using DerivedCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedCRS */ using DerivedCRSNNPtr = util::nn; // --------------------------------------------------------------------------- class ProjectedCRS; /** Shared pointer of ProjectedCRS */ using ProjectedCRSPtr = std::shared_ptr; /** Non-null shared pointer of ProjectedCRS */ using ProjectedCRSNNPtr = util::nn; /** \brief A derived coordinate reference system which has a geodetic * (usually geographic) coordinate reference system as its base CRS, thereby * inheriting a geodetic reference frame, and is converted using a map * projection. * * It has a Cartesian coordinate system, usually two-dimensional but may be * three-dimensional; in the 3D case the base geographic CRSs ellipsoidal * height is passed through unchanged and forms the vertical axis of the * projected CRS's Cartesian coordinate system. * * \remark Implements ProjectedCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ProjectedCRS final : public DerivedCRS, public io::IPROJStringExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~ProjectedCRS() override; //! @endcond PROJ_DLL const GeodeticCRSNNPtr &baseCRS() PROJ_PURE_DECL; PROJ_DLL const cs::CartesianCSNNPtr &coordinateSystem() PROJ_PURE_DECL; PROJ_DLL static ProjectedCRSNNPtr create(const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn); PROJ_DLL std::list> identify(const io::AuthorityFactoryPtr &authorityFactory) const; PROJ_DLL ProjectedCRSNNPtr demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, bool axisSpecFound) const; PROJ_INTERNAL static void addUnitConvertAndAxisSwap( const std::vector &axisListIn, io::PROJStringFormatter *formatter, bool axisSpecFound); PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_FOR_TEST ProjectedCRSNNPtr alterParametersLinearUnit( const common::UnitOfMeasure &unit, bool convertToNewUnit) const; //! @endcond protected: PROJ_INTERNAL ProjectedCRS(const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn); PROJ_INTERNAL ProjectedCRS(const ProjectedCRS &other); PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; PROJ_INTERNAL const char *className() const override { return "ProjectedCRS"; } INLINED_MAKE_SHARED PROJ_INTERNAL CRSNNPtr _shallowClone() const override; private: PROJ_OPAQUE_PRIVATE_DATA ProjectedCRS &operator=(const ProjectedCRS &other) = delete; }; // --------------------------------------------------------------------------- class TemporalCRS; /** Shared pointer of TemporalCRS */ using TemporalCRSPtr = std::shared_ptr; /** Non-null shared pointer of TemporalCRS */ using TemporalCRSNNPtr = util::nn; /** \brief A coordinate reference system associated with a temporal datum and a * one-dimensional temporal coordinate system. * * \remark Implements TemporalCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL TemporalCRS : virtual public SingleCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~TemporalCRS() override; //! @endcond PROJ_DLL const datum::TemporalDatumNNPtr datum() const; PROJ_DLL const cs::TemporalCSNNPtr coordinateSystem() const; PROJ_DLL static TemporalCRSNNPtr create(const util::PropertyMap &properties, const datum::TemporalDatumNNPtr &datumIn, const cs::TemporalCSNNPtr &csIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: PROJ_INTERNAL TemporalCRS(const datum::TemporalDatumNNPtr &datumIn, const cs::TemporalCSNNPtr &csIn); PROJ_INTERNAL TemporalCRS(const TemporalCRS &other); INLINED_MAKE_SHARED PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; private: PROJ_OPAQUE_PRIVATE_DATA TemporalCRS &operator=(const TemporalCRS &other) = delete; }; // --------------------------------------------------------------------------- class EngineeringCRS; /** Shared pointer of EngineeringCRS */ using EngineeringCRSPtr = std::shared_ptr; /** Non-null shared pointer of EngineeringCRS */ using EngineeringCRSNNPtr = util::nn; /** \brief Contextually local coordinate reference system associated with an * engineering datum. * * It is applied either to activities on or near the surface of the Earth * without geodetic corrections, or on moving platforms such as road vehicles, * vessels, aircraft or spacecraft, or as the internal CRS of an image. * * In \ref WKT2, it maps to a ENGINEERINGCRS / ENGCRS keyword. In \ref WKT1, * it maps to a LOCAL_CS keyword. * * \remark Implements EngineeringCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL EngineeringCRS : virtual public SingleCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~EngineeringCRS() override; //! @endcond PROJ_DLL const datum::EngineeringDatumNNPtr datum() const; PROJ_DLL static EngineeringCRSNNPtr create(const util::PropertyMap &properties, const datum::EngineeringDatumNNPtr &datumIn, const cs::CoordinateSystemNNPtr &csIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: PROJ_INTERNAL EngineeringCRS(const datum::EngineeringDatumNNPtr &datumIn, const cs::CoordinateSystemNNPtr &csIn); PROJ_INTERNAL EngineeringCRS(const EngineeringCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA EngineeringCRS &operator=(const EngineeringCRS &other) = delete; }; // --------------------------------------------------------------------------- class ParametricCRS; /** Shared pointer of ParametricCRS */ using ParametricCRSPtr = std::shared_ptr; /** Non-null shared pointer of ParametricCRS */ using ParametricCRSNNPtr = util::nn; /** \brief Contextually local coordinate reference system associated with an * engineering datum. * * This is applied either to activities on or near the surface of the Earth * without geodetic corrections, or on moving platforms such as road vehicles * vessels, aircraft or spacecraft, or as the internal CRS of an image. * * \remark Implements ParametricCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL ParametricCRS : virtual public SingleCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~ParametricCRS() override; //! @endcond PROJ_DLL const datum::ParametricDatumNNPtr datum() const; PROJ_DLL const cs::ParametricCSNNPtr coordinateSystem() const; PROJ_DLL static ParametricCRSNNPtr create(const util::PropertyMap &properties, const datum::ParametricDatumNNPtr &datumIn, const cs::ParametricCSNNPtr &csIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: PROJ_INTERNAL ParametricCRS(const datum::ParametricDatumNNPtr &datumIn, const cs::ParametricCSNNPtr &csIn); PROJ_INTERNAL ParametricCRS(const ParametricCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA ParametricCRS &operator=(const ParametricCRS &other) = delete; }; // --------------------------------------------------------------------------- /** \brief Exception thrown when attempting to create an invalid compound CRS */ class PROJ_GCC_DLL InvalidCompoundCRSException : public util::Exception { public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit InvalidCompoundCRSException(const char *message); PROJ_INTERNAL explicit InvalidCompoundCRSException( const std::string &message); PROJ_DLL InvalidCompoundCRSException(const InvalidCompoundCRSException &other); PROJ_DLL ~InvalidCompoundCRSException() override; //! @endcond }; // --------------------------------------------------------------------------- /** \brief A coordinate reference system describing the position of points * through two or more independent single coordinate reference systems. * * \note Two coordinate reference systems are independent of each other * if coordinate values in one cannot be converted or transformed into * coordinate values in the other. * * \note As a departure to \ref ISO_19111_2019, we allow to build a CompoundCRS * from CRS objects, whereas ISO19111:2019 restricts the components to * SingleCRS. * * \remark Implements CompoundCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL CompoundCRS final : public CRS, public io::IPROJStringExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~CompoundCRS() override; //! @endcond PROJ_DLL const std::vector & componentReferenceSystems() PROJ_PURE_DECL; PROJ_DLL std::list> identify(const io::AuthorityFactoryPtr &authorityFactory) const; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond PROJ_DLL static CompoundCRSNNPtr create(const util::PropertyMap &properties, const std::vector &components); // throw InvalidCompoundCRSException //! @cond Doxygen_Suppress PROJ_INTERNAL static CRSNNPtr createLax(const util::PropertyMap &properties, const std::vector &components, const io::DatabaseContextPtr &dbContext); // throw InvalidCompoundCRSException //! @endcond protected: // relaxed: standard say SingleCRSNNPtr PROJ_INTERNAL explicit CompoundCRS(const std::vector &components); PROJ_INTERNAL CompoundCRS(const CompoundCRS &other); PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA CompoundCRS &operator=(const CompoundCRS &other) = delete; }; // --------------------------------------------------------------------------- /** \brief A coordinate reference system with an associated transformation to * a target/hub CRS. * * The definition of a CRS is not dependent upon any relationship to an * independent CRS. However in an implementation that merges datasets * referenced to differing CRSs, it is sometimes useful to associate the * definition of the transformation that has been used with the CRS definition. * This facilitates the interrelationship of CRS by concatenating * transformations via a common or hub CRS. This is sometimes referred to as * "early-binding". \ref WKT2 permits the association of an abridged coordinate * transformation description with a coordinate reference system description in * a single text string. In a BoundCRS, the abridged coordinate transformation * is applied to the source CRS with the target CRS being the common or hub * system. * * Coordinates referring to a BoundCRS are expressed into its source/base CRS. * * This abstraction can for example model the concept of TOWGS84 datum shift * present in \ref WKT1. * * \note Contrary to other CRS classes of this package, there is no * \ref ISO_19111_2019 modelling of a BoundCRS. * * \remark Implements BoundCRS from \ref WKT2 */ class PROJ_GCC_DLL BoundCRS final : public CRS, public io::IPROJStringExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~BoundCRS() override; //! @endcond PROJ_DLL const CRSNNPtr &baseCRS() PROJ_PURE_DECL; PROJ_DLL CRSNNPtr baseCRSWithCanonicalBoundCRS() const; PROJ_DLL const CRSNNPtr &hubCRS() PROJ_PURE_DECL; PROJ_DLL const operation::TransformationNNPtr & transformation() PROJ_PURE_DECL; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond PROJ_DLL static BoundCRSNNPtr create(const util::PropertyMap &properties, const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn); PROJ_DLL static BoundCRSNNPtr create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn); PROJ_DLL static BoundCRSNNPtr createFromTOWGS84(const CRSNNPtr &baseCRSIn, const std::vector &TOWGS84Parameters); PROJ_DLL static BoundCRSNNPtr createFromNadgrids(const CRSNNPtr &baseCRSIn, const std::string &filename); protected: PROJ_INTERNAL BoundCRS(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn); PROJ_INTERNAL BoundCRS(const BoundCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL BoundCRSNNPtr shallowCloneAsBoundCRS() const; PROJ_INTERNAL bool isTOWGS84Compatible() const; PROJ_INTERNAL std::string getHDatumPROJ4GRIDS(const io::DatabaseContextPtr &databaseContext) const; PROJ_INTERNAL std::string getVDatumPROJ4GRIDS(const crs::GeographicCRS *geogCRSOfCompoundCRS, const char **outGeoidCRSValue) const; PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA BoundCRS &operator=(const BoundCRS &other) = delete; }; // --------------------------------------------------------------------------- class DerivedGeodeticCRS; /** Shared pointer of DerivedGeodeticCRS */ using DerivedGeodeticCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedGeodeticCRS */ using DerivedGeodeticCRSNNPtr = util::nn; /** \brief A derived coordinate reference system which has either a geodetic * or a geographic coordinate reference system as its base CRS, thereby * inheriting a geodetic reference frame, and associated with a 3D Cartesian * or spherical coordinate system. * * \remark Implements DerivedGeodeticCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DerivedGeodeticCRS final : public GeodeticCRS, public DerivedCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~DerivedGeodeticCRS() override; //! @endcond PROJ_DLL const GeodeticCRSNNPtr baseCRS() const; PROJ_DLL static DerivedGeodeticCRSNNPtr create(const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn); PROJ_DLL static DerivedGeodeticCRSNNPtr create(const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::SphericalCSNNPtr &csIn); //! @cond Doxygen_Suppress void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override { return DerivedCRS::_exportToJSON(formatter); } //! @endcond protected: PROJ_INTERNAL DerivedGeodeticCRS(const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn); PROJ_INTERNAL DerivedGeodeticCRS(const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::SphericalCSNNPtr &csIn); PROJ_INTERNAL DerivedGeodeticCRS(const DerivedGeodeticCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; // cppcheck-suppress functionStatic PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) PROJ_INTERNAL const char *className() const override { return "DerivedGeodeticCRS"; } INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DerivedGeodeticCRS &operator=(const DerivedGeodeticCRS &other) = delete; }; // --------------------------------------------------------------------------- class DerivedGeographicCRS; /** Shared pointer of DerivedGeographicCRS */ using DerivedGeographicCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedGeographicCRS */ using DerivedGeographicCRSNNPtr = util::nn; /** \brief A derived coordinate reference system which has either a geodetic or * a geographic coordinate reference system as its base CRS, thereby inheriting * a geodetic reference frame, and an ellipsoidal coordinate system. * * A derived geographic CRS can be based on a geodetic CRS only if that * geodetic CRS definition includes an ellipsoid. * * \remark Implements DerivedGeographicCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DerivedGeographicCRS final : public GeographicCRS, public DerivedCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~DerivedGeographicCRS() override; //! @endcond PROJ_DLL const GeodeticCRSNNPtr baseCRS() const; PROJ_DLL static DerivedGeographicCRSNNPtr create(const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::EllipsoidalCSNNPtr &csIn); PROJ_DLL DerivedGeographicCRSNNPtr demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override { return DerivedCRS::_exportToJSON(formatter); } //! @endcond protected: PROJ_INTERNAL DerivedGeographicCRS(const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::EllipsoidalCSNNPtr &csIn); PROJ_INTERNAL DerivedGeographicCRS(const DerivedGeographicCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; PROJ_INTERNAL const char *className() const override { return "DerivedGeographicCRS"; } // cppcheck-suppress functionStatic PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DerivedGeographicCRS &operator=(const DerivedGeographicCRS &other) = delete; }; // --------------------------------------------------------------------------- class DerivedProjectedCRS; /** Shared pointer of DerivedProjectedCRS */ using DerivedProjectedCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedProjectedCRS */ using DerivedProjectedCRSNNPtr = util::nn; /** \brief A derived coordinate reference system which has a projected * coordinate reference system as its base CRS, thereby inheriting a geodetic * reference frame, but also inheriting the distortion characteristics of the * base projected CRS. * * A DerivedProjectedCRS is not a ProjectedCRS. * * \remark Implements DerivedProjectedCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DerivedProjectedCRS final : public DerivedCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~DerivedProjectedCRS() override; //! @endcond PROJ_DLL const ProjectedCRSNNPtr baseCRS() const; PROJ_DLL static DerivedProjectedCRSNNPtr create(const util::PropertyMap &properties, const ProjectedCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr &csIn); PROJ_DLL DerivedProjectedCRSNNPtr demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter) const; //! @endcond protected: PROJ_INTERNAL DerivedProjectedCRS(const ProjectedCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr &csIn); PROJ_INTERNAL DerivedProjectedCRS(const DerivedProjectedCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL const char *className() const override { return "DerivedProjectedCRS"; } INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DerivedProjectedCRS &operator=(const DerivedProjectedCRS &other) = delete; }; // --------------------------------------------------------------------------- class DerivedVerticalCRS; /** Shared pointer of DerivedVerticalCRS */ using DerivedVerticalCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedVerticalCRS */ using DerivedVerticalCRSNNPtr = util::nn; /** \brief A derived coordinate reference system which has a vertical * coordinate reference system as its base CRS, thereby inheriting a vertical * reference frame, and a vertical coordinate system. * * \remark Implements DerivedVerticalCRS from \ref ISO_19111_2019 */ class PROJ_GCC_DLL DerivedVerticalCRS final : public VerticalCRS, public DerivedCRS { public: //! @cond Doxygen_Suppress PROJ_DLL ~DerivedVerticalCRS() override; //! @endcond PROJ_DLL const VerticalCRSNNPtr baseCRS() const; PROJ_DLL static DerivedVerticalCRSNNPtr create(const util::PropertyMap &properties, const VerticalCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::VerticalCSNNPtr &csIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override { return DerivedCRS::_exportToJSON(formatter); } //! @endcond protected: PROJ_INTERNAL DerivedVerticalCRS(const VerticalCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::VerticalCSNNPtr &csIn); PROJ_INTERNAL DerivedVerticalCRS(const DerivedVerticalCRS &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL std::list> _identify(const io::AuthorityFactoryPtr &authorityFactory) const override; PROJ_INTERNAL const char *className() const override { return "DerivedVerticalCRS"; } // cppcheck-suppress functionStatic PROJ_INTERNAL void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA DerivedVerticalCRS &operator=(const DerivedVerticalCRS &other) = delete; }; // --------------------------------------------------------------------------- /** \brief Template representing a derived coordinate reference system. */ template class PROJ_GCC_DLL DerivedCRSTemplate final : public DerivedCRSTraits::BaseType, public DerivedCRS { protected: /** Base type */ typedef typename DerivedCRSTraits::BaseType BaseType; /** CSType */ typedef typename DerivedCRSTraits::CSType CSType; public: //! @cond Doxygen_Suppress PROJ_DLL ~DerivedCRSTemplate() override; //! @endcond /** Non-null shared pointer of DerivedCRSTemplate */ typedef typename util::nn> NNPtr; /** Non-null shared pointer of BaseType */ typedef util::nn> BaseNNPtr; /** Non-null shared pointer of CSType */ typedef util::nn> CSNNPtr; /** \brief Return the base CRS of a DerivedCRSTemplate. * * @return the base CRS. */ PROJ_DLL const BaseNNPtr baseCRS() const; /** \brief Instantiate a DerivedCRSTemplate from a base CRS, a deriving * conversion and a cs::CoordinateSystem. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn base CRS. * @param derivingConversionIn the deriving conversion from the base CRS to * this * CRS. * @param csIn the coordinate system. * @return new DerivedCRSTemplate. */ PROJ_DLL static NNPtr create(const util::PropertyMap &properties, const BaseNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn); //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override { return DerivedCRS::_exportToJSON(formatter); } //! @endcond protected: PROJ_INTERNAL DerivedCRSTemplate(const BaseNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn); // cppcheck-suppress noExplicitConstructor PROJ_INTERNAL DerivedCRSTemplate(const DerivedCRSTemplate &other); PROJ_INTERNAL CRSNNPtr _shallowClone() const override; PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; PROJ_INTERNAL const char *className() const override; INLINED_MAKE_SHARED private: struct PROJ_INTERNAL Private; std::unique_ptr d; DerivedCRSTemplate &operator=(const DerivedCRSTemplate &other) = delete; }; // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PROJ_GCC_DLL DerivedEngineeringCRSTraits { typedef EngineeringCRS BaseType; typedef cs::CoordinateSystem CSType; // old x86_64-w64-mingw32-g++ has issues with static variables. use method // instead inline static const std::string &CRSName(); inline static const std::string &WKTKeyword(); inline static const std::string &WKTBaseKeyword(); static const bool wkt2_2019_only = true; }; //! @endcond /** \brief A derived coordinate reference system which has an engineering * coordinate reference system as its base CRS, thereby inheriting an * engineering datum, and is associated with one of the coordinate system * types for an EngineeringCRS * * \remark Implements DerivedEngineeringCRS from \ref ISO_19111_2019 */ #ifdef DOXYGEN_ENABLED class DerivedEngineeringCRS : public DerivedCRSTemplate {}; #else using DerivedEngineeringCRS = DerivedCRSTemplate; #endif #ifndef DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE extern template class DerivedCRSTemplate; #endif /** Shared pointer of DerivedEngineeringCRS */ using DerivedEngineeringCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedEngineeringCRS */ using DerivedEngineeringCRSNNPtr = util::nn; // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PROJ_GCC_DLL DerivedParametricCRSTraits { typedef ParametricCRS BaseType; typedef cs::ParametricCS CSType; // old x86_64-w64-mingw32-g++ has issues with static variables. use method // instead inline static const std::string &CRSName(); inline static const std::string &WKTKeyword(); inline static const std::string &WKTBaseKeyword(); static const bool wkt2_2019_only = false; }; //! @endcond /** \brief A derived coordinate reference system which has a parametric * coordinate reference system as its base CRS, thereby inheriting a parametric * datum, and a parametric coordinate system. * * \remark Implements DerivedParametricCRS from \ref ISO_19111_2019 */ #ifdef DOXYGEN_ENABLED class DerivedParametricCRS : public DerivedCRSTemplate {}; #else using DerivedParametricCRS = DerivedCRSTemplate; #endif #ifndef DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE extern template class DerivedCRSTemplate; #endif /** Shared pointer of DerivedParametricCRS */ using DerivedParametricCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedParametricCRS */ using DerivedParametricCRSNNPtr = util::nn; // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PROJ_GCC_DLL DerivedTemporalCRSTraits { typedef TemporalCRS BaseType; typedef cs::TemporalCS CSType; // old x86_64-w64-mingw32-g++ has issues with static variables. use method // instead inline static const std::string &CRSName(); inline static const std::string &WKTKeyword(); inline static const std::string &WKTBaseKeyword(); static const bool wkt2_2019_only = false; }; //! @endcond /** \brief A derived coordinate reference system which has a temporal * coordinate reference system as its base CRS, thereby inheriting a temporal * datum, and a temporal coordinate system. * * \remark Implements DerivedTemporalCRS from \ref ISO_19111_2019 */ #ifdef DOXYGEN_ENABLED class DerivedTemporalCRS : public DerivedCRSTemplate { }; #else using DerivedTemporalCRS = DerivedCRSTemplate; #endif #ifndef DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE extern template class DerivedCRSTemplate; #endif /** Shared pointer of DerivedTemporalCRS */ using DerivedTemporalCRSPtr = std::shared_ptr; /** Non-null shared pointer of DerivedTemporalCRS */ using DerivedTemporalCRSNNPtr = util::nn; // --------------------------------------------------------------------------- } // namespace crs NS_PROJ_END #endif // CRS_HH_INCLUDED proj-9.8.1/include/proj/metadata.hpp000664 001750 001750 00000040620 15166171715 017343 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef METADATA_HH_INCLUDED #define METADATA_HH_INCLUDED #include #include #include #include "io.hpp" #include "util.hpp" NS_PROJ_START namespace common { class UnitOfMeasure; using UnitOfMeasurePtr = std::shared_ptr; using UnitOfMeasureNNPtr = util::nn; class IdentifiedObject; } // namespace common /** osgeo.proj.metadata namespace * * \brief Common classes from \ref ISO_19115 standard */ namespace metadata { // --------------------------------------------------------------------------- /** \brief Standardized resource reference. * * A citation contains a title. * * \remark Simplified version of [Citation] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/citation/Citation.html) * from \ref GeoAPI */ class PROJ_GCC_DLL Citation : public util::BaseObject { public: PROJ_DLL explicit Citation(const std::string &titleIn); //! @cond Doxygen_Suppress PROJ_DLL Citation(); PROJ_DLL Citation(const Citation &other); PROJ_DLL ~Citation() override; //! @endcond PROJ_DLL const util::optional &title() PROJ_PURE_DECL; protected: PROJ_FRIEND_OPTIONAL(Citation); PROJ_INTERNAL Citation &operator=(const Citation &other); private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class GeographicExtent; /** Shared pointer of GeographicExtent. */ using GeographicExtentPtr = std::shared_ptr; /** Non-null shared pointer of GeographicExtent. */ using GeographicExtentNNPtr = util::nn; /** \brief Base interface for geographic area of the dataset. * * \remark Simplified version of [GeographicExtent] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/extent/GeographicExtent.html) * from \ref GeoAPI */ class PROJ_GCC_DLL GeographicExtent : public util::BaseObject, public util::IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeographicExtent() override; //! @endcond // GeoAPI has a getInclusion() method. We assume that it is included for our // use //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override = 0; //! @endcond /** \brief Returns whether this extent contains the other one. */ PROJ_DLL virtual bool contains(const GeographicExtentNNPtr &other) const = 0; /** \brief Returns whether this extent intersects the other one. */ PROJ_DLL virtual bool intersects(const GeographicExtentNNPtr &other) const = 0; /** \brief Returns the intersection of this extent with another one. */ PROJ_DLL virtual GeographicExtentPtr intersection(const GeographicExtentNNPtr &other) const = 0; protected: PROJ_INTERNAL GeographicExtent(); private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class GeographicBoundingBox; /** Shared pointer of GeographicBoundingBox. */ using GeographicBoundingBoxPtr = std::shared_ptr; /** Non-null shared pointer of GeographicBoundingBox. */ using GeographicBoundingBoxNNPtr = util::nn; /** \brief Geographic position of the dataset. * * This is only an approximate so specifying the coordinate reference system is * unnecessary. * * \remark Implements [GeographicBoundingBox] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/extent/GeographicBoundingBox.html) * from \ref GeoAPI */ class PROJ_GCC_DLL GeographicBoundingBox : public GeographicExtent { public: //! @cond Doxygen_Suppress PROJ_DLL ~GeographicBoundingBox() override; //! @endcond PROJ_DLL double westBoundLongitude() PROJ_PURE_DECL; PROJ_DLL double southBoundLatitude() PROJ_PURE_DECL; PROJ_DLL double eastBoundLongitude() PROJ_PURE_DECL; PROJ_DLL double northBoundLatitude() PROJ_PURE_DECL; PROJ_DLL static GeographicBoundingBoxNNPtr create(double west, double south, double east, double north); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond PROJ_INTERNAL bool contains(const GeographicExtentNNPtr &other) const override; PROJ_INTERNAL bool intersects(const GeographicExtentNNPtr &other) const override; PROJ_INTERNAL GeographicExtentPtr intersection(const GeographicExtentNNPtr &other) const override; protected: PROJ_INTERNAL GeographicBoundingBox(double west, double south, double east, double north); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class TemporalExtent; /** Shared pointer of TemporalExtent. */ using TemporalExtentPtr = std::shared_ptr; /** Non-null shared pointer of TemporalExtent. */ using TemporalExtentNNPtr = util::nn; /** \brief Time period covered by the content of the dataset. * * \remark Simplified version of [TemporalExtent] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/extent/TemporalExtent.html) * from \ref GeoAPI */ class PROJ_GCC_DLL TemporalExtent : public util::BaseObject, public util::IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL ~TemporalExtent() override; //! @endcond PROJ_DLL const std::string &start() PROJ_PURE_DECL; PROJ_DLL const std::string &stop() PROJ_PURE_DECL; PROJ_DLL static TemporalExtentNNPtr create(const std::string &start, const std::string &stop); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond PROJ_DLL bool contains(const TemporalExtentNNPtr &other) const; PROJ_DLL bool intersects(const TemporalExtentNNPtr &other) const; protected: PROJ_INTERNAL TemporalExtent(const std::string &start, const std::string &stop); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class VerticalExtent; /** Shared pointer of VerticalExtent. */ using VerticalExtentPtr = std::shared_ptr; /** Non-null shared pointer of VerticalExtent. */ using VerticalExtentNNPtr = util::nn; /** \brief Vertical domain of dataset. * * \remark Simplified version of [VerticalExtent] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/extent/VerticalExtent.html) * from \ref GeoAPI */ class PROJ_GCC_DLL VerticalExtent : public util::BaseObject, public util::IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL ~VerticalExtent() override; //! @endcond PROJ_DLL double minimumValue() PROJ_PURE_DECL; PROJ_DLL double maximumValue() PROJ_PURE_DECL; PROJ_DLL common::UnitOfMeasureNNPtr &unit() PROJ_PURE_DECL; PROJ_DLL static VerticalExtentNNPtr create(double minimumValue, double maximumValue, const common::UnitOfMeasureNNPtr &unitIn); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond PROJ_DLL bool contains(const VerticalExtentNNPtr &other) const; PROJ_DLL bool intersects(const VerticalExtentNNPtr &other) const; protected: PROJ_INTERNAL VerticalExtent(double minimumValue, double maximumValue, const common::UnitOfMeasureNNPtr &unitIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class Extent; /** Shared pointer of Extent. */ using ExtentPtr = std::shared_ptr; /** Non-null shared pointer of Extent. */ using ExtentNNPtr = util::nn; /** \brief Information about spatial, vertical, and temporal extent. * * \remark Simplified version of [Extent] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/extent/Extent.html) * from \ref GeoAPI */ class PROJ_GCC_DLL Extent : public util::BaseObject, public util::IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL Extent(const Extent &other); PROJ_DLL ~Extent() override; //! @endcond PROJ_DLL const util::optional &description() PROJ_PURE_DECL; PROJ_DLL const std::vector & geographicElements() PROJ_PURE_DECL; PROJ_DLL const std::vector & temporalElements() PROJ_PURE_DECL; PROJ_DLL const std::vector & verticalElements() PROJ_PURE_DECL; PROJ_DLL static ExtentNNPtr create(const util::optional &descriptionIn, const std::vector &geographicElementsIn, const std::vector &verticalElementsIn, const std::vector &temporalElementsIn); PROJ_DLL static ExtentNNPtr createFromBBOX(double west, double south, double east, double north, const util::optional &descriptionIn = util::optional()); //! @cond Doxygen_Suppress PROJ_INTERNAL bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; //! @endcond PROJ_DLL bool contains(const ExtentNNPtr &other) const; PROJ_DLL bool intersects(const ExtentNNPtr &other) const; PROJ_DLL ExtentPtr intersection(const ExtentNNPtr &other) const; PROJ_DLL static const ExtentNNPtr WORLD; protected: PROJ_INTERNAL Extent(); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA Extent &operator=(const Extent &other) = delete; }; // --------------------------------------------------------------------------- class Identifier; /** Shared pointer of Identifier. */ using IdentifierPtr = std::shared_ptr; /** Non-null shared pointer of Identifier. */ using IdentifierNNPtr = util::nn; /** \brief Value uniquely identifying an object within a namespace. * * \remark Implements Identifier as described in \ref ISO_19111_2019 but which * originates from \ref ISO_19115 */ class PROJ_GCC_DLL Identifier : public util::BaseObject, public io::IWKTExportable, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL Identifier(const Identifier &other); PROJ_DLL ~Identifier() override; //! @endcond PROJ_DLL static IdentifierNNPtr create(const std::string &codeIn = std::string(), const util::PropertyMap &properties = util::PropertyMap()); // throw(InvalidValueTypeException) PROJ_DLL static const std::string AUTHORITY_KEY; PROJ_DLL static const std::string CODE_KEY; PROJ_DLL static const std::string CODESPACE_KEY; PROJ_DLL static const std::string VERSION_KEY; PROJ_DLL static const std::string DESCRIPTION_KEY; PROJ_DLL static const std::string URI_KEY; PROJ_DLL static const std::string EPSG; PROJ_DLL static const std::string OGC; PROJ_DLL const util::optional &authority() PROJ_PURE_DECL; PROJ_DLL const std::string &code() PROJ_PURE_DECL; PROJ_DLL const util::optional &codeSpace() PROJ_PURE_DECL; PROJ_DLL const util::optional &version() PROJ_PURE_DECL; PROJ_DLL const util::optional &description() PROJ_PURE_DECL; PROJ_DLL const util::optional &uri() PROJ_PURE_DECL; PROJ_DLL static bool isEquivalentName(const char *a, const char *b) noexcept; PROJ_DLL static bool isEquivalentName(const char *a, const char *b, bool biggerDifferencesAllowed) noexcept; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL static std::string canonicalizeName(const std::string &str, bool biggerDifferencesAllowed = true); PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond protected: PROJ_INTERNAL explicit Identifier(const std::string &codeIn, const util::PropertyMap &properties); PROJ_INTERNAL explicit Identifier(); PROJ_FRIEND_OPTIONAL(Identifier); INLINED_MAKE_SHARED Identifier &operator=(const Identifier &other) = delete; PROJ_FRIEND(common::IdentifiedObject); PROJ_INTERNAL static IdentifierNNPtr createFromDescription(const std::string &descriptionIn); private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class PositionalAccuracy; /** Shared pointer of PositionalAccuracy. */ using PositionalAccuracyPtr = std::shared_ptr; /** Non-null shared pointer of PositionalAccuracy. */ using PositionalAccuracyNNPtr = util::nn; /** \brief Accuracy of the position of features. * * \remark Simplified version of [PositionalAccuracy] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/metadata/quality/PositionalAccuracy.html) * from \ref GeoAPI, which originates from \ref ISO_19115 */ class PROJ_GCC_DLL PositionalAccuracy : public util::BaseObject { public: //! @cond Doxygen_Suppress PROJ_DLL ~PositionalAccuracy() override; //! @endcond PROJ_DLL const std::string &value() PROJ_PURE_DECL; PROJ_DLL static PositionalAccuracyNNPtr create(const std::string &valueIn); protected: PROJ_INTERNAL explicit PositionalAccuracy(const std::string &valueIn); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA PositionalAccuracy(const PositionalAccuracy &other) = delete; PositionalAccuracy &operator=(const PositionalAccuracy &other) = delete; }; } // namespace metadata NS_PROJ_END #endif // METADATA_HH_INCLUDED proj-9.8.1/include/proj/internal/000775 001750 001750 00000000000 15166171735 016666 5ustar00eveneven000000 000000 proj-9.8.1/include/proj/internal/tracing.hpp000664 001750 001750 00000005333 15166171715 021030 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Tracing/profiling * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef TRACING_HH_INCLUDED #define TRACING_HH_INCLUDED //! @cond Doxygen_Suppress #include #include "proj/util.hpp" #ifdef ENABLE_TRACING NS_PROJ_START namespace tracing { void logTrace(const std::string &str, const std::string &component = std::string()); class EnterBlock { public: EnterBlock(const std::string &msg); ~EnterBlock(); private: PROJ_OPAQUE_PRIVATE_DATA }; #define TRACING_MERGE(a, b) a##b #define TRACING_UNIQUE_NAME(a) TRACING_MERGE(unique_name_, a) #define ENTER_BLOCK(x) EnterBlock TRACING_UNIQUE_NAME(__LINE__)(x) #define ENTER_FUNCTION() ENTER_BLOCK(__FUNCTION__ + std::string("()")) } // namespace tracing NS_PROJ_END using namespace NS_PROJ::tracing; #else // ENABLE_TRACING inline void logTrace(const std::string &, const std::string & = std::string()) { } #define ENTER_BLOCK(x) \ do { \ } while (0); #define ENTER_FUNCTION() \ do { \ } while (0) #endif // ENABLE_TRACING //! @endcond #endif // TRACING_HH_INCLUDED proj-9.8.1/include/proj/internal/include_nlohmann_json.hpp000664 001750 001750 00000003716 15166171715 023752 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Wrapper for nlohmann/json.hpp * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef INCLUDE_NLOHMANN_JSON_HPP #define INCLUDE_NLOHMANN_JSON_HPP #if defined(__GNUC__) #pragma GCC system_header #endif #ifdef EXTERNAL_NLOHMANN_JSON #include #else // !EXTERNAL_NLOHMANN_JSON // to avoid any clash if PROJ users have another version of nlohmann/json.hpp #define nlohmann proj_nlohmann #if !defined(DOXYGEN_ENABLED) #include "vendor/nlohmann/json.hpp" #endif #endif // EXTERNAL_NLOHMANN_JSON #endif // INCLUDE_NLOHMANN_JSON_HPP proj-9.8.1/include/proj/internal/lru_cache.hpp000664 001750 001750 00000015361 15166171715 021330 0ustar00eveneven000000 000000 /* * LRUCache11 - a templated C++11 based LRU cache class that allows * specification of * key, value and optionally the map container type (defaults to * std::unordered_map) * By using the std::map and a linked list of keys it allows O(1) insert, delete * and * refresh operations. * * This is a header-only library and all you need is the LRUCache11.hpp file * * Github: https://github.com/mohaps/lrucache11 * * This is a follow-up to the LRUCache project - * https://github.com/mohaps/lrucache * * Copyright (c) 2012-22 SAURAV MOHAPATRA * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /*! @cond Doxygen_Suppress */ #pragma once #include #include #include #include #include #include #include NS_PROJ_START namespace lru11 { /* * a noop lockable concept that can be used in place of std::mutex */ class NullLock { public: // cppcheck-suppress functionStatic void lock() {} // cppcheck-suppress functionStatic void unlock() {} // cppcheck-suppress functionStatic bool try_lock() { return true; } }; /** * error raised when a key not in cache is passed to get() */ class KeyNotFound : public std::invalid_argument { public: KeyNotFound() : std::invalid_argument("key_not_found") {} ~KeyNotFound() override; }; #ifndef LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS KeyNotFound::~KeyNotFound() = default; #endif template struct KeyValuePair { public: K key; V value; KeyValuePair(const K &keyIn, const V &v) : key(keyIn), value(v) {} }; /** * The LRU Cache class templated by * Key - key type * Value - value type * MapType - an associative container like std::unordered_map * LockType - a lock type derived from the Lock class (default: *NullLock = no synchronization) * * The default NullLock based template is not thread-safe, however passing *Lock=std::mutex will make it * thread-safe */ template >::iterator>> class Cache { public: typedef KeyValuePair node_type; typedef std::list> list_type; typedef Map map_type; typedef Lock lock_type; using Guard = std::lock_guard; /** * the max size is the hard limit of keys and (maxSize + elasticity) is the * soft limit * the cache is allowed to grow till maxSize + elasticity and is pruned back * to maxSize keys * set maxSize = 0 for an unbounded cache (but in that case, you're better * off using a std::unordered_map directly anyway! :) */ explicit Cache(size_t maxSize = 64, size_t elasticity = 10) : maxSize_(maxSize), elasticity_(elasticity) {} virtual ~Cache() = default; size_t size() const { Guard g(lock_); return cache_.size(); } bool empty() const { Guard g(lock_); return cache_.empty(); } void clear() { Guard g(lock_); cache_.clear(); keys_.clear(); } void insert(const Key &key, const Value &v) { Guard g(lock_); const auto iter = cache_.find(key); if (iter != cache_.end()) { iter->second->value = v; keys_.splice(keys_.begin(), keys_, iter->second); return; } keys_.emplace_front(key, v); cache_[key] = keys_.begin(); prune(); } bool tryGet(const Key &kIn, Value &vOut) { Guard g(lock_); const auto iter = cache_.find(kIn); if (iter == cache_.end()) { return false; } keys_.splice(keys_.begin(), keys_, iter->second); vOut = iter->second->value; return true; } /** * The const reference returned here is only * guaranteed to be valid till the next insert/delete */ const Value &get(const Key &key) { Guard g(lock_); const auto iter = cache_.find(key); if (iter == cache_.end()) { throw KeyNotFound(); } keys_.splice(keys_.begin(), keys_, iter->second); return iter->second->value; } /** * The const reference returned here is only * guaranteed to be valid till the next insert/delete */ const Value *getPtr(const Key &key) { Guard g(lock_); const auto iter = cache_.find(key); if (iter == cache_.end()) { return nullptr; } keys_.splice(keys_.begin(), keys_, iter->second); return &(iter->second->value); } /** * returns a copy of the stored object (if found) */ Value getCopy(const Key &key) { return get(key); } bool remove(const Key &key) { Guard g(lock_); auto iter = cache_.find(key); if (iter == cache_.end()) { return false; } keys_.erase(iter->second); cache_.erase(iter); return true; } bool contains(const Key &key) { Guard g(lock_); return cache_.find(key) != cache_.end(); } size_t getMaxSize() const { return maxSize_; } size_t getElasticity() const { return elasticity_; } size_t getMaxAllowedSize() const { return maxSize_ + elasticity_; } template void cwalk(F &f) const { Guard g(lock_); std::for_each(keys_.begin(), keys_.end(), f); } protected: size_t prune() { size_t maxAllowed = maxSize_ + elasticity_; if (maxSize_ == 0 || cache_.size() <= maxAllowed) { /* ERO: changed < to <= */ return 0; } size_t count = 0; while (cache_.size() > maxSize_) { cache_.erase(keys_.back().key); keys_.pop_back(); ++count; } return count; } private: // Disallow copying. Cache(const Cache &) = delete; Cache &operator=(const Cache &) = delete; mutable Lock lock_{}; Map cache_{}; list_type keys_{}; size_t maxSize_; size_t elasticity_; }; } // namespace lru11 NS_PROJ_END /*! @endcond */ proj-9.8.1/include/proj/internal/coordinatesystem_internal.hpp000664 001750 001750 00000006634 15166171715 024676 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #error This file should only be included from a PROJ cpp file #endif #ifndef COORDINATESYSTEM_INTERNAL_HH_INCLUDED #define COORDINATESYSTEM_INTERNAL_HH_INCLUDED #include "proj/util.hpp" #include #include #include //! @cond Doxygen_Suppress NS_PROJ_START namespace cs { // --------------------------------------------------------------------------- class AxisDirectionWKT1 : public util::CodeList { public: static const AxisDirectionWKT1 *valueOf(const std::string &nameIn); static const AxisDirectionWKT1 NORTH; static const AxisDirectionWKT1 SOUTH; static const AxisDirectionWKT1 EAST; static const AxisDirectionWKT1 WEST; static const AxisDirectionWKT1 UP; static const AxisDirectionWKT1 DOWN; static const AxisDirectionWKT1 OTHER; private: explicit AxisDirectionWKT1(const std::string &nameIn); static std::map registry; }; // --------------------------------------------------------------------------- class AxisName { public: static const std::string Longitude; static const std::string Latitude; static const std::string Easting; static const std::string Northing; static const std::string Westing; static const std::string Southing; static const std::string Ellipsoidal_height; static const std::string Geocentric_X; static const std::string Geocentric_Y; static const std::string Geocentric_Z; }; // --------------------------------------------------------------------------- class AxisAbbreviation { public: static const std::string lon; static const std::string lat; static const std::string E; static const std::string N; static const std::string h; static const std::string X; static const std::string Y; static const std::string Z; }; } // namespace cs NS_PROJ_END //! @endcond #endif // COORDINATESYSTEM_INTERNAL_HH_INCLUDED proj-9.8.1/include/proj/internal/crs_internal.hpp000664 001750 001750 00000003603 15166171715 022062 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2021, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #error This file should only be included from a PROJ cpp file #endif #ifndef CRS_INTERNAL_HH_INCLUDED #define CRS_INTERNAL_HH_INCLUDED #define NORMALIZED_AXIS_ORDER_SUFFIX_STR \ " (with axis order normalized for visualization)" #define AXIS_ORDER_REVERSED_SUFFIX_STR " (with axis order reversed)" #endif // CRS_INTERNAL_HH_INCLUDED proj-9.8.1/include/proj/internal/io_internal.hpp000664 001750 001750 00000021304 15166171715 021700 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #error This file should only be included from a PROJ cpp file #endif #ifndef IO_INTERNAL_HH_INCLUDED #define IO_INTERNAL_HH_INCLUDED #include #include #include "proj/io.hpp" #include "proj/util.hpp" //! @cond Doxygen_Suppress NS_PROJ_START namespace io { // --------------------------------------------------------------------------- class WKTConstants { public: // WKT1 static const std::string GEOCCS; static const std::string GEOGCS; static const std::string DATUM; // WKT2 preferred too static const std::string UNIT; static const std::string SPHEROID; static const std::string AXIS; // WKT2 too static const std::string PRIMEM; // WKT2 too static const std::string AUTHORITY; static const std::string PROJCS; static const std::string PROJECTION; static const std::string PARAMETER; // WKT2 too static const std::string VERT_CS; static const std::string VERTCS; // WKT1 ESRI static const std::string VERT_DATUM; static const std::string COMPD_CS; static const std::string TOWGS84; // WKT1 only static const std::string EXTENSION; // WKT1 only - GDAL specific static const std::string LOCAL_CS; // WKT1 only static const std::string LOCAL_DATUM; // WKT1 only static const std::string LINUNIT; // WKT1 ESRI (ArcGIS Pro >= 2.7) // WKT2 preferred static const std::string GEODCRS; static const std::string LENGTHUNIT; static const std::string ANGLEUNIT; static const std::string SCALEUNIT; static const std::string TIMEUNIT; static const std::string ELLIPSOID; // underscore, since there is a CS macro in Solaris system headers static const std::string CS_; static const std::string ID; static const std::string PROJCRS; static const std::string BASEGEODCRS; static const std::string MERIDIAN; static const std::string ORDER; static const std::string ANCHOR; static const std::string ANCHOREPOCH; // WKT2-2019 static const std::string CONVERSION; static const std::string METHOD; static const std::string REMARK; static const std::string GEOGCRS; // WKT2-2019 static const std::string BASEGEOGCRS; // WKT2-2019 static const std::string SCOPE; static const std::string AREA; static const std::string BBOX; static const std::string CITATION; static const std::string URI; static const std::string VERTCRS; static const std::string VDATUM; // WKT2 and WKT1 ESRI static const std::string COMPOUNDCRS; static const std::string PARAMETERFILE; static const std::string COORDINATEOPERATION; static const std::string SOURCECRS; static const std::string TARGETCRS; static const std::string INTERPOLATIONCRS; static const std::string OPERATIONACCURACY; static const std::string CONCATENATEDOPERATION; // WKT2-2019 static const std::string STEP; // WKT2-2019 static const std::string BOUNDCRS; static const std::string ABRIDGEDTRANSFORMATION; static const std::string DERIVINGCONVERSION; static const std::string TDATUM; static const std::string CALENDAR; // WKT2-2019 static const std::string TIMEORIGIN; static const std::string TIMECRS; static const std::string VERTICALEXTENT; static const std::string TIMEEXTENT; static const std::string USAGE; // WKT2-2019 static const std::string DYNAMIC; // WKT2-2019 static const std::string FRAMEEPOCH; // WKT2-2019 static const std::string MODEL; // WKT2-2019 static const std::string VELOCITYGRID; // WKT2-2019 static const std::string ENSEMBLE; // WKT2-2019 static const std::string MEMBER; // WKT2-2019 static const std::string ENSEMBLEACCURACY; // WKT2-2019 static const std::string DERIVEDPROJCRS; // WKT2-2019 static const std::string BASEPROJCRS; // WKT2-2019 static const std::string EDATUM; static const std::string ENGCRS; static const std::string PDATUM; static const std::string PARAMETRICCRS; static const std::string PARAMETRICUNIT; static const std::string BASEVERTCRS; static const std::string BASEENGCRS; static const std::string BASEPARAMCRS; static const std::string BASETIMECRS; static const std::string VERSION; static const std::string GEOIDMODEL; // WKT2-2019 static const std::string COORDINATEMETADATA; // WKT2-2019 static const std::string EPOCH; // WKT2-2019 static const std::string AXISMINVALUE; // WKT2-2019 static const std::string AXISMAXVALUE; // WKT2-2019 static const std::string RANGEMEANING; // WKT2-2019 static const std::string POINTMOTIONOPERATION; // WKT2-2019 // WKT2 alternate (longer or shorter) static const std::string GEODETICCRS; static const std::string GEODETICDATUM; static const std::string PROJECTEDCRS; static const std::string PRIMEMERIDIAN; static const std::string GEOGRAPHICCRS; // WKT2-2019 static const std::string TRF; // WKT2-2019 static const std::string VERTICALCRS; static const std::string VERTICALDATUM; static const std::string VRF; // WKT2-2019 static const std::string TIMEDATUM; static const std::string TEMPORALQUANTITY; static const std::string ENGINEERINGDATUM; static const std::string ENGINEERINGCRS; static const std::string PARAMETRICDATUM; static const std::vector &constants() { return constants_; } private: static std::vector constants_; static const char *createAndAddToConstantList(const char *text); }; } // namespace io NS_PROJ_END // --------------------------------------------------------------------------- /** Auxiliary structure to PJ_CONTEXT storing C++ context stuff. */ struct PROJ_GCC_DLL projCppContext { private: NS_PROJ::io::DatabaseContextPtr databaseContext_{}; PJ_CONTEXT *ctx_ = nullptr; std::string dbPath_{}; std::vector auxDbPaths_{}; projCppContext(const projCppContext &) = delete; projCppContext &operator=(const projCppContext &) = delete; public: std::string lastDbPath_{}; std::string lastDbMetadataItem_{}; std::string lastUOMName_{}; std::string lastGridFullName_{}; std::string lastGridPackageName_{}; std::string lastGridUrl_{}; static std::vector toVector(const char *const *auxDbPaths); explicit projCppContext(PJ_CONTEXT *ctx, const char *dbPath = nullptr, const std::vector &auxDbPaths = {}); projCppContext *clone(PJ_CONTEXT *ctx) const; // cppcheck-suppress functionStatic inline const std::string &getDbPath() const { return dbPath_; } // cppcheck-suppress functionStatic inline const std::vector &getAuxDbPaths() const { return auxDbPaths_; } NS_PROJ::io::DatabaseContextNNPtr PROJ_FOR_TEST getDatabaseContext(); /** Return the database context only if already opened. * Does not attempt to open proj.db. */ inline NS_PROJ::io::DatabaseContextPtr getDatabaseContextIfOpen() const { return databaseContext_; } void closeDb() { databaseContext_ = nullptr; } }; //! @endcond #endif // IO_INTERNAL_HH_INCLUDED proj-9.8.1/include/proj/internal/datum_internal.hpp000664 001750 001750 00000004102 15166171715 022400 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2023, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #error This file should only be included from a PROJ cpp file #endif #ifndef DATUM_INTERNAL_HH_INCLUDED #define DATUM_INTERNAL_HH_INCLUDED #define UNKNOWN_ENGINEERING_DATUM "Unknown engineering datum" constexpr const char *NON_EARTH_BODY = "Non-Earth body"; // Mars (2015) - Sphere uses R=3396190 // and Mars polar radius (as used by HIRISE JPEG2000) is 3376200m // which is a 0.59% relative difference. constexpr double REL_ERROR_FOR_SAME_CELESTIAL_BODY = 0.007; constexpr const char *UNKNOWN_BASED_ON = "Unknown based on "; #endif // DATUM_INTERNAL_HH_INCLUDED proj-9.8.1/include/proj/internal/internal.hpp000664 001750 001750 00000015241 15166171715 021214 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #error This file should only be included from a PROJ cpp file #endif #ifndef INTERNAL_HH_INCLUDED #define INTERNAL_HH_INCLUDED #if !(__cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900)) #error Must have C++11 or newer. #endif #include #include #include #include #ifndef DOXYGEN_ENABLED #include // for std::is_base_of #endif #include #include "proj/util.hpp" //! @cond Doxygen_Suppress // Use "PROJ_FALLTHROUGH;" to annotate deliberate fall-through in switches, // use it analogously to "break;". The trailing semi-colon is required. #if !defined(PROJ_FALLTHROUGH) && defined(__has_cpp_attribute) #if __cplusplus >= 201703L && __has_cpp_attribute(fallthrough) #define PROJ_FALLTHROUGH [[fallthrough]] #elif __cplusplus >= 201103L && __has_cpp_attribute(gnu::fallthrough) #define PROJ_FALLTHROUGH [[gnu::fallthrough]] #elif __cplusplus >= 201103L && __has_cpp_attribute(clang::fallthrough) #define PROJ_FALLTHROUGH [[clang::fallthrough]] #endif #endif #ifndef PROJ_FALLTHROUGH #define PROJ_FALLTHROUGH ((void)0) #endif #if defined(__clang__) || defined(_MSC_VER) #define COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT #endif NS_PROJ_START namespace operation { class OperationParameterValue; } // namespace operation namespace internal { /** Use cpl::down_cast(pointer_to_base) as equivalent of * static_cast(pointer_to_base) with safe checking in debug * mode. * * Only works if no virtual inheritance is involved. * * @param f pointer to a base class * @return pointer to a derived class */ template inline To down_cast(From *f) { static_assert( (std::is_base_of::type>::value), "target type not derived from source type"); assert(f == nullptr || dynamic_cast(f) != nullptr); return static_cast(f); } PROJ_FOR_TEST std::string replaceAll(const std::string &str, const std::string &before, const std::string &after); PROJ_DLL size_t ci_find(const std::string &osStr, const char *needle) noexcept; size_t ci_find(const std::string &osStr, const std::string &needle, size_t startPos = 0) noexcept; inline bool starts_with(const std::string &str, const std::string &prefix) noexcept { if (str.size() < prefix.size()) { return false; } return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; } inline bool starts_with(const std::string &str, const char *prefix) noexcept { const size_t prefixSize = std::strlen(prefix); if (str.size() < prefixSize) { return false; } return std::memcmp(str.c_str(), prefix, prefixSize) == 0; } bool ci_less(const std::string &a, const std::string &b) noexcept; PROJ_DLL bool ci_starts_with(const char *str, const char *prefix) noexcept; bool ci_starts_with(const std::string &str, const std::string &prefix) noexcept; bool ends_with(const std::string &str, const std::string &suffix) noexcept; PROJ_FOR_TEST std::string tolower(const std::string &osStr); std::string toupper(const std::string &osStr); PROJ_FOR_TEST std::vector split(const std::string &osStr, char separator); PROJ_FOR_TEST std::vector split(const std::string &osStr, const std::string &separator); bool ci_equal(const char *a, const char *b) noexcept; bool ci_equal(const char *a, const std::string &b) = delete; PROJ_FOR_TEST bool ci_equal(const std::string &a, const char *b) noexcept; PROJ_FOR_TEST bool ci_equal(const std::string &a, const std::string &b) noexcept; std::string stripQuotes(const std::string &osStr); std::string toString(int val); PROJ_FOR_TEST std::string toString(double val, int precision = 15); PROJ_FOR_TEST double c_locale_stod(const std::string &s); // throw(std::invalid_argument) // Variant of above that doesn't emit exceptions double c_locale_stod(const std::string &s, bool &success); std::string concat(const std::string &, const std::string &) = delete; std::string concat(const char *, const char *) = delete; std::string concat(const char *a, const std::string &b); std::string concat(const std::string &, const char *) = delete; std::string concat(const char *, const char *, const char *) = delete; std::string concat(const char *, const char *, const std::string &) = delete; std::string concat(const char *a, const std::string &b, const char *c); std::string concat(const char *, const std::string &, const std::string &) = delete; std::string concat(const std::string &, const char *, const char *) = delete; std::string concat(const std::string &, const char *, const std::string &) = delete; std::string concat(const std::string &, const std::string &, const char *) = delete; std::string concat(const std::string &, const std::string &, const std::string &) = delete; double getRoundedEpochInDecimalYear(double year); } // namespace internal NS_PROJ_END //! @endcond #endif // INTERNAL_HH_INCLUDED proj-9.8.1/include/proj/internal/vendor/000775 001750 001750 00000000000 15166171735 020163 5ustar00eveneven000000 000000 proj-9.8.1/include/proj/internal/vendor/nlohmann/000775 001750 001750 00000000000 15166171735 021775 5ustar00eveneven000000 000000 proj-9.8.1/include/proj/internal/vendor/nlohmann/json.hpp000664 001750 001750 00003421035 15166171715 023465 0ustar00eveneven000000 000000 /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ | | |__ | | | | | | version 3.9.1 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2013-2019 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ #define NLOHMANN_JSON_VERSION_MAJOR 3 #define NLOHMANN_JSON_VERSION_MINOR 9 #define NLOHMANN_JSON_VERSION_PATCH 1 #include // all_of, find, for_each #include // nullptr_t, ptrdiff_t, size_t #include // hash, less #include // initializer_list #include // istream, ostream #include // random_access_iterator_tag #include // unique_ptr #include // accumulate #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector // #include #include // #include #include // transform #include // array #include // forward_list #include // inserter, front_inserter, end #include // map #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible #include // unordered_map #include // pair, declval #include // valarray // #include #include // exception #include // runtime_error #include // to_string // #include #include // size_t namespace nlohmann { namespace detail { /// struct to capture the start position of the current token struct position_t { /// the total number of characters read std::size_t chars_read_total = 0; /// the number of characters read in the current line std::size_t chars_read_current_line = 0; /// the number of lines read std::size_t lines_read = 0; /// conversion to size_t to preserve SAX interface constexpr operator size_t() const { return chars_read_total; } }; } // namespace detail } // namespace nlohmann // #include #include // pair // #include /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to * the public domain worldwide. This software is distributed without * any warranty. * * For details, see . * SPDX-License-Identifier: CC0-1.0 */ #if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 13) #if defined(JSON_HEDLEY_VERSION) #undef JSON_HEDLEY_VERSION #endif #define JSON_HEDLEY_VERSION 13 #if defined(JSON_HEDLEY_STRINGIFY_EX) #undef JSON_HEDLEY_STRINGIFY_EX #endif #define JSON_HEDLEY_STRINGIFY_EX(x) #x #if defined(JSON_HEDLEY_STRINGIFY) #undef JSON_HEDLEY_STRINGIFY #endif #define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) #if defined(JSON_HEDLEY_CONCAT_EX) #undef JSON_HEDLEY_CONCAT_EX #endif #define JSON_HEDLEY_CONCAT_EX(a,b) a##b #if defined(JSON_HEDLEY_CONCAT) #undef JSON_HEDLEY_CONCAT #endif #define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) #if defined(JSON_HEDLEY_CONCAT3_EX) #undef JSON_HEDLEY_CONCAT3_EX #endif #define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c #if defined(JSON_HEDLEY_CONCAT3) #undef JSON_HEDLEY_CONCAT3 #endif #define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) #if defined(JSON_HEDLEY_VERSION_ENCODE) #undef JSON_HEDLEY_VERSION_ENCODE #endif #define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) #if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) #undef JSON_HEDLEY_VERSION_DECODE_MAJOR #endif #define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) #if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) #undef JSON_HEDLEY_VERSION_DECODE_MINOR #endif #define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) #if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) #undef JSON_HEDLEY_VERSION_DECODE_REVISION #endif #define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) #if defined(JSON_HEDLEY_GNUC_VERSION) #undef JSON_HEDLEY_GNUC_VERSION #endif #if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #elif defined(__GNUC__) #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) #endif #if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) #undef JSON_HEDLEY_GNUC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_GNUC_VERSION) #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION) #undef JSON_HEDLEY_MSVC_VERSION #endif #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) #elif defined(_MSC_FULL_VER) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) #elif defined(_MSC_VER) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) #undef JSON_HEDLEY_MSVC_VERSION_CHECK #endif #if !defined(_MSC_VER) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) #elif defined(_MSC_VER) && (_MSC_VER >= 1400) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) #elif defined(_MSC_VER) && (_MSC_VER >= 1200) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) #else #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) #endif #if defined(JSON_HEDLEY_INTEL_VERSION) #undef JSON_HEDLEY_INTEL_VERSION #endif #if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) #elif defined(__INTEL_COMPILER) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif #if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) #undef JSON_HEDLEY_INTEL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_INTEL_VERSION) #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_PGI_VERSION) #undef JSON_HEDLEY_PGI_VERSION #endif #if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) #endif #if defined(JSON_HEDLEY_PGI_VERSION_CHECK) #undef JSON_HEDLEY_PGI_VERSION_CHECK #endif #if defined(JSON_HEDLEY_PGI_VERSION) #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION) #undef JSON_HEDLEY_SUNPRO_VERSION #endif #if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) #elif defined(__SUNPRO_C) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) #elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) #elif defined(__SUNPRO_CC) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION) #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) #undef JSON_HEDLEY_EMSCRIPTEN_VERSION #endif #if defined(__EMSCRIPTEN__) #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_ARM_VERSION) #undef JSON_HEDLEY_ARM_VERSION #endif #if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) #elif defined(__CC_ARM) && defined(__ARMCC_VERSION) #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) #endif #if defined(JSON_HEDLEY_ARM_VERSION_CHECK) #undef JSON_HEDLEY_ARM_VERSION_CHECK #endif #if defined(JSON_HEDLEY_ARM_VERSION) #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_IBM_VERSION) #undef JSON_HEDLEY_IBM_VERSION #endif #if defined(__ibmxl__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) #elif defined(__xlC__) && defined(__xlC_ver__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) #elif defined(__xlC__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) #endif #if defined(JSON_HEDLEY_IBM_VERSION_CHECK) #undef JSON_HEDLEY_IBM_VERSION_CHECK #endif #if defined(JSON_HEDLEY_IBM_VERSION) #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_VERSION) #undef JSON_HEDLEY_TI_VERSION #endif #if \ defined(__TI_COMPILER_VERSION__) && \ ( \ defined(__TMS470__) || defined(__TI_ARM__) || \ defined(__MSP430__) || \ defined(__TMS320C2000__) \ ) #if (__TI_COMPILER_VERSION__ >= 16000000) #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #endif #if defined(JSON_HEDLEY_TI_VERSION_CHECK) #undef JSON_HEDLEY_TI_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_VERSION) #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION) #undef JSON_HEDLEY_TI_CL2000_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION) #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION) #undef JSON_HEDLEY_TI_CL430_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION) #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION) #undef JSON_HEDLEY_TI_ARMCL_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION) #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION) #undef JSON_HEDLEY_TI_CL6X_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION) #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION) #undef JSON_HEDLEY_TI_CL7X_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION) #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION) #undef JSON_HEDLEY_TI_CLPRU_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION) #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_CRAY_VERSION) #undef JSON_HEDLEY_CRAY_VERSION #endif #if defined(_CRAYC) #if defined(_RELEASE_PATCHLEVEL) #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) #else #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) #endif #endif #if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) #undef JSON_HEDLEY_CRAY_VERSION_CHECK #endif #if defined(JSON_HEDLEY_CRAY_VERSION) #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_IAR_VERSION) #undef JSON_HEDLEY_IAR_VERSION #endif #if defined(__IAR_SYSTEMS_ICC__) #if __VER__ > 1000 #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) #else #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) #endif #endif #if defined(JSON_HEDLEY_IAR_VERSION_CHECK) #undef JSON_HEDLEY_IAR_VERSION_CHECK #endif #if defined(JSON_HEDLEY_IAR_VERSION) #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TINYC_VERSION) #undef JSON_HEDLEY_TINYC_VERSION #endif #if defined(__TINYC__) #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) #endif #if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) #undef JSON_HEDLEY_TINYC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TINYC_VERSION) #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_DMC_VERSION) #undef JSON_HEDLEY_DMC_VERSION #endif #if defined(__DMC__) #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) #endif #if defined(JSON_HEDLEY_DMC_VERSION_CHECK) #undef JSON_HEDLEY_DMC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_DMC_VERSION) #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION) #undef JSON_HEDLEY_COMPCERT_VERSION #endif #if defined(__COMPCERT_VERSION__) #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION) #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_PELLES_VERSION) #undef JSON_HEDLEY_PELLES_VERSION #endif #if defined(__POCC__) #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) #endif #if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) #undef JSON_HEDLEY_PELLES_VERSION_CHECK #endif #if defined(JSON_HEDLEY_PELLES_VERSION) #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_GCC_VERSION) #undef JSON_HEDLEY_GCC_VERSION #endif #if \ defined(JSON_HEDLEY_GNUC_VERSION) && \ !defined(__clang__) && \ !defined(JSON_HEDLEY_INTEL_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_ARM_VERSION) && \ !defined(JSON_HEDLEY_TI_VERSION) && \ !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ !defined(__COMPCERT__) #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION #endif #if defined(JSON_HEDLEY_GCC_VERSION_CHECK) #undef JSON_HEDLEY_GCC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_GCC_VERSION) #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_HAS_ATTRIBUTE) #undef JSON_HEDLEY_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) #else #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE #endif #if \ defined(__has_cpp_attribute) && \ defined(__cplusplus) && \ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS #endif #if !defined(__cplusplus) || !defined(__has_cpp_attribute) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) #elif \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION) && \ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) #else #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_BUILTIN) #undef JSON_HEDLEY_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) #else #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) #undef JSON_HEDLEY_GNUC_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) #undef JSON_HEDLEY_GCC_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_FEATURE) #undef JSON_HEDLEY_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) #else #define JSON_HEDLEY_HAS_FEATURE(feature) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) #undef JSON_HEDLEY_GNUC_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_FEATURE) #undef JSON_HEDLEY_GCC_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_EXTENSION) #undef JSON_HEDLEY_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) #else #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) #undef JSON_HEDLEY_GNUC_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) #undef JSON_HEDLEY_GCC_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_WARNING) #undef JSON_HEDLEY_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) #else #define JSON_HEDLEY_HAS_WARNING(warning) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_WARNING) #undef JSON_HEDLEY_GNUC_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_WARNING) #undef JSON_HEDLEY_GCC_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif /* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ #endif #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") # if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # endif # endif #endif #if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x #endif #if defined(JSON_HEDLEY_CONST_CAST) #undef JSON_HEDLEY_CONST_CAST #endif #if defined(__cplusplus) # define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) #elif \ JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ ((T) (expr)); \ JSON_HEDLEY_DIAGNOSTIC_POP \ })) #else # define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_REINTERPRET_CAST) #undef JSON_HEDLEY_REINTERPRET_CAST #endif #if defined(__cplusplus) #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) #else #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_STATIC_CAST) #undef JSON_HEDLEY_STATIC_CAST #endif #if defined(__cplusplus) #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) #else #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_CPP_CAST) #undef JSON_HEDLEY_CPP_CAST #endif #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ ((T) (expr)) \ JSON_HEDLEY_DIAGNOSTIC_POP # elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("diag_suppress=Pe137") \ JSON_HEDLEY_DIAGNOSTIC_POP \ # else # define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) # endif #else # define JSON_HEDLEY_CPP_CAST(T, expr) (expr) #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ defined(__clang__) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_PRAGMA(value) __pragma(value) #else #define JSON_HEDLEY_PRAGMA(value) #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) #undef JSON_HEDLEY_DIAGNOSTIC_PUSH #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_POP) #undef JSON_HEDLEY_DIAGNOSTIC_POP #endif #if defined(__clang__) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) #elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #else #define JSON_HEDLEY_DIAGNOSTIC_PUSH #define JSON_HEDLEY_DIAGNOSTIC_POP #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) #elif \ JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") #elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if defined(JSON_HEDLEY_DEPRECATED) #undef JSON_HEDLEY_DEPRECATED #endif #if defined(JSON_HEDLEY_DEPRECATED_FOR) #undef JSON_HEDLEY_DEPRECATED_FOR #endif #if JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) #elif defined(__cplusplus) && (__cplusplus >= 201402L) #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) #elif \ JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") #else #define JSON_HEDLEY_DEPRECATED(since) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) #endif #if defined(JSON_HEDLEY_UNAVAILABLE) #undef JSON_HEDLEY_UNAVAILABLE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) #else #define JSON_HEDLEY_UNAVAILABLE(available_since) #endif #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) #undef JSON_HEDLEY_WARN_UNUSED_RESULT #endif #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #endif #if (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) #elif defined(_Check_return_) /* SAL */ #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ #else #define JSON_HEDLEY_WARN_UNUSED_RESULT #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) #endif #if defined(JSON_HEDLEY_SENTINEL) #undef JSON_HEDLEY_SENTINEL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) #else #define JSON_HEDLEY_SENTINEL(position) #endif #if defined(JSON_HEDLEY_NO_RETURN) #undef JSON_HEDLEY_NO_RETURN #endif #if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NO_RETURN __noreturn #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define JSON_HEDLEY_NO_RETURN _Noreturn #elif defined(__cplusplus) && (__cplusplus >= 201103L) #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") #elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #else #define JSON_HEDLEY_NO_RETURN #endif #if defined(JSON_HEDLEY_NO_ESCAPE) #undef JSON_HEDLEY_NO_ESCAPE #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) #else #define JSON_HEDLEY_NO_ESCAPE #endif #if defined(JSON_HEDLEY_UNREACHABLE) #undef JSON_HEDLEY_UNREACHABLE #endif #if defined(JSON_HEDLEY_UNREACHABLE_RETURN) #undef JSON_HEDLEY_UNREACHABLE_RETURN #endif #if defined(JSON_HEDLEY_ASSUME) #undef JSON_HEDLEY_ASSUME #endif #if \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_ASSUME(expr) __assume(expr) #elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) #elif \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) #if defined(__cplusplus) #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) #else #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) #endif #endif #if \ (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() #elif defined(JSON_HEDLEY_ASSUME) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) #endif #if !defined(JSON_HEDLEY_ASSUME) #if defined(JSON_HEDLEY_UNREACHABLE) #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) #else #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) #endif #endif #if defined(JSON_HEDLEY_UNREACHABLE) #if \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) #else #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() #endif #else #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) #endif #if !defined(JSON_HEDLEY_UNREACHABLE) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) #endif JSON_HEDLEY_DIAGNOSTIC_PUSH #if JSON_HEDLEY_HAS_WARNING("-Wpedantic") #pragma clang diagnostic ignored "-Wpedantic" #endif #if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" #endif #if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) #if defined(__clang__) #pragma clang diagnostic ignored "-Wvariadic-macros" #elif defined(JSON_HEDLEY_GCC_VERSION) #pragma GCC diagnostic ignored "-Wvariadic-macros" #endif #endif #if defined(JSON_HEDLEY_NON_NULL) #undef JSON_HEDLEY_NON_NULL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) #else #define JSON_HEDLEY_NON_NULL(...) #endif JSON_HEDLEY_DIAGNOSTIC_POP #if defined(JSON_HEDLEY_PRINTF_FORMAT) #undef JSON_HEDLEY_PRINTF_FORMAT #endif #if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) #elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) #else #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) #endif #if defined(JSON_HEDLEY_CONSTEXPR) #undef JSON_HEDLEY_CONSTEXPR #endif #if defined(__cplusplus) #if __cplusplus >= 201103L #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) #endif #endif #if !defined(JSON_HEDLEY_CONSTEXPR) #define JSON_HEDLEY_CONSTEXPR #endif #if defined(JSON_HEDLEY_PREDICT) #undef JSON_HEDLEY_PREDICT #endif #if defined(JSON_HEDLEY_LIKELY) #undef JSON_HEDLEY_LIKELY #endif #if defined(JSON_HEDLEY_UNLIKELY) #undef JSON_HEDLEY_UNLIKELY #endif #if defined(JSON_HEDLEY_UNPREDICTABLE) #undef JSON_HEDLEY_UNPREDICTABLE #endif #if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) #endif #if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) # define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) #elif \ JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) # define JSON_HEDLEY_PREDICT(expr, expected, probability) \ (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ (__extension__ ({ \ double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ })) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ (__extension__ ({ \ double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ })) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) #else # define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) # define JSON_HEDLEY_LIKELY(expr) (!!(expr)) # define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) #endif #if !defined(JSON_HEDLEY_UNPREDICTABLE) #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) #endif #if defined(JSON_HEDLEY_MALLOC) #undef JSON_HEDLEY_MALLOC #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) #define JSON_HEDLEY_MALLOC __declspec(restrict) #else #define JSON_HEDLEY_MALLOC #endif #if defined(JSON_HEDLEY_PURE) #undef JSON_HEDLEY_PURE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) # define JSON_HEDLEY_PURE __attribute__((__pure__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) # define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") #elif defined(__cplusplus) && \ ( \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ ) # define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") #else # define JSON_HEDLEY_PURE #endif #if defined(JSON_HEDLEY_CONST) #undef JSON_HEDLEY_CONST #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_CONST __attribute__((__const__)) #elif \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_CONST _Pragma("no_side_effect") #else #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE #endif #if defined(JSON_HEDLEY_RESTRICT) #undef JSON_HEDLEY_RESTRICT #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT restrict #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ defined(__clang__) #define JSON_HEDLEY_RESTRICT __restrict #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT _Restrict #else #define JSON_HEDLEY_RESTRICT #endif #if defined(JSON_HEDLEY_INLINE) #undef JSON_HEDLEY_INLINE #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ (defined(__cplusplus) && (__cplusplus >= 199711L)) #define JSON_HEDLEY_INLINE inline #elif \ defined(JSON_HEDLEY_GCC_VERSION) || \ JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) #define JSON_HEDLEY_INLINE __inline__ #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_INLINE __inline #else #define JSON_HEDLEY_INLINE #endif #if defined(JSON_HEDLEY_ALWAYS_INLINE) #undef JSON_HEDLEY_ALWAYS_INLINE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) # define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE #elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) # define JSON_HEDLEY_ALWAYS_INLINE __forceinline #elif defined(__cplusplus) && \ ( \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ ) # define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) # define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") #else # define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE #endif #if defined(JSON_HEDLEY_NEVER_INLINE) #undef JSON_HEDLEY_NEVER_INLINE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") #elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #else #define JSON_HEDLEY_NEVER_INLINE #endif #if defined(JSON_HEDLEY_PRIVATE) #undef JSON_HEDLEY_PRIVATE #endif #if defined(JSON_HEDLEY_PUBLIC) #undef JSON_HEDLEY_PUBLIC #endif #if defined(JSON_HEDLEY_IMPORT) #undef JSON_HEDLEY_IMPORT #endif #if defined(_WIN32) || defined(__CYGWIN__) # define JSON_HEDLEY_PRIVATE # define JSON_HEDLEY_PUBLIC __declspec(dllexport) # define JSON_HEDLEY_IMPORT __declspec(dllimport) #else # if \ JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ ( \ defined(__TI_EABI__) && \ ( \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ ) \ ) # define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) # define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) # else # define JSON_HEDLEY_PRIVATE # define JSON_HEDLEY_PUBLIC # endif # define JSON_HEDLEY_IMPORT extern #endif #if defined(JSON_HEDLEY_NO_THROW) #undef JSON_HEDLEY_NO_THROW #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NO_THROW __declspec(nothrow) #else #define JSON_HEDLEY_NO_THROW #endif #if defined(JSON_HEDLEY_FALL_THROUGH) #undef JSON_HEDLEY_FALL_THROUGH #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) #elif defined(__fallthrough) /* SAL */ #define JSON_HEDLEY_FALL_THROUGH __fallthrough #else #define JSON_HEDLEY_FALL_THROUGH #endif #if defined(JSON_HEDLEY_RETURNS_NON_NULL) #undef JSON_HEDLEY_RETURNS_NON_NULL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) #elif defined(_Ret_notnull_) /* SAL */ #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ #else #define JSON_HEDLEY_RETURNS_NON_NULL #endif #if defined(JSON_HEDLEY_ARRAY_PARAM) #undef JSON_HEDLEY_ARRAY_PARAM #endif #if \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ !defined(__STDC_NO_VLA__) && \ !defined(__cplusplus) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_TINYC_VERSION) #define JSON_HEDLEY_ARRAY_PARAM(name) (name) #else #define JSON_HEDLEY_ARRAY_PARAM(name) #endif #if defined(JSON_HEDLEY_IS_CONSTANT) #undef JSON_HEDLEY_IS_CONSTANT #endif #if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) #undef JSON_HEDLEY_REQUIRE_CONSTEXPR #endif /* JSON_HEDLEY_IS_CONSTEXPR_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_IS_CONSTEXPR_) #undef JSON_HEDLEY_IS_CONSTEXPR_ #endif #if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) #endif #if !defined(__cplusplus) # if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) #if defined(__INTPTR_TYPE__) #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) #else #include #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) #endif # elif \ ( \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION)) || \ JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) #if defined(__INTPTR_TYPE__) #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) #else #include #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) #endif # elif \ defined(JSON_HEDLEY_GCC_VERSION) || \ defined(JSON_HEDLEY_INTEL_VERSION) || \ defined(JSON_HEDLEY_TINYC_VERSION) || \ defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ defined(__clang__) # define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ sizeof(void) != \ sizeof(*( \ 1 ? \ ((void*) ((expr) * 0L) ) : \ ((struct { char v[sizeof(void) * 2]; } *) 1) \ ) \ ) \ ) # endif #endif #if defined(JSON_HEDLEY_IS_CONSTEXPR_) #if !defined(JSON_HEDLEY_IS_CONSTANT) #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) #endif #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) #else #if !defined(JSON_HEDLEY_IS_CONSTANT) #define JSON_HEDLEY_IS_CONSTANT(expr) (0) #endif #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) #endif #if defined(JSON_HEDLEY_BEGIN_C_DECLS) #undef JSON_HEDLEY_BEGIN_C_DECLS #endif #if defined(JSON_HEDLEY_END_C_DECLS) #undef JSON_HEDLEY_END_C_DECLS #endif #if defined(JSON_HEDLEY_C_DECL) #undef JSON_HEDLEY_C_DECL #endif #if defined(__cplusplus) #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { #define JSON_HEDLEY_END_C_DECLS } #define JSON_HEDLEY_C_DECL extern "C" #else #define JSON_HEDLEY_BEGIN_C_DECLS #define JSON_HEDLEY_END_C_DECLS #define JSON_HEDLEY_C_DECL #endif #if defined(JSON_HEDLEY_STATIC_ASSERT) #undef JSON_HEDLEY_STATIC_ASSERT #endif #if \ !defined(__cplusplus) && ( \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ defined(_Static_assert) \ ) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) #elif \ (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) #else # define JSON_HEDLEY_STATIC_ASSERT(expr, message) #endif #if defined(JSON_HEDLEY_NULL) #undef JSON_HEDLEY_NULL #endif #if defined(__cplusplus) #if __cplusplus >= 201103L #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) #elif defined(NULL) #define JSON_HEDLEY_NULL NULL #else #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) #endif #elif defined(NULL) #define JSON_HEDLEY_NULL NULL #else #define JSON_HEDLEY_NULL ((void*) 0) #endif #if defined(JSON_HEDLEY_MESSAGE) #undef JSON_HEDLEY_MESSAGE #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define JSON_HEDLEY_MESSAGE(msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ JSON_HEDLEY_PRAGMA(message msg) \ JSON_HEDLEY_DIAGNOSTIC_POP #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) #elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_MESSAGE(msg) #endif #if defined(JSON_HEDLEY_WARNING) #undef JSON_HEDLEY_WARNING #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define JSON_HEDLEY_WARNING(msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ JSON_HEDLEY_PRAGMA(clang warning msg) \ JSON_HEDLEY_DIAGNOSTIC_POP #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) #endif #if defined(JSON_HEDLEY_REQUIRE) #undef JSON_HEDLEY_REQUIRE #endif #if defined(JSON_HEDLEY_REQUIRE_MSG) #undef JSON_HEDLEY_REQUIRE_MSG #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) # if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") # define JSON_HEDLEY_REQUIRE(expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((diagnose_if(!(expr), #expr, "error"))) \ JSON_HEDLEY_DIAGNOSTIC_POP # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((diagnose_if(!(expr), msg, "error"))) \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) # endif #else # define JSON_HEDLEY_REQUIRE(expr) # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) #endif #if defined(JSON_HEDLEY_FLAGS) #undef JSON_HEDLEY_FLAGS #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) #endif #if defined(JSON_HEDLEY_FLAGS_CAST) #undef JSON_HEDLEY_FLAGS_CAST #endif #if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) # define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("warning(disable:188)") \ ((T) (expr)); \ JSON_HEDLEY_DIAGNOSTIC_POP \ })) #else # define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) #endif #if defined(JSON_HEDLEY_EMPTY_BASES) #undef JSON_HEDLEY_EMPTY_BASES #endif #if JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0) #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) #else #define JSON_HEDLEY_EMPTY_BASES #endif /* Remaining macros are deprecated. */ #if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #endif #if defined(__clang__) #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) #else #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) #undef JSON_HEDLEY_CLANG_HAS_BUILTIN #endif #define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) #if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) #undef JSON_HEDLEY_CLANG_HAS_FEATURE #endif #define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) #if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) #undef JSON_HEDLEY_CLANG_HAS_EXTENSION #endif #define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) #if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_WARNING) #undef JSON_HEDLEY_CLANG_HAS_WARNING #endif #define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) #endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ // This file contains all internal macro definitions // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them // exclude unsupported compilers #if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif #endif #endif // C++ language standard detection #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // disable float-equal warnings on GCC/clang #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif // disable documentation warnings on clang #if defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdocumentation" #endif // allow to disable exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) #define JSON_INTERNAL_CATCH(exception) catch(exception) #else #include #define JSON_THROW(exception) std::abort() #define JSON_TRY if(true) #define JSON_CATCH(exception) if(false) #define JSON_INTERNAL_CATCH(exception) if(false) #endif // override exception macros #if defined(JSON_THROW_USER) #undef JSON_THROW #define JSON_THROW JSON_THROW_USER #endif #if defined(JSON_TRY_USER) #undef JSON_TRY #define JSON_TRY JSON_TRY_USER #endif #if defined(JSON_CATCH_USER) #undef JSON_CATCH #define JSON_CATCH JSON_CATCH_USER #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_CATCH_USER #endif #if defined(JSON_INTERNAL_CATCH_USER) #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif // allow to override assert #if !defined(JSON_ASSERT) #include // assert #define JSON_ASSERT(x) assert(x) #endif /*! @brief macro to briefly define a mapping between an enum and JSON @def NLOHMANN_JSON_SERIALIZE_ENUM @since version 3.4.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ { \ return ej_pair.first == e; \ }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer, \ class BinaryType> #define NLOHMANN_BASIC_JSON_TPL \ basic_json // Macros to simplify conversion from/to types #define NLOHMANN_JSON_EXPAND( x ) x #define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME #define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ NLOHMANN_JSON_PASTE64, \ NLOHMANN_JSON_PASTE63, \ NLOHMANN_JSON_PASTE62, \ NLOHMANN_JSON_PASTE61, \ NLOHMANN_JSON_PASTE60, \ NLOHMANN_JSON_PASTE59, \ NLOHMANN_JSON_PASTE58, \ NLOHMANN_JSON_PASTE57, \ NLOHMANN_JSON_PASTE56, \ NLOHMANN_JSON_PASTE55, \ NLOHMANN_JSON_PASTE54, \ NLOHMANN_JSON_PASTE53, \ NLOHMANN_JSON_PASTE52, \ NLOHMANN_JSON_PASTE51, \ NLOHMANN_JSON_PASTE50, \ NLOHMANN_JSON_PASTE49, \ NLOHMANN_JSON_PASTE48, \ NLOHMANN_JSON_PASTE47, \ NLOHMANN_JSON_PASTE46, \ NLOHMANN_JSON_PASTE45, \ NLOHMANN_JSON_PASTE44, \ NLOHMANN_JSON_PASTE43, \ NLOHMANN_JSON_PASTE42, \ NLOHMANN_JSON_PASTE41, \ NLOHMANN_JSON_PASTE40, \ NLOHMANN_JSON_PASTE39, \ NLOHMANN_JSON_PASTE38, \ NLOHMANN_JSON_PASTE37, \ NLOHMANN_JSON_PASTE36, \ NLOHMANN_JSON_PASTE35, \ NLOHMANN_JSON_PASTE34, \ NLOHMANN_JSON_PASTE33, \ NLOHMANN_JSON_PASTE32, \ NLOHMANN_JSON_PASTE31, \ NLOHMANN_JSON_PASTE30, \ NLOHMANN_JSON_PASTE29, \ NLOHMANN_JSON_PASTE28, \ NLOHMANN_JSON_PASTE27, \ NLOHMANN_JSON_PASTE26, \ NLOHMANN_JSON_PASTE25, \ NLOHMANN_JSON_PASTE24, \ NLOHMANN_JSON_PASTE23, \ NLOHMANN_JSON_PASTE22, \ NLOHMANN_JSON_PASTE21, \ NLOHMANN_JSON_PASTE20, \ NLOHMANN_JSON_PASTE19, \ NLOHMANN_JSON_PASTE18, \ NLOHMANN_JSON_PASTE17, \ NLOHMANN_JSON_PASTE16, \ NLOHMANN_JSON_PASTE15, \ NLOHMANN_JSON_PASTE14, \ NLOHMANN_JSON_PASTE13, \ NLOHMANN_JSON_PASTE12, \ NLOHMANN_JSON_PASTE11, \ NLOHMANN_JSON_PASTE10, \ NLOHMANN_JSON_PASTE9, \ NLOHMANN_JSON_PASTE8, \ NLOHMANN_JSON_PASTE7, \ NLOHMANN_JSON_PASTE6, \ NLOHMANN_JSON_PASTE5, \ NLOHMANN_JSON_PASTE4, \ NLOHMANN_JSON_PASTE3, \ NLOHMANN_JSON_PASTE2, \ NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) #define NLOHMANN_JSON_PASTE2(func, v1) func(v1) #define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) #define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) #define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) #define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) #define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) #define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) #define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) #define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) #define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) #define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) #define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) #define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) #define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) #define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) #define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) #define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) #define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) #define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) #define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) #define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) #define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) #define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) #define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) #define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) #define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) #define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) #define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) #define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) #define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) #define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) #define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) #define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) #define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) #define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) #define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) #define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) #define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) #define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) #define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) #define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) #define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) #define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) #define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) #define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) #define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) #define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) #define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) #define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) #define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) #define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) #define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) #define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) #define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) #define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) #define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) #define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) #define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) #define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) #define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) #define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) #define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) #define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } #ifndef JSON_USE_IMPLICIT_CONVERSIONS #define JSON_USE_IMPLICIT_CONVERSIONS 1 #endif #if JSON_USE_IMPLICIT_CONVERSIONS #define JSON_EXPLICIT #else #define JSON_EXPLICIT explicit #endif namespace nlohmann { namespace detail { //////////////// // exceptions // //////////////// /*! @brief general exception of the @ref basic_json class This class is an extension of `std::exception` objects with a member @a id for exception ids. It is used as the base class for all exceptions thrown by the @ref basic_json class. This class can hence be used as "wildcard" to catch exceptions. Subclasses: - @ref parse_error for exceptions indicating a parse error - @ref invalid_iterator for exceptions indicating errors with iterators - @ref type_error for exceptions indicating executing a member function with a wrong type - @ref out_of_range for exceptions indicating access out of the defined range - @ref other_error for exceptions indicating other library errors @internal @note To have nothrow-copy-constructible exceptions, we internally use `std::runtime_error` which can cope with arbitrary-length error messages. Intermediate strings are built with static functions and then passed to the actual constructor. @endinternal @liveexample{The following code shows how arbitrary library exceptions can be caught.,exception} @since version 3.0.0 */ class exception : public std::exception { public: /// returns the explanatory string JSON_HEDLEY_RETURNS_NON_NULL const char* what() const noexcept override { return m.what(); } /// the id of the exception const int id; protected: JSON_HEDLEY_NON_NULL(3) exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} static std::string name(const std::string& ename, int id_) { return "[json.exception." + ename + "." + std::to_string(id_) + "] "; } private: /// an exception object as storage for error messages std::runtime_error m; }; /*! @brief exception indicating a parse error This exception is thrown by the library when a parse error occurs. Parse errors can occur during the deserialization of JSON text, CBOR, MessagePack, as well as when using JSON Patch. Member @a byte holds the byte index of the last read character in the input file. Exceptions have ids 1xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). @liveexample{The following code shows how a `parse_error` exception can be caught.,parse_error} @sa - @ref exception for the base class of the library exceptions @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class parse_error : public exception { public: /*! @brief create a parse error exception @param[in] id_ the id of the exception @param[in] pos the position where the error occurred (or with chars_read_total=0 if the position cannot be determined) @param[in] what_arg the explanatory string @return parse_error object */ static parse_error create(int id_, const position_t& pos, const std::string& what_arg) { std::string w = exception::name("parse_error", id_) + "parse error" + position_string(pos) + ": " + what_arg; return parse_error(id_, pos.chars_read_total, w.c_str()); } static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) { std::string w = exception::name("parse_error", id_) + "parse error" + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + ": " + what_arg; return parse_error(id_, byte_, w.c_str()); } /*! @brief byte index of the parse error The byte index of the last read character in the input file. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). */ const std::size_t byte; private: parse_error(int id_, std::size_t byte_, const char* what_arg) : exception(id_, what_arg), byte(byte_) {} static std::string position_string(const position_t& pos) { return " at line " + std::to_string(pos.lines_read + 1) + ", column " + std::to_string(pos.chars_read_current_line); } }; /*! @brief exception indicating errors with iterators This exception is thrown if iterators passed to a library function do not match the expected semantics. Exceptions have ids 2xx. name / id | example message | description ----------------------------------- | --------------- | ------------------------- json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). @liveexample{The following code shows how an `invalid_iterator` exception can be caught.,invalid_iterator} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class invalid_iterator : public exception { public: static invalid_iterator create(int id_, const std::string& what_arg) { std::string w = exception::name("invalid_iterator", id_) + what_arg; return invalid_iterator(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating executing a member function with a wrong type This exception is thrown in case of a type error; that is, a library function is executed on a JSON value whose type does not match the expected semantics. Exceptions have ids 3xx. name / id | example message | description ----------------------------- | --------------- | ------------------------- json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | @liveexample{The following code shows how a `type_error` exception can be caught.,type_error} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class type_error : public exception { public: static type_error create(int id_, const std::string& what_arg) { std::string w = exception::name("type_error", id_) + what_arg; return type_error(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating access out of the defined range This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance in case of array indices or nonexisting object keys. Exceptions have ids 4xx. name / id | example message | description ------------------------------- | --------------- | ------------------------- json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | @liveexample{The following code shows how an `out_of_range` exception can be caught.,out_of_range} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class out_of_range : public exception { public: static out_of_range create(int id_, const std::string& what_arg) { std::string w = exception::name("out_of_range", id_) + what_arg; return out_of_range(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating other library errors This exception is thrown in case of errors that cannot be classified with the other exception types. Exceptions have ids 5xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @liveexample{The following code shows how an `other_error` exception can be caught.,other_error} @since version 3.0.0 */ class other_error : public exception { public: static other_error create(int id_, const std::string& what_arg) { std::string w = exception::name("other_error", id_) + what_arg; return other_error(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; } // namespace detail } // namespace nlohmann // #include // #include #include // size_t #include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type namespace nlohmann { namespace detail { // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; template using uncvref_t = typename std::remove_cv::type>::type; // implementation of C++14 index_sequence and affiliates // source: https://stackoverflow.com/a/32223343 template struct index_sequence { using type = index_sequence; using value_type = std::size_t; static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; template struct merge_and_renumber; template struct merge_and_renumber, index_sequence> : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; template struct make_index_sequence : merge_and_renumber < typename make_index_sequence < N / 2 >::type, typename make_index_sequence < N - N / 2 >::type > {}; template<> struct make_index_sequence<0> : index_sequence<> {}; template<> struct make_index_sequence<1> : index_sequence<0> {}; template using index_sequence_for = make_index_sequence; // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; // taken from ranges-v3 template struct static_const { static constexpr T value{}; }; template constexpr T static_const::value; } // namespace detail } // namespace nlohmann // #include #include // numeric_limits #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval // #include #include // random_access_iterator_tag // #include namespace nlohmann { namespace detail { template struct make_void { using type = void; }; template using void_t = typename make_void::type; } // namespace detail } // namespace nlohmann // #include namespace nlohmann { namespace detail { template struct iterator_types {}; template struct iterator_types < It, void_t> { using difference_type = typename It::difference_type; using value_type = typename It::value_type; using pointer = typename It::pointer; using reference = typename It::reference; using iterator_category = typename It::iterator_category; }; // This is required as some compilers implement std::iterator_traits in a way that // doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. template struct iterator_traits { }; template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> : iterator_types { }; template struct iterator_traits::value>> { using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = ptrdiff_t; using pointer = T*; using reference = T&; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // #include // https://en.cppreference.com/w/cpp/experimental/is_detected namespace nlohmann { namespace detail { struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; nonesuch(nonesuch const&&) = delete; void operator=(nonesuch const&) = delete; void operator=(nonesuch&&) = delete; }; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template class Op, class... Args> using is_detected = typename detector::value_t; template class Op, class... Args> using detected_t = typename detector::type; template class Op, class... Args> using detected_or = detector; template class Op, class... Args> using detected_or_t = typename detected_or::type; template class Op, class... Args> using is_detected_exact = std::is_same>; template class Op, class... Args> using is_detected_convertible = std::is_convertible, To>; } // namespace detail } // namespace nlohmann // #include #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ #include // int64_t, uint64_t #include // map #include // allocator #include // string #include // vector /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @brief default JSONSerializer template argument This serializer ignores the template arguments and uses ADL ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) for serialization. */ template struct adl_serializer; template class ObjectType = std::map, template class ArrayType = std::vector, class StringType = std::string, class BooleanType = bool, class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, class BinaryType = std::vector> class basic_json; /*! @brief JSON Pointer A JSON pointer defines a string syntax for identifying a specific value within a JSON document. It can be used with functions `at` and `operator[]`. Furthermore, JSON pointers are the base for JSON patches. @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) @since version 2.0.0 */ template class json_pointer; /*! @brief default JSON class This type is the default specialization of the @ref basic_json class which uses the standard template types. @since version 1.0.0 */ using json = basic_json<>; template struct ordered_map; /*! @brief ordered JSON class This type preserves the insertion order of object keys. @since version 3.9.0 */ using ordered_json = basic_json; } // namespace nlohmann #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ namespace nlohmann { /*! @brief detail namespace with internal helper functions This namespace collects functions that should not be exposed, implementations of some @ref basic_json methods, and meta-programming helpers. @since version 2.1.0 */ namespace detail { ///////////// // helpers // ///////////// // Note to maintainers: // // Every trait in this file expects a non CV-qualified type. // The only exceptions are in the 'aliases for detected' section // (i.e. those of the form: decltype(T::member_function(std::declval()))) // // In this case, T has to be properly CV-qualified to constraint the function arguments // (e.g. to_json(BasicJsonType&, const T&)) template struct is_basic_json : std::false_type {}; NLOHMANN_BASIC_JSON_TPL_DECLARATION struct is_basic_json : std::true_type {}; ////////////////////// // json_ref helpers // ////////////////////// template class json_ref; template struct is_json_ref : std::false_type {}; template struct is_json_ref> : std::true_type {}; ////////////////////////// // aliases for detected // ////////////////////////// template using mapped_type_t = typename T::mapped_type; template using key_type_t = typename T::key_type; template using value_type_t = typename T::value_type; template using difference_type_t = typename T::difference_type; template using pointer_t = typename T::pointer; template using reference_t = typename T::reference; template using iterator_category_t = typename T::iterator_category; template using iterator_t = typename T::iterator; template using to_json_function = decltype(T::to_json(std::declval()...)); template using from_json_function = decltype(T::from_json(std::declval()...)); template using get_template_function = decltype(std::declval().template get()); // trait checking if JSONSerializer::from_json(json const&, udt&) exists template struct has_from_json : std::false_type {}; // trait checking if j.get is valid // use this trait instead of std::is_constructible or std::is_convertible, // both rely on, or make use of implicit conversions, and thus fail when T // has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) template struct is_getable { static constexpr bool value = is_detected::value; }; template struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; // This trait checks if JSONSerializer::from_json(json const&) exists // this overload is used for non-default-constructible user-defined-types template struct has_non_default_from_json : std::false_type {}; template struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; // This trait checks if BasicJsonType::json_serializer::to_json exists // Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. template struct has_to_json : std::false_type {}; template struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; /////////////////// // is_ functions // /////////////////// template struct is_iterator_traits : std::false_type {}; template struct is_iterator_traits> { private: using traits = iterator_traits; public: static constexpr auto value = is_detected::value && is_detected::value && is_detected::value && is_detected::value && is_detected::value; }; // source: https://stackoverflow.com/a/37193089/4116453 template struct is_complete_type : std::false_type {}; template struct is_complete_type : std::true_type {}; template struct is_compatible_object_type_impl : std::false_type {}; template struct is_compatible_object_type_impl < BasicJsonType, CompatibleObjectType, enable_if_t < is_detected::value&& is_detected::value >> { using object_t = typename BasicJsonType::object_t; // macOS's is_constructible does not play well with nonesuch... static constexpr bool value = std::is_constructible::value && std::is_constructible::value; }; template struct is_compatible_object_type : is_compatible_object_type_impl {}; template struct is_constructible_object_type_impl : std::false_type {}; template struct is_constructible_object_type_impl < BasicJsonType, ConstructibleObjectType, enable_if_t < is_detected::value&& is_detected::value >> { using object_t = typename BasicJsonType::object_t; static constexpr bool value = (std::is_default_constructible::value && (std::is_move_assignable::value || std::is_copy_assignable::value) && (std::is_constructible::value && std::is_same < typename object_t::mapped_type, typename ConstructibleObjectType::mapped_type >::value)) || (has_from_json::value || has_non_default_from_json < BasicJsonType, typename ConstructibleObjectType::mapped_type >::value); }; template struct is_constructible_object_type : is_constructible_object_type_impl {}; template struct is_compatible_string_type_impl : std::false_type {}; template struct is_compatible_string_type_impl < BasicJsonType, CompatibleStringType, enable_if_t::value >> { static constexpr auto value = std::is_constructible::value; }; template struct is_compatible_string_type : is_compatible_string_type_impl {}; template struct is_constructible_string_type_impl : std::false_type {}; template struct is_constructible_string_type_impl < BasicJsonType, ConstructibleStringType, enable_if_t::value >> { static constexpr auto value = std::is_constructible::value; }; template struct is_constructible_string_type : is_constructible_string_type_impl {}; template struct is_compatible_array_type_impl : std::false_type {}; template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, enable_if_t < is_detected::value&& is_detected::value&& // This is needed because json_reverse_iterator has a ::iterator type... // Therefore it is detected as a CompatibleArrayType. // The real fix would be to have an Iterable concept. !is_iterator_traits < iterator_traits>::value >> { static constexpr bool value = std::is_constructible::value; }; template struct is_compatible_array_type : is_compatible_array_type_impl {}; template struct is_constructible_array_type_impl : std::false_type {}; template struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t::value >> : std::true_type {}; template struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t < !std::is_same::value&& std::is_default_constructible::value&& (std::is_move_assignable::value || std::is_copy_assignable::value)&& is_detected::value&& is_detected::value&& is_complete_type < detected_t>::value >> { static constexpr bool value = // This is needed because json_reverse_iterator has a ::iterator type, // furthermore, std::back_insert_iterator (and other iterators) have a // base class `iterator`... Therefore it is detected as a // ConstructibleArrayType. The real fix would be to have an Iterable // concept. !is_iterator_traits>::value && (std::is_same::value || has_from_json::value || has_non_default_from_json < BasicJsonType, typename ConstructibleArrayType::value_type >::value); }; template struct is_constructible_array_type : is_constructible_array_type_impl {}; template struct is_compatible_integer_type_impl : std::false_type {}; template struct is_compatible_integer_type_impl < RealIntegerType, CompatibleNumberIntegerType, enable_if_t < std::is_integral::value&& std::is_integral::value&& !std::is_same::value >> { // is there an assert somewhere on overflows? using RealLimits = std::numeric_limits; using CompatibleLimits = std::numeric_limits; static constexpr auto value = std::is_constructible::value && CompatibleLimits::is_integer && RealLimits::is_signed == CompatibleLimits::is_signed; }; template struct is_compatible_integer_type : is_compatible_integer_type_impl {}; template struct is_compatible_type_impl: std::false_type {}; template struct is_compatible_type_impl < BasicJsonType, CompatibleType, enable_if_t::value >> { static constexpr bool value = has_to_json::value; }; template struct is_compatible_type : is_compatible_type_impl {}; // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; template struct conjunction : B1 { }; template struct conjunction : std::conditional, B1>::type {}; template struct is_constructible_tuple : std::false_type {}; template struct is_constructible_tuple> : conjunction...> {}; } // namespace detail } // namespace nlohmann // #include #include // array #include // size_t #include // uint8_t #include // string namespace nlohmann { namespace detail { /////////////////////////// // JSON type enumeration // /////////////////////////// /*! @brief the JSON type enumeration This enumeration collects the different JSON types. It is internally used to distinguish the stored values, and the functions @ref basic_json::is_null(), @ref basic_json::is_object(), @ref basic_json::is_array(), @ref basic_json::is_string(), @ref basic_json::is_boolean(), @ref basic_json::is_number() (with @ref basic_json::is_number_integer(), @ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), @ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and @ref basic_json::is_structured() rely on it. @note There are three enumeration entries (number_integer, number_unsigned, and number_float), because the library distinguishes these three types for numbers: @ref basic_json::number_unsigned_t is used for unsigned integers, @ref basic_json::number_integer_t is used for signed integers, and @ref basic_json::number_float_t is used for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. @sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON value with the default value for a given type @since version 1.0.0 */ enum class value_t : std::uint8_t { null, ///< null value object, ///< object (unordered set of name/value pairs) array, ///< array (ordered collection of values) string, ///< string value boolean, ///< boolean value number_integer, ///< number value (signed integer) number_unsigned, ///< number value (unsigned integer) number_float, ///< number value (floating-point) binary, ///< binary array (ordered collection of bytes) discarded ///< discarded by the parser callback function }; /*! @brief comparison operator for JSON types Returns an ordering that is similar to Python: - order: null < boolean < number < object < array < string < binary - furthermore, each type is not smaller than itself - discarded values are not comparable - binary is represented as a b"" string in python and directly comparable to a string; however, making a binary array directly comparable with a string would be surprising behavior in a JSON file. @since version 1.0.0 */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, 6 /* binary */ } }; const auto l_index = static_cast(lhs); const auto r_index = static_cast(rhs); return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; } } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { template void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); } n = nullptr; } // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& !std::is_same::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); } } template void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); } b = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); } s = *j.template get_ptr(); } template < typename BasicJsonType, typename ConstructibleStringType, enable_if_t < is_constructible_string_type::value&& !std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ConstructibleStringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); } s = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); e = static_cast(val); } // forward_list doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } l.clear(); std::transform(j.rbegin(), j.rend(), std::front_inserter(l), [](const BasicJsonType & i) { return i.template get(); }); } // valarray doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), [](const BasicJsonType & elem) { return elem.template get(); }); } template auto from_json(const BasicJsonType& j, T (&arr)[N]) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( arr.reserve(std::declval()), j.template get(), void()) { using std::end; ConstructibleArrayType ret; ret.reserve(j.size()); std::transform(j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<0> /*unused*/) { using std::end; ConstructibleArrayType ret; std::transform( j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < is_constructible_array_type::value&& !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) -> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), j.template get(), void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } from_json_array_impl(j, arr, priority_tag<3> {}); } template void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()))); } bin = *j.template get_ptr(); } template::value, int> = 0> void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); } ConstructibleObjectType ret; auto inner_object = j.template get_ptr(); using value_type = typename ConstructibleObjectType::value_type; std::transform( inner_object->begin(), inner_object->end(), std::inserter(ret, ret.begin()), [](typename BasicJsonType::object_t::value_type const & p) { return value_type(p.first, p.second.template get()); }); obj = std::move(ret); } // overload for arithmetic types, not chosen for basic_json template arguments // (BooleanType, etc..); note: Is it really necessary to provide explicit // overloads for boolean_t etc. in case of a custom BooleanType which is not // an arithmetic type? template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& !std::is_same::value&& !std::is_same::value&& !std::is_same::value&& !std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } case value_t::boolean: { val = static_cast(*j.template get_ptr()); break; } default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); } } template void from_json(const BasicJsonType& j, std::pair& p) { p = {j.at(0).template get(), j.at(1).template get()}; } template void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) { t = std::make_tuple(j.at(Idx).template get::type>()...); } template void from_json(const BasicJsonType& j, std::tuple& t) { from_json_tuple_impl(j, t, index_sequence_for {}); } template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } struct from_json_fn { template auto operator()(const BasicJsonType& j, T& val) const noexcept(noexcept(from_json(j, val))) -> decltype(from_json(j, val), void()) { return from_json(j, val); } }; } // namespace detail /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace { constexpr const auto& from_json = detail::static_const::value; } // namespace } // namespace nlohmann // #include #include // copy #include // begin, end #include // string #include // tuple, get #include // is_same, is_constructible, is_floating_point, is_enum, underlying_type #include // move, forward, declval, pair #include // valarray #include // vector // #include #include // size_t #include // input_iterator_tag #include // string, to_string #include // tuple_size, get, tuple_element // #include // #include namespace nlohmann { namespace detail { template void int_to_string( string_type& target, std::size_t value ) { // For ADL using std::to_string; target = to_string(value); } template class iteration_proxy_value { public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; using pointer = value_type * ; using reference = value_type & ; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator IteratorType anchor; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index mutable std::size_t array_index_last = 0; /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) const string_type empty_str = ""; public: explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {} /// dereference operator (needed for range-based for) iteration_proxy_value& operator*() { return *this; } /// increment operator (needed for range-based for) iteration_proxy_value& operator++() { ++anchor; ++array_index; return *this; } /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { return anchor == o.anchor; } /// inequality operator (needed for range-based for) bool operator!=(const iteration_proxy_value& o) const { return anchor != o.anchor; } /// return key of the iterator const string_type& key() const { JSON_ASSERT(anchor.m_object != nullptr); switch (anchor.m_object->type()) { // use integer array index as key case value_t::array: { if (array_index != array_index_last) { int_to_string( array_index_str, array_index ); array_index_last = array_index; } return array_index_str; } // use key from the object case value_t::object: return anchor.key(); // use an empty key for all primitive types default: return empty_str; } } /// return value of the iterator typename IteratorType::reference value() const { return anchor.value(); } }; /// proxy class for the items() function template class iteration_proxy { private: /// the container to iterate typename IteratorType::reference container; public: /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept : container(cont) {} /// return iterator begin (needed for range-based for) iteration_proxy_value begin() noexcept { return iteration_proxy_value(container.begin()); } /// return iterator end (needed for range-based for) iteration_proxy_value end() noexcept { return iteration_proxy_value(container.end()); } }; // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) { return i.key(); } // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) { return i.value(); } } // namespace detail } // namespace nlohmann // The Addition to the STD Namespace is required to add // Structured Bindings Support to the iteration_proxy_value class // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 namespace std { #if defined(__clang__) // Fix: https://github.com/nlohmann/json/issues/1401 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-tags" #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> : public std::integral_constant {}; template class tuple_element> { public: using type = decltype( get(std::declval < ::nlohmann::detail::iteration_proxy_value> ())); }; #if defined(__clang__) #pragma clang diagnostic pop #endif } // namespace std // #include // #include // #include namespace nlohmann { namespace detail { ////////////////// // constructors // ////////////////// template struct external_constructor; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept { j.m_type = value_t::boolean; j.m_value = b; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { j.m_type = value_t::string; j.m_value = s; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { j.m_type = value_t::string; j.m_value = std::move(s); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleStringType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { j.m_type = value_t::string; j.m_value.string = j.template create(str); j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { j.m_type = value_t::binary; typename BasicJsonType::binary_t value{b}; j.m_value = value; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { j.m_type = value_t::binary; typename BasicJsonType::binary_t value{std::move(b)}; j.m_value = value; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept { j.m_type = value_t::number_float; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept { j.m_type = value_t::number_unsigned; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept { j.m_type = value_t::number_integer; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { j.m_type = value_t::array; j.m_value = arr; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { j.m_type = value_t::array; j.m_value = std::move(arr); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; using std::end; j.m_type = value_t::array; j.m_value.array = j.template create(begin(arr), end(arr)); j.assert_invariant(); } template static void construct(BasicJsonType& j, const std::vector& arr) { j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); for (const bool x : arr) { j.m_value.array->push_back(x); } j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->resize(arr.size()); if (arr.size() > 0) { std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); } j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { j.m_type = value_t::object; j.m_value = obj; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { j.m_type = value_t::object; j.m_value = std::move(obj); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleObjectType& obj) { using std::begin; using std::end; j.m_type = value_t::object; j.m_value.object = j.template create(begin(obj), end(obj)); j.assert_invariant(); } }; ///////////// // to_json // ///////////// template::value, int> = 0> void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < is_compatible_array_type::value&& !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& !is_basic_json::value, int > = 0 > void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !std::is_constructible::value, int > = 0 > void to_json(BasicJsonType& j, const T(&arr)[N]) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } struct to_json_fn { template auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) -> decltype(to_json(j, std::forward(val)), void()) { return to_json(j, std::forward(val)); } }; } // namespace detail /// namespace to hold default `to_json` function namespace { constexpr const auto& to_json = detail::static_const::value; } // namespace } // namespace nlohmann namespace nlohmann { template struct adl_serializer { /*! @brief convert a JSON value to any value type This function is usually called by the `get()` function of the @ref basic_json class (either explicit or via conversion operators). @param[in] j JSON value to read from @param[in,out] val value to write to */ template static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( noexcept(::nlohmann::from_json(std::forward(j), val))) -> decltype(::nlohmann::from_json(std::forward(j), val), void()) { ::nlohmann::from_json(std::forward(j), val); } /*! @brief convert any value type to a JSON value This function is usually called by the constructors of the @ref basic_json class. @param[in,out] j JSON value to write to @param[in] val value to read from */ template static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( noexcept(::nlohmann::to_json(j, std::forward(val)))) -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) { ::nlohmann::to_json(j, std::forward(val)); } }; } // namespace nlohmann // #include #include // uint8_t #include // tie #include // move namespace nlohmann { /*! @brief an internal type for a backed binary type This type extends the template parameter @a BinaryType provided to `basic_json` with a subtype used by BSON and MessagePack. This type exists so that the user does not have to specify a type themselves with a specific naming scheme in order to override the binary type. @tparam BinaryType container to store bytes (`std::vector` by default) @since version 3.8.0 */ template class byte_container_with_subtype : public BinaryType { public: /// the type of the underlying container using container_type = BinaryType; byte_container_with_subtype() noexcept(noexcept(container_type())) : container_type() {} byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) : container_type(b) {} byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) {} byte_container_with_subtype(const container_type& b, std::uint8_t subtype) noexcept(noexcept(container_type(b))) : container_type(b) , m_subtype(subtype) , m_has_subtype(true) {} byte_container_with_subtype(container_type&& b, std::uint8_t subtype) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) , m_subtype(subtype) , m_has_subtype(true) {} bool operator==(const byte_container_with_subtype& rhs) const { return std::tie(static_cast(*this), m_subtype, m_has_subtype) == std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); } bool operator!=(const byte_container_with_subtype& rhs) const { return !(rhs == *this); } /*! @brief sets the binary subtype Sets the binary subtype of the value, also flags a binary JSON value as having a subtype, which has implications for serialization. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref subtype() -- return the binary subtype @sa @ref clear_subtype() -- clears the binary subtype @sa @ref has_subtype() -- returns whether or not the binary value has a subtype @since version 3.8.0 */ void set_subtype(std::uint8_t subtype) noexcept { m_subtype = subtype; m_has_subtype = true; } /*! @brief return the binary subtype Returns the numerical subtype of the value if it has a subtype. If it does not have a subtype, this function will return size_t(-1) as a sentinel value. @return the numerical subtype of the binary value @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref set_subtype() -- sets the binary subtype @sa @ref clear_subtype() -- clears the binary subtype @sa @ref has_subtype() -- returns whether or not the binary value has a subtype @since version 3.8.0 */ constexpr std::uint8_t subtype() const noexcept { return m_subtype; } /*! @brief return whether the value has a subtype @return whether the value has a subtype @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref subtype() -- return the binary subtype @sa @ref set_subtype() -- sets the binary subtype @sa @ref clear_subtype() -- clears the binary subtype @since version 3.8.0 */ constexpr bool has_subtype() const noexcept { return m_has_subtype; } /*! @brief clears the binary subtype Clears the binary subtype and flags the value as not having a subtype, which has implications for serialization; for instance MessagePack will prefer the bin family over the ext family. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref subtype() -- return the binary subtype @sa @ref set_subtype() -- sets the binary subtype @sa @ref has_subtype() -- returns whether or not the binary value has a subtype @since version 3.8.0 */ void clear_subtype() noexcept { m_subtype = 0; m_has_subtype = false; } private: std::uint8_t m_subtype = 0; bool m_has_subtype = false; }; } // namespace nlohmann // #include // #include // #include // #include #include // size_t, uint8_t #include // hash namespace nlohmann { namespace detail { // boost::hash_combine inline std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; } /*! @brief hash a JSON value The hash function tries to rely on std::hash where possible. Furthermore, the type of the JSON value is taken into account to have different hash values for null, 0, 0U, and false, etc. @tparam BasicJsonType basic_json specialization @param j JSON value to hash @return hash value of j */ template std::size_t hash(const BasicJsonType& j) { using string_t = typename BasicJsonType::string_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; const auto type = static_cast(j.type()); switch (j.type()) { case BasicJsonType::value_t::null: case BasicJsonType::value_t::discarded: { return combine(type, 0); } case BasicJsonType::value_t::object: { auto seed = combine(type, j.size()); for (const auto& element : j.items()) { const auto h = std::hash {}(element.key()); seed = combine(seed, h); seed = combine(seed, hash(element.value())); } return seed; } case BasicJsonType::value_t::array: { auto seed = combine(type, j.size()); for (const auto& element : j) { seed = combine(seed, hash(element)); } return seed; } case BasicJsonType::value_t::string: { const auto h = std::hash {}(j.template get_ref()); return combine(type, h); } case BasicJsonType::value_t::boolean: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case BasicJsonType::value_t::number_integer: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case nlohmann::detail::value_t::number_unsigned: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case nlohmann::detail::value_t::number_float: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case nlohmann::detail::value_t::binary: { auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); seed = combine(seed, j.get_binary().subtype()); for (const auto byte : j.get_binary()) { seed = combine(seed, std::hash {}(byte)); } return seed; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } } // namespace detail } // namespace nlohmann // #include #include // generate_n #include // array #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t #include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits #include // char_traits, string #include // make_pair, move // #include // #include #include // array #include // size_t #include //FILE * #include // strlen #include // istream #include // begin, end, iterator_traits, random_access_iterator_tag, distance, next #include // shared_ptr, make_shared, addressof #include // accumulate #include // string, char_traits #include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include // pair, declval // #include // #include namespace nlohmann { namespace detail { /// the supported input formats enum class input_format_t { json, cbor, msgpack, ubjson, bson }; //////////////////// // input adapters // //////////////////// /*! Input adapter for stdio file access. This adapter read only 1 byte and do not use any buffer. This adapter is a very low level adapter. */ class file_input_adapter { public: using char_type = char; JSON_HEDLEY_NON_NULL(2) explicit file_input_adapter(std::FILE* f) noexcept : m_file(f) {} // make class move-only file_input_adapter(const file_input_adapter&) = delete; file_input_adapter(file_input_adapter&&) = default; file_input_adapter& operator=(const file_input_adapter&) = delete; file_input_adapter& operator=(file_input_adapter&&) = delete; std::char_traits::int_type get_character() noexcept { return std::fgetc(m_file); } private: /// the file pointer to read from std::FILE* m_file; }; /*! Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at beginning of input. Does not support changing the underlying std::streambuf in mid-input. Maintains underlying std::istream and std::streambuf to support subsequent use of standard std::istream operations to process any input characters following those used in parsing the JSON input. Clears the std::istream flags; any input errors (e.g., EOF) will be detected by the first subsequent call for input from the std::istream. */ class input_stream_adapter { public: using char_type = char; ~input_stream_adapter() { // clear stream flags; we use underlying streambuf I/O, do not // maintain ifstream flags, except eof if (is != nullptr) { is->clear(is->rdstate() & std::ios::eofbit); } } explicit input_stream_adapter(std::istream& i) : is(&i), sb(i.rdbuf()) {} // delete because of pointer members input_stream_adapter(const input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&& rhs) = delete; input_stream_adapter(input_stream_adapter&& rhs) noexcept : is(rhs.is), sb(rhs.sb) { rhs.is = nullptr; rhs.sb = nullptr; } // std::istream/std::streambuf use std::char_traits::to_int_type, to // ensure that std::char_traits::eof() and the character 0xFF do not // end up as the same value, eg. 0xFFFFFFFF. std::char_traits::int_type get_character() { auto res = sb->sbumpc(); // set eof manually, as we don't use the istream interface. if (JSON_HEDLEY_UNLIKELY(res == EOF)) { is->clear(is->rdstate() | std::ios::eofbit); } return res; } private: /// the associated input stream std::istream* is = nullptr; std::streambuf* sb = nullptr; }; // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. template class iterator_input_adapter { public: using char_type = typename std::iterator_traits::value_type; iterator_input_adapter(IteratorType first, IteratorType last) : current(std::move(first)), end(std::move(last)) {} typename std::char_traits::int_type get_character() { if (JSON_HEDLEY_LIKELY(current != end)) { auto result = std::char_traits::to_int_type(*current); std::advance(current, 1); return result; } else { return std::char_traits::eof(); } } private: IteratorType current; IteratorType end; template friend struct wide_string_input_helper; bool empty() const { return current == end; } }; template struct wide_string_input_helper; template struct wide_string_input_helper { // UTF-32 static void fill_buffer(BaseInputAdapter& input, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (JSON_HEDLEY_UNLIKELY(input.empty())) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = input.get_character(); // UTF-32 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 2; } else if (wc <= 0xFFFF) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 3; } else if (wc <= 0x10FFFF) { utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 4; } else { // unknown character utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } }; template struct wide_string_input_helper { // UTF-16 static void fill_buffer(BaseInputAdapter& input, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (JSON_HEDLEY_UNLIKELY(input.empty())) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = input.get_character(); // UTF-16 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 2; } else if (0xD800 > wc || wc >= 0xE000) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 3; } else { if (JSON_HEDLEY_UNLIKELY(!input.empty())) { const auto wc2 = static_cast(input.get_character()); const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); utf8_bytes_filled = 4; } else { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } } }; // Wraps another input adapter to convert wide character types into individual bytes. template class wide_string_input_adapter { public: using char_type = char; wide_string_input_adapter(BaseInputAdapter base) : base_adapter(base) {} typename std::char_traits::int_type get_character() noexcept { // check if buffer needs to be filled if (utf8_bytes_index == utf8_bytes_filled) { fill_buffer(); JSON_ASSERT(utf8_bytes_filled > 0); JSON_ASSERT(utf8_bytes_index == 0); } // use buffer JSON_ASSERT(utf8_bytes_filled > 0); JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); return utf8_bytes[utf8_bytes_index++]; } private: BaseInputAdapter base_adapter; template void fill_buffer() { wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); } /// a buffer for UTF-8 bytes std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; /// index to the utf8_codes array for the next valid byte std::size_t utf8_bytes_index = 0; /// number of valid bytes in the utf8_codes array std::size_t utf8_bytes_filled = 0; }; template struct iterator_input_adapter_factory { using iterator_type = IteratorType; using char_type = typename std::iterator_traits::value_type; using adapter_type = iterator_input_adapter; static adapter_type create(IteratorType first, IteratorType last) { return adapter_type(std::move(first), std::move(last)); } }; template struct is_iterator_of_multibyte { using value_type = typename std::iterator_traits::value_type; enum { value = sizeof(value_type) > 1 }; }; template struct iterator_input_adapter_factory::value>> { using iterator_type = IteratorType; using char_type = typename std::iterator_traits::value_type; using base_adapter_type = iterator_input_adapter; using adapter_type = wide_string_input_adapter; static adapter_type create(IteratorType first, IteratorType last) { return adapter_type(base_adapter_type(std::move(first), std::move(last))); } }; // General purpose iterator-based input template typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) { using factory_type = iterator_input_adapter_factory; return factory_type::create(first, last); } // Convenience shorthand from container to iterator template auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container))) { // Enable ADL using std::begin; using std::end; return input_adapter(begin(container), end(container)); } // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { return file_input_adapter(file); } inline input_stream_adapter input_adapter(std::istream& stream) { return input_stream_adapter(stream); } inline input_stream_adapter input_adapter(std::istream&& stream) { return input_stream_adapter(stream); } using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); // Null-delimited strings, and the like. template < typename CharT, typename std::enable_if < std::is_pointer::value&& !std::is_array::value&& std::is_integral::type>::value&& sizeof(typename std::remove_pointer::type) == 1, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); return input_adapter(ptr, ptr + length); } template auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) { return input_adapter(array, array + N); } // This class only handles inputs of input_buffer_adapter type. // It's required so that expressions like {ptr, len} can be implicitly casted // to the correct adapter. class span_input_adapter { public: template < typename CharT, typename std::enable_if < std::is_pointer::value&& std::is_integral::type>::value&& sizeof(typename std::remove_pointer::type) == 1, int >::type = 0 > span_input_adapter(CharT b, std::size_t l) : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} template::iterator_category, std::random_access_iterator_tag>::value, int>::type = 0> span_input_adapter(IteratorType first, IteratorType last) : ia(input_adapter(first, last)) {} contiguous_bytes_input_adapter&& get() { return std::move(ia); } private: contiguous_bytes_input_adapter ia; }; } // namespace detail } // namespace nlohmann // #include #include #include // string #include // move #include // vector // #include // #include namespace nlohmann { /*! @brief SAX interface This class describes the SAX interface used by @ref nlohmann::json::sax_parse. Each function is called in different situations while the input is parsed. The boolean return value informs the parser whether to continue processing the input. */ template struct json_sax { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; /*! @brief a null value was read @return whether parsing should proceed */ virtual bool null() = 0; /*! @brief a boolean value was read @param[in] val boolean value @return whether parsing should proceed */ virtual bool boolean(bool val) = 0; /*! @brief an integer number was read @param[in] val integer value @return whether parsing should proceed */ virtual bool number_integer(number_integer_t val) = 0; /*! @brief an unsigned integer number was read @param[in] val unsigned integer value @return whether parsing should proceed */ virtual bool number_unsigned(number_unsigned_t val) = 0; /*! @brief an floating-point number was read @param[in] val floating-point value @param[in] s raw token value @return whether parsing should proceed */ virtual bool number_float(number_float_t val, const string_t& s) = 0; /*! @brief a string was read @param[in] val string value @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool string(string_t& val) = 0; /*! @brief a binary string was read @param[in] val binary value @return whether parsing should proceed @note It is safe to move the passed binary. */ virtual bool binary(binary_t& val) = 0; /*! @brief the beginning of an object was read @param[in] elements number of object elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_object(std::size_t elements) = 0; /*! @brief an object key was read @param[in] val object key @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool key(string_t& val) = 0; /*! @brief the end of an object was read @return whether parsing should proceed */ virtual bool end_object() = 0; /*! @brief the beginning of an array was read @param[in] elements number of array elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_array(std::size_t elements) = 0; /*! @brief the end of an array was read @return whether parsing should proceed */ virtual bool end_array() = 0; /*! @brief a parse error occurred @param[in] position the position in the input where the error occurs @param[in] last_token the last read token @param[in] ex an exception object describing the error @return whether parsing should proceed (must return false) */ virtual bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex) = 0; virtual ~json_sax() = default; }; namespace detail { /*! @brief SAX implementation to create a JSON value from SAX events This class implements the @ref json_sax interface and processes the SAX events to create a JSON value which makes it basically a DOM parser. The structure or hierarchy of the JSON value is managed by the stack `ref_stack` which contains a pointer to the respective array or object for each recursion depth. After successful parsing, the value that is passed by reference to the constructor contains the parsed value. @tparam BasicJsonType the JSON type */ template class json_sax_dom_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; /*! @param[in, out] r reference to a JSON value that is manipulated while parsing @param[in] allow_exceptions_ whether parse errors yield exceptions */ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) : root(r), allow_exceptions(allow_exceptions_) {} // make class move-only json_sax_dom_parser(const json_sax_dom_parser&) = delete; json_sax_dom_parser(json_sax_dom_parser&&) = default; json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; ~json_sax_dom_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool binary(binary_t& val) { handle_value(std::move(val)); return true; } bool start_object(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); } return true; } bool key(string_t& val) { // add null at given key and store the reference for later object_element = &(ref_stack.back()->m_value.object->operator[](val)); return true; } bool end_object() { ref_stack.pop_back(); return true; } bool start_array(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); } return true; } bool end_array() { ref_stack.pop_back(); return true; } template bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex) { errored = true; static_cast(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; } constexpr bool is_errored() const { return errored; } private: /*! @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements */ template JSON_HEDLEY_RETURNS_NON_NULL BasicJsonType* handle_value(Value&& v) { if (ref_stack.empty()) { root = BasicJsonType(std::forward(v)); return &root; } JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->emplace_back(std::forward(v)); return &(ref_stack.back()->m_value.array->back()); } JSON_ASSERT(ref_stack.back()->is_object()); JSON_ASSERT(object_element); *object_element = BasicJsonType(std::forward(v)); return object_element; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; template class json_sax_dom_callback_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using parser_callback_t = typename BasicJsonType::parser_callback_t; using parse_event_t = typename BasicJsonType::parse_event_t; json_sax_dom_callback_parser(BasicJsonType& r, const parser_callback_t cb, const bool allow_exceptions_ = true) : root(r), callback(cb), allow_exceptions(allow_exceptions_) { keep_stack.push_back(true); } // make class move-only json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; ~json_sax_dom_callback_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool binary(binary_t& val) { handle_value(std::move(val)); return true; } bool start_object(std::size_t len) { // check callback for object start const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::object, true); ref_stack.push_back(val.second); // check object limit if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); } return true; } bool key(string_t& val) { BasicJsonType k = BasicJsonType(val); // check callback for key const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); key_keep_stack.push_back(keep); // add discarded value at given key and store the reference for later if (keep && ref_stack.back()) { object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); } return true; } bool end_object() { if (ref_stack.back() && !callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) { // discard object *ref_stack.back() = discarded; } JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { // remove discarded value for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) { if (it->is_discarded()) { ref_stack.back()->erase(it); break; } } } return true; } bool start_array(std::size_t len) { const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::array, true); ref_stack.push_back(val.second); // check array limit if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); } return true; } bool end_array() { bool keep = true; if (ref_stack.back()) { keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); if (!keep) { // discard array *ref_stack.back() = discarded; } } JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); // remove discarded value if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->pop_back(); } return true; } template bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex) { errored = true; static_cast(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; } constexpr bool is_errored() const { return errored; } private: /*! @param[in] v value to add to the JSON value we build during parsing @param[in] skip_callback whether we should skip calling the callback function; this is required after start_array() and start_object() SAX events, because otherwise we would call the callback function with an empty array or object, respectively. @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements @return pair of boolean (whether value should be kept) and pointer (to the passed value in the ref_stack hierarchy; nullptr if not kept) */ template std::pair handle_value(Value&& v, const bool skip_callback = false) { JSON_ASSERT(!keep_stack.empty()); // do not handle this value if we know it would be added to a discarded // container if (!keep_stack.back()) { return {false, nullptr}; } // create value auto value = BasicJsonType(std::forward(v)); // check callback const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); // do not handle this value if we just learnt it shall be discarded if (!keep) { return {false, nullptr}; } if (ref_stack.empty()) { root = std::move(value); return {true, &root}; } // skip this value if we already decided to skip the parent // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) if (!ref_stack.back()) { return {false, nullptr}; } // we now only expect arrays and objects JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); // array if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->push_back(std::move(value)); return {true, &(ref_stack.back()->m_value.array->back())}; } // object JSON_ASSERT(ref_stack.back()->is_object()); // check if we should store an element for the current key JSON_ASSERT(!key_keep_stack.empty()); const bool store_element = key_keep_stack.back(); key_keep_stack.pop_back(); if (!store_element) { return {false, nullptr}; } JSON_ASSERT(object_element); *object_element = std::move(value); return {true, object_element}; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// stack to manage which values to keep std::vector keep_stack {}; /// stack to manage which object keys to keep std::vector key_keep_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// callback function const parser_callback_t callback = nullptr; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; /// a discarded value for the callback BasicJsonType discarded = BasicJsonType::value_t::discarded; }; template class json_sax_acceptor { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; bool null() { return true; } bool boolean(bool /*unused*/) { return true; } bool number_integer(number_integer_t /*unused*/) { return true; } bool number_unsigned(number_unsigned_t /*unused*/) { return true; } bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) { return true; } bool string(string_t& /*unused*/) { return true; } bool binary(binary_t& /*unused*/) { return true; } bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { return true; } bool key(string_t& /*unused*/) { return true; } bool end_object() { return true; } bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { return true; } bool end_array() { return true; } bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) { return false; } }; } // namespace detail } // namespace nlohmann // #include #include // array #include // localeconv #include // size_t #include // snprintf #include // strtof, strtod, strtold, strtoll, strtoull #include // initializer_list #include // char_traits, string #include // move #include // vector // #include // #include // #include namespace nlohmann { namespace detail { /////////// // lexer // /////////// template class lexer_base { public: /// token types for the parser enum class token_type { uninitialized, ///< indicating the scanner is uninitialized literal_true, ///< the `true` literal literal_false, ///< the `false` literal literal_null, ///< the `null` literal value_string, ///< a string -- use get_string() for actual value value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value value_integer, ///< a signed integer -- use get_number_integer() for actual value value_float, ///< an floating point number -- use get_number_float() for actual value begin_array, ///< the character for array begin `[` begin_object, ///< the character for object begin `{` end_array, ///< the character for array end `]` end_object, ///< the character for object end `}` name_separator, ///< the name separator `:` value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error end_of_input, ///< indicating the end of the input buffer literal_or_value ///< a literal or the begin of a value (only for diagnostics) }; /// return name of values of type token_type (only used for errors) JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_CONST static const char* token_type_name(const token_type t) noexcept { switch (t) { case token_type::uninitialized: return ""; case token_type::literal_true: return "true literal"; case token_type::literal_false: return "false literal"; case token_type::literal_null: return "null literal"; case token_type::value_string: return "string literal"; case token_type::value_unsigned: case token_type::value_integer: case token_type::value_float: return "number literal"; case token_type::begin_array: return "'['"; case token_type::begin_object: return "'{'"; case token_type::end_array: return "']'"; case token_type::end_object: return "'}'"; case token_type::name_separator: return "':'"; case token_type::value_separator: return "','"; case token_type::parse_error: return ""; case token_type::end_of_input: return "end of input"; case token_type::literal_or_value: return "'[', '{', or a literal"; // LCOV_EXCL_START default: // catch non-enum values return "unknown token"; // LCOV_EXCL_STOP } } }; /*! @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. */ template class lexer : public lexer_base { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using char_type = typename InputAdapterType::char_type; using char_int_type = typename std::char_traits::int_type; public: using token_type = typename lexer_base::token_type; explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) : ia(std::move(adapter)) , ignore_comments(ignore_comments_) , decimal_point_char(static_cast(get_decimal_point())) {} // delete because of pointer members lexer(const lexer&) = delete; lexer(lexer&&) = default; lexer& operator=(lexer&) = delete; lexer& operator=(lexer&&) = default; ~lexer() = default; private: ///////////////////// // locales ///////////////////// /// return the locale-dependent decimal point JSON_HEDLEY_PURE static char get_decimal_point() noexcept { const auto* loc = localeconv(); JSON_ASSERT(loc != nullptr); return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } ///////////////////// // scan functions ///////////////////// /*! @brief get codepoint from 4 hex characters following `\u` For input "\u c1 c2 c3 c4" the codepoint is: (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The conversion is done by subtracting the offset (0x30, 0x37, and 0x57) between the ASCII value of the character and the desired integer value. @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or non-hex character) */ int get_codepoint() { // this function only makes sense after reading `\u` JSON_ASSERT(current == 'u'); int codepoint = 0; const auto factors = { 12u, 8u, 4u, 0u }; for (const auto factor : factors) { get(); if (current >= '0' && current <= '9') { codepoint += static_cast((static_cast(current) - 0x30u) << factor); } else if (current >= 'A' && current <= 'F') { codepoint += static_cast((static_cast(current) - 0x37u) << factor); } else if (current >= 'a' && current <= 'f') { codepoint += static_cast((static_cast(current) - 0x57u) << factor); } else { return -1; } } JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); return codepoint; } /*! @brief check if the next byte(s) are inside a given range Adds the current byte and, for each passed range, reads a new byte and checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. @param[in] ranges list of integers; interpreted as list of pairs of inclusive lower and upper bound, respectively @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, 1, 2, or 3 pairs. This precondition is enforced by an assertion. @return true if and only if no range violation was detected */ bool next_byte_in_range(std::initializer_list ranges) { JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); add(current); for (auto range = ranges.begin(); range != ranges.end(); ++range) { get(); if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) { add(current); } else { error_message = "invalid string: ill-formed UTF-8 byte"; return false; } } return true; } /*! @brief scan a string literal This function scans a string according to Sect. 7 of RFC 7159. While scanning, bytes are escaped and copied into buffer token_buffer. Then the function returns successfully, token_buffer is *not* null-terminated (as it may contain \0 bytes), and token_buffer.size() is the number of bytes in the string. @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. */ token_type scan_string() { // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote JSON_ASSERT(current == '\"'); while (true) { // get next character switch (get()) { // end of file while parsing string case std::char_traits::eof(): { error_message = "invalid string: missing closing quote"; return token_type::parse_error; } // closing quote case '\"': { return token_type::value_string; } // escapes case '\\': { switch (get()) { // quotation mark case '\"': add('\"'); break; // reverse solidus case '\\': add('\\'); break; // solidus case '/': add('/'); break; // backspace case 'b': add('\b'); break; // form feed case 'f': add('\f'); break; // line feed case 'n': add('\n'); break; // carriage return case 'r': add('\r'); break; // tab case 't': add('\t'); break; // unicode escapes case 'u': { const int codepoint1 = get_codepoint(); int codepoint = codepoint1; // start with codepoint1 if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if code point is a high surrogate if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) { // expect next \uxxxx entry if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) { const int codepoint2 = get_codepoint(); if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if codepoint2 is a low surrogate if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) { // overwrite codepoint codepoint = static_cast( // high surrogate occupies the most significant 22 bits (static_cast(codepoint1) << 10u) // low surrogate occupies the least significant 15 bits + static_cast(codepoint2) // there is still the 0xD800, 0xDC00 and 0x10000 noise // in the result so we have to subtract with: // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - 0x35FDC00u); } else { error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) { error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; return token_type::parse_error; } } // result of the above calculation yields a proper codepoint JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes if (codepoint < 0x80) { // 1-byte characters: 0xxxxxxx (ASCII) add(static_cast(codepoint)); } else if (codepoint <= 0x7FF) { // 2-byte characters: 110xxxxx 10xxxxxx add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else if (codepoint <= 0xFFFF) { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else { // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } break; } // other characters after escape default: error_message = "invalid string: forbidden character after backslash"; return token_type::parse_error; } break; } // invalid control characters case 0x00: { error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; return token_type::parse_error; } case 0x01: { error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; return token_type::parse_error; } case 0x02: { error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; return token_type::parse_error; } case 0x03: { error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; return token_type::parse_error; } case 0x04: { error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; return token_type::parse_error; } case 0x05: { error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; return token_type::parse_error; } case 0x06: { error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; return token_type::parse_error; } case 0x07: { error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; return token_type::parse_error; } case 0x08: { error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; return token_type::parse_error; } case 0x09: { error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; return token_type::parse_error; } case 0x0A: { error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; return token_type::parse_error; } case 0x0B: { error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; return token_type::parse_error; } case 0x0C: { error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; return token_type::parse_error; } case 0x0D: { error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; return token_type::parse_error; } case 0x0E: { error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; return token_type::parse_error; } case 0x0F: { error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; return token_type::parse_error; } case 0x10: { error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; return token_type::parse_error; } case 0x11: { error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; return token_type::parse_error; } case 0x12: { error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; return token_type::parse_error; } case 0x13: { error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; return token_type::parse_error; } case 0x14: { error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; return token_type::parse_error; } case 0x15: { error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; return token_type::parse_error; } case 0x16: { error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; return token_type::parse_error; } case 0x17: { error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; return token_type::parse_error; } case 0x18: { error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; return token_type::parse_error; } case 0x19: { error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; return token_type::parse_error; } case 0x1A: { error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; return token_type::parse_error; } case 0x1B: { error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; return token_type::parse_error; } case 0x1C: { error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; return token_type::parse_error; } case 0x1D: { error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; return token_type::parse_error; } case 0x1E: { error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; return token_type::parse_error; } case 0x1F: { error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; return token_type::parse_error; } // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) case 0x20: case 0x21: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: { add(current); break; } // U+0080..U+07FF: bytes C2..DF 80..BF case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: { if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) { return token_type::parse_error; } break; } // U+0800..U+0FFF: bytes E0 A0..BF 80..BF case 0xE0: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xEE: case 0xEF: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+D000..U+D7FF: bytes ED 80..9F 80..BF case 0xED: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF case 0xF0: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF case 0xF1: case 0xF2: case 0xF3: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF case 0xF4: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // remaining bytes (80..C1 and F5..FF) are ill-formed default: { error_message = "invalid string: ill-formed UTF-8 byte"; return token_type::parse_error; } } } } /*! * @brief scan a comment * @return whether comment could be scanned successfully */ bool scan_comment() { switch (get()) { // single-line comments skip input until a newline or EOF is read case '/': { while (true) { switch (get()) { case '\n': case '\r': case std::char_traits::eof(): case '\0': return true; default: break; } } } // multi-line comments skip input until */ is read case '*': { while (true) { switch (get()) { case std::char_traits::eof(): case '\0': { error_message = "invalid comment; missing closing '*/'"; return false; } case '*': { switch (get()) { case '/': return true; default: { unget(); continue; } } } default: continue; } } } // unexpected character after reading '/' default: { error_message = "invalid comment; expecting '/' or '*' after '/'"; return false; } } } JSON_HEDLEY_NON_NULL(2) static void strtof(float& f, const char* str, char** endptr) noexcept { f = std::strtof(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(double& f, const char* str, char** endptr) noexcept { f = std::strtod(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(long double& f, const char* str, char** endptr) noexcept { f = std::strtold(str, endptr); } /*! @brief scan a number literal This function scans a string according to Sect. 6 of RFC 7159. The function is realized with a deterministic finite state machine derived from the grammar described in RFC 7159. Starting in state "init", the input is read and used to determined the next state. Only state "done" accepts the number. State "error" is a trap state to model errors. In the table below, "anything" means any character but the ones listed before. state | 0 | 1-9 | e E | + | - | . | anything ---------|----------|----------|----------|---------|---------|----------|----------- init | zero | any1 | [error] | [error] | minus | [error] | [error] minus | zero | any1 | [error] | [error] | [error] | [error] | [error] zero | done | done | exponent | done | done | decimal1 | done any1 | any1 | any1 | exponent | done | done | decimal1 | done decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] decimal2 | decimal2 | decimal2 | exponent | done | done | done | done exponent | any2 | any2 | [error] | sign | sign | [error] | [error] sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] any2 | any2 | any2 | done | done | done | done | done The state machine is realized with one label per state (prefixed with "scan_number_") and `goto` statements between them. The state machine contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @return token_type::value_unsigned, token_type::value_integer, or token_type::value_float if number could be successfully scanned, token_type::parse_error otherwise @note The scanner is independent of the current locale. Internally, the locale's decimal point is used instead of `.` to work with the locale-dependent converters. */ token_type scan_number() // lgtm [cpp/use-of-goto] { // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be // changed if minus sign, decimal point or exponent is read token_type number_type = token_type::value_unsigned; // state (init): we just found out we need to scan a number switch (current) { case '-': { add(current); goto scan_number_minus; } case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } // all other characters are rejected outside scan_number() default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } scan_number_minus: // state: we just parsed a leading minus sign number_type = token_type::value_integer; switch (get()) { case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } default: { error_message = "invalid number; expected digit after '-'"; return token_type::parse_error; } } scan_number_zero: // state: we just parse a zero (maybe with a leading minus sign) switch (get()) { case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_any1: // state: we just parsed a number 0-9 (maybe with a leading minus sign) switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_decimal1: // state: we just parsed a decimal point number_type = token_type::value_float; switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } default: { error_message = "invalid number; expected digit after '.'"; return token_type::parse_error; } } scan_number_decimal2: // we just parsed at least one number after a decimal point switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_exponent: // we just parsed an exponent number_type = token_type::value_float; switch (get()) { case '+': case '-': { add(current); goto scan_number_sign; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected '+', '-', or digit after exponent"; return token_type::parse_error; } } scan_number_sign: // we just parsed an exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected digit after exponent sign"; return token_type::parse_error; } } scan_number_any2: // we just parsed a number after the exponent or exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: goto scan_number_done; } scan_number_done: // unget the character after the number (we only read it to know that // we are done scanning a number) unget(); char* endptr = nullptr; errno = 0; // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { const auto x = std::strtoull(token_buffer.data(), &endptr, 10); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_unsigned = static_cast(x); if (value_unsigned == x) { return token_type::value_unsigned; } } } else if (number_type == token_type::value_integer) { const auto x = std::strtoll(token_buffer.data(), &endptr, 10); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_integer = static_cast(x); if (value_integer == x) { return token_type::value_integer; } } } // this code is reached if we parse a floating-point number or if an // integer conversion above failed strtof(value_float, token_buffer.data(), &endptr); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); return token_type::value_float; } /*! @param[in] literal_text the literal text to expect @param[in] length the length of the passed literal text @param[in] return_type the token type to return on success */ JSON_HEDLEY_NON_NULL(2) token_type scan_literal(const char_type* literal_text, const std::size_t length, token_type return_type) { JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); for (std::size_t i = 1; i < length; ++i) { if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) { error_message = "invalid literal"; return token_type::parse_error; } } return return_type; } ///////////////////// // input management ///////////////////// /// reset token_buffer; current character is beginning of token void reset() noexcept { token_buffer.clear(); token_string.clear(); token_string.push_back(std::char_traits::to_char_type(current)); } /* @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a `std::char_traits::eof()` in that case. Stores the scanned characters for use in error messages. @return character read from the input */ char_int_type get() { ++position.chars_read_total; ++position.chars_read_current_line; if (next_unget) { // just reset the next_unget variable and work with current next_unget = false; } else { current = ia.get_character(); } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { token_string.push_back(std::char_traits::to_char_type(current)); } if (current == '\n') { ++position.lines_read; position.chars_read_current_line = 0; } return current; } /*! @brief unget current character (read it again on next get) We implement unget by setting variable next_unget to true. The input is not changed - we just simulate ungetting by modifying chars_read_total, chars_read_current_line, and token_string. The next call to get() will behave as if the unget character is read again. */ void unget() { next_unget = true; --position.chars_read_total; // in case we "unget" a newline, we have to also decrement the lines_read if (position.chars_read_current_line == 0) { if (position.lines_read > 0) { --position.lines_read; } } else { --position.chars_read_current_line; } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { JSON_ASSERT(!token_string.empty()); token_string.pop_back(); } } /// add a character to token_buffer void add(char_int_type c) { token_buffer.push_back(static_cast(c)); } public: ///////////////////// // value getters ///////////////////// /// return integer value constexpr number_integer_t get_number_integer() const noexcept { return value_integer; } /// return unsigned integer value constexpr number_unsigned_t get_number_unsigned() const noexcept { return value_unsigned; } /// return floating-point value constexpr number_float_t get_number_float() const noexcept { return value_float; } /// return current string value (implicitly resets the token; useful only once) string_t& get_string() { return token_buffer; } ///////////////////// // diagnostics ///////////////////// /// return position of last read token constexpr position_t get_position() const noexcept { return position; } /// return the last read token (for errors only). Will never contain EOF /// (an arbitrary value that is not a valid char value, often -1), because /// 255 may legitimately occur. May contain NUL, which should be escaped. std::string get_token_string() const { // escape control characters std::string result; for (const auto c : token_string) { if (static_cast(c) <= '\x1F') { // escape control characters std::array cs{{}}; (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); result += cs.data(); } else { // add character as is result.push_back(static_cast(c)); } } return result; } /// return syntax error message JSON_HEDLEY_RETURNS_NON_NULL constexpr const char* get_error_message() const noexcept { return error_message; } ///////////////////// // actual scanner ///////////////////// /*! @brief skip the UTF-8 byte order mark @return true iff there is no BOM or the correct BOM has been skipped */ bool skip_bom() { if (get() == 0xEF) { // check if we completely parse the BOM return get() == 0xBB && get() == 0xBF; } // the first character is not the beginning of the BOM; unget it to // process is later unget(); return true; } void skip_whitespace() { do { get(); } while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); } token_type scan() { // initially, skip the BOM if (position.chars_read_total == 0 && !skip_bom()) { error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; return token_type::parse_error; } // read next character and ignore whitespace skip_whitespace(); // ignore comments while (ignore_comments && current == '/') { if (!scan_comment()) { return token_type::parse_error; } // skip following whitespace skip_whitespace(); } switch (current) { // structural characters case '[': return token_type::begin_array; case ']': return token_type::end_array; case '{': return token_type::begin_object; case '}': return token_type::end_object; case ':': return token_type::name_separator; case ',': return token_type::value_separator; // literals case 't': { std::array true_literal = {{'t', 'r', 'u', 'e'}}; return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); } case 'f': { std::array false_literal = {{'f', 'a', 'l', 's', 'e'}}; return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); } case 'n': { std::array null_literal = {{'n', 'u', 'l', 'l'}}; return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); } // string case '\"': return scan_string(); // number case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return scan_number(); // end of input (the null byte is needed when parsing from // string literals) case '\0': case std::char_traits::eof(): return token_type::end_of_input; // error default: error_message = "invalid literal"; return token_type::parse_error; } } private: /// input adapter InputAdapterType ia; /// whether comments should be ignored (true) or signaled as errors (false) const bool ignore_comments = false; /// the current character char_int_type current = std::char_traits::eof(); /// whether the next get() call should just return current bool next_unget = false; /// the start position of the current token position_t position {}; /// raw input token string (for error messages) std::vector token_string {}; /// buffer for variable-length tokens (numbers, strings) string_t token_buffer {}; /// a description of occurred lexer errors const char* error_message = ""; // number values number_integer_t value_integer = 0; number_unsigned_t value_unsigned = 0; number_float_t value_float = 0; /// the decimal point const char_int_type decimal_point_char = '.'; }; } // namespace detail } // namespace nlohmann // #include // #include #include // size_t #include // declval #include // string // #include // #include namespace nlohmann { namespace detail { template using null_function_t = decltype(std::declval().null()); template using boolean_function_t = decltype(std::declval().boolean(std::declval())); template using number_integer_function_t = decltype(std::declval().number_integer(std::declval())); template using number_unsigned_function_t = decltype(std::declval().number_unsigned(std::declval())); template using number_float_function_t = decltype(std::declval().number_float( std::declval(), std::declval())); template using string_function_t = decltype(std::declval().string(std::declval())); template using binary_function_t = decltype(std::declval().binary(std::declval())); template using start_object_function_t = decltype(std::declval().start_object(std::declval())); template using key_function_t = decltype(std::declval().key(std::declval())); template using end_object_function_t = decltype(std::declval().end_object()); template using start_array_function_t = decltype(std::declval().start_array(std::declval())); template using end_array_function_t = decltype(std::declval().end_array()); template using parse_error_function_t = decltype(std::declval().parse_error( std::declval(), std::declval(), std::declval())); template struct is_sax { private: static_assert(is_basic_json::value, "BasicJsonType must be of type basic_json<...>"); using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using exception_t = typename BasicJsonType::exception; public: static constexpr bool value = is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value; }; template struct is_sax_static_asserts { private: static_assert(is_basic_json::value, "BasicJsonType must be of type basic_json<...>"); using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using exception_t = typename BasicJsonType::exception; public: static_assert(is_detected_exact::value, "Missing/invalid function: bool null()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool number_integer(number_integer_t)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool string(string_t&)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool binary(binary_t&)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool start_object(std::size_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool key(string_t&)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool end_object()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool start_array(std::size_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool end_array()"); static_assert( is_detected_exact::value, "Missing/invalid function: bool parse_error(std::size_t, const " "std::string&, const exception&)"); }; } // namespace detail } // namespace nlohmann // #include namespace nlohmann { namespace detail { /// how to treat CBOR tags enum class cbor_tag_handler_t { error, ///< throw a parse_error exception in case of a tag ignore ///< ignore tags }; /*! @brief determine system byte order @return true if and only if system's byte order is little endian @note from https://stackoverflow.com/a/1001328/266378 */ static inline bool little_endianess(int num = 1) noexcept { return *reinterpret_cast(&num) == 1; } /////////////////// // binary reader // /////////////////// /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using json_sax_t = SAX; using char_type = typename InputAdapterType::char_type; using char_int_type = typename std::char_traits::int_type; public: /*! @brief create a binary reader @param[in] adapter input adapter to read from */ explicit binary_reader(InputAdapterType&& adapter) : ia(std::move(adapter)) { (void)detail::is_sax_static_asserts {}; } // make class move-only binary_reader(const binary_reader&) = delete; binary_reader(binary_reader&&) = default; binary_reader& operator=(const binary_reader&) = delete; binary_reader& operator=(binary_reader&&) = default; ~binary_reader() = default; /*! @param[in] format the binary format to parse @param[in] sax_ a SAX event processor @param[in] strict whether to expect the input to be consumed completed @param[in] tag_handler how to treat CBOR tags @return */ JSON_HEDLEY_NON_NULL(3) bool sax_parse(const input_format_t format, json_sax_t* sax_, const bool strict = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { sax = sax_; bool result = false; switch (format) { case input_format_t::bson: result = parse_bson_internal(); break; case input_format_t::cbor: result = parse_cbor_internal(true, tag_handler); break; case input_format_t::msgpack: result = parse_msgpack_internal(); break; case input_format_t::ubjson: result = parse_ubjson_internal(); break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } // strict mode: next byte must be EOF if (result && strict) { if (format == input_format_t::ubjson) { get_ignore_noop(); } else { get(); } if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) { return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"))); } } return result; } private: ////////// // BSON // ////////// /*! @brief Reads in a BSON-object and passes it to the SAX-parser. @return whether a valid BSON-value was passed to the SAX parser */ bool parse_bson_internal() { std::int32_t document_size{}; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) { return false; } return sax->end_object(); } /*! @brief Parses a C-style string from the BSON input. @param[in, out] result A reference to the string variable where the read string is to be stored. @return `true` if the \x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ bool get_bson_cstr(string_t& result) { auto out = std::back_inserter(result); while (true) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) { return false; } if (current == 0x00) { return true; } *out++ = static_cast(current); } } /*! @brief Parses a zero-terminated string of length @a len from the BSON input. @param[in] len The length (including the zero-byte at the end) of the string to be read. @param[in, out] result A reference to the string variable where the read string is to be stored. @tparam NumberType The type of the length @a len @pre len >= 1 @return `true` if the string was successfully parsed */ template bool get_bson_string(const NumberType len, string_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 1)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"))); } return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); } /*! @brief Parses a byte array input of length @a len from the BSON input. @param[in] len The length of the byte array to be read. @param[in, out] result A reference to the binary variable where the read array is to be stored. @tparam NumberType The type of the length @a len @pre len >= 0 @return `true` if the byte array was successfully parsed */ template bool get_bson_binary(const NumberType len, binary_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 0)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"))); } // All BSON binary values have a subtype std::uint8_t subtype{}; get_number(input_format_t::bson, subtype); result.set_subtype(subtype); return get_binary(input_format_t::bson, len, result); } /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @param[in] element_type_parse_position The position in the input stream, where the `element_type` was read. @warning Not all BSON element types are supported yet. An unsupported @a element_type will give rise to a parse_error.114: Unsupported BSON record type 0x... @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_internal(const char_int_type element_type, const std::size_t element_type_parse_position) { switch (element_type) { case 0x01: // double { double number{}; return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); } case 0x02: // string { std::int32_t len{}; string_t value; return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); } case 0x03: // object { return parse_bson_internal(); } case 0x04: // array { return parse_bson_array(); } case 0x05: // binary { std::int32_t len{}; binary_t value; return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); } case 0x08: // boolean { return sax->boolean(get() != 0); } case 0x0A: // null { return sax->null(); } case 0x10: // int32 { std::int32_t value{}; return get_number(input_format_t::bson, value) && sax->number_integer(value); } case 0x12: // int64 { std::int64_t value{}; return get_number(input_format_t::bson, value) && sax->number_integer(value); } default: // anything else not supported (yet) { std::array cr{{}}; (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()))); } } } /*! @brief Read a BSON element list (as specified in the BSON-spec) The same binary layout is used for objects and arrays, hence it must be indicated with the argument @a is_array which one is expected (true --> array, false --> object). @param[in] is_array Determines if the element list being read is to be treated as an object (@a is_array == false), or as an array (@a is_array == true). @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_list(const bool is_array) { string_t key; while (auto element_type = get()) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) { return false; } const std::size_t element_type_parse_position = chars_read; if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) { return false; } if (!is_array && !sax->key(key)) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) { return false; } // get_bson_cstr only appends key.clear(); } return true; } /*! @brief Reads an array from the BSON input and passes it to the SAX-parser. @return whether a valid BSON-array was passed to the SAX parser */ bool parse_bson_array() { std::int32_t document_size{}; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) { return false; } return sax->end_array(); } ////////// // CBOR // ////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true) or whether the last read character should be considered instead (false) @param[in] tag_handler how CBOR tags should be treated @return whether a valid CBOR value was passed to the SAX parser */ bool parse_cbor_internal(const bool get_char, const cbor_tag_handler_t tag_handler) { switch (get_char ? get() : current) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::cbor, "value"); // Integer 0x00..0x17 (0..23) case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: return sax->number_unsigned(static_cast(current)); case 0x18: // Unsigned integer (one-byte uint8_t follows) { std::uint8_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x19: // Unsigned integer (two-byte uint16_t follows) { std::uint16_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x1A: // Unsigned integer (four-byte uint32_t follows) { std::uint32_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x1B: // Unsigned integer (eight-byte uint64_t follows) { std::uint64_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } // Negative integer -1-0x00..-1-0x17 (-1..-24) case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: return sax->number_integer(static_cast(0x20 - 1 - current)); case 0x38: // Negative integer (one-byte uint8_t follows) { std::uint8_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x39: // Negative integer -1-n (two-byte uint16_t follows) { std::uint16_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) { std::uint32_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) { std::uint64_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - static_cast(number)); } // Binary data (0x00..0x17 bytes follow) case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: // Binary data (one-byte uint8_t for n follows) case 0x59: // Binary data (two-byte uint16_t for n follow) case 0x5A: // Binary data (four-byte uint32_t for n follow) case 0x5B: // Binary data (eight-byte uint64_t for n follow) case 0x5F: // Binary data (indefinite length) { binary_t b; return get_cbor_binary(b) && sax->binary(b); } // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x79: // UTF-8 string (two-byte uint16_t for n follow) case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) case 0x7F: // UTF-8 string (indefinite length) { string_t s; return get_cbor_string(s) && sax->string(s); } // array (0x00..0x17 data items follow) case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0x98: // array (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x99: // array (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9A: // array (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9B: // array (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9F: // array (indefinite length) return get_cbor_array(std::size_t(-1), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0xB8: // map (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xB9: // map (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBA: // map (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBB: // map (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBF: // map (indefinite length) return get_cbor_object(std::size_t(-1), tag_handler); case 0xC6: // tagged item case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD8: // tagged item (1 bytes follow) case 0xD9: // tagged item (2 bytes follow) case 0xDA: // tagged item (4 bytes follow) case 0xDB: // tagged item (8 bytes follow) { switch (tag_handler) { case cbor_tag_handler_t::error: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); } case cbor_tag_handler_t::ignore: { switch (current) { case 0xD8: { std::uint8_t len{}; get_number(input_format_t::cbor, len); break; } case 0xD9: { std::uint16_t len{}; get_number(input_format_t::cbor, len); break; } case 0xDA: { std::uint32_t len{}; get_number(input_format_t::cbor, len); break; } case 0xDB: { std::uint64_t len{}; get_number(input_format_t::cbor, len); break; } default: break; } return parse_cbor_internal(true, tag_handler); } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } case 0xF4: // false return sax->boolean(false); case 0xF5: // true return sax->boolean(true); case 0xF6: // null return sax->null(); case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const auto byte1_raw = get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte2_raw = get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte1 = static_cast(byte1_raw); const auto byte2 = static_cast(byte2_raw); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added // to IEEE 754 in 2008, today's programming platforms often // still only have limited support for them. It is very // easy to include at least decoding support for them even // without such support. An example of a small decoder for // half-precision floating-point numbers in the C language // is shown in Fig. 3. const auto half = static_cast((byte1 << 8u) + byte2); const double val = [&half] { const int exp = (half >> 10u) & 0x1Fu; const unsigned int mant = half & 0x3FFu; JSON_ASSERT(0 <= exp&& exp <= 32); JSON_ASSERT(mant <= 1024); switch (exp) { case 0: return std::ldexp(mant, -24); case 31: return (mant == 0) ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); default: return std::ldexp(mant + 1024, exp - 25); } }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) : static_cast(val), ""); } case 0xFA: // Single-Precision Float (four-byte IEEE 754) { float number{}; return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); } case 0xFB: // Double-Precision Float (eight-byte IEEE 754) { double number{}; return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); } default: // anything else (0xFF is handled inside the other types) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); } } } /*! @brief reads a CBOR string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. Additionally, CBOR's strings with indefinite lengths are supported. @param[out] result created string @return whether string creation completed */ bool get_cbor_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) { return false; } switch (current) { // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: { return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7F: // UTF-8 string (indefinite length) { while (get() != 0xFF) { string_t chunk; if (!get_cbor_string(chunk)) { return false; } result.append(chunk); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"))); } } } /*! @brief reads a CBOR byte array This function first reads starting bytes to determine the expected byte array length and then copies this number of bytes into the byte array. Additionally, CBOR's byte arrays with indefinite lengths are supported. @param[out] result created byte array @return whether byte array creation completed */ bool get_cbor_binary(binary_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) { return false; } switch (current) { // Binary data (0x00..0x17 bytes follow) case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: { return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x58: // Binary data (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x59: // Binary data (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5A: // Binary data (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5B: // Binary data (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5F: // Binary data (indefinite length) { while (get() != 0xFF) { binary_t chunk; if (!get_cbor_binary(chunk)) { return false; } result.insert(result.end(), chunk.begin(), chunk.end()); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"))); } } } /*! @param[in] len the length of the array or std::size_t(-1) for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed */ bool get_cbor_array(const std::size_t len, const cbor_tag_handler_t tag_handler) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) { return false; } if (len != std::size_t(-1)) { for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) { return false; } } } return sax->end_array(); } /*! @param[in] len the length of the object or std::size_t(-1) for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed */ bool get_cbor_object(const std::size_t len, const cbor_tag_handler_t tag_handler) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) { return false; } string_t key; if (len != std::size_t(-1)) { for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } key.clear(); } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } key.clear(); } } return sax->end_object(); } ///////////// // MsgPack // ///////////// /*! @return whether a valid MessagePack value was passed to the SAX parser */ bool parse_msgpack_internal() { switch (get()) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::msgpack, "value"); // positive fixint case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: return sax->number_unsigned(static_cast(current)); // fixmap case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); // fixarray case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xD9: // str 8 case 0xDA: // str 16 case 0xDB: // str 32 { string_t s; return get_msgpack_string(s) && sax->string(s); } case 0xC0: // nil return sax->null(); case 0xC2: // false return sax->boolean(false); case 0xC3: // true return sax->boolean(true); case 0xC4: // bin 8 case 0xC5: // bin 16 case 0xC6: // bin 32 case 0xC7: // ext 8 case 0xC8: // ext 16 case 0xC9: // ext 32 case 0xD4: // fixext 1 case 0xD5: // fixext 2 case 0xD6: // fixext 4 case 0xD7: // fixext 8 case 0xD8: // fixext 16 { binary_t b; return get_msgpack_binary(b) && sax->binary(b); } case 0xCA: // float 32 { float number{}; return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); } case 0xCB: // float 64 { double number{}; return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); } case 0xCC: // uint 8 { std::uint8_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCD: // uint 16 { std::uint16_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCE: // uint 32 { std::uint32_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCF: // uint 64 { std::uint64_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xD0: // int 8 { std::int8_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD1: // int 16 { std::int16_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD2: // int 32 { std::int32_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD3: // int 64 { std::int64_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xDC: // array 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); } case 0xDD: // array 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); } case 0xDE: // map 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); } case 0xDF: // map 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); } // negative fixint case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: return sax->number_integer(static_cast(current)); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"))); } } } /*! @brief reads a MessagePack string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. @param[out] result created string @return whether string creation completed */ bool get_msgpack_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) { return false; } switch (current) { // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: { return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); } case 0xD9: // str 8 { std::uint8_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } case 0xDA: // str 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } case 0xDB: // str 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"))); } } } /*! @brief reads a MessagePack byte array This function first reads starting bytes to determine the expected byte array length and then copies this number of bytes into a byte array. @param[out] result created byte array @return whether byte array creation completed */ bool get_msgpack_binary(binary_t& result) { // helper function to set the subtype auto assign_and_return_true = [&result](std::int8_t subtype) { result.set_subtype(static_cast(subtype)); return true; }; switch (current) { case 0xC4: // bin 8 { std::uint8_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC5: // bin 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC6: // bin 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC7: // ext 8 { std::uint8_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xC8: // ext 16 { std::uint16_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xC9: // ext 32 { std::uint32_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xD4: // fixext 1 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 1, result) && assign_and_return_true(subtype); } case 0xD5: // fixext 2 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 2, result) && assign_and_return_true(subtype); } case 0xD6: // fixext 4 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 4, result) && assign_and_return_true(subtype); } case 0xD7: // fixext 8 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 8, result) && assign_and_return_true(subtype); } case 0xD8: // fixext 16 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 16, result) && assign_and_return_true(subtype); } default: // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE } } /*! @param[in] len the length of the array @return whether array creation completed */ bool get_msgpack_array(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) { return false; } for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) { return false; } } return sax->end_array(); } /*! @param[in] len the length of the object @return whether object creation completed */ bool get_msgpack_object(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) { return false; } string_t key; for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) { return false; } key.clear(); } return sax->end_object(); } //////////// // UBJSON // //////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether a valid UBJSON value was passed to the SAX parser */ bool parse_ubjson_internal(const bool get_char = true) { return get_ubjson_value(get_char ? get_ignore_noop() : current); } /*! @brief reads a UBJSON string This function is either called after reading the 'S' byte explicitly indicating a string, or in case of an object key where the 'S' byte can be left out. @param[out] result created string @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether string creation completed */ bool get_ubjson_string(string_t& result, const bool get_char = true) { if (get_char) { get(); // TODO(niels): may we ignore N here? } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) { return false; } switch (current) { case 'U': { std::uint8_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'i': { std::int8_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'I': { std::int16_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'l': { std::int32_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'L': { std::int64_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } default: auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"))); } } /*! @param[out] result determined size @return whether size determination completed */ bool get_ubjson_size_value(std::size_t& result) { switch (get_ignore_noop()) { case 'U': { std::uint8_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'i': { std::int8_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'I': { std::int16_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'l': { std::int32_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'L': { std::int64_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"))); } } } /*! @brief determine the type and size for a container In the optimized UBJSON format, a type and a size can be provided to allow for a more compact representation. @param[out] result pair of the size and the type @return whether pair creation completed */ bool get_ubjson_size_type(std::pair& result) { result.first = string_t::npos; // size result.second = 0; // type get_ignore_noop(); if (current == '$') { result.second = get(); // must not ignore 'N', because 'N' maybe the type if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) { return false; } get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) { return false; } auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"))); } return get_ubjson_size_value(result.first); } if (current == '#') { return get_ubjson_size_value(result.first); } return true; } /*! @param prefix the previously read or set type prefix @return whether value creation completed */ bool get_ubjson_value(const char_int_type prefix) { switch (prefix) { case std::char_traits::eof(): // EOF return unexpect_eof(input_format_t::ubjson, "value"); case 'T': // true return sax->boolean(true); case 'F': // false return sax->boolean(false); case 'Z': // null return sax->null(); case 'U': { std::uint8_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); } case 'i': { std::int8_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'I': { std::int16_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'l': { std::int32_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'L': { std::int64_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'd': { float number{}; return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); } case 'D': { double number{}; return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); } case 'H': { return get_ubjson_high_precision_number(); } case 'C': // char { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) { return false; } if (JSON_HEDLEY_UNLIKELY(current > 127)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"))); } string_t s(1, static_cast(current)); return sax->string(s); } case 'S': // string { string_t s; return get_ubjson_string(s) && sax->string(s); } case '[': // array return get_ubjson_array(); case '{': // object return get_ubjson_object(); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"))); } } } /*! @return whether array creation completed */ bool get_ubjson_array() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { return false; } if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) { return false; } if (size_and_type.second != 0) { if (size_and_type.second != 'N') { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; } } } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } } } } else { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) { return false; } while (current != ']') { if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) { return false; } get_ignore_noop(); } } return sax->end_array(); } /*! @return whether object creation completed */ bool get_ubjson_object() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { return false; } string_t key; if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) { return false; } if (size_and_type.second != 0) { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; } key.clear(); } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } key.clear(); } } } else { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) { return false; } while (current != '}') { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } get_ignore_noop(); key.clear(); } } return sax->end_object(); } // Note, no reader for UBJSON binary types is implemented because they do // not exist bool get_ubjson_high_precision_number() { // get size of following number string std::size_t size{}; auto res = get_ubjson_size_value(size); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; } // get number string std::vector number_vector; for (std::size_t i = 0; i < size; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) { return false; } number_vector.push_back(static_cast(current)); } // parse number string auto number_ia = detail::input_adapter(std::forward(number_vector)); auto number_lexer = detail::lexer(std::move(number_ia), false); const auto result_number = number_lexer.scan(); const auto number_string = number_lexer.get_token_string(); const auto result_remainder = number_lexer.scan(); using token_type = typename detail::lexer_base::token_type; if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); } switch (result_number) { case token_type::value_integer: return sax->number_integer(number_lexer.get_number_integer()); case token_type::value_unsigned: return sax->number_unsigned(number_lexer.get_number_unsigned()); case token_type::value_float: return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); default: return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); } } /////////////////////// // Utility functions // /////////////////////// /*! @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a -'ve valued `std::char_traits::eof()` in that case. @return character read from the input */ char_int_type get() { ++chars_read; return current = ia.get_character(); } /*! @return character read from the input after ignoring all 'N' entries */ char_int_type get_ignore_noop() { do { get(); } while (current == 'N'); return current; } /* @brief read a number from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[out] result number of type @a NumberType @return whether conversion completed @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template bool get_number(const input_format_t format, NumberType& result) { // step 1: read input into array with system's byte order std::array vec; for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) { return false; } // reverse byte order prior to conversion if necessary if (is_little_endian != InputIsLittleEndian) { vec[sizeof(NumberType) - i - 1] = static_cast(current); } else { vec[i] = static_cast(current); // LCOV_EXCL_LINE } } // step 2: convert array into number of type T and return std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } /*! @brief create a string by reading characters from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of characters to read @param[out] result string created by reading @a len bytes @return whether string creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of string memory. */ template bool get_string(const input_format_t format, const NumberType len, string_t& result) { bool success = true; for (NumberType i = 0; i < len; i++) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) { success = false; break; } result.push_back(static_cast(current)); }; return success; } /*! @brief create a byte array by reading bytes from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of bytes to read @param[out] result byte array created by reading @a len bytes @return whether byte array creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of memory. */ template bool get_binary(const input_format_t format, const NumberType len, binary_t& result) { bool success = true; for (NumberType i = 0; i < len; i++) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) { success = false; break; } result.push_back(static_cast(current)); } return success; } /*! @param[in] format the current format (for diagnostics) @param[in] context further context information (for diagnostics) @return whether the last read character is not EOF */ JSON_HEDLEY_NON_NULL(3) bool unexpect_eof(const input_format_t format, const char* context) const { if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) { return sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context))); } return true; } /*! @return a string representation of the last read byte */ std::string get_token_string() const { std::array cr{{}}; (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); return std::string{cr.data()}; } /*! @param[in] format the current format @param[in] detail a detailed error message @param[in] context further context information @return a message string to use in the parse_error exceptions */ std::string exception_message(const input_format_t format, const std::string& detail, const std::string& context) const { std::string error_msg = "syntax error while parsing "; switch (format) { case input_format_t::cbor: error_msg += "CBOR"; break; case input_format_t::msgpack: error_msg += "MessagePack"; break; case input_format_t::ubjson: error_msg += "UBJSON"; break; case input_format_t::bson: error_msg += "BSON"; break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } return error_msg + " " + context + ": " + detail; } private: /// input adapter InputAdapterType ia; /// the current character char_int_type current = std::char_traits::eof(); /// the number of characters read std::size_t chars_read = 0; /// whether we can assume little endianness const bool is_little_endian = little_endianess(); /// the SAX parser json_sax_t* sax = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // isfinite #include // uint8_t #include // function #include // string #include // move #include // vector // #include // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { //////////// // parser // //////////// enum class parse_event_t : uint8_t { /// the parser read `{` and started to process a JSON object object_start, /// the parser read `}` and finished processing a JSON object object_end, /// the parser read `[` and started to process a JSON array array_start, /// the parser read `]` and finished processing a JSON array array_end, /// the parser read a key of a value in an object key, /// the parser finished reading a JSON value value }; template using parser_callback_t = std::function; /*! @brief syntax analysis This class implements a recursive descent parser. */ template class parser { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using lexer_t = lexer; using token_type = typename lexer_t::token_type; public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, const parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool skip_comments = false) : callback(cb) , m_lexer(std::move(adapter), skip_comments) , allow_exceptions(allow_exceptions_) { // read first token get_token(); } /*! @brief public parser interface @param[in] strict whether to expect the last token to be EOF @param[in,out] result parsed JSON value @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails */ void parse(const bool strict, BasicJsonType& result) { if (callback) { json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); sax_parse_internal(&sdp); result.assert_invariant(); // in strict mode, input must be completely read if (strict && (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } // set top-level value to null if it was discarded by the callback // function if (result.is_discarded()) { result = nullptr; } } else { json_sax_dom_parser sdp(result, allow_exceptions); sax_parse_internal(&sdp); result.assert_invariant(); // in strict mode, input must be completely read if (strict && (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } } } /*! @brief public accept interface @param[in] strict whether to expect the last token to be EOF @return whether the input is a proper JSON text */ bool accept(const bool strict = true) { json_sax_acceptor sax_acceptor; return sax_parse(&sax_acceptor, strict); } template JSON_HEDLEY_NON_NULL(2) bool sax_parse(SAX* sax, const bool strict = true) { (void)detail::is_sax_static_asserts {}; const bool result = sax_parse_internal(sax); // strict mode: next byte must be EOF if (result && strict && (get_token() != token_type::end_of_input)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } return result; } private: template JSON_HEDLEY_NON_NULL(2) bool sax_parse_internal(SAX* sax) { // stack to remember the hierarchy of structured values we are parsing // true = array; false = object std::vector states; // value to avoid a goto (see comment where set to true) bool skip_to_state_evaluation = false; while (true) { if (!skip_to_state_evaluation) { // invariant: get_token() was called before each iteration switch (last_token) { case token_type::begin_object: { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) { return false; } // closing } -> we are done if (get_token() == token_type::end_object) { if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) { return false; } break; } // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"))); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"))); } // remember we are now inside an object states.push_back(false); // parse values get_token(); continue; } case token_type::begin_array: { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) { return false; } // closing ] -> we are done if (get_token() == token_type::end_array) { if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) { return false; } break; } // remember we are now inside an array states.push_back(true); // parse values (no need to call get_token) continue; } case token_type::value_float: { const auto res = m_lexer.get_number_float(); if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) { return false; } break; } case token_type::literal_false: { if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) { return false; } break; } case token_type::literal_null: { if (JSON_HEDLEY_UNLIKELY(!sax->null())) { return false; } break; } case token_type::literal_true: { if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) { return false; } break; } case token_type::value_integer: { if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) { return false; } break; } case token_type::value_string: { if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) { return false; } break; } case token_type::value_unsigned: { if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) { return false; } break; } case token_type::parse_error: { // using "uninitialized" to avoid "expected" message return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"))); } default: // the last token was unexpected { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"))); } } } else { skip_to_state_evaluation = false; } // we reached this line after we successfully parsed a value if (states.empty()) { // empty stack: we reached the end of the hierarchy: done return true; } if (states.back()) // array { // comma -> next value if (get_token() == token_type::value_separator) { // parse a new value get_token(); continue; } // closing ] if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) { if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) { return false; } // We are done with this array. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. JSON_ASSERT(!states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"))); } else // object { // comma -> next value if (get_token() == token_type::value_separator) { // parse key if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"))); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"))); } // parse values get_token(); continue; } // closing } if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) { if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) { return false; } // We are done with this object. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. JSON_ASSERT(!states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"))); } } } /// get next token from lexer token_type get_token() { return last_token = m_lexer.scan(); } std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; if (!context.empty()) { error_msg += "while parsing " + context + " "; } error_msg += "- "; if (last_token == token_type::parse_error) { error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + m_lexer.get_token_string() + "'"; } else { error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); } if (expected != token_type::uninitialized) { error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); } return error_msg; } private: /// callback function const parser_callback_t callback = nullptr; /// the type of the last read token token_type last_token = token_type::uninitialized; /// the lexer lexer_t m_lexer; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; } // namespace detail } // namespace nlohmann // #include // #include #include // ptrdiff_t #include // numeric_limits namespace nlohmann { namespace detail { /* @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, string). It's only purpose is to allow the iterator/const_iterator classes to "iterate" over primitive values. Internally, the iterator is modeled by a `difference_type` variable. Value begin_value (`0`) models the begin, end_value (`1`) models past the end. */ class primitive_iterator_t { private: using difference_type = std::ptrdiff_t; static constexpr difference_type begin_value = 0; static constexpr difference_type end_value = begin_value + 1; /// iterator as signed integer type difference_type m_it = (std::numeric_limits::min)(); public: constexpr difference_type get_value() const noexcept { return m_it; } /// set iterator to a defined beginning void set_begin() noexcept { m_it = begin_value; } /// set iterator to a defined past the end void set_end() noexcept { m_it = end_value; } /// return whether the iterator can be dereferenced constexpr bool is_begin() const noexcept { return m_it == begin_value; } /// return whether the iterator is at end constexpr bool is_end() const noexcept { return m_it == end_value; } friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it == rhs.m_it; } friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it < rhs.m_it; } primitive_iterator_t operator+(difference_type n) noexcept { auto result = *this; result += n; return result; } friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it - rhs.m_it; } primitive_iterator_t& operator++() noexcept { ++m_it; return *this; } primitive_iterator_t const operator++(int) noexcept { auto result = *this; ++m_it; return result; } primitive_iterator_t& operator--() noexcept { --m_it; return *this; } primitive_iterator_t const operator--(int) noexcept { auto result = *this; --m_it; return result; } primitive_iterator_t& operator+=(difference_type n) noexcept { m_it += n; return *this; } primitive_iterator_t& operator-=(difference_type n) noexcept { m_it -= n; return *this; } }; } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { /*! @brief an iterator value @note This structure could easily be a union, but MSVC currently does not allow unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. */ template struct internal_iterator { /// iterator for JSON objects typename BasicJsonType::object_t::iterator object_iterator {}; /// iterator for JSON arrays typename BasicJsonType::array_t::iterator array_iterator {}; /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; } // namespace detail } // namespace nlohmann // #include #include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next #include // conditional, is_const, remove_const // #include // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { // forward declare, to be able to friend it later on template class iteration_proxy; template class iteration_proxy_value; /*! @brief a template for a bidirectional iterator for the @ref basic_json class This class implements a both iterators (iterator and const_iterator) for the @ref basic_json class. @note An iterator is called *initialized* when a pointer to a JSON value has been set (e.g., by a constructor or a copy assignment). If the iterator is default-constructed, it is *uninitialized* and most methods are undefined. **The library uses assertions to detect calls on uninitialized iterators.** @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). @since version 1.0.0, simplified in version 2.0.9, change to bidirectional iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) */ template class iter_impl { /// allow basic_json to access private members friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; friend BasicJsonType; friend iteration_proxy; friend iteration_proxy_value; using object_t = typename BasicJsonType::object_t; using array_t = typename BasicJsonType::array_t; // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); public: /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The C++ Standard has never required user-defined iterators to derive from std::iterator. /// A user-defined iterator should provide publicly accessible typedefs named /// iterator_category, value_type, difference_type, pointer, and reference. /// Note that value_type is required to be non-const, even for constant iterators. using iterator_category = std::bidirectional_iterator_tag; /// the type of the values when the iterator is dereferenced using value_type = typename BasicJsonType::value_type; /// a type to represent differences between iterators using difference_type = typename BasicJsonType::difference_type; /// defines a pointer to the type iterated over (value_type) using pointer = typename std::conditional::value, typename BasicJsonType::const_pointer, typename BasicJsonType::pointer>::type; /// defines a reference to the type iterated over (value_type) using reference = typename std::conditional::value, typename BasicJsonType::const_reference, typename BasicJsonType::reference>::type; /// default constructor iter_impl() = default; /*! @brief constructor for a given JSON instance @param[in] object pointer to a JSON object for this iterator @pre object != nullptr @post The iterator is initialized; i.e. `m_object != nullptr`. */ explicit iter_impl(pointer object) noexcept : m_object(object) { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = typename object_t::iterator(); break; } case value_t::array: { m_it.array_iterator = typename array_t::iterator(); break; } default: { m_it.primitive_iterator = primitive_iterator_t(); break; } } } /*! @note The conventional copy constructor and copy assignment are implicitly defined. Combined with the following converting constructor and assignment, they support: (1) copy from iterator to iterator, (2) copy from const iterator to const iterator, and (3) conversion from iterator to const iterator. However conversion from const iterator to iterator is not defined. */ /*! @brief const copy constructor @param[in] other const iterator to copy from @note This copy constructor had to be defined explicitly to circumvent a bug occurring on msvc v19.0 compiler (VS 2015) debug build. For more information refer to: https://github.com/nlohmann/json/issues/1608 */ iter_impl(const iter_impl& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl& other) noexcept { m_object = other.m_object; m_it = other.m_it; return *this; } /*! @brief converting constructor @param[in] other non-const iterator to copy from @note It is not checked whether @a other is initialized. */ iter_impl(const iter_impl::type>& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other non-const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl::type>& other) noexcept { m_object = other.m_object; m_it = other.m_it; return *this; } private: /*! @brief set the iterator to the first value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_begin() noexcept { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->begin(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->begin(); break; } case value_t::null: { // set to end so begin()==end() is true: null is empty m_it.primitive_iterator.set_end(); break; } default: { m_it.primitive_iterator.set_begin(); break; } } } /*! @brief set the iterator past the last value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_end() noexcept { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->end(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->end(); break; } default: { m_it.primitive_iterator.set_end(); break; } } } public: /*! @brief return a reference to the value pointed to by the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator*() const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); return m_it.object_iterator->second; } case value_t::array: { JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value")); default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief dereference the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ pointer operator->() const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); return &(m_it.object_iterator->second); } case value_t::array: { JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief post-increment (it++) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator++(int) { auto result = *this; ++(*this); return result; } /*! @brief pre-increment (++it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator++() { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, 1); break; } case value_t::array: { std::advance(m_it.array_iterator, 1); break; } default: { ++m_it.primitive_iterator; break; } } return *this; } /*! @brief post-decrement (it--) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator--(int) { auto result = *this; --(*this); return result; } /*! @brief pre-decrement (--it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator--() { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, -1); break; } case value_t::array: { std::advance(m_it.array_iterator, -1); break; } default: { --m_it.primitive_iterator; break; } } return *this; } /*! @brief comparison: equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator==(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); } JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: return (m_it.object_iterator == other.m_it.object_iterator); case value_t::array: return (m_it.array_iterator == other.m_it.array_iterator); default: return (m_it.primitive_iterator == other.m_it.primitive_iterator); } } /*! @brief comparison: not equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator!=(const iter_impl& other) const { return !operator==(other); } /*! @brief comparison: smaller @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); } JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); default: return (m_it.primitive_iterator < other.m_it.primitive_iterator); } } /*! @brief comparison: less than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<=(const iter_impl& other) const { return !other.operator < (*this); } /*! @brief comparison: greater than @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>(const iter_impl& other) const { return !operator<=(other); } /*! @brief comparison: greater than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>=(const iter_impl& other) const { return !operator<(other); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator+=(difference_type i) { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); case value_t::array: { std::advance(m_it.array_iterator, i); break; } default: { m_it.primitive_iterator += i; break; } } return *this; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator-=(difference_type i) { return operator+=(-i); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator+(difference_type i) const { auto result = *this; result += i; return result; } /*! @brief addition of distance and iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ friend iter_impl operator+(difference_type i, const iter_impl& it) { auto result = it; result += i; return result; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator-(difference_type i) const { auto result = *this; result -= i; return result; } /*! @brief return difference @pre The iterator is initialized; i.e. `m_object != nullptr`. */ difference_type operator-(const iter_impl& other) const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; default: return m_it.primitive_iterator - other.m_it.primitive_iterator; } } /*! @brief access to successor @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator[](difference_type n) const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value")); default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief return the key of an object iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ const typename object_t::key_type& key() const { JSON_ASSERT(m_object != nullptr); if (JSON_HEDLEY_LIKELY(m_object->is_object())) { return m_it.object_iterator->first; } JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); } /*! @brief return the value of an iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference value() const { return operator*(); } private: /// associated JSON instance pointer m_object = nullptr; /// the actual iterator of the associated instance internal_iterator::type> m_it {}; }; } // namespace detail } // namespace nlohmann // #include // #include #include // ptrdiff_t #include // reverse_iterator #include // declval namespace nlohmann { namespace detail { ////////////////////// // reverse_iterator // ////////////////////// /*! @brief a template for a reverse iterator class @tparam Base the base iterator type to reverse. Valid types are @ref iterator (to create @ref reverse_iterator) and @ref const_iterator (to create @ref const_reverse_iterator). @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). - [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). @since version 1.0.0 */ template class json_reverse_iterator : public std::reverse_iterator { public: using difference_type = std::ptrdiff_t; /// shortcut to the reverse iterator adapter using base_iterator = std::reverse_iterator; /// the reference type for the pointed-to element using reference = typename Base::reference; /// create reverse iterator from iterator explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept : base_iterator(it) {} /// create reverse iterator from base class explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} /// post-increment (it++) json_reverse_iterator const operator++(int) { return static_cast(base_iterator::operator++(1)); } /// pre-increment (++it) json_reverse_iterator& operator++() { return static_cast(base_iterator::operator++()); } /// post-decrement (it--) json_reverse_iterator const operator--(int) { return static_cast(base_iterator::operator--(1)); } /// pre-decrement (--it) json_reverse_iterator& operator--() { return static_cast(base_iterator::operator--()); } /// add to iterator json_reverse_iterator& operator+=(difference_type i) { return static_cast(base_iterator::operator+=(i)); } /// add to iterator json_reverse_iterator operator+(difference_type i) const { return static_cast(base_iterator::operator+(i)); } /// subtract from iterator json_reverse_iterator operator-(difference_type i) const { return static_cast(base_iterator::operator-(i)); } /// return difference difference_type operator-(const json_reverse_iterator& other) const { return base_iterator(*this) - base_iterator(other); } /// access to successor reference operator[](difference_type n) const { return *(this->operator+(n)); } /// return the key of an object iterator auto key() const -> decltype(std::declval().key()) { auto it = --this->base(); return it.key(); } /// return the value of an iterator reference value() const { auto it = --this->base(); return it.operator * (); } }; } // namespace detail } // namespace nlohmann // #include // #include #include // all_of #include // isdigit #include // max #include // accumulate #include // string #include // move #include // vector // #include // #include // #include namespace nlohmann { template class json_pointer { // allow basic_json to access private members NLOHMANN_BASIC_JSON_TPL_DECLARATION friend class basic_json; public: /*! @brief create JSON pointer Create a JSON pointer according to the syntax described in [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). @param[in] s string representing the JSON pointer; if omitted, the empty string is assumed which references the whole JSON value @throw parse_error.107 if the given JSON pointer @a s is nonempty and does not begin with a slash (`/`); see example below @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is not followed by `0` (representing `~`) or `1` (representing `/`); see example below @liveexample{The example shows the construction several valid JSON pointers as well as the exceptional behavior.,json_pointer} @since version 2.0.0 */ explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} /*! @brief return a string representation of the JSON pointer @invariant For each JSON pointer `ptr`, it holds: @code {.cpp} ptr == json_pointer(ptr.to_string()); @endcode @return a string representation of the JSON pointer @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} @since version 2.0.0 */ std::string to_string() const { return std::accumulate(reference_tokens.begin(), reference_tokens.end(), std::string{}, [](const std::string & a, const std::string & b) { return a + "/" + escape(b); }); } /// @copydoc to_string() operator std::string() const { return to_string(); } /*! @brief append another JSON pointer at the end of this JSON pointer @param[in] ptr JSON pointer to append @return JSON pointer with @a ptr appended @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::string) to append a reference token @sa @ref operator/=(std::size_t) to append an array index @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(const json_pointer& ptr) { reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); return *this; } /*! @brief append an unescaped reference token at the end of this JSON pointer @param[in] token reference token to append @return JSON pointer with @a token appended without escaping @a token @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Amortized constant. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @sa @ref operator/=(std::size_t) to append an array index @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(std::string token) { push_back(std::move(token)); return *this; } /*! @brief append an array index at the end of this JSON pointer @param[in] array_idx array index to append @return JSON pointer with @a array_idx appended @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Amortized constant. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @sa @ref operator/=(std::string) to append a reference token @sa @ref operator/(const json_pointer&, std::string) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(std::size_t array_idx) { return *this /= std::to_string(array_idx); } /*! @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer @param[in] lhs JSON pointer @param[in] rhs JSON pointer @return a new JSON pointer with @a rhs appended to @a lhs @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a lhs and @a rhs. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& lhs, const json_pointer& rhs) { return json_pointer(lhs) /= rhs; } /*! @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer @param[in] ptr JSON pointer @param[in] token reference token @return a new JSON pointer with unescaped @a token appended to @a ptr @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::string) to append a reference token @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& ptr, std::string token) { return json_pointer(ptr) /= std::move(token); } /*! @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer @param[in] ptr JSON pointer @param[in] array_idx array index @return a new JSON pointer with @a array_idx appended to @a ptr @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::size_t) to append an array index @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) { return json_pointer(ptr) /= array_idx; } /*! @brief returns the parent of this JSON pointer @return parent of this JSON pointer; in case this JSON pointer is the root, the root itself is returned @complexity Linear in the length of the JSON pointer. @liveexample{The example shows the result of `parent_pointer` for different JSON Pointers.,json_pointer__parent_pointer} @since version 3.6.0 */ json_pointer parent_pointer() const { if (empty()) { return *this; } json_pointer res = *this; res.pop_back(); return res; } /*! @brief remove last reference token @pre not `empty()` @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} @complexity Constant. @throw out_of_range.405 if JSON pointer has no parent @since version 3.6.0 */ void pop_back() { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } reference_tokens.pop_back(); } /*! @brief return last reference token @pre not `empty()` @return last reference token @liveexample{The example shows the usage of `back`.,json_pointer__back} @complexity Constant. @throw out_of_range.405 if JSON pointer has no parent @since version 3.6.0 */ const std::string& back() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } return reference_tokens.back(); } /*! @brief append an unescaped token at the end of the reference pointer @param[in] token token to add @complexity Amortized constant. @liveexample{The example shows the result of `push_back` for different JSON Pointers.,json_pointer__push_back} @since version 3.6.0 */ void push_back(const std::string& token) { reference_tokens.push_back(token); } /// @copydoc push_back(const std::string&) void push_back(std::string&& token) { reference_tokens.push_back(std::move(token)); } /*! @brief return whether pointer points to the root document @return true iff the JSON pointer points to the root document @complexity Constant. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example shows the result of `empty` for different JSON Pointers.,json_pointer__empty} @since version 3.6.0 */ bool empty() const noexcept { return reference_tokens.empty(); } private: /*! @param[in] s reference token to be converted into an array index @return integer representation of @a s @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index begins not with a digit @throw out_of_range.404 if string @a s could not be converted to an integer @throw out_of_range.410 if an array index exceeds size_type */ static typename BasicJsonType::size_type array_index(const std::string& s) { using size_type = typename BasicJsonType::size_type; // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + s + "' must not begin with '0'")); } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number")); } std::size_t processed_chars = 0; unsigned long long res = 0; JSON_TRY { res = std::stoull(s, &processed_chars); } JSON_CATCH(std::out_of_range&) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); } // check if the string was completely read if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); } // only triggered on special platforms (like 32bit), see also // https://github.com/nlohmann/json/pull/2203 if (res >= static_cast((std::numeric_limits::max)())) { JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE } return static_cast(res); } json_pointer top() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } json_pointer result = *this; result.reference_tokens = {reference_tokens[0]}; return result; } /*! @brief create and return a reference to the pointed to value @complexity Linear in the number of reference tokens. @throw parse_error.109 if array index is not a number @throw type_error.313 if value cannot be unflattened */ BasicJsonType& get_and_create(BasicJsonType& j) const { auto result = &j; // in case no reference tokens exist, return a reference to the JSON value // j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->type()) { case detail::value_t::null: { if (reference_token == "0") { // start a new array if reference token is 0 result = &result->operator[](0); } else { // start a new object otherwise result = &result->operator[](reference_token); } break; } case detail::value_t::object: { // create an entry in the object result = &result->operator[](reference_token); break; } case detail::value_t::array: { // create an entry in the array result = &result->operator[](array_index(reference_token)); break; } /* The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have an error situation, because primitive values may only occur as single value; that is, with an empty list of reference tokens. */ default: JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); } } return *result; } /*! @brief return a reference to the pointed to value @note This version does not throw if a value is not present, but tries to create nested values instead. For instance, calling this function with pointer `"/this/that"` on a null value is equivalent to calling `operator[]("this").operator[]("that")` on that value, effectively changing the null value to an object. @param[in] ptr a JSON value @return reference to the JSON value pointed to by the JSON pointer @complexity Linear in the length of the JSON pointer. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_unchecked(BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { // convert null values to arrays or objects before continuing if (ptr->is_null()) { // check if reference token is a number const bool nums = std::all_of(reference_token.begin(), reference_token.end(), [](const unsigned char x) { return std::isdigit(x); }); // change value to array for numbers or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; } switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (reference_token == "-") { // explicitly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { // convert array index to number; unchecked access ptr = &ptr->operator[](array_index(reference_token)); } break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_checked(BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // note: at performs range check ptr = &ptr->at(array_index(reference_token)); break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @brief return a const reference to the pointed to value @param[in] ptr a JSON value @return const reference to the JSON value pointed to by the JSON pointer @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // use unchecked array access ptr = &ptr->operator[](array_index(reference_token)); break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_checked(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // note: at performs range check ptr = &ptr->at(array_index(reference_token)); break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number */ bool contains(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { if (!ptr->contains(reference_token)) { // we did not find the key in the object return false; } ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check return false; } if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) { // invalid char return false; } if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) { if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) { // first char should be between '1' and '9' return false; } for (std::size_t i = 1; i < reference_token.size(); i++) { if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) { // other char should be between '0' and '9' return false; } } } const auto idx = array_index(reference_token); if (idx >= ptr->size()) { // index out of range return false; } ptr = &ptr->operator[](idx); break; } default: { // we do not expect primitive values if there is still a // reference token to process return false; } } } // no reference token left means we found a primitive value return true; } /*! @brief split the string input to reference tokens @note This function is only called by the json_pointer constructor. All exceptions below are documented there. @throw parse_error.107 if the pointer is not empty or begins with '/' @throw parse_error.108 if character '~' is not followed by '0' or '1' */ static std::vector split(const std::string& reference_string) { std::vector result; // special case: empty reference string -> no reference tokens if (reference_string.empty()) { return result; } // check if nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { JSON_THROW(detail::parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'")); } // extract the reference tokens: // - slash: position of the last read slash (or end of string) // - start: position after the previous slash for ( // search for the first slash after the first character std::size_t slash = reference_string.find_first_of('/', 1), // set the beginning of the first reference token start = 1; // we can stop if start == 0 (if slash == std::string::npos) start != 0; // set the beginning of the next reference token // (will eventually be 0 if slash == std::string::npos) start = (slash == std::string::npos) ? 0 : slash + 1, // find next slash slash = reference_string.find_first_of('/', start)) { // use the text between the beginning of the reference token // (start) and the last slash (slash). auto reference_token = reference_string.substr(start, slash - start); // check reference tokens are properly escaped for (std::size_t pos = reference_token.find_first_of('~'); pos != std::string::npos; pos = reference_token.find_first_of('~', pos + 1)) { JSON_ASSERT(reference_token[pos] == '~'); // ~ must be followed by 0 or 1 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); } } // finally, store the reference token unescape(reference_token); result.push_back(reference_token); } return result; } /*! @brief replace all occurrences of a substring by another string @param[in,out] s the string to manipulate; changed so that all occurrences of @a f are replaced with @a t @param[in] f the substring to replace with @a t @param[in] t the string to replace @a f @pre The search string @a f must not be empty. **This precondition is enforced with an assertion.** @since version 2.0.0 */ static void replace_substring(std::string& s, const std::string& f, const std::string& t) { JSON_ASSERT(!f.empty()); for (auto pos = s.find(f); // find first occurrence of f pos != std::string::npos; // make sure f was found s.replace(pos, f.size(), t), // replace with t, and pos = s.find(f, pos + t.size())) // find next occurrence of f {} } /// escape "~" to "~0" and "/" to "~1" static std::string escape(std::string s) { replace_substring(s, "~", "~0"); replace_substring(s, "/", "~1"); return s; } /// unescape "~1" to tilde and "~0" to slash (order is important!) static void unescape(std::string& s) { replace_substring(s, "~1", "/"); replace_substring(s, "~0", "~"); } /*! @param[in] reference_string the reference string to the current value @param[in] value the value to consider @param[in,out] result the result object to insert values to @note Empty objects or arrays are flattened to `null`. */ static void flatten(const std::string& reference_string, const BasicJsonType& value, BasicJsonType& result) { switch (value.type()) { case detail::value_t::array: { if (value.m_value.array->empty()) { // flatten empty array as null result[reference_string] = nullptr; } else { // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_value.array->size(); ++i) { flatten(reference_string + "/" + std::to_string(i), value.m_value.array->operator[](i), result); } } break; } case detail::value_t::object: { if (value.m_value.object->empty()) { // flatten empty object as null result[reference_string] = nullptr; } else { // iterate object and use keys as reference string for (const auto& element : *value.m_value.object) { flatten(reference_string + "/" + escape(element.first), element.second, result); } } break; } default: { // add primitive value with its reference string result[reference_string] = value; break; } } } /*! @param[in] value flattened JSON @return unflattened JSON @throw parse_error.109 if array index is not a number @throw type_error.314 if value is not an object @throw type_error.315 if object values are not primitive @throw type_error.313 if value cannot be unflattened */ static BasicJsonType unflatten(const BasicJsonType& value) { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); } BasicJsonType result; // iterate the JSON object values for (const auto& element : *value.m_value.object) { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); } // assign value to reference pointed to by JSON pointer; Note that if // the JSON pointer is "" (i.e., points to the whole value), function // get_and_create returns a reference to result itself. An assignment // will then create a primitive value. json_pointer(element.first).get_and_create(result) = element.second; } return result; } /*! @brief compares two JSON pointers for equality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is equal to @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept { return lhs.reference_tokens == rhs.reference_tokens; } /*! @brief compares two JSON pointers for inequality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is not equal @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept { return !(lhs == rhs); } /// the reference tokens std::vector reference_tokens; }; } // namespace nlohmann // #include #include #include // #include namespace nlohmann { namespace detail { template class json_ref { public: using value_type = BasicJsonType; json_ref(value_type&& value) : owned_value(std::move(value)) , value_ref(&owned_value) , is_rvalue(true) {} json_ref(const value_type& value) : value_ref(const_cast(&value)) , is_rvalue(false) {} json_ref(std::initializer_list init) : owned_value(init) , value_ref(&owned_value) , is_rvalue(true) {} template < class... Args, enable_if_t::value, int> = 0 > json_ref(Args && ... args) : owned_value(std::forward(args)...) , value_ref(&owned_value) , is_rvalue(true) {} // class should be movable only json_ref(json_ref&&) = default; json_ref(const json_ref&) = delete; json_ref& operator=(const json_ref&) = delete; json_ref& operator=(json_ref&&) = delete; ~json_ref() = default; value_type moved_or_copied() const { if (is_rvalue) { return std::move(*value_ref); } return *value_ref; } value_type const& operator*() const { return *static_cast(value_ref); } value_type const* operator->() const { return static_cast(value_ref); } private: mutable value_type owned_value = nullptr; value_type* value_ref = nullptr; const bool is_rvalue = true; }; } // namespace detail } // namespace nlohmann // #include // #include // #include // #include #include // reverse #include // array #include // uint8_t, uint16_t, uint32_t, uint64_t #include // memcpy #include // numeric_limits #include // string #include // isnan, isinf // #include // #include // #include #include // copy #include // size_t #include // streamsize #include // back_inserter #include // shared_ptr, make_shared #include // basic_ostream #include // basic_string #include // vector // #include namespace nlohmann { namespace detail { /// abstract output adapter interface template struct output_adapter_protocol { virtual void write_character(CharType c) = 0; virtual void write_characters(const CharType* s, std::size_t length) = 0; virtual ~output_adapter_protocol() = default; }; /// a type to simplify interfaces template using output_adapter_t = std::shared_ptr>; /// output adapter for byte vectors template class output_vector_adapter : public output_adapter_protocol { public: explicit output_vector_adapter(std::vector& vec) noexcept : v(vec) {} void write_character(CharType c) override { v.push_back(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { std::copy(s, s + length, std::back_inserter(v)); } private: std::vector& v; }; /// output adapter for output streams template class output_stream_adapter : public output_adapter_protocol { public: explicit output_stream_adapter(std::basic_ostream& s) noexcept : stream(s) {} void write_character(CharType c) override { stream.put(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { stream.write(s, static_cast(length)); } private: std::basic_ostream& stream; }; /// output adapter for basic_string template> class output_string_adapter : public output_adapter_protocol { public: explicit output_string_adapter(StringType& s) noexcept : str(s) {} void write_character(CharType c) override { str.push_back(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { str.append(s, length); } private: StringType& str; }; template> class output_adapter { public: output_adapter(std::vector& vec) : oa(std::make_shared>(vec)) {} output_adapter(std::basic_ostream& s) : oa(std::make_shared>(s)) {} output_adapter(StringType& s) : oa(std::make_shared>(s)) {} operator output_adapter_t() { return oa; } private: output_adapter_t oa = nullptr; }; } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { /////////////////// // binary writer // /////////////////// /*! @brief serialization to CBOR and MessagePack values */ template class binary_writer { using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using number_float_t = typename BasicJsonType::number_float_t; public: /*! @brief create a binary writer @param[in] adapter output adapter to write to */ explicit binary_writer(output_adapter_t adapter) : oa(adapter) { JSON_ASSERT(oa); } /*! @param[in] j JSON value to serialize @pre j.type() == value_t::object */ void write_bson(const BasicJsonType& j) { switch (j.type()) { case value_t::object: { write_bson_object(*j.m_value.object); break; } default: { JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); } } } /*! @param[in] j JSON value to serialize */ void write_cbor(const BasicJsonType& j) { switch (j.type()) { case value_t::null: { oa->write_character(to_char_type(0xF6)); break; } case value_t::boolean: { oa->write_character(j.m_value.boolean ? to_char_type(0xF5) : to_char_type(0xF4)); break; } case value_t::number_integer: { if (j.m_value.number_integer >= 0) { // CBOR does not differentiate between positive signed // integers and unsigned integers. Therefore, we used the // code from the value_t::number_unsigned case here. if (j.m_value.number_integer <= 0x17) { write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x18)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x19)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x1A)); write_number(static_cast(j.m_value.number_integer)); } else { oa->write_character(to_char_type(0x1B)); write_number(static_cast(j.m_value.number_integer)); } } else { // The conversions below encode the sign in the first // byte, and the value is converted to a positive number. const auto positive_number = -1 - j.m_value.number_integer; if (j.m_value.number_integer >= -24) { write_number(static_cast(0x20 + positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x38)); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x39)); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x3A)); write_number(static_cast(positive_number)); } else { oa->write_character(to_char_type(0x3B)); write_number(static_cast(positive_number)); } } break; } case value_t::number_unsigned: { if (j.m_value.number_unsigned <= 0x17) { write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x18)); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x19)); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x1A)); write_number(static_cast(j.m_value.number_unsigned)); } else { oa->write_character(to_char_type(0x1B)); write_number(static_cast(j.m_value.number_unsigned)); } break; } case value_t::number_float: { if (std::isnan(j.m_value.number_float)) { // NaN is 0xf97e00 in CBOR oa->write_character(to_char_type(0xF9)); oa->write_character(to_char_type(0x7E)); oa->write_character(to_char_type(0x00)); } else if (std::isinf(j.m_value.number_float)) { // Infinity is 0xf97c00, -Infinity is 0xf9fc00 oa->write_character(to_char_type(0xf9)); oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); oa->write_character(to_char_type(0x00)); } else { write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); } break; } case value_t::string: { // step 1: write control byte and the string length const auto N = j.m_value.string->size(); if (N <= 0x17) { write_number(static_cast(0x60 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x78)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x79)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x7A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x7B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write the string oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { // step 1: write control byte and the array size const auto N = j.m_value.array->size(); if (N <= 0x17) { write_number(static_cast(0x80 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x98)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x99)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x9A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x9B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element for (const auto& el : *j.m_value.array) { write_cbor(el); } break; } case value_t::binary: { if (j.m_value.binary->has_subtype()) { write_number(static_cast(0xd8)); write_number(j.m_value.binary->subtype()); } // step 1: write control byte and the binary array size const auto N = j.m_value.binary->size(); if (N <= 0x17) { write_number(static_cast(0x40 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x58)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x59)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x5A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x5B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element oa->write_characters( reinterpret_cast(j.m_value.binary->data()), N); break; } case value_t::object: { // step 1: write control byte and the object size const auto N = j.m_value.object->size(); if (N <= 0x17) { write_number(static_cast(0xA0 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xB8)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xB9)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xBA)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xBB)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element for (const auto& el : *j.m_value.object) { write_cbor(el.first); write_cbor(el.second); } break; } default: break; } } /*! @param[in] j JSON value to serialize */ void write_msgpack(const BasicJsonType& j) { switch (j.type()) { case value_t::null: // nil { oa->write_character(to_char_type(0xC0)); break; } case value_t::boolean: // true and false { oa->write_character(j.m_value.boolean ? to_char_type(0xC3) : to_char_type(0xC2)); break; } case value_t::number_integer: { if (j.m_value.number_integer >= 0) { // MessagePack does not differentiate between positive // signed integers and unsigned integers. Therefore, we used // the code from the value_t::number_unsigned case here. if (j.m_value.number_unsigned < 128) { // positive fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 oa->write_character(to_char_type(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 oa->write_character(to_char_type(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 oa->write_character(to_char_type(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 oa->write_character(to_char_type(0xCF)); write_number(static_cast(j.m_value.number_integer)); } } else { if (j.m_value.number_integer >= -32) { // negative fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 8 oa->write_character(to_char_type(0xD0)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 16 oa->write_character(to_char_type(0xD1)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 32 oa->write_character(to_char_type(0xD2)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 64 oa->write_character(to_char_type(0xD3)); write_number(static_cast(j.m_value.number_integer)); } } break; } case value_t::number_unsigned: { if (j.m_value.number_unsigned < 128) { // positive fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 oa->write_character(to_char_type(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 oa->write_character(to_char_type(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 oa->write_character(to_char_type(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 oa->write_character(to_char_type(0xCF)); write_number(static_cast(j.m_value.number_integer)); } break; } case value_t::number_float: { write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); break; } case value_t::string: { // step 1: write control byte and the string length const auto N = j.m_value.string->size(); if (N <= 31) { // fixstr write_number(static_cast(0xA0 | N)); } else if (N <= (std::numeric_limits::max)()) { // str 8 oa->write_character(to_char_type(0xD9)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // str 16 oa->write_character(to_char_type(0xDA)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // str 32 oa->write_character(to_char_type(0xDB)); write_number(static_cast(N)); } // step 2: write the string oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { // step 1: write control byte and the array size const auto N = j.m_value.array->size(); if (N <= 15) { // fixarray write_number(static_cast(0x90 | N)); } else if (N <= (std::numeric_limits::max)()) { // array 16 oa->write_character(to_char_type(0xDC)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // array 32 oa->write_character(to_char_type(0xDD)); write_number(static_cast(N)); } // step 2: write each element for (const auto& el : *j.m_value.array) { write_msgpack(el); } break; } case value_t::binary: { // step 0: determine if the binary type has a set subtype to // determine whether or not to use the ext or fixext types const bool use_ext = j.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length const auto N = j.m_value.binary->size(); if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type{}; bool fixed = true; if (use_ext) { switch (N) { case 1: output_type = 0xD4; // fixext 1 break; case 2: output_type = 0xD5; // fixext 2 break; case 4: output_type = 0xD6; // fixext 4 break; case 8: output_type = 0xD7; // fixext 8 break; case 16: output_type = 0xD8; // fixext 16 break; default: output_type = 0xC7; // ext 8 fixed = false; break; } } else { output_type = 0xC4; // bin 8 fixed = false; } oa->write_character(to_char_type(output_type)); if (!fixed) { write_number(static_cast(N)); } } else if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type = use_ext ? 0xC8 // ext 16 : 0xC5; // bin 16 oa->write_character(to_char_type(output_type)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type = use_ext ? 0xC9 // ext 32 : 0xC6; // bin 32 oa->write_character(to_char_type(output_type)); write_number(static_cast(N)); } // step 1.5: if this is an ext type, write the subtype if (use_ext) { write_number(static_cast(j.m_value.binary->subtype())); } // step 2: write the byte string oa->write_characters( reinterpret_cast(j.m_value.binary->data()), N); break; } case value_t::object: { // step 1: write control byte and the object size const auto N = j.m_value.object->size(); if (N <= 15) { // fixmap write_number(static_cast(0x80 | (N & 0xF))); } else if (N <= (std::numeric_limits::max)()) { // map 16 oa->write_character(to_char_type(0xDE)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // map 32 oa->write_character(to_char_type(0xDF)); write_number(static_cast(N)); } // step 2: write each element for (const auto& el : *j.m_value.object) { write_msgpack(el.first); write_msgpack(el.second); } break; } default: break; } } /*! @param[in] j JSON value to serialize @param[in] use_count whether to use '#' prefixes (optimized format) @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true) { switch (j.type()) { case value_t::null: { if (add_prefix) { oa->write_character(to_char_type('Z')); } break; } case value_t::boolean: { if (add_prefix) { oa->write_character(j.m_value.boolean ? to_char_type('T') : to_char_type('F')); } break; } case value_t::number_integer: { write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); break; } case value_t::number_unsigned: { write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); break; } case value_t::number_float: { write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); break; } case value_t::string: { if (add_prefix) { oa->write_character(to_char_type('S')); } write_number_with_ubjson_prefix(j.m_value.string->size(), true); oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { if (add_prefix) { oa->write_character(to_char_type('[')); } bool prefix_required = true; if (use_type && !j.m_value.array->empty()) { JSON_ASSERT(use_count); const CharType first_prefix = ubjson_prefix(j.front()); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), [this, first_prefix](const BasicJsonType & v) { return ubjson_prefix(v) == first_prefix; }); if (same_prefix) { prefix_required = false; oa->write_character(to_char_type('$')); oa->write_character(first_prefix); } } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.array->size(), true); } for (const auto& el : *j.m_value.array) { write_ubjson(el, use_count, use_type, prefix_required); } if (!use_count) { oa->write_character(to_char_type(']')); } break; } case value_t::binary: { if (add_prefix) { oa->write_character(to_char_type('[')); } if (use_type && !j.m_value.binary->empty()) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); oa->write_character('U'); } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.binary->size(), true); } if (use_type) { oa->write_characters( reinterpret_cast(j.m_value.binary->data()), j.m_value.binary->size()); } else { for (size_t i = 0; i < j.m_value.binary->size(); ++i) { oa->write_character(to_char_type('U')); oa->write_character(j.m_value.binary->data()[i]); } } if (!use_count) { oa->write_character(to_char_type(']')); } break; } case value_t::object: { if (add_prefix) { oa->write_character(to_char_type('{')); } bool prefix_required = true; if (use_type && !j.m_value.object->empty()) { JSON_ASSERT(use_count); const CharType first_prefix = ubjson_prefix(j.front()); const bool same_prefix = std::all_of(j.begin(), j.end(), [this, first_prefix](const BasicJsonType & v) { return ubjson_prefix(v) == first_prefix; }); if (same_prefix) { prefix_required = false; oa->write_character(to_char_type('$')); oa->write_character(first_prefix); } } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.object->size(), true); } for (const auto& el : *j.m_value.object) { write_number_with_ubjson_prefix(el.first.size(), true); oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); write_ubjson(el.second, use_count, use_type, prefix_required); } if (!use_count) { oa->write_character(to_char_type('}')); } break; } default: break; } } private: ////////// // BSON // ////////// /*! @return The size of a BSON document entry header, including the id marker and the entry name size (and its null-terminator). */ static std::size_t calc_bson_entry_header_size(const string_t& name) { const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { JSON_THROW(out_of_range::create(409, "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")")); } return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; } /*! @brief Writes the given @a element_type and @a name to the output adapter */ void write_bson_entry_header(const string_t& name, const std::uint8_t element_type) { oa->write_character(to_char_type(element_type)); // boolean oa->write_characters( reinterpret_cast(name.c_str()), name.size() + 1u); } /*! @brief Writes a BSON element with key @a name and boolean value @a value */ void write_bson_boolean(const string_t& name, const bool value) { write_bson_entry_header(name, 0x08); oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); } /*! @brief Writes a BSON element with key @a name and double value @a value */ void write_bson_double(const string_t& name, const double value) { write_bson_entry_header(name, 0x01); write_number(value); } /*! @return The size of the BSON-encoded string in @a value */ static std::size_t calc_bson_string_size(const string_t& value) { return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and string value @a value */ void write_bson_string(const string_t& name, const string_t& value) { write_bson_entry_header(name, 0x02); write_number(static_cast(value.size() + 1ul)); oa->write_characters( reinterpret_cast(value.c_str()), value.size() + 1); } /*! @brief Writes a BSON element with key @a name and null value */ void write_bson_null(const string_t& name) { write_bson_entry_header(name, 0x0A); } /*! @return The size of the BSON-encoded integer @a value */ static std::size_t calc_bson_integer_size(const std::int64_t value) { return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() ? sizeof(std::int32_t) : sizeof(std::int64_t); } /*! @brief Writes a BSON element with key @a name and integer @a value */ void write_bson_integer(const string_t& name, const std::int64_t value) { if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) { write_bson_entry_header(name, 0x10); // int32 write_number(static_cast(value)); } else { write_bson_entry_header(name, 0x12); // int64 write_number(static_cast(value)); } } /*! @return The size of the BSON-encoded unsigned integer in @a j */ static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept { return (value <= static_cast((std::numeric_limits::max)())) ? sizeof(std::int32_t) : sizeof(std::int64_t); } /*! @brief Writes a BSON element with key @a name and unsigned @a value */ void write_bson_unsigned(const string_t& name, const std::uint64_t value) { if (value <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x10 /* int32 */); write_number(static_cast(value)); } else if (value <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x12 /* int64 */); write_number(static_cast(value)); } else { JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64")); } } /*! @brief Writes a BSON element with key @a name and object @a value */ void write_bson_object_entry(const string_t& name, const typename BasicJsonType::object_t& value) { write_bson_entry_header(name, 0x03); // object write_bson_object(value); } /*! @return The size of the BSON-encoded array @a value */ static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) { std::size_t array_index = 0ul; const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), std::size_t(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) { return result + calc_bson_element_size(std::to_string(array_index++), el); }); return sizeof(std::int32_t) + embedded_document_size + 1ul; } /*! @return The size of the BSON-encoded binary array @a value */ static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) { return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and array @a value */ void write_bson_array(const string_t& name, const typename BasicJsonType::array_t& value) { write_bson_entry_header(name, 0x04); // array write_number(static_cast(calc_bson_array_size(value))); std::size_t array_index = 0ul; for (const auto& el : value) { write_bson_element(std::to_string(array_index++), el); } oa->write_character(to_char_type(0x00)); } /*! @brief Writes a BSON element with key @a name and binary value @a value */ void write_bson_binary(const string_t& name, const binary_t& value) { write_bson_entry_header(name, 0x05); write_number(static_cast(value.size())); write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); } /*! @brief Calculates the size necessary to serialize the JSON value @a j with its @a name @return The calculated size for the BSON document entry for @a j with the given @a name. */ static std::size_t calc_bson_element_size(const string_t& name, const BasicJsonType& j) { const auto header_size = calc_bson_entry_header_size(name); switch (j.type()) { case value_t::object: return header_size + calc_bson_object_size(*j.m_value.object); case value_t::array: return header_size + calc_bson_array_size(*j.m_value.array); case value_t::binary: return header_size + calc_bson_binary_size(*j.m_value.binary); case value_t::boolean: return header_size + 1ul; case value_t::number_float: return header_size + 8ul; case value_t::number_integer: return header_size + calc_bson_integer_size(j.m_value.number_integer); case value_t::number_unsigned: return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); case value_t::string: return header_size + calc_bson_string_size(*j.m_value.string); case value_t::null: return header_size + 0ul; // LCOV_EXCL_START default: JSON_ASSERT(false); return 0ul; // LCOV_EXCL_STOP } } /*! @brief Serializes the JSON value @a j to BSON and associates it with the key @a name. @param name The name to associate with the JSON entity @a j within the current BSON document @return The size of the BSON entry */ void write_bson_element(const string_t& name, const BasicJsonType& j) { switch (j.type()) { case value_t::object: return write_bson_object_entry(name, *j.m_value.object); case value_t::array: return write_bson_array(name, *j.m_value.array); case value_t::binary: return write_bson_binary(name, *j.m_value.binary); case value_t::boolean: return write_bson_boolean(name, j.m_value.boolean); case value_t::number_float: return write_bson_double(name, j.m_value.number_float); case value_t::number_integer: return write_bson_integer(name, j.m_value.number_integer); case value_t::number_unsigned: return write_bson_unsigned(name, j.m_value.number_unsigned); case value_t::string: return write_bson_string(name, *j.m_value.string); case value_t::null: return write_bson_null(name); // LCOV_EXCL_START default: JSON_ASSERT(false); return; // LCOV_EXCL_STOP } } /*! @brief Calculates the size of the BSON serialization of the given JSON-object @a j. @param[in] j JSON value to serialize @pre j.type() == value_t::object */ static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) { std::size_t document_size = std::accumulate(value.begin(), value.end(), std::size_t(0), [](size_t result, const typename BasicJsonType::object_t::value_type & el) { return result += calc_bson_element_size(el.first, el.second); }); return sizeof(std::int32_t) + document_size + 1ul; } /*! @param[in] j JSON value to serialize @pre j.type() == value_t::object */ void write_bson_object(const typename BasicJsonType::object_t& value) { write_number(static_cast(calc_bson_object_size(value))); for (const auto& el : value) { write_bson_element(el.first, el.second); } oa->write_character(to_char_type(0x00)); } ////////// // CBOR // ////////// static constexpr CharType get_cbor_float_prefix(float /*unused*/) { return to_char_type(0xFA); // Single-Precision Float } static constexpr CharType get_cbor_float_prefix(double /*unused*/) { return to_char_type(0xFB); // Double-Precision Float } ///////////// // MsgPack // ///////////// static constexpr CharType get_msgpack_float_prefix(float /*unused*/) { return to_char_type(0xCA); // float 32 } static constexpr CharType get_msgpack_float_prefix(double /*unused*/) { return to_char_type(0xCB); // float 64 } //////////// // UBJSON // //////////// // UBJSON: write number (floating point) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if (add_prefix) { oa->write_character(get_ubjson_float_prefix(n)); } write_number(n); } // UBJSON: write number (unsigned integer) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('i')); // int8 } write_number(static_cast(n)); } else if (n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('U')); // uint8 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('I')); // int16 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('l')); // int32 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('L')); // int64 } write_number(static_cast(n)); } else { if (add_prefix) { oa->write_character(to_char_type('H')); // high-precision number } const auto number = BasicJsonType(n).dump(); write_number_with_ubjson_prefix(number.size(), true); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); } } } // UBJSON: write number (signed integer) template < typename NumberType, typename std::enable_if < std::is_signed::value&& !std::is_floating_point::value, int >::type = 0 > void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('i')); // int8 } write_number(static_cast(n)); } else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('U')); // uint8 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('I')); // int16 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('l')); // int32 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('L')); // int64 } write_number(static_cast(n)); } // LCOV_EXCL_START else { if (add_prefix) { oa->write_character(to_char_type('H')); // high-precision number } const auto number = BasicJsonType(n).dump(); write_number_with_ubjson_prefix(number.size(), true); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); } } // LCOV_EXCL_STOP } /*! @brief determine the type prefix of container values */ CharType ubjson_prefix(const BasicJsonType& j) const noexcept { switch (j.type()) { case value_t::null: return 'Z'; case value_t::boolean: return j.m_value.boolean ? 'T' : 'F'; case value_t::number_integer: { if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'i'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'U'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'I'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'l'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'L'; } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } case value_t::number_unsigned: { if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'i'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'U'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'I'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'l'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'L'; } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } case value_t::number_float: return get_ubjson_float_prefix(j.m_value.number_float); case value_t::string: return 'S'; case value_t::array: // fallthrough case value_t::binary: return '['; case value_t::object: return '{'; default: // discarded values return 'N'; } } static constexpr CharType get_ubjson_float_prefix(float /*unused*/) { return 'd'; // float 32 } static constexpr CharType get_ubjson_float_prefix(double /*unused*/) { return 'D'; // float 64 } /////////////////////// // Utility functions // /////////////////////// /* @brief write a number to output input @param[in] n number of type @a NumberType @tparam NumberType the type of the number @tparam OutputIsLittleEndian Set to true if output data is required to be little endian @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template void write_number(const NumberType n) { // step 1: write number to array of length NumberType std::array vec; std::memcpy(vec.data(), &n, sizeof(NumberType)); // step 2: write array to output (with possible reordering) if (is_little_endian != OutputIsLittleEndian) { // reverse byte order prior to conversion if necessary std::reverse(vec.begin(), vec.end()); } oa->write_characters(vec.data(), sizeof(NumberType)); } void write_compact_float(const number_float_t n, detail::input_format_t format) { if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && static_cast(n) <= static_cast((std::numeric_limits::max)()) && static_cast(static_cast(n)) == static_cast(n)) { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(static_cast(n)) : get_msgpack_float_prefix(static_cast(n))); write_number(static_cast(n)); } else { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(n) : get_msgpack_float_prefix(n)); write_number(n); } } public: // The following to_char_type functions are implement the conversion // between uint8_t and CharType. In case CharType is not unsigned, // such a conversion is required to allow values greater than 128. // See for a discussion. template < typename C = CharType, enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > static constexpr CharType to_char_type(std::uint8_t x) noexcept { return *reinterpret_cast(&x); } template < typename C = CharType, enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > static CharType to_char_type(std::uint8_t x) noexcept { static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); static_assert(std::is_trivial::value, "CharType must be trivial"); CharType result; std::memcpy(&result, &x, sizeof(x)); return result; } template::value>* = nullptr> static constexpr CharType to_char_type(std::uint8_t x) noexcept { return x; } template < typename InputCharType, typename C = CharType, enable_if_t < std::is_signed::value && std::is_signed::value && std::is_same::type>::value > * = nullptr > static constexpr CharType to_char_type(InputCharType x) noexcept { return x; } private: /// whether we can assume little endianness const bool is_little_endian = little_endianess(); /// the output output_adapter_t oa = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include #include // reverse, remove, fill, find, none_of #include // array #include // localeconv, lconv #include // labs, isfinite, isnan, signbit #include // size_t, ptrdiff_t #include // uint8_t #include // snprintf #include // numeric_limits #include // string, char_traits #include // is_same #include // move // #include #include // array #include // signbit, isfinite #include // intN_t, uintN_t #include // memcpy, memmove #include // numeric_limits #include // conditional // #include namespace nlohmann { namespace detail { /*! @brief implements the Grisu2 algorithm for binary to decimal floating-point conversion. This implementation is a slightly modified version of the reference implementation which may be obtained from http://florian.loitsch.com/publications (bench.tar.gz). The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation, PLDI 1996 */ namespace dtoa_impl { template Target reinterpret_bits(const Source source) { static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); Target target; std::memcpy(&target, &source, sizeof(Source)); return target; } struct diyfp // f * 2^e { static constexpr int kPrecision = 64; // = q std::uint64_t f = 0; int e = 0; constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} /*! @brief returns x - y @pre x.e == y.e and x.f >= y.f */ static diyfp sub(const diyfp& x, const diyfp& y) noexcept { JSON_ASSERT(x.e == y.e); JSON_ASSERT(x.f >= y.f); return {x.f - y.f, x.e}; } /*! @brief returns x * y @note The result is rounded. (Only the upper q bits are returned.) */ static diyfp mul(const diyfp& x, const diyfp& y) noexcept { static_assert(kPrecision == 64, "internal error"); // Computes: // f = round((x.f * y.f) / 2^q) // e = x.e + y.e + q // Emulate the 64-bit * 64-bit multiplication: // // p = u * v // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) // // (Since Q might be larger than 2^32 - 1) // // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) // // (Q_hi + H does not overflow a 64-bit int) // // = p_lo + 2^64 p_hi const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; const std::uint64_t u_hi = x.f >> 32u; const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; const std::uint64_t v_hi = y.f >> 32u; const std::uint64_t p0 = u_lo * v_lo; const std::uint64_t p1 = u_lo * v_hi; const std::uint64_t p2 = u_hi * v_lo; const std::uint64_t p3 = u_hi * v_hi; const std::uint64_t p0_hi = p0 >> 32u; const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; const std::uint64_t p1_hi = p1 >> 32u; const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; const std::uint64_t p2_hi = p2 >> 32u; std::uint64_t Q = p0_hi + p1_lo + p2_lo; // The full product might now be computed as // // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) // p_lo = p0_lo + (Q << 32) // // But in this particular case here, the full p_lo is not required. // Effectively we only need to add the highest bit in p_lo to p_hi (and // Q_hi + 1 does not overflow). Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); return {h, x.e + y.e + 64}; } /*! @brief normalize x such that the significand is >= 2^(q-1) @pre x.f != 0 */ static diyfp normalize(diyfp x) noexcept { JSON_ASSERT(x.f != 0); while ((x.f >> 63u) == 0) { x.f <<= 1u; x.e--; } return x; } /*! @brief normalize x such that the result has the exponent E @pre e >= x.e and the upper e - x.e bits of x.f must be zero. */ static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept { const int delta = x.e - target_exponent; JSON_ASSERT(delta >= 0); JSON_ASSERT(((x.f << delta) >> delta) == x.f); return {x.f << delta, target_exponent}; } }; struct boundaries { diyfp w; diyfp minus; diyfp plus; }; /*! Compute the (normalized) diyfp representing the input number 'value' and its boundaries. @pre value must be finite and positive */ template boundaries compute_boundaries(FloatType value) { JSON_ASSERT(std::isfinite(value)); JSON_ASSERT(value > 0); // Convert the IEEE representation into a diyfp. // // If v is denormal: // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) // If v is normalized: // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) static_assert(std::numeric_limits::is_iec559, "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); constexpr int kMinExp = 1 - kBias; constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) using bits_type = typename std::conditional::type; const std::uint64_t bits = reinterpret_bits(value); const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t F = bits & (kHiddenBit - 1); const bool is_denormal = E == 0; const diyfp v = is_denormal ? diyfp(F, kMinExp) : diyfp(F + kHiddenBit, static_cast(E) - kBias); // Compute the boundaries m- and m+ of the floating-point value // v = f * 2^e. // // Determine v- and v+, the floating-point predecessor and successor if v, // respectively. // // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) // // v+ = v + 2^e // // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ // between m- and m+ round to v, regardless of how the input rounding // algorithm breaks ties. // // ---+-------------+-------------+-------------+-------------+--- (A) // v- m- v m+ v+ // // -----------------+------+------+-------------+-------------+--- (B) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer ? diyfp(4 * v.f - 1, v.e - 2) // (B) : diyfp(2 * v.f - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); // Determine w- = m- such that e_(w-) = e_(w+). const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); return {diyfp::normalize(v), w_minus, w_plus}; } // Given normalized diyfp w, Grisu needs to find a (normalized) cached // power-of-ten c, such that the exponent of the product c * w = f * 2^e lies // within a certain range [alpha, gamma] (Definition 3.2 from [1]) // // alpha <= e = e_c + e_w + q <= gamma // // or // // f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q // <= f_c * f_w * 2^gamma // // Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies // // 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma // // or // // 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) // // The choice of (alpha,gamma) determines the size of the table and the form of // the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well // in practice: // // The idea is to cut the number c * w = f * 2^e into two parts, which can be // processed independently: An integral part p1, and a fractional part p2: // // f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e // = (f div 2^-e) + (f mod 2^-e) * 2^e // = p1 + p2 * 2^e // // The conversion of p1 into decimal form requires a series of divisions and // modulos by (a power of) 10. These operations are faster for 32-bit than for // 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be // achieved by choosing // // -e >= 32 or e <= -32 := gamma // // In order to convert the fractional part // // p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... // // into decimal form, the fraction is repeatedly multiplied by 10 and the digits // d[-i] are extracted in order: // // (10 * p2) div 2^-e = d[-1] // (10 * p2) mod 2^-e = d[-2] / 10^1 + ... // // The multiplication by 10 must not overflow. It is sufficient to choose // // 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. // // Since p2 = f mod 2^-e < 2^-e, // // -e <= 60 or e >= -60 := alpha constexpr int kAlpha = -60; constexpr int kGamma = -32; struct cached_power // c = f * 2^e ~= 10^k { std::uint64_t f; int e; int k; }; /*! For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c satisfies (Definition 3.2 from [1]) alpha <= e_c + e + q <= gamma. */ inline cached_power get_cached_power_for_binary_exponent(int e) { // Now // // alpha <= e_c + e + q <= gamma (1) // ==> f_c * 2^alpha <= c * 2^e * 2^q // // and since the c's are normalized, 2^(q-1) <= f_c, // // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) // ==> 2^(alpha - e - 1) <= c // // If c were an exact power of ten, i.e. c = 10^k, one may determine k as // // k = ceil( log_10( 2^(alpha - e - 1) ) ) // = ceil( (alpha - e - 1) * log_10(2) ) // // From the paper: // "In theory the result of the procedure could be wrong since c is rounded, // and the computation itself is approximated [...]. In practice, however, // this simple function is sufficient." // // For IEEE double precision floating-point numbers converted into // normalized diyfp's w = f * 2^e, with q = 64, // // e >= -1022 (min IEEE exponent) // -52 (p - 1) // -52 (p - 1, possibly normalize denormal IEEE numbers) // -11 (normalize the diyfp) // = -1137 // // and // // e <= +1023 (max IEEE exponent) // -52 (p - 1) // -11 (normalize the diyfp) // = 960 // // This binary exponent range [-1137,960] results in a decimal exponent // range [-307,324]. One does not need to store a cached power for each // k in this range. For each such k it suffices to find a cached power // such that the exponent of the product lies in [alpha,gamma]. // This implies that the difference of the decimal exponents of adjacent // table entries must be less than or equal to // // floor( (gamma - alpha) * log_10(2) ) = 8. // // (A smaller distance gamma-alpha would require a larger table.) // NB: // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. constexpr int kCachedPowersMinDecExp = -300; constexpr int kCachedPowersDecStep = 8; static constexpr std::array kCachedPowers = { { { 0xAB70FE17C79AC6CA, -1060, -300 }, { 0xFF77B1FCBEBCDC4F, -1034, -292 }, { 0xBE5691EF416BD60C, -1007, -284 }, { 0x8DD01FAD907FFC3C, -980, -276 }, { 0xD3515C2831559A83, -954, -268 }, { 0x9D71AC8FADA6C9B5, -927, -260 }, { 0xEA9C227723EE8BCB, -901, -252 }, { 0xAECC49914078536D, -874, -244 }, { 0x823C12795DB6CE57, -847, -236 }, { 0xC21094364DFB5637, -821, -228 }, { 0x9096EA6F3848984F, -794, -220 }, { 0xD77485CB25823AC7, -768, -212 }, { 0xA086CFCD97BF97F4, -741, -204 }, { 0xEF340A98172AACE5, -715, -196 }, { 0xB23867FB2A35B28E, -688, -188 }, { 0x84C8D4DFD2C63F3B, -661, -180 }, { 0xC5DD44271AD3CDBA, -635, -172 }, { 0x936B9FCEBB25C996, -608, -164 }, { 0xDBAC6C247D62A584, -582, -156 }, { 0xA3AB66580D5FDAF6, -555, -148 }, { 0xF3E2F893DEC3F126, -529, -140 }, { 0xB5B5ADA8AAFF80B8, -502, -132 }, { 0x87625F056C7C4A8B, -475, -124 }, { 0xC9BCFF6034C13053, -449, -116 }, { 0x964E858C91BA2655, -422, -108 }, { 0xDFF9772470297EBD, -396, -100 }, { 0xA6DFBD9FB8E5B88F, -369, -92 }, { 0xF8A95FCF88747D94, -343, -84 }, { 0xB94470938FA89BCF, -316, -76 }, { 0x8A08F0F8BF0F156B, -289, -68 }, { 0xCDB02555653131B6, -263, -60 }, { 0x993FE2C6D07B7FAC, -236, -52 }, { 0xE45C10C42A2B3B06, -210, -44 }, { 0xAA242499697392D3, -183, -36 }, { 0xFD87B5F28300CA0E, -157, -28 }, { 0xBCE5086492111AEB, -130, -20 }, { 0x8CBCCC096F5088CC, -103, -12 }, { 0xD1B71758E219652C, -77, -4 }, { 0x9C40000000000000, -50, 4 }, { 0xE8D4A51000000000, -24, 12 }, { 0xAD78EBC5AC620000, 3, 20 }, { 0x813F3978F8940984, 30, 28 }, { 0xC097CE7BC90715B3, 56, 36 }, { 0x8F7E32CE7BEA5C70, 83, 44 }, { 0xD5D238A4ABE98068, 109, 52 }, { 0x9F4F2726179A2245, 136, 60 }, { 0xED63A231D4C4FB27, 162, 68 }, { 0xB0DE65388CC8ADA8, 189, 76 }, { 0x83C7088E1AAB65DB, 216, 84 }, { 0xC45D1DF942711D9A, 242, 92 }, { 0x924D692CA61BE758, 269, 100 }, { 0xDA01EE641A708DEA, 295, 108 }, { 0xA26DA3999AEF774A, 322, 116 }, { 0xF209787BB47D6B85, 348, 124 }, { 0xB454E4A179DD1877, 375, 132 }, { 0x865B86925B9BC5C2, 402, 140 }, { 0xC83553C5C8965D3D, 428, 148 }, { 0x952AB45CFA97A0B3, 455, 156 }, { 0xDE469FBD99A05FE3, 481, 164 }, { 0xA59BC234DB398C25, 508, 172 }, { 0xF6C69A72A3989F5C, 534, 180 }, { 0xB7DCBF5354E9BECE, 561, 188 }, { 0x88FCF317F22241E2, 588, 196 }, { 0xCC20CE9BD35C78A5, 614, 204 }, { 0x98165AF37B2153DF, 641, 212 }, { 0xE2A0B5DC971F303A, 667, 220 }, { 0xA8D9D1535CE3B396, 694, 228 }, { 0xFB9B7CD9A4A7443C, 720, 236 }, { 0xBB764C4CA7A44410, 747, 244 }, { 0x8BAB8EEFB6409C1A, 774, 252 }, { 0xD01FEF10A657842C, 800, 260 }, { 0x9B10A4E5E9913129, 827, 268 }, { 0xE7109BFBA19C0C9D, 853, 276 }, { 0xAC2820D9623BF429, 880, 284 }, { 0x80444B5E7AA7CF85, 907, 292 }, { 0xBF21E44003ACDD2D, 933, 300 }, { 0x8E679C2F5E44FF8F, 960, 308 }, { 0xD433179D9C8CB841, 986, 316 }, { 0x9E19DB92B4E31BA9, 1013, 324 }, } }; // This computation gives exactly the same results for k as // k = ceil((kAlpha - e - 1) * 0.30102999566398114) // for |e| <= 1500, but doesn't require floating-point operations. // NB: log_10(2) ~= 78913 / 2^18 JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); JSON_ASSERT(static_cast(index) < kCachedPowers.size()); const cached_power cached = kCachedPowers[static_cast(index)]; JSON_ASSERT(kAlpha <= cached.e + e + 64); JSON_ASSERT(kGamma >= cached.e + e + 64); return cached; } /*! For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. For n == 0, returns 1 and sets pow10 := 1. */ inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) { // LCOV_EXCL_START if (n >= 1000000000) { pow10 = 1000000000; return 10; } // LCOV_EXCL_STOP else if (n >= 100000000) { pow10 = 100000000; return 9; } else if (n >= 10000000) { pow10 = 10000000; return 8; } else if (n >= 1000000) { pow10 = 1000000; return 7; } else if (n >= 100000) { pow10 = 100000; return 6; } else if (n >= 10000) { pow10 = 10000; return 5; } else if (n >= 1000) { pow10 = 1000; return 4; } else if (n >= 100) { pow10 = 100; return 3; } else if (n >= 10) { pow10 = 10; return 2; } else { pow10 = 1; return 1; } } inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, std::uint64_t rest, std::uint64_t ten_k) { JSON_ASSERT(len >= 1); JSON_ASSERT(dist <= delta); JSON_ASSERT(rest <= delta); JSON_ASSERT(ten_k > 0); // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // ten_k // <------> // <---- rest ----> // --------------[------------------+----+--------------]-------------- // w V // = buf * 10^k // // ten_k represents a unit-in-the-last-place in the decimal representation // stored in buf. // Decrement buf by ten_k while this takes buf closer to w. // The tests are written in this order to avoid overflow in unsigned // integer arithmetic. while (rest < dist && delta - rest >= ten_k && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) { JSON_ASSERT(buf[len - 1] != '0'); buf[len - 1]--; rest += ten_k; } } /*! Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. M- and M+ must be normalized and share the same exponent -60 <= e <= -32. */ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, diyfp M_minus, diyfp w, diyfp M_plus) { static_assert(kAlpha >= -60, "internal error"); static_assert(kGamma <= -32, "internal error"); // Generates the digits (and the exponent) of a decimal floating-point // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. // // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // Grisu2 generates the digits of M+ from left to right and stops as soon as // V is in [M-,M+]. JSON_ASSERT(M_plus.e >= kAlpha); JSON_ASSERT(M_plus.e <= kGamma); std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): // // M+ = f * 2^e // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e // = ((p1 ) * 2^-e + (p2 )) * 2^e // = p1 + p2 * 2^e const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e // 1) // // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] JSON_ASSERT(p1 > 0); std::uint32_t pow10; const int k = find_largest_pow10(p1, pow10); // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) // // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) // // M+ = p1 + p2 * 2^e // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e // = d[k-1] * 10^(k-1) + ( rest) * 2^e // // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) // // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] // // but stop as soon as // // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e int n = k; while (n > 0) { // Invariants: // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) // pow10 = 10^(n-1) <= p1 < 10^n // const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) // // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) // JSON_ASSERT(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) // p1 = r; n--; // // M+ = buffer * 10^n + (p1 + p2 * 2^e) // pow10 = 10^n // // Now check if enough digits have been generated. // Compute // // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e // // Note: // Since rest and delta share the same exponent e, it suffices to // compare the significands. const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; if (rest <= delta) { // V = buffer * 10^n, with M- <= V <= M+. decimal_exponent += n; // We may now just stop. But instead look if the buffer could be // decremented to bring V closer to w. // // pow10 = 10^n is now 1 ulp in the decimal representation V. // The rounding procedure works with diyfp's with an implicit // exponent of e. // // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e // const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; grisu2_round(buffer, length, dist, delta, rest, ten_n); return; } pow10 /= 10; // // pow10 = 10^(n-1) <= p1 < 10^n // Invariants restored. } // 2) // // The digits of the integral part have been generated: // // M+ = d[k-1]...d[1]d[0] + p2 * 2^e // = buffer + p2 * 2^e // // Now generate the digits of the fractional part p2 * 2^e. // // Note: // No decimal point is generated: the exponent is adjusted instead. // // p2 actually represents the fraction // // p2 * 2^e // = p2 / 2^-e // = d[-1] / 10^1 + d[-2] / 10^2 + ... // // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) // // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) // // using // // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) // = ( d) * 2^-e + ( r) // // or // 10^m * p2 * 2^e = d + r * 2^e // // i.e. // // M+ = buffer + p2 * 2^e // = buffer + 10^-m * (d + r * 2^e) // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e // // and stop as soon as 10^-m * r * 2^e <= delta * 2^e JSON_ASSERT(p2 > delta); int m = 0; for (;;) { // Invariant: // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e // = buffer * 10^-m + 10^-m * (p2 ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e // JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); p2 *= 10; const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e // // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e // JSON_ASSERT(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e // p2 = r; m++; // // M+ = buffer * 10^-m + 10^-m * p2 * 2^e // Invariant restored. // Check if enough digits have been generated. // // 10^-m * p2 * 2^e <= delta * 2^e // p2 * 2^e <= 10^m * delta * 2^e // p2 <= 10^m * delta delta *= 10; dist *= 10; if (p2 <= delta) { break; } } // V = buffer * 10^-m, with M- <= V <= M+. decimal_exponent -= m; // 1 ulp in the decimal representation is now 10^-m. // Since delta and dist are now scaled by 10^m, we need to do the // same with ulp in order to keep the units in sync. // // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e // const std::uint64_t ten_m = one.f; grisu2_round(buffer, length, dist, delta, p2, ten_m); // By construction this algorithm generates the shortest possible decimal // number (Loitsch, Theorem 6.2) which rounds back to w. // For an input number of precision p, at least // // N = 1 + ceil(p * log_10(2)) // // decimal digits are sufficient to identify all binary floating-point // numbers (Matula, "In-and-Out conversions"). // This implies that the algorithm does not produce more than N decimal // digits. // // N = 17 for p = 53 (IEEE double precision) // N = 9 for p = 24 (IEEE single precision) } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ JSON_HEDLEY_NON_NULL(1) inline void grisu2(char* buf, int& len, int& decimal_exponent, diyfp m_minus, diyfp v, diyfp m_plus) { JSON_ASSERT(m_plus.e == m_minus.e); JSON_ASSERT(m_plus.e == v.e); // --------(-----------------------+-----------------------)-------- (A) // m- v m+ // // --------------------(-----------+-----------------------)-------- (B) // m- v m+ // // First scale v (and m- and m+) such that the exponent is in the range // [alpha, gamma]. const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] const diyfp w = diyfp::mul(v, c_minus_k); const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); // ----(---+---)---------------(---+---)---------------(---+---)---- // w- w w+ // = c*m- = c*v = c*m+ // // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and // w+ are now off by a small amount. // In fact: // // w - v * 10^k < 1 ulp // // To account for this inaccuracy, add resp. subtract 1 ulp. // // --------+---[---------------(---+---)---------------]---+-------- // w- M- w M+ w+ // // Now any number in [M-, M+] (bounds included) will round to w when input, // regardless of how the input rounding algorithm breaks ties. // // And digit_gen generates the shortest possible such number in [M-, M+]. // Note that this does not mean that Grisu2 always generates the shortest // possible number in the interval (m-, m+). const diyfp M_minus(w_minus.f + 1, w_minus.e); const diyfp M_plus (w_plus.f - 1, w_plus.e ); decimal_exponent = -cached.k; // = -(-k) = k grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ template JSON_HEDLEY_NON_NULL(1) void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) { static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, "internal error: not enough precision"); JSON_ASSERT(std::isfinite(value)); JSON_ASSERT(value > 0); // If the neighbors (and boundaries) of 'value' are always computed for double-precision // numbers, all float's can be recovered using strtod (and strtof). However, the resulting // decimal representations are not exactly "short". // // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) // says "value is converted to a string as if by std::sprintf in the default ("C") locale" // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' // does. // On the other hand, the documentation for 'std::to_chars' requires that "parsing the // representation using the corresponding std::from_chars function recovers value exactly". That // indicates that single precision floating-point numbers should be recovered using // 'std::strtof'. // // NB: If the neighbors are computed for single-precision numbers, there is a single float // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision // value is off by 1 ulp. #if 0 const boundaries w = compute_boundaries(static_cast(value)); #else const boundaries w = compute_boundaries(value); #endif grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); } /*! @brief appends a decimal representation of e to buf @return a pointer to the element following the exponent. @pre -1000 < e < 1000 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* append_exponent(char* buf, int e) { JSON_ASSERT(e > -1000); JSON_ASSERT(e < 1000); if (e < 0) { e = -e; *buf++ = '-'; } else { *buf++ = '+'; } auto k = static_cast(e); if (k < 10) { // Always print at least two digits in the exponent. // This is for compatibility with printf("%g"). *buf++ = '0'; *buf++ = static_cast('0' + k); } else if (k < 100) { *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } else { *buf++ = static_cast('0' + k / 100); k %= 100; *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } return buf; } /*! @brief prettify v = buf * 10^decimal_exponent If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point notation. Otherwise it will be printed in exponential notation. @pre min_exp < 0 @pre max_exp > 0 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* format_buffer(char* buf, int len, int decimal_exponent, int min_exp, int max_exp) { JSON_ASSERT(min_exp < 0); JSON_ASSERT(max_exp > 0); const int k = len; const int n = len + decimal_exponent; // v = buf * 10^(n-k) // k is the length of the buffer (number of decimal digits) // n is the position of the decimal point relative to the start of the buffer. if (k <= n && n <= max_exp) { // digits[000] // len <= max_exp + 2 std::memset(buf + k, '0', static_cast(n) - static_cast(k)); // Make it look like a floating-point number (#362, #378) buf[n + 0] = '.'; buf[n + 1] = '0'; return buf + (static_cast(n) + 2); } if (0 < n && n <= max_exp) { // dig.its // len <= max_digits10 + 1 JSON_ASSERT(k > n); std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); buf[n] = '.'; return buf + (static_cast(k) + 1U); } if (min_exp < n && n <= 0) { // 0.[000]digits // len <= 2 + (-min_exp - 1) + max_digits10 std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); buf[0] = '0'; buf[1] = '.'; std::memset(buf + 2, '0', static_cast(-n)); return buf + (2U + static_cast(-n) + static_cast(k)); } if (k == 1) { // dE+123 // len <= 1 + 5 buf += 1; } else { // d.igitsE+123 // len <= max_digits10 + 1 + 5 std::memmove(buf + 2, buf + 1, static_cast(k) - 1); buf[1] = '.'; buf += 1 + static_cast(k); } *buf++ = 'e'; return append_exponent(buf, n - 1); } } // namespace dtoa_impl /*! @brief generates a decimal representation of the floating-point number value in [first, last). The format of the resulting decimal representation is similar to printf's %g format. Returns an iterator pointing past-the-end of the decimal representation. @note The input number must be finite, i.e. NaN's and Inf's are not supported. @note The buffer must be large enough. @note The result is NOT null-terminated. */ template JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_RETURNS_NON_NULL char* to_chars(char* first, const char* last, FloatType value) { static_cast(last); // maybe unused - fix warning JSON_ASSERT(std::isfinite(value)); // Use signbit(value) instead of (value < 0) since signbit works for -0. if (std::signbit(value)) { value = -value; *first++ = '-'; } if (value == 0) // +-0 { *first++ = '0'; // Make it look like a floating-point number (#362, #378) *first++ = '.'; *first++ = '0'; return first; } JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted // as an unsigned decimal integer. // len is the length of the buffer, i.e. the number of decimal digits. int len = 0; int decimal_exponent = 0; dtoa_impl::grisu2(first, len, decimal_exponent, value); JSON_ASSERT(len <= std::numeric_limits::max_digits10); // Format the buffer like printf("%.*g", prec, value) constexpr int kMinExp = -4; // Use digits10 here to increase compatibility with version 2. constexpr int kMaxExp = std::numeric_limits::digits10; JSON_ASSERT(last - first >= kMaxExp + 2); JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); } } // namespace detail } // namespace nlohmann // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { /////////////////// // serialization // /////////////////// /// how to treat decoding errors enum class error_handler_t { strict, ///< throw a type_error exception in case of invalid UTF-8 replace, ///< replace invalid UTF-8 sequences with U+FFFD ignore ///< ignore invalid UTF-8 sequences }; template class serializer { using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using binary_char_t = typename BasicJsonType::binary_t::value_type; static constexpr std::uint8_t UTF8_ACCEPT = 0; static constexpr std::uint8_t UTF8_REJECT = 1; public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use @param[in] error_handler_ how to react on decoding errors */ serializer(output_adapter_t s, const char ichar, error_handler_t error_handler_ = error_handler_t::strict) : o(std::move(s)) , loc(std::localeconv()) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) , indent_char(ichar) , indent_string(512, indent_char) , error_handler(error_handler_) {} // delete because of pointer members serializer(const serializer&) = delete; serializer& operator=(const serializer&) = delete; serializer(serializer&&) = delete; serializer& operator=(serializer&&) = delete; ~serializer() = default; /*! @brief internal implementation of the serialization function This function is called by the public member function dump and organizes the serialization internally. The indentation level is propagated as additional parameter. In case of arrays and objects, the function is called recursively. - strings and object keys are escaped using `escape_string()` - integer numbers are converted implicitly via `operator<<` - floating-point numbers are converted to a string using `"%g"` format - binary values are serialized as objects containing the subtype and the byte array @param[in] val value to serialize @param[in] pretty_print whether the output shall be pretty-printed @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ void dump(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const unsigned int indent_step, const unsigned int current_indent = 0) { switch (val.m_type) { case value_t::object: { if (val.m_value.object->empty()) { o->write_characters("{}", 2); return; } if (pretty_print) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } // first n-1 elements auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); } // last element JSON_ASSERT(i != val.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character('}'); } else { o->write_character('{'); // first n-1 elements auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); } // last element JSON_ASSERT(i != val.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character('}'); } return; } case value_t::array: { if (val.m_value.array->empty()) { o->write_characters("[]", 2); return; } if (pretty_print) { o->write_characters("[\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } // first n-1 elements for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); dump(*i, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); } // last element JSON_ASSERT(!val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character(']'); } else { o->write_character('['); // first n-1 elements for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { dump(*i, false, ensure_ascii, indent_step, current_indent); o->write_character(','); } // last element JSON_ASSERT(!val.m_value.array->empty()); dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); o->write_character(']'); } return; } case value_t::string: { o->write_character('\"'); dump_escaped(*val.m_value.string, ensure_ascii); o->write_character('\"'); return; } case value_t::binary: { if (pretty_print) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } o->write_characters(indent_string.c_str(), new_indent); o->write_characters("\"bytes\": [", 10); if (!val.m_value.binary->empty()) { for (auto i = val.m_value.binary->cbegin(); i != val.m_value.binary->cend() - 1; ++i) { dump_integer(*i); o->write_characters(", ", 2); } dump_integer(val.m_value.binary->back()); } o->write_characters("],\n", 3); o->write_characters(indent_string.c_str(), new_indent); o->write_characters("\"subtype\": ", 11); if (val.m_value.binary->has_subtype()) { dump_integer(val.m_value.binary->subtype()); } else { o->write_characters("null", 4); } o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character('}'); } else { o->write_characters("{\"bytes\":[", 10); if (!val.m_value.binary->empty()) { for (auto i = val.m_value.binary->cbegin(); i != val.m_value.binary->cend() - 1; ++i) { dump_integer(*i); o->write_character(','); } dump_integer(val.m_value.binary->back()); } o->write_characters("],\"subtype\":", 12); if (val.m_value.binary->has_subtype()) { dump_integer(val.m_value.binary->subtype()); o->write_character('}'); } else { o->write_characters("null}", 5); } } return; } case value_t::boolean: { if (val.m_value.boolean) { o->write_characters("true", 4); } else { o->write_characters("false", 5); } return; } case value_t::number_integer: { dump_integer(val.m_value.number_integer); return; } case value_t::number_unsigned: { dump_integer(val.m_value.number_unsigned); return; } case value_t::number_float: { dump_float(val.m_value.number_float); return; } case value_t::discarded: { o->write_characters("", 11); return; } case value_t::null: { o->write_characters("null", 4); return; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } private: /*! @brief dump escaped string Escape a string by replacing certain special characters by a sequence of an escape character (backslash) and another character and other control characters by a sequence of "\u" followed by a four-digit hex representation. The escaped string is written to output stream @a o. @param[in] s the string to escape @param[in] ensure_ascii whether to escape non-ASCII characters with \uXXXX sequences @complexity Linear in the length of string @a s. */ void dump_escaped(const string_t& s, const bool ensure_ascii) { std::uint32_t codepoint; std::uint8_t state = UTF8_ACCEPT; std::size_t bytes = 0; // number of bytes written to string_buffer // number of bytes written at the point of the last valid byte std::size_t bytes_after_last_accept = 0; std::size_t undumped_chars = 0; for (std::size_t i = 0; i < s.size(); ++i) { const auto byte = static_cast(s[i]); switch (decode(state, codepoint, byte)) { case UTF8_ACCEPT: // decode found a new code point { switch (codepoint) { case 0x08: // backspace { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'b'; break; } case 0x09: // horizontal tab { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 't'; break; } case 0x0A: // newline { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'n'; break; } case 0x0C: // formfeed { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'f'; break; } case 0x0D: // carriage return { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'r'; break; } case 0x22: // quotation mark { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\"'; break; } case 0x5C: // reverse solidus { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\\'; break; } default: { // escape control characters (0x00..0x1F) or, if // ensure_ascii parameter is used, non-ASCII characters if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) { if (codepoint <= 0xFFFF) { (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", static_cast(codepoint)); bytes += 6; } else { (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", static_cast(0xD7C0u + (codepoint >> 10u)), static_cast(0xDC00u + (codepoint & 0x3FFu))); bytes += 12; } } else { // copy byte to buffer (all previous bytes // been copied have in default case above) string_buffer[bytes++] = s[i]; } break; } } // write buffer and reset index; there must be 13 bytes // left, as this is the maximal number of bytes to be // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { o->write_characters(string_buffer.data(), bytes); bytes = 0; } // remember the byte position of this accept bytes_after_last_accept = bytes; undumped_chars = 0; break; } case UTF8_REJECT: // decode found invalid UTF-8 byte { switch (error_handler) { case error_handler_t::strict: { std::string sn(3, '\0'); (std::snprintf)(&sn[0], sn.size(), "%.2X", byte); JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); } case error_handler_t::ignore: case error_handler_t::replace: { // in case we saw this character the first time, we // would like to read it again, because the byte // may be OK for itself, but just not OK for the // previous sequence if (undumped_chars > 0) { --i; } // reset length buffer to the last accepted index; // thus removing/ignoring the invalid characters bytes = bytes_after_last_accept; if (error_handler == error_handler_t::replace) { // add a replacement character if (ensure_ascii) { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'u'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'd'; } else { string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); } // write buffer and reset index; there must be 13 bytes // left, as this is the maximal number of bytes to be // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { o->write_characters(string_buffer.data(), bytes); bytes = 0; } bytes_after_last_accept = bytes; } undumped_chars = 0; // continue processing the string state = UTF8_ACCEPT; break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } break; } default: // decode found yet incomplete multi-byte code point { if (!ensure_ascii) { // code point will not be escaped - copy byte to buffer string_buffer[bytes++] = s[i]; } ++undumped_chars; break; } } } // we finished processing the string if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) { // write buffer if (bytes > 0) { o->write_characters(string_buffer.data(), bytes); } } else { // we finish reading, but do not accept: string was incomplete switch (error_handler) { case error_handler_t::strict: { std::string sn(3, '\0'); (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast(s.back())); JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); } case error_handler_t::ignore: { // write all accepted bytes o->write_characters(string_buffer.data(), bytes_after_last_accept); break; } case error_handler_t::replace: { // write all accepted bytes o->write_characters(string_buffer.data(), bytes_after_last_accept); // add a replacement character if (ensure_ascii) { o->write_characters("\\ufffd", 6); } else { o->write_characters("\xEF\xBF\xBD", 3); } break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } } /*! @brief count digits Count the number of decimal (base 10) digits for an input unsigned integer. @param[in] x unsigned integer number to count its digits @return number of decimal digits */ inline unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) { if (x < 10) { return n_digits; } if (x < 100) { return n_digits + 1; } if (x < 1000) { return n_digits + 2; } if (x < 10000) { return n_digits + 3; } x = x / 10000u; n_digits += 4; } } /*! @brief dump an integer Dump a given integer to output stream @a o. Works internally with @a number_buffer. @param[in] x integer number (signed or unsigned) to dump @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < std::is_same::value || std::is_same::value || std::is_same::value, int > = 0 > void dump_integer(NumberType x) { static constexpr std::array, 100> digits_to_99 { { {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, } }; // special case for "0" if (x == 0) { o->write_character('0'); return; } // use a pointer to fill the buffer auto buffer_ptr = number_buffer.begin(); const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 number_unsigned_t abs_value; unsigned int n_chars; if (is_negative) { *buffer_ptr = '-'; abs_value = remove_sign(static_cast(x)); // account one more byte for the minus sign n_chars = 1 + count_digits(abs_value); } else { abs_value = static_cast(x); n_chars = count_digits(abs_value); } // spare 1 byte for '\0' JSON_ASSERT(n_chars < number_buffer.size() - 1); // jump to the end to generate the string from backward // so we later avoid reversing the result buffer_ptr += n_chars; // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg while (abs_value >= 100) { const auto digits_index = static_cast((abs_value % 100)); abs_value /= 100; *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } if (abs_value >= 10) { const auto digits_index = static_cast(abs_value); *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } else { *(--buffer_ptr) = static_cast('0' + abs_value); } o->write_characters(number_buffer.data(), n_chars); } /*! @brief dump a floating-point number Dump a given floating-point number to output stream @a o. Works internally with @a number_buffer. @param[in] x floating-point number to dump */ void dump_float(number_float_t x) { // NaN / inf if (!std::isfinite(x)) { o->write_characters("null", 4); return; } // If number_float_t is an IEEE-754 single or double precision number, // use the Grisu2 algorithm to produce short numbers which are // guaranteed to round-trip, using strtof and strtod, resp. // // NB: The test below works if == . static constexpr bool is_ieee_single_or_double = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); dump_float(x, std::integral_constant()); } void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { char* begin = number_buffer.data(); char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); o->write_characters(begin, static_cast(end - begin)); } void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // get number of digits for a float -> text -> float round-trip static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); // negative value indicates an error JSON_ASSERT(len > 0); // check if buffer was large enough JSON_ASSERT(static_cast(len) < number_buffer.size()); // erase thousands separator if (thousands_sep != '\0') { const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep); std::fill(end, number_buffer.end(), '\0'); JSON_ASSERT((end - number_buffer.begin()) <= len); len = (end - number_buffer.begin()); } // convert decimal point to '.' if (decimal_point != '\0' && decimal_point != '.') { const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); if (dec_pos != number_buffer.end()) { *dec_pos = '.'; } } o->write_characters(number_buffer.data(), static_cast(len)); // determine if need to append ".0" const bool value_is_int_like = std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, [](char c) { return c == '.' || c == 'e'; }); if (value_is_int_like) { o->write_characters(".0", 2); } } /*! @brief check whether a string is UTF-8 encoded The function checks each byte of a string whether it is UTF-8 encoded. The result of the check is stored in the @a state parameter. The function must be called initially with state 0 (accept). State 1 means the string must be rejected, because the current byte is not allowed. If the string is completely processed, but the state is non-zero, the string ended prematurely; that is, the last byte indicated more bytes should have followed. @param[in,out] state the state of the decoding @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in] byte next byte to decode @return new state @note The function has been edited: a std::array is used. @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept { static const std::array utf8d = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 } }; const std::uint8_t type = utf8d[byte]; codep = (state != UTF8_ACCEPT) ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); JSON_ASSERT(index < 400); state = utf8d[index]; return state; } /* * Overload to make the compiler happy while it is instantiating * dump_integer for number_unsigned_t. * Must never be called. */ number_unsigned_t remove_sign(number_unsigned_t x) { JSON_ASSERT(false); // LCOV_EXCL_LINE return x; // LCOV_EXCL_LINE } /* * Helper function for dump_integer * * This function takes a negative signed integer and returns its absolute * value as unsigned integer. The plus/minus shuffling is necessary as we can * not directly remove the sign of an arbitrary signed integer as the * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ inline number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); return static_cast(-(x + 1)) + 1; } private: /// the output of the serializer output_adapter_t o = nullptr; /// a (hopefully) large enough character buffer std::array number_buffer{{}}; /// the locale const std::lconv* loc = nullptr; /// the locale's thousand separator character const char thousands_sep = '\0'; /// the locale's decimal point character const char decimal_point = '\0'; /// string buffer std::array string_buffer{{}}; /// the indentation character const char indent_char; /// the indentation string string_t indent_string; /// error_handler how to react on decoding errors const error_handler_t error_handler; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // less #include // allocator #include // pair #include // vector namespace nlohmann { /// ordered_map: a minimal map-like container that preserves insertion order /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; using Container = std::vector, Allocator>; using typename Container::iterator; using typename Container::const_iterator; using typename Container::size_type; using typename Container::value_type; // Explicit constructors instead of `using Container::Container` // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} template ordered_map(It first, It last, const Allocator& alloc = Allocator()) : Container{first, last, alloc} {} ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) : Container{init, alloc} {} std::pair emplace(const key_type& key, T&& t) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return {it, false}; } } Container::emplace_back(key, t); return {--this->end(), true}; } T& operator[](const Key& key) { return emplace(key, T{}).first->second; } const T& operator[](const Key& key) const { return at(key); } T& at(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it->second; } } throw std::out_of_range("key not found"); } const T& at(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it->second; } } throw std::out_of_range("key not found"); } size_type erase(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { // Since we cannot move const Keys, re-construct them in place for (auto next = it; ++next != this->end(); ++it) { it->~value_type(); // Destroy but keep allocation new (&*it) value_type{std::move(*next)}; } Container::pop_back(); return 1; } } return 0; } iterator erase(iterator pos) { auto it = pos; // Since we cannot move const Keys, re-construct them in place for (auto next = it; ++next != this->end(); ++it) { it->~value_type(); // Destroy but keep allocation new (&*it) value_type{std::move(*next)}; } Container::pop_back(); return pos; } size_type count(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return 1; } } return 0; } iterator find(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it; } } return Container::end(); } const_iterator find(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it; } } return Container::end(); } std::pair insert( value_type&& value ) { return emplace(value.first, std::move(value.second)); } std::pair insert( const value_type& value ) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == value.first) { return {it, false}; } } Container::push_back(value); return {--this->end(), true}; } }; } // namespace nlohmann /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @brief a class to store JSON values @tparam ObjectType type for JSON objects (`std::map` by default; will be used in @ref object_t) @tparam ArrayType type for JSON arrays (`std::vector` by default; will be used in @ref array_t) @tparam StringType type for JSON strings and object keys (`std::string` by default; will be used in @ref string_t) @tparam BooleanType type for JSON booleans (`bool` by default; will be used in @ref boolean_t) @tparam NumberIntegerType type for JSON integer numbers (`int64_t` by default; will be used in @ref number_integer_t) @tparam NumberUnsignedType type for JSON unsigned integer numbers (@c `uint64_t` by default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (`double` by default; will be used in @ref number_float_t) @tparam BinaryType type for packed binary data for compatibility with binary serialization formats (`std::vector` by default; will be used in @ref binary_t) @tparam AllocatorType type of the allocator to use (`std::allocator` by default) @tparam JSONSerializer the serializer to resolve internal calls to `to_json()` and `from_json()` (@ref adl_serializer by default) @requirement The class satisfies the following concept requirements: - Basic - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): JSON values can be default constructed. The result will be a JSON null value. - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): A JSON value can be constructed from an rvalue argument. - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible): A JSON value can be copy-constructed from an lvalue expression. - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable): A JSON value van be assigned from an rvalue argument. - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): A JSON value can be copy-assigned from an lvalue expression. - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible): JSON values can be destructed. - Layout - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType): JSON values have [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout): All non-static data members are private and standard layout types, the class has no virtual functions or (virtual) base classes. - Library-wide - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable): JSON values can be compared with `==`, see @ref operator==(const_reference,const_reference). - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): JSON values can be compared with `<`, see @ref operator<(const_reference,const_reference). - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable): Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of other compatible types, using unqualified function call @ref swap(). - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer): JSON values can be compared against `std::nullptr_t` objects which are used to model the `null` value. - Container - [Container](https://en.cppreference.com/w/cpp/named_req/Container): JSON values can be used like STL containers and provide iterator access. - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer); JSON values can be used like STL containers and provide reverse iterator access. @invariant The member variables @a m_value and @a m_type have the following relationship: - If `m_type == value_t::object`, then `m_value.object != nullptr`. - If `m_type == value_t::array`, then `m_value.array != nullptr`. - If `m_type == value_t::string`, then `m_value.string != nullptr`. The invariants are checked by member function assert_invariant(). @internal @note ObjectType trick from https://stackoverflow.com/a/9860911 @endinternal @see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](http://rfc7159.net/rfc7159) @since version 1.0.0 @nosubgrouping */ NLOHMANN_BASIC_JSON_TPL_DECLARATION class basic_json { private: template friend struct detail::external_constructor; friend ::nlohmann::json_pointer; template friend class ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template friend class ::nlohmann::detail::iter_impl; template friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; template friend class ::nlohmann::detail::json_sax_dom_parser; template friend class ::nlohmann::detail::json_sax_dom_callback_parser; /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; // convenience aliases for types residing in namespace detail; using lexer = ::nlohmann::detail::lexer_base; template static ::nlohmann::detail::parser parser( InputAdapterType adapter, detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false ) { return ::nlohmann::detail::parser(std::move(adapter), std::move(cb), allow_exceptions, ignore_comments); } using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; template using internal_iterator = ::nlohmann::detail::internal_iterator; template using iter_impl = ::nlohmann::detail::iter_impl; template using iteration_proxy = ::nlohmann::detail::iteration_proxy; template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; template using output_adapter_t = ::nlohmann::detail::output_adapter_t; template using binary_reader = ::nlohmann::detail::binary_reader; template using binary_writer = ::nlohmann::detail::binary_writer; using serializer = ::nlohmann::detail::serializer; public: using value_t = detail::value_t; /// JSON Pointer, see @ref nlohmann::json_pointer using json_pointer = ::nlohmann::json_pointer; template using json_serializer = JSONSerializer; /// how to treat decoding errors using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; using input_format_t = detail::input_format_t; /// SAX interface type, see @ref nlohmann::json_sax using json_sax_t = json_sax; //////////////// // exceptions // //////////////// /// @name exceptions /// Classes to implement user-defined exceptions. /// @{ /// @copydoc detail::exception using exception = detail::exception; /// @copydoc detail::parse_error using parse_error = detail::parse_error; /// @copydoc detail::invalid_iterator using invalid_iterator = detail::invalid_iterator; /// @copydoc detail::type_error using type_error = detail::type_error; /// @copydoc detail::out_of_range using out_of_range = detail::out_of_range; /// @copydoc detail::other_error using other_error = detail::other_error; /// @} ///////////////////// // container types // ///////////////////// /// @name container types /// The canonic container types to use @ref basic_json like any other STL /// container. /// @{ /// the type of elements in a basic_json container using value_type = basic_json; /// the type of an element reference using reference = value_type&; /// the type of an element const reference using const_reference = const value_type&; /// a type to represent differences between iterators using difference_type = std::ptrdiff_t; /// a type to represent container sizes using size_type = std::size_t; /// the allocator type using allocator_type = AllocatorType; /// the type of an element pointer using pointer = typename std::allocator_traits::pointer; /// the type of an element const pointer using const_pointer = typename std::allocator_traits::const_pointer; /// an iterator for a basic_json container using iterator = iter_impl; /// a const iterator for a basic_json container using const_iterator = iter_impl; /// a reverse iterator for a basic_json container using reverse_iterator = json_reverse_iterator; /// a const reverse iterator for a basic_json container using const_reverse_iterator = json_reverse_iterator; /// @} /*! @brief returns the allocator associated with the container */ static allocator_type get_allocator() { return allocator_type(); } /*! @brief returns version information on the library This function returns a JSON object with information about the library, including the version number and information on the platform and compiler. @return JSON object holding version information key | description ----------- | --------------- `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). `copyright` | The copyright line for the library as string. `name` | The name of the library as string. `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. `url` | The URL of the project as string. `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). @liveexample{The following code shows an example output of the `meta()` function.,meta} @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @complexity Constant. @since 2.1.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json meta() { basic_json result; result["copyright"] = "(C) 2013-2020 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + std::to_string(NLOHMANN_JSON_VERSION_PATCH); result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 result["platform"] = "win32"; #elif defined __linux__ result["platform"] = "linux"; #elif defined __APPLE__ result["platform"] = "apple"; #elif defined __unix__ result["platform"] = "unix"; #else result["platform"] = "unknown"; #endif #if defined(__ICC) || defined(__INTEL_COMPILER) result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; #elif defined(__clang__) result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; #elif defined(__GNUC__) || defined(__GNUG__) result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; #elif defined(__HP_cc) || defined(__HP_aCC) result["compiler"] = "hp" #elif defined(__IBMCPP__) result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; #elif defined(_MSC_VER) result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; #elif defined(__PGI) result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; #elif defined(__SUNPRO_CC) result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; #else result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; #endif #ifdef __cplusplus result["compiler"]["c++"] = std::to_string(__cplusplus); #else result["compiler"]["c++"] = "unknown"; #endif return result; } /////////////////////////// // JSON value data types // /////////////////////////// /// @name JSON value data types /// The data types to store a JSON value. These types are derived from /// the template arguments passed to class @ref basic_json. /// @{ #if defined(JSON_HAS_CPP_14) // Use transparent comparator if possible, combined with perfect forwarding // on find() and count() calls prevents unnecessary string construction. using object_comparator_t = std::less<>; #else using object_comparator_t = std::less; #endif /*! @brief a type for an object [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: > An object is an unordered collection of zero or more name/value pairs, > where a name is a string and a value is a string, number, boolean, null, > object, or array. To store objects in C++, a type is defined by the template parameters described below. @tparam ObjectType the container to store objects (e.g., `std::map` or `std::unordered_map`) @tparam StringType the type of the keys or names (e.g., `std::string`). The comparison function `std::less` is used to order elements inside the container. @tparam AllocatorType the allocator to use for objects (e.g., `std::allocator`) #### Default type With the default values for @a ObjectType (`std::map`), @a StringType (`std::string`), and @a AllocatorType (`std::allocator`), the default value for @a object_t is: @code {.cpp} std::map< std::string, // key_type basic_json, // value_type std::less, // key_compare std::allocator> // allocator_type > @endcode #### Behavior The choice of @a object_t influences the behavior of the JSON class. With the default type, objects have the following behavior: - When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. - When the names within an object are not unique, it is unspecified which one of the values for a given key will be chosen. For instance, `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or `{"key": 2}`. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see @ref dump) in this order. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored and serialized as `{"a": 2, "b": 1}`. - When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be treated as equal. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the maximum depth of nesting. In this class, the object's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the @ref max_size function of a JSON object. #### Storage Objects are stored as pointers in a @ref basic_json type. That is, for any access to object values, a pointer of type `object_t*` must be dereferenced. @sa @ref array_t -- type for an array value @since version 1.0.0 @note The order name/value pairs are added to the object is *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as `std::map` with `std::less` is used by default. Please note this behavior conforms to [RFC 7159](http://rfc7159.net/rfc7159), because any order implements the specified "unordered" nature of JSON objects. */ using object_t = ObjectType>>; /*! @brief a type for an array [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: > An array is an ordered sequence of zero or more values. To store objects in C++, a type is defined by the template parameters explained below. @tparam ArrayType container type to store arrays (e.g., `std::vector` or `std::list`) @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) #### Default type With the default values for @a ArrayType (`std::vector`) and @a AllocatorType (`std::allocator`), the default value for @a array_t is: @code {.cpp} std::vector< basic_json, // value_type std::allocator // allocator_type > @endcode #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the maximum depth of nesting. In this class, the array's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the @ref max_size function of a JSON array. #### Storage Arrays are stored as pointers in a @ref basic_json type. That is, for any access to array values, a pointer of type `array_t*` must be dereferenced. @sa @ref object_t -- type for an object value @since version 1.0.0 */ using array_t = ArrayType>; /*! @brief a type for a string [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: > A string is a sequence of zero or more Unicode characters. To store objects in C++, a type is defined by the template parameter described below. Unicode values are split by the JSON class into byte-sized characters during deserialization. @tparam StringType the container to store strings (e.g., `std::string`). Note this container is used for keys/names in objects, see @ref object_t. #### Default type With the default values for @a StringType (`std::string`), the default value for @a string_t is: @code {.cpp} std::string @endcode #### Encoding Strings are stored in UTF-8 encoding. Therefore, functions like `std::string::size()` or `std::string::length()` return the number of bytes in the string rather than the number of characters or glyphs. #### String comparison [RFC 7159](http://rfc7159.net/rfc7159) states: > Software implementations are typically required to test names of object > members for equality. Implementations that transform the textual > representation into sequences of Unicode code units and then perform the > comparison numerically, code unit by code unit, are interoperable in the > sense that implementations will agree in all cases on equality or > inequality of two strings. For example, implementations that compare > strings with escaped characters unconverted may incorrectly find that > `"a\\b"` and `"a\u005Cb"` are not equal. This implementation is interoperable as it does compare strings code unit by code unit. #### Storage String values are stored as pointers in a @ref basic_json type. That is, for any access to string values, a pointer of type `string_t*` must be dereferenced. @since version 1.0.0 */ using string_t = StringType; /*! @brief a type for a boolean [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a type which differentiates the two literals `true` and `false`. To store objects in C++, a type is defined by the template parameter @a BooleanType which chooses the type to use. #### Default type With the default values for @a BooleanType (`bool`), the default value for @a boolean_t is: @code {.cpp} bool @endcode #### Storage Boolean values are stored directly inside a @ref basic_json type. @since version 1.0.0 */ using boolean_t = BooleanType; /*! @brief a type for a number (integer) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: > The representation of numbers is similar to that used in most > programming languages. A number is represented in base 10 using decimal > digits. It contains an integer component that may be prefixed with an > optional minus sign, which may be followed by a fraction part and/or an > exponent part. Leading zeros are not allowed. (...) Numeric values that > cannot be represented in the grammar below (such as Infinity and NaN) > are not permitted. This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template parameter @a NumberIntegerType which chooses the type to use. #### Default type With the default values for @a NumberIntegerType (`int64_t`), the default value for @a number_integer_t is: @code {.cpp} int64_t @endcode #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For instance, the C++ integer literal `010` will be serialized to `8`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the range and precision of numbers. When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be automatically be stored as @ref number_unsigned_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense > that implementations will agree exactly on their numeric values. As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is interoperable. #### Storage Integer number values are stored directly inside a @ref basic_json type. @sa @ref number_float_t -- type for number values (floating-point) @sa @ref number_unsigned_t -- type for number values (unsigned integer) @since version 1.0.0 */ using number_integer_t = NumberIntegerType; /*! @brief a type for a number (unsigned) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: > The representation of numbers is similar to that used in most > programming languages. A number is represented in base 10 using decimal > digits. It contains an integer component that may be prefixed with an > optional minus sign, which may be followed by a fraction part and/or an > exponent part. Leading zeros are not allowed. (...) Numeric values that > cannot be represented in the grammar below (such as Infinity and NaN) > are not permitted. This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store unsigned integer numbers in C++, a type is defined by the template parameter @a NumberUnsignedType which chooses the type to use. #### Default type With the default values for @a NumberUnsignedType (`uint64_t`), the default value for @a number_unsigned_t is: @code {.cpp} uint64_t @endcode #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For instance, the C++ integer literal `010` will be serialized to `8`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the range and precision of numbers. When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be automatically be stored as @ref number_integer_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense > that implementations will agree exactly on their numeric values. As this range is a subrange (when considered in conjunction with the number_integer_t type) of the exactly supported range [0, UINT64_MAX], this class's integer type is interoperable. #### Storage Integer number values are stored directly inside a @ref basic_json type. @sa @ref number_float_t -- type for number values (floating-point) @sa @ref number_integer_t -- type for number values (integer) @since version 2.0.0 */ using number_unsigned_t = NumberUnsignedType; /*! @brief a type for a number (floating-point) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: > The representation of numbers is similar to that used in most > programming languages. A number is represented in base 10 using decimal > digits. It contains an integer component that may be prefixed with an > optional minus sign, which may be followed by a fraction part and/or an > exponent part. Leading zeros are not allowed. (...) Numeric values that > cannot be represented in the grammar below (such as Infinity and NaN) > are not permitted. This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template parameter @a NumberFloatType which chooses the type to use. #### Default type With the default values for @a NumberFloatType (`double`), the default value for @a number_float_t is: @code {.cpp} double @endcode #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in floating-point literals will be ignored. Internally, the value will be stored as decimal number. For instance, the C++ floating-point literal `01.2` will be serialized to `1.2`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) states: > This specification allows implementations to set limits on the range and > precision of numbers accepted. Since software that implements IEEE > 754-2008 binary64 (double precision) numbers is generally available and > widely used, good interoperability can be achieved by implementations > that expect no more precision or range than these provide, in the sense > that implementations will approximate JSON numbers within the expected > precision. This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note values smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally and be serialized to `null`. #### Storage Floating-point number values are stored directly inside a @ref basic_json type. @sa @ref number_integer_t -- type for number values (integer) @sa @ref number_unsigned_t -- type for number values (unsigned integer) @since version 1.0.0 */ using number_float_t = NumberFloatType; /*! @brief a type for a packed binary type This type is a type designed to carry binary data that appears in various serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and BSON's generic binary subtype. This type is NOT a part of standard JSON and exists solely for compatibility with these binary types. As such, it is simply defined as an ordered sequence of zero or more byte values. Additionally, as an implementation detail, the subtype of the binary data is carried around as a `std::uint8_t`, which is compatible with both of the binary data formats that use binary subtyping, (though the specific numbering is incompatible with each other, and it is up to the user to translate between them). [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type as: > Major type 2: a byte string. The string's length in bytes is represented > following the rules for positive integers (major type 0). [MessagePack's documentation on the bin type family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as: > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes > in addition to the size of the byte array. [BSON's specifications](http://bsonspec.org/spec.html) describe several binary types; however, this type is intended to represent the generic binary type which has the description: > Generic binary subtype - This is the most commonly used binary subtype and > should be the 'default' for drivers and tools. None of these impose any limitations on the internal representation other than the basic unit of storage be some type of array whose parts are decomposable into bytes. The default representation of this binary format is a `std::vector`, which is a very common way to represent a byte array in modern C++. #### Default type The default values for @a BinaryType is `std::vector` #### Storage Binary Arrays are stored as pointers in a @ref basic_json type. That is, for any access to array values, a pointer of the type `binary_t*` must be dereferenced. #### Notes on subtypes - CBOR - Binary values are represented as byte strings. No subtypes are supported and will be ignored when CBOR is written. - MessagePack - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The subtype is then added as signed 8-bit integer. - If no subtype is given, the bin family (bin8, bin16, bin32) is used. - BSON - If a subtype is given, it is used and added as unsigned 8-bit integer. - If no subtype is given, the generic binary subtype 0x00 is used. @sa @ref binary -- create a binary array @since version 3.8.0 */ using binary_t = nlohmann::byte_container_with_subtype; /// @} private: /// helper for exception-safe object creation template JSON_HEDLEY_RETURNS_NON_NULL static T* create(Args&& ... args) { AllocatorType alloc; using AllocatorTraits = std::allocator_traits>; auto deleter = [&](T * object) { AllocatorTraits::deallocate(alloc, object, 1); }; std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); JSON_ASSERT(object != nullptr); return object.release(); } //////////////////////// // JSON value storage // //////////////////////// /*! @brief a JSON value The actual storage for a JSON value of the @ref basic_json class. This union combines the different storage types for the JSON value types defined in @ref value_t. JSON type | value_t type | used type --------- | --------------- | ------------------------ object | object | pointer to @ref object_t array | array | pointer to @ref array_t string | string | pointer to @ref string_t boolean | boolean | @ref boolean_t number | number_integer | @ref number_integer_t number | number_unsigned | @ref number_unsigned_t number | number_float | @ref number_float_t binary | binary | pointer to @ref binary_t null | null | *no value is stored* @note Variable-length types (objects, arrays, and strings) are stored as pointers. The size of the union should not exceed 64 bits if the default value types are used. @since version 1.0.0 */ union json_value { /// object (stored with pointer to save storage) object_t* object; /// array (stored with pointer to save storage) array_t* array; /// string (stored with pointer to save storage) string_t* string; /// binary (stored with pointer to save storage) binary_t* binary; /// boolean boolean_t boolean; /// number (integer) number_integer_t number_integer; /// number (unsigned integer) number_unsigned_t number_unsigned; /// number (floating-point) number_float_t number_float; /// default constructor (for null values) json_value() = default; /// constructor for booleans json_value(boolean_t v) noexcept : boolean(v) {} /// constructor for numbers (integer) json_value(number_integer_t v) noexcept : number_integer(v) {} /// constructor for numbers (unsigned) json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} /// constructor for numbers (floating-point) json_value(number_float_t v) noexcept : number_float(v) {} /// constructor for empty values of a given type json_value(value_t t) { switch (t) { case value_t::object: { object = create(); break; } case value_t::array: { array = create(); break; } case value_t::string: { string = create(""); break; } case value_t::binary: { binary = create(); break; } case value_t::boolean: { boolean = boolean_t(false); break; } case value_t::number_integer: { number_integer = number_integer_t(0); break; } case value_t::number_unsigned: { number_unsigned = number_unsigned_t(0); break; } case value_t::number_float: { number_float = number_float_t(0.0); break; } case value_t::null: { object = nullptr; // silence warning, see #821 break; } default: { object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.9.1")); // LCOV_EXCL_LINE } break; } } } /// constructor for strings json_value(const string_t& value) { string = create(value); } /// constructor for rvalue strings json_value(string_t&& value) { string = create(std::move(value)); } /// constructor for objects json_value(const object_t& value) { object = create(value); } /// constructor for rvalue objects json_value(object_t&& value) { object = create(std::move(value)); } /// constructor for arrays json_value(const array_t& value) { array = create(value); } /// constructor for rvalue arrays json_value(array_t&& value) { array = create(std::move(value)); } /// constructor for binary arrays json_value(const typename binary_t::container_type& value) { binary = create(value); } /// constructor for rvalue binary arrays json_value(typename binary_t::container_type&& value) { binary = create(std::move(value)); } /// constructor for binary arrays (internal type) json_value(const binary_t& value) { binary = create(value); } /// constructor for rvalue binary arrays (internal type) json_value(binary_t&& value) { binary = create(std::move(value)); } void destroy(value_t t) noexcept { // flatten the current json_value to a heap-allocated stack std::vector stack; // move the top-level items to stack if (t == value_t::array) { stack.reserve(array->size()); std::move(array->begin(), array->end(), std::back_inserter(stack)); } else if (t == value_t::object) { stack.reserve(object->size()); for (auto&& it : *object) { stack.push_back(std::move(it.second)); } } while (!stack.empty()) { // move the last item to local variable to be processed basic_json current_item(std::move(stack.back())); stack.pop_back(); // if current_item is array/object, move // its children to the stack to be processed later if (current_item.is_array()) { std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), std::back_inserter(stack)); current_item.m_value.array->clear(); } else if (current_item.is_object()) { for (auto&& it : *current_item.m_value.object) { stack.push_back(std::move(it.second)); } current_item.m_value.object->clear(); } // it's now safe that current_item get destructed // since it doesn't have any children } switch (t) { case value_t::object: { AllocatorType alloc; std::allocator_traits::destroy(alloc, object); std::allocator_traits::deallocate(alloc, object, 1); break; } case value_t::array: { AllocatorType alloc; std::allocator_traits::destroy(alloc, array); std::allocator_traits::deallocate(alloc, array, 1); break; } case value_t::string: { AllocatorType alloc; std::allocator_traits::destroy(alloc, string); std::allocator_traits::deallocate(alloc, string, 1); break; } case value_t::binary: { AllocatorType alloc; std::allocator_traits::destroy(alloc, binary); std::allocator_traits::deallocate(alloc, binary, 1); break; } default: { break; } } } }; /*! @brief checks the class invariants This function asserts the class invariants. It needs to be called at the end of every constructor to make sure that created objects respect the invariant. Furthermore, it has to be called each time the type of a JSON value is changed, because the invariant expresses a relationship between @a m_type and @a m_value. */ void assert_invariant() const noexcept { JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); } public: ////////////////////////// // JSON parser callback // ////////////////////////// /*! @brief parser event types The parser callback distinguishes the following events: - `object_start`: the parser read `{` and started to process a JSON object - `key`: the parser read a key of a value in an object - `object_end`: the parser read `}` and finished processing a JSON object - `array_start`: the parser read `[` and started to process a JSON array - `array_end`: the parser read `]` and finished processing a JSON array - `value`: the parser finished reading a JSON value @image html callback_events.png "Example when certain parse events are triggered" @sa @ref parser_callback_t for more information and examples */ using parse_event_t = detail::parse_event_t; /*! @brief per-element parser callback type With a parser callback function, the result of parsing a JSON text can be influenced. When passed to @ref parse, it is called on certain events (passed as @ref parse_event_t via parameter @a event) with a set recursion depth @a depth and context JSON value @a parsed. The return value of the callback function is a boolean indicating whether the element that emitted the callback shall be kept or not. We distinguish six scenarios (determined by the event type) in which the callback function can be called. The following table describes the values of the parameters @a depth, @a event, and @a parsed. parameter @a event | description | parameter @a depth | parameter @a parsed ------------------ | ----------- | ------------------ | ------------------- parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value @image html callback_events.png "Example when certain parse events are triggered" Discarding a value (i.e., returning `false`) has different effects depending on the context in which function was called: - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never read. - In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level element is skipped. @param[in] depth the depth of the recursion during parsing @param[in] event an event of type parse_event_t indicating the context in the callback function has been called @param[in,out] parsed the current intermediate parse result; note that writing to this value has no effect for parse_event_t::key events @return Whether the JSON value which called the function during parsing should be kept (`true`) or not (`false`). In the latter case, it is either skipped completely or replaced by an empty discarded object. @sa @ref parse for examples @since version 1.0.0 */ using parser_callback_t = detail::parser_callback_t; ////////////////// // constructors // ////////////////// /// @name constructors and destructors /// Constructors of class @ref basic_json, copy/move constructor, copy /// assignment, static functions creating objects, and the destructor. /// @{ /*! @brief create an empty value with a given type Create an empty JSON value with a given type. The value will be default initialized with an empty value which depends on the type: Value type | initial value ----------- | ------------- null | `null` boolean | `false` string | `""` number | `0` object | `{}` array | `[]` binary | empty array @param[in] v the type of the value to create @complexity Constant. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows the constructor for different @ref value_t values,basic_json__value_t} @sa @ref clear() -- restores the postcondition of this constructor @since version 1.0.0 */ basic_json(const value_t v) : m_type(v), m_value(v) { assert_invariant(); } /*! @brief create a null object Create a `null` JSON value. It either takes a null pointer as parameter (explicitly creating `null`) or no parameter (implicitly creating `null`). The passed null pointer itself is not read -- it is only used to choose the right constructor. @complexity Constant. @exceptionsafety No-throw guarantee: this constructor never throws exceptions. @liveexample{The following code shows the constructor with and without a null pointer parameter.,basic_json__nullptr_t} @since version 1.0.0 */ basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null) { assert_invariant(); } /*! @brief create a JSON value This is a "catch all" constructor for all compatible JSON types; that is, types for which a `to_json()` method exists. The constructor forwards the parameter @a val to that method (to `json_serializer::to_json` method with `U = uncvref_t`, to be exact). Template type @a CompatibleType includes, but is not limited to, the following types: - **arrays**: @ref array_t and all kinds of compatible containers such as `std::vector`, `std::deque`, `std::list`, `std::forward_list`, `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, `std::multiset`, and `std::unordered_multiset` with a `value_type` from which a @ref basic_json value can be constructed. - **objects**: @ref object_t and all kinds of compatible associative containers such as `std::map`, `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to @ref string_t and a `value_type` from which a @ref basic_json value can be constructed. - **strings**: @ref string_t, string literals, and all compatible string containers can be used. - **numbers**: @ref number_integer_t, @ref number_unsigned_t, @ref number_float_t, and all convertible number types such as `int`, `size_t`, `int64_t`, `float` or `double` can be used. - **boolean**: @ref boolean_t / `bool` can be used. - **binary**: @ref binary_t / `std::vector` may be used, unfortunately because string literals cannot be distinguished from binary character arrays by the C++ type system, all types compatible with `const char*` will be directed to the string constructor instead. This is both for backwards compatibility, and due to the fact that a binary type is not a standard JSON type. See the examples below. @tparam CompatibleType a type such that: - @a CompatibleType is not derived from `std::istream`, - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move constructors), - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) - @a CompatibleType is not a @ref basic_json nested type (e.g., @ref json_pointer, @ref iterator, etc ...) - @ref @ref json_serializer has a `to_json(basic_json_t&, CompatibleType&&)` method @tparam U = `uncvref_t` @param[in] val the value to be forwarded to the respective constructor @complexity Usually linear in the size of the passed @a val, also depending on the implementation of the called `to_json()` method. @exceptionsafety Depends on the called constructor. For types directly supported by the library (i.e., all types for which no `to_json()` function was provided), strong guarantee holds: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows the constructor with several compatible types.,basic_json__CompatibleType} @since version 2.1.0 */ template < typename CompatibleType, typename U = detail::uncvref_t, detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( JSONSerializer::to_json(std::declval(), std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); assert_invariant(); } /*! @brief create a JSON value from an existing one This is a constructor for existing @ref basic_json types. It does not hijack copy/move constructors, since the parameter has different template arguments than the current ones. The constructor tries to convert the internal @ref m_value of the parameter. @tparam BasicJsonType a type such that: - @a BasicJsonType is a @ref basic_json type. - @a BasicJsonType has different template arguments than @ref basic_json_t. @param[in] val the @ref basic_json value to be converted. @complexity Usually linear in the size of the passed @a val, also depending on the implementation of the called `to_json()` method. @exceptionsafety Depends on the called constructor. For types directly supported by the library (i.e., all types for which no `to_json()` function was provided), strong guarantee holds: if an exception is thrown, there are no changes to any JSON value. @since version 3.2.0 */ template < typename BasicJsonType, detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; using other_number_integer_t = typename BasicJsonType::number_integer_t; using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; using other_string_t = typename BasicJsonType::string_t; using other_object_t = typename BasicJsonType::object_t; using other_array_t = typename BasicJsonType::array_t; using other_binary_t = typename BasicJsonType::binary_t; switch (val.type()) { case value_t::boolean: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_float: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_integer: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_unsigned: JSONSerializer::to_json(*this, val.template get()); break; case value_t::string: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::object: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::array: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::null: *this = nullptr; break; case value_t::discarded: m_type = value_t::discarded; break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } assert_invariant(); } /*! @brief create a container (array or object) from an initializer list Creates a JSON value of type array or object from the passed initializer list @a init. In case @a type_deduction is `true` (default), the type of the JSON value to be created is deducted from the initializer list @a init according to the following rules: 1. If the list is empty, an empty JSON object value `{}` is created. 2. If the list consists of pairs whose first element is a string, a JSON object value is created where the first elements of the pairs are treated as keys and the second elements are as values. 3. In all other cases, an array is created. The rules aim to create the best fit between a C++ initializer list and JSON values. The rationale is as follows: 1. The empty initializer list is written as `{}` which is exactly an empty JSON object. 2. C++ has no way of describing mapped types other than to list a list of pairs. As JSON requires that keys must be of type string, rule 2 is the weakest constraint one can pose on initializer lists to interpret them as an object. 3. In all other cases, the initializer list could not be interpreted as JSON object type, so interpreting it as JSON array type is safe. With the rules described above, the following JSON values cannot be expressed by an initializer list: - the empty array (`[]`): use @ref array(initializer_list_t) with an empty initializer list in this case - arrays whose elements satisfy rule 2: use @ref array(initializer_list_t) with the same initializer list in this case @note When used without parentheses around an empty initializer list, @ref basic_json() is called instead of this function, yielding the JSON null value. @param[in] init initializer list with JSON values @param[in] type_deduction internal parameter; when set to `true`, the type of the JSON value is deducted from the initializer list @a init; when set to `false`, the type provided via @a manual_type is forced. This mode is used by the functions @ref array(initializer_list_t) and @ref object(initializer_list_t). @param[in] manual_type internal parameter; when @a type_deduction is set to `false`, the created JSON value will use the provided type (only @ref value_t::array and @ref value_t::object are valid); when @a type_deduction is set to `true`, this parameter has no effect @throw type_error.301 if @a type_deduction is `false`, @a manual_type is `value_t::object`, but @a init contains an element which is not a pair whose first element is a string. In this case, the constructor could not create an object. If @a type_deduction would have be `true`, an array would have been created. See @ref object(initializer_list_t) for an example. @complexity Linear in the size of the initializer list @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The example below shows how JSON values are created from initializer lists.,basic_json__list_init_t} @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ basic_json(initializer_list_t init, bool type_deduction = true, value_t manual_type = value_t::array) { // check if each element is an array with two elements whose first // element is a string bool is_an_object = std::all_of(init.begin(), init.end(), [](const detail::json_ref& element_ref) { return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); }); // adjust type if type deduction is not wanted if (!type_deduction) { // if array is wanted, do not create an object though possible if (manual_type == value_t::array) { is_an_object = false; } // if object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { JSON_THROW(type_error::create(301, "cannot create object from initializer list")); } } if (is_an_object) { // the initializer list is a list of pairs -> create object m_type = value_t::object; m_value = value_t::object; std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) { auto element = element_ref.moved_or_copied(); m_value.object->emplace( std::move(*((*element.m_value.array)[0].m_value.string)), std::move((*element.m_value.array)[1])); }); } else { // the initializer list describes an array -> create array m_type = value_t::array; m_value.array = create(init.begin(), init.end()); } assert_invariant(); } /*! @brief explicitly create a binary array (without subtype) Creates a JSON binary array value from a given binary container. Binary values are part of various binary formats, such as CBOR, MessagePack, and BSON. This constructor is used to create a value for serialization to those formats. @note Note, this function exists because of the difficulty in correctly specifying the correct template overload in the standard value ctor, as both JSON arrays and JSON binary arrays are backed with some form of a `std::vector`. Because JSON binary arrays are a non-standard extension it was decided that it would be best to prevent automatic initialization of a binary array type, for backwards compatibility and so it does not happen on accident. @param[in] init container containing bytes to use as binary type @return JSON binary array value @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @since version 3.8.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(const typename binary_t::container_type& init) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = init; return res; } /*! @brief explicitly create a binary array (with subtype) Creates a JSON binary array value from a given binary container. Binary values are part of various binary formats, such as CBOR, MessagePack, and BSON. This constructor is used to create a value for serialization to those formats. @note Note, this function exists because of the difficulty in correctly specifying the correct template overload in the standard value ctor, as both JSON arrays and JSON binary arrays are backed with some form of a `std::vector`. Because JSON binary arrays are a non-standard extension it was decided that it would be best to prevent automatic initialization of a binary array type, for backwards compatibility and so it does not happen on accident. @param[in] init container containing bytes to use as binary type @param[in] subtype subtype to use in MessagePack and BSON @return JSON binary array value @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @since version 3.8.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = binary_t(init, subtype); return res; } /// @copydoc binary(const typename binary_t::container_type&) JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(typename binary_t::container_type&& init) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = std::move(init); return res; } /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = binary_t(std::move(init), subtype); return res; } /*! @brief explicitly create an array from an initializer list Creates a JSON array value from a given initializer list. That is, given a list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the initializer list is empty, the empty array `[]` is created. @note This function is only needed to express two edge cases that cannot be realized with the initializer list constructor (@ref basic_json(initializer_list_t, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a string -- in this case, the initializer list constructor would create an object, taking the first elements as keys 2. creating an empty array -- passing the empty initializer list to the initializer list constructor yields an empty object @param[in] init initializer list with JSON values to create an array from (optional) @return JSON array value @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows an example for the `array` function.,array} @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json array(initializer_list_t init = {}) { return basic_json(init, false, value_t::array); } /*! @brief explicitly create an object from an initializer list Creates a JSON object value from a given initializer list. The initializer lists elements must be pairs, and their first elements must be strings. If the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the related function @ref array(initializer_list_t), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list constructor @ref basic_json(initializer_list_t, bool, value_t). @param[in] init initializer list to create an object from (optional) @return JSON object value @throw type_error.301 if @a init is not a list of pairs whose first elements are strings. In this case, no object can be created. When such a value is passed to @ref basic_json(initializer_list_t, bool, value_t), an array would have been created from the passed initializer list @a init. See example below. @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows an example for the `object` function.,object} @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @since version 1.0.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json object(initializer_list_t init = {}) { return basic_json(init, false, value_t::object); } /*! @brief construct an array with count copies of given value Constructs a JSON array value by creating @a cnt copies of a passed value. In case @a cnt is `0`, an empty array is created. @param[in] cnt the number of JSON copies of @a val to create @param[in] val the JSON value to copy @post `std::distance(begin(),end()) == cnt` holds. @complexity Linear in @a cnt. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows examples for the @ref basic_json(size_type\, const basic_json&) constructor.,basic_json__size_type_basic_json} @since version 1.0.0 */ basic_json(size_type cnt, const basic_json& val) : m_type(value_t::array) { m_value.array = create(cnt, val); assert_invariant(); } /*! @brief construct a JSON container given an iterator range Constructs the JSON value with the contents of the range `[first, last)`. The semantics depends on the different types a JSON value can have: - In case of a null type, invalid_iterator.206 is thrown. - In case of other primitive types (number, boolean, or string), @a first must be `begin()` and @a last must be `end()`. In this case, the value is copied. Otherwise, invalid_iterator.204 is thrown. - In case of structured types (array, object), the constructor behaves as similar versions for `std::vector` or `std::map`; that is, a JSON array or object is constructed from the values in the range. @tparam InputIT an input iterator type (@ref iterator or @ref const_iterator) @param[in] first begin of the range to copy from (included) @param[in] last end of the range to copy from (excluded) @pre Iterators @a first and @a last must be initialized. **This precondition is enforced with an assertion (see warning).** If assertions are switched off, a violation of this precondition yields undefined behavior. @pre Range `[first, last)` is valid. Usually, this precondition cannot be checked efficiently. Only certain edge cases are detected; see the description of the exceptions below. A violation of this precondition yields undefined behavior. @warning A precondition is enforced with a runtime assertion that will result in calling `std::abort` if this precondition is not met. Assertions can be disabled by defining `NDEBUG` at compile time. See https://en.cppreference.com/w/cpp/error/assert for more information. @throw invalid_iterator.201 if iterators @a first and @a last are not compatible (i.e., do not belong to the same JSON value). In this case, the range `[first, last)` is undefined. @throw invalid_iterator.204 if iterators @a first and @a last belong to a primitive type (number, boolean, or string), but @a first does not point to the first element any more. In this case, the range `[first, last)` is undefined. See example code below. @throw invalid_iterator.206 if iterators @a first and @a last belong to a null value. In this case, the range `[first, last)` is undefined. @complexity Linear in distance between @a first and @a last. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The example below shows several ways to create JSON values by specifying a subrange with iterators.,basic_json__InputIt_InputIt} @since version 1.0.0 */ template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > basic_json(InputIT first, InputIT last) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); } // copy type from first iterator m_type = first.m_object->m_type; // check if iterator range is complete for primitive values switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: { if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range")); } break; } default: break; } switch (m_type) { case value_t::number_integer: { m_value.number_integer = first.m_object->m_value.number_integer; break; } case value_t::number_unsigned: { m_value.number_unsigned = first.m_object->m_value.number_unsigned; break; } case value_t::number_float: { m_value.number_float = first.m_object->m_value.number_float; break; } case value_t::boolean: { m_value.boolean = first.m_object->m_value.boolean; break; } case value_t::string: { m_value = *first.m_object->m_value.string; break; } case value_t::object: { m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); break; } case value_t::array: { m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); break; } case value_t::binary: { m_value = *first.m_object->m_value.binary; break; } default: JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + std::string(first.m_object->type_name()))); } assert_invariant(); } /////////////////////////////////////// // other constructors and destructor // /////////////////////////////////////// template, std::is_same>::value, int> = 0 > basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} /*! @brief copy constructor Creates a copy of a given JSON value. @param[in] other the JSON value to copy @post `*this == other` @complexity Linear in the size of @a other. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is linear. - As postcondition, it holds: `other == basic_json(other)`. @liveexample{The following code shows an example for the copy constructor.,basic_json__basic_json} @since version 1.0.0 */ basic_json(const basic_json& other) : m_type(other.m_type) { // check of passed value is valid other.assert_invariant(); switch (m_type) { case value_t::object: { m_value = *other.m_value.object; break; } case value_t::array: { m_value = *other.m_value.array; break; } case value_t::string: { m_value = *other.m_value.string; break; } case value_t::boolean: { m_value = other.m_value.boolean; break; } case value_t::number_integer: { m_value = other.m_value.number_integer; break; } case value_t::number_unsigned: { m_value = other.m_value.number_unsigned; break; } case value_t::number_float: { m_value = other.m_value.number_float; break; } case value_t::binary: { m_value = *other.m_value.binary; break; } default: break; } assert_invariant(); } /*! @brief move constructor Move constructor. Constructs a JSON value with the contents of the given value @a other using move semantics. It "steals" the resources from @a other and leaves it as JSON null value. @param[in,out] other value to move to this object @post `*this` has the same value as @a other before the call. @post @a other is a JSON null value. @complexity Constant. @exceptionsafety No-throw guarantee: this constructor never throws exceptions. @requirement This function helps `basic_json` satisfying the [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible) requirements. @liveexample{The code below shows the move constructor explicitly called via std::move.,basic_json__moveconstructor} @since version 1.0.0 */ basic_json(basic_json&& other) noexcept : m_type(std::move(other.m_type)), m_value(std::move(other.m_value)) { // check that passed value is valid other.assert_invariant(); // invalidate payload other.m_type = value_t::null; other.m_value = {}; assert_invariant(); } /*! @brief copy assignment Copy assignment operator. Copies a JSON value via the "copy and swap" strategy: It is expressed in terms of the copy constructor, destructor, and the `swap()` member function. @param[in] other value to copy from @complexity Linear. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is linear. @liveexample{The code below shows and example for the copy assignment. It creates a copy of value `a` which is then swapped with `b`. Finally\, the copy of `a` (which is the null value after the swap) is destroyed.,basic_json__copyassignment} @since version 1.0.0 */ basic_json& operator=(basic_json other) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { // check that passed value is valid other.assert_invariant(); using std::swap; swap(m_type, other.m_type); swap(m_value, other.m_value); assert_invariant(); return *this; } /*! @brief destructor Destroys the JSON value and frees all allocated memory. @complexity Linear. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is linear. - All stored elements are destroyed and all memory is freed. @since version 1.0.0 */ ~basic_json() noexcept { assert_invariant(); m_value.destroy(m_type); } /// @} public: /////////////////////// // object inspection // /////////////////////// /// @name object inspection /// Functions to inspect the type of a JSON value. /// @{ /*! @brief serialization Serialization function for JSON values. The function tries to mimic Python's `json.dumps()` function, and currently supports its @a indent and @a ensure_ascii parameters. @param[in] indent If indent is nonnegative, then array elements and object members will be pretty-printed with that indent level. An indent level of `0` will only insert newlines. `-1` (the default) selects the most compact representation. @param[in] indent_char The character to use for indentation if @a indent is greater than `0`. The default is ` ` (space). @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] error_handler how to react on decoding errors; there are three possible values: `strict` (throws and exception in case a decoding error occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), and `ignore` (ignore invalid UTF-8 sequences during serialization; all bytes are copied to the output unchanged). @return string containing the serialization of the JSON value @throw type_error.316 if a string stored inside the JSON value is not UTF-8 encoded and @a error_handler is set to strict @note Binary values are serialized as object containing two keys: - "bytes": an array of bytes as integers - "subtype": the subtype as integer or "null" if the binary has no subtype @complexity Linear. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @liveexample{The following example shows the effect of different @a indent\, @a indent_char\, and @a ensure_ascii parameters to the result of the serialization.,dump} @see https://docs.python.org/2/library/json.html#json.dump @since version 1.0.0; indentation character @a indent_char, option @a ensure_ascii and exceptions added in version 3.0.0; error handlers added in version 3.4.0; serialization of binary values added in version 3.8.0. */ string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false, const error_handler_t error_handler = error_handler_t::strict) const { string_t result; serializer s(detail::output_adapter(result), indent_char, error_handler); if (indent >= 0) { s.dump(*this, true, ensure_ascii, static_cast(indent)); } else { s.dump(*this, false, ensure_ascii, 0); } return result; } /*! @brief return the type of the JSON value (explicit) Return the type of the JSON value as a value from the @ref value_t enumeration. @return the type of the JSON value Value type | return value ------------------------- | ------------------------- null | value_t::null boolean | value_t::boolean string | value_t::string number (integer) | value_t::number_integer number (unsigned integer) | value_t::number_unsigned number (floating-point) | value_t::number_float object | value_t::object array | value_t::array binary | value_t::binary discarded | value_t::discarded @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `type()` for all JSON types.,type} @sa @ref operator value_t() -- return the type of the JSON value (implicit) @sa @ref type_name() -- return the type as string @since version 1.0.0 */ constexpr value_t type() const noexcept { return m_type; } /*! @brief return whether type is primitive This function returns true if and only if the JSON type is primitive (string, number, boolean, or null). @return `true` if type is primitive (string, number, boolean, or null), `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_primitive()` for all JSON types.,is_primitive} @sa @ref is_structured() -- returns whether JSON value is structured @sa @ref is_null() -- returns whether JSON value is `null` @sa @ref is_string() -- returns whether JSON value is a string @sa @ref is_boolean() -- returns whether JSON value is a boolean @sa @ref is_number() -- returns whether JSON value is a number @sa @ref is_binary() -- returns whether JSON value is a binary array @since version 1.0.0 */ constexpr bool is_primitive() const noexcept { return is_null() || is_string() || is_boolean() || is_number() || is_binary(); } /*! @brief return whether type is structured This function returns true if and only if the JSON type is structured (array or object). @return `true` if type is structured (array or object), `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_structured()` for all JSON types.,is_structured} @sa @ref is_primitive() -- returns whether value is primitive @sa @ref is_array() -- returns whether value is an array @sa @ref is_object() -- returns whether value is an object @since version 1.0.0 */ constexpr bool is_structured() const noexcept { return is_array() || is_object(); } /*! @brief return whether value is null This function returns true if and only if the JSON value is null. @return `true` if type is null, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_null()` for all JSON types.,is_null} @since version 1.0.0 */ constexpr bool is_null() const noexcept { return m_type == value_t::null; } /*! @brief return whether value is a boolean This function returns true if and only if the JSON value is a boolean. @return `true` if type is boolean, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_boolean()` for all JSON types.,is_boolean} @since version 1.0.0 */ constexpr bool is_boolean() const noexcept { return m_type == value_t::boolean; } /*! @brief return whether value is a number This function returns true if and only if the JSON value is a number. This includes both integer (signed and unsigned) and floating-point values. @return `true` if type is number (regardless whether integer, unsigned integer or floating-type), `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number()` for all JSON types.,is_number} @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 */ constexpr bool is_number() const noexcept { return is_number_integer() || is_number_float(); } /*! @brief return whether value is an integer number This function returns true if and only if the JSON value is a signed or unsigned integer number. This excludes floating-point values. @return `true` if type is an integer or unsigned integer number, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number_integer()` for all JSON types.,is_number_integer} @sa @ref is_number() -- check if value is a number @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 */ constexpr bool is_number_integer() const noexcept { return m_type == value_t::number_integer || m_type == value_t::number_unsigned; } /*! @brief return whether value is an unsigned integer number This function returns true if and only if the JSON value is an unsigned integer number. This excludes floating-point and signed integer values. @return `true` if type is an unsigned integer number, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number_unsigned()` for all JSON types.,is_number_unsigned} @sa @ref is_number() -- check if value is a number @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 2.0.0 */ constexpr bool is_number_unsigned() const noexcept { return m_type == value_t::number_unsigned; } /*! @brief return whether value is a floating-point number This function returns true if and only if the JSON value is a floating-point number. This excludes signed and unsigned integer values. @return `true` if type is a floating-point number, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number_float()` for all JSON types.,is_number_float} @sa @ref is_number() -- check if value is number @sa @ref is_number_integer() -- check if value is an integer number @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @since version 1.0.0 */ constexpr bool is_number_float() const noexcept { return m_type == value_t::number_float; } /*! @brief return whether value is an object This function returns true if and only if the JSON value is an object. @return `true` if type is object, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_object()` for all JSON types.,is_object} @since version 1.0.0 */ constexpr bool is_object() const noexcept { return m_type == value_t::object; } /*! @brief return whether value is an array This function returns true if and only if the JSON value is an array. @return `true` if type is array, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_array()` for all JSON types.,is_array} @since version 1.0.0 */ constexpr bool is_array() const noexcept { return m_type == value_t::array; } /*! @brief return whether value is a string This function returns true if and only if the JSON value is a string. @return `true` if type is string, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_string()` for all JSON types.,is_string} @since version 1.0.0 */ constexpr bool is_string() const noexcept { return m_type == value_t::string; } /*! @brief return whether value is a binary array This function returns true if and only if the JSON value is a binary array. @return `true` if type is binary array, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_binary()` for all JSON types.,is_binary} @since version 3.8.0 */ constexpr bool is_binary() const noexcept { return m_type == value_t::binary; } /*! @brief return whether value is discarded This function returns true if and only if the JSON value was discarded during parsing with a callback function (see @ref parser_callback_t). @note This function will always be `false` for JSON values after parsing. That is, discarded values can only occur during parsing, but will be removed when inside a structured value or replaced by null in other cases. @return `true` if type is discarded, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_discarded()` for all JSON types.,is_discarded} @since version 1.0.0 */ constexpr bool is_discarded() const noexcept { return m_type == value_t::discarded; } /*! @brief return the type of the JSON value (implicit) Implicitly return the type of the JSON value as a value from the @ref value_t enumeration. @return the type of the JSON value @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies the @ref value_t operator for all JSON types.,operator__value_t} @sa @ref type() -- return the type of the JSON value (explicit) @sa @ref type_name() -- return the type as string @since version 1.0.0 */ constexpr operator value_t() const noexcept { return m_type; } /// @} private: ////////////////// // value access // ////////////////// /// get a boolean (explicit) boolean_t get_impl(boolean_t* /*unused*/) const { if (JSON_HEDLEY_LIKELY(is_boolean())) { return m_value.boolean; } JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); } /// get a pointer to the value (object) object_t* get_impl_ptr(object_t* /*unused*/) noexcept { return is_object() ? m_value.object : nullptr; } /// get a pointer to the value (object) constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept { return is_object() ? m_value.object : nullptr; } /// get a pointer to the value (array) array_t* get_impl_ptr(array_t* /*unused*/) noexcept { return is_array() ? m_value.array : nullptr; } /// get a pointer to the value (array) constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept { return is_array() ? m_value.array : nullptr; } /// get a pointer to the value (string) string_t* get_impl_ptr(string_t* /*unused*/) noexcept { return is_string() ? m_value.string : nullptr; } /// get a pointer to the value (string) constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept { return is_string() ? m_value.string : nullptr; } /// get a pointer to the value (boolean) boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept { return is_boolean() ? &m_value.boolean : nullptr; } /// get a pointer to the value (boolean) constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept { return is_boolean() ? &m_value.boolean : nullptr; } /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (unsigned number) constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept { return is_number_float() ? &m_value.number_float : nullptr; } /// get a pointer to the value (floating-point number) constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept { return is_number_float() ? &m_value.number_float : nullptr; } /// get a pointer to the value (binary) binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept { return is_binary() ? m_value.binary : nullptr; } /// get a pointer to the value (binary) constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept { return is_binary() ? m_value.binary : nullptr; } /*! @brief helper function to implement get_ref() This function helps to implement get_ref() without code duplication for const and non-const overloads @tparam ThisType will be deduced as `basic_json` or `const basic_json` @throw type_error.303 if ReferenceType does not match underlying value type of the current JSON */ template static ReferenceType get_ref_impl(ThisType& obj) { // delegate the call to get_ptr<>() auto ptr = obj.template get_ptr::type>(); if (JSON_HEDLEY_LIKELY(ptr != nullptr)) { return *ptr; } JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); } public: /// @name value access /// Direct access to the stored value of a JSON value. /// @{ /*! @brief get special-case overload This overloads avoids a lot of template boilerplate, it can be seen as the identity method @tparam BasicJsonType == @ref basic_json @return a copy of *this @complexity Constant. @since version 2.1.0 */ template::type, basic_json_t>::value, int> = 0> basic_json get() const { return *this; } /*! @brief get special-case overload This overloads converts the current @ref basic_json in a different @ref basic_json type @tparam BasicJsonType == @ref basic_json @return a copy of *this, converted into @tparam BasicJsonType @complexity Depending on the implementation of the called `from_json()` method. @since version 3.2.0 */ template < typename BasicJsonType, detail::enable_if_t < !std::is_same::value&& detail::is_basic_json::value, int > = 0 > BasicJsonType get() const { return *this; } /*! @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} ValueType ret; JSONSerializer::from_json(*this, ret); return ret; @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json, - @ref json_serializer has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`, and - @ref json_serializer does not have a `from_json()` method of the form `ValueType from_json(const basic_json&)` @tparam ValueTypeCV the provided value type @tparam ValueType the returned value type @return copy of the JSON value, converted to @a ValueType @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,get__ValueType_const} @since version 2.1.0 */ template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, detail::enable_if_t < !detail::is_basic_json::value && detail::has_from_json::value && !detail::has_non_default_from_json::value, int > = 0 > ValueType get() const noexcept(noexcept( JSONSerializer::from_json(std::declval(), std::declval()))) { // we cannot static_assert on ValueTypeCV being non-const, because // there is support for get(), which is why we // still need the uncvref static_assert(!std::is_reference::value, "get() cannot be used with reference types, you might want to use get_ref()"); static_assert(std::is_default_constructible::value, "types must be DefaultConstructible when used with get()"); ValueType ret; JSONSerializer::from_json(*this, ret); return ret; } /*! @brief get a value (explicit); special case Explicit type conversion between the JSON value and a compatible value which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} return JSONSerializer::from_json(*this); @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json and - @ref json_serializer has a `from_json()` method of the form `ValueType from_json(const basic_json&)` @note If @ref json_serializer has both overloads of `from_json()`, this one is chosen. @tparam ValueTypeCV the provided value type @tparam ValueType the returned value type @return copy of the JSON value, converted to @a ValueType @throw what @ref json_serializer `from_json()` method throws @since version 2.1.0 */ template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, detail::enable_if_t < !std::is_same::value && detail::has_non_default_from_json::value, int > = 0 > ValueType get() const noexcept(noexcept( JSONSerializer::from_json(std::declval()))) { static_assert(!std::is_reference::value, "get() cannot be used with reference types, you might want to use get_ref()"); return JSONSerializer::from_json(*this); } /*! @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value. The value is filled into the input parameter by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} ValueType v; JSONSerializer::from_json(*this, v); @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json, - @ref json_serializer has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`, and @tparam ValueType the input parameter type. @return the input parameter, allowing chaining calls. @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,get_to} @since version 3.3.0 */ template < typename ValueType, detail::enable_if_t < !detail::is_basic_json::value&& detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; } // specialization to allow to call get_to with a basic_json value // see https://github.com/nlohmann/json/issues/2175 template::value, int> = 0> ValueType & get_to(ValueType& v) const { v = *this; return v; } template < typename T, std::size_t N, typename Array = T (&)[N], detail::enable_if_t < detail::has_from_json::value, int > = 0 > Array get_to(T (&v)[N]) const noexcept(noexcept(JSONSerializer::from_json( std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; } /*! @brief get a pointer value (implicit) Implicit pointer access to the internally stored JSON value. No copies are made. @warning Writing data to the pointee of the result yields an undefined state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. Enforced by a static assertion. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @complexity Constant. @liveexample{The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `nullptr` is returned if the value and the requested pointer type does not match.,get_ptr} @since version 1.0.0 */ template::value, int>::type = 0> auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) { // delegate the call to get_impl_ptr<>() return get_impl_ptr(static_cast(nullptr)); } /*! @brief get a pointer value (implicit) @copydoc get_ptr() */ template < typename PointerType, typename std::enable_if < std::is_pointer::value&& std::is_const::type>::value, int >::type = 0 > constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) { // delegate the call to get_impl_ptr<>() const return get_impl_ptr(static_cast(nullptr)); } /*! @brief get a pointer value (explicit) Explicit pointer access to the internally stored JSON value. No copies are made. @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @complexity Constant. @liveexample{The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `nullptr` is returned if the value and the requested pointer type does not match.,get__PointerType} @sa @ref get_ptr() for explicit pointer-member access @since version 1.0.0 */ template::value, int>::type = 0> auto get() noexcept -> decltype(std::declval().template get_ptr()) { // delegate the call to get_ptr return get_ptr(); } /*! @brief get a pointer value (explicit) @copydoc get() */ template::value, int>::type = 0> constexpr auto get() const noexcept -> decltype(std::declval().template get_ptr()) { // delegate the call to get_ptr return get_ptr(); } /*! @brief get a reference value (implicit) Implicit reference access to the internally stored JSON value. No copies are made. @warning Writing data to the referee of the result yields an undefined state. @tparam ReferenceType reference type; must be a reference to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or @ref number_float_t. Enforced by static assertion. @return reference to the internally stored JSON value if the requested reference type @a ReferenceType fits to the JSON value; throws type_error.303 otherwise @throw type_error.303 in case passed type @a ReferenceType is incompatible with the stored JSON value; see example below @complexity Constant. @liveexample{The example shows several calls to `get_ref()`.,get_ref} @since version 1.1.0 */ template::value, int>::type = 0> ReferenceType get_ref() { // delegate call to get_ref_impl return get_ref_impl(*this); } /*! @brief get a reference value (implicit) @copydoc get_ref() */ template < typename ReferenceType, typename std::enable_if < std::is_reference::value&& std::is_const::type>::value, int >::type = 0 > ReferenceType get_ref() const { // delegate call to get_ref_impl return get_ref_impl(*this); } /*! @brief get a value (implicit) Implicit type conversion between the JSON value and a compatible value. The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. The character type of @ref string_t as well as an initializer list of this type is excluded to avoid ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @throw type_error.302 in case passed type @a ValueType is incompatible to the JSON value type (e.g., the JSON value is of type boolean, but a string is requested); see example below @complexity Linear in the size of the JSON value. @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,operator__ValueType} @since version 1.0.0 */ template < typename ValueType, typename std::enable_if < !std::is_pointer::value&& !std::is_same>::value&& !std::is_same::value&& !detail::is_basic_json::value && !std::is_same>::value #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) && !std::is_same::value #endif && detail::is_detected::value , int >::type = 0 > JSON_EXPLICIT operator ValueType() const { // delegate the call to get<>() const return get(); } /*! @return reference to the binary value @throw type_error.302 if the value is not binary @sa @ref is_binary() to check if the value is binary @since version 3.8.0 */ binary_t& get_binary() { if (!is_binary()) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); } return *get_ptr(); } /// @copydoc get_binary() const binary_t& get_binary() const { if (!is_binary()) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); } return *get_ptr(); } /// @} //////////////////// // element access // //////////////////// /// @name element access /// Access to the JSON value. /// @{ /*! @brief access specified array element with bounds checking Returns a reference to the element at specified location @a idx, with bounds checking. @param[in] idx index of the element to access @return reference to the element at index @a idx @throw type_error.304 if the JSON value is not an array; in this case, calling `at` with an index makes no sense. See example below. @throw out_of_range.401 if the index @a idx is out of range of the array; that is, `idx >= size()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 1.0.0 @liveexample{The example below shows how array elements can be read and written using `at()`. It also demonstrates the different exceptions that can be thrown.,at__size_type} */ reference at(size_type idx) { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return m_value.array->at(idx); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified array element with bounds checking Returns a const reference to the element at specified location @a idx, with bounds checking. @param[in] idx index of the element to access @return const reference to the element at index @a idx @throw type_error.304 if the JSON value is not an array; in this case, calling `at` with an index makes no sense. See example below. @throw out_of_range.401 if the index @a idx is out of range of the array; that is, `idx >= size()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 1.0.0 @liveexample{The example below shows how array elements can be read using `at()`. It also demonstrates the different exceptions that can be thrown., at__size_type_const} */ const_reference at(size_type idx) const { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return m_value.array->at(idx); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified object element with bounds checking Returns a reference to the element at with specified key @a key, with bounds checking. @param[in] key key of the element to access @return reference to the element at key @a key @throw type_error.304 if the JSON value is not an object; in this case, calling `at` with a key makes no sense. See example below. @throw out_of_range.403 if the key @a key is is not stored in the object; that is, `find(key) == end()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Logarithmic in the size of the container. @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @since version 1.0.0 @liveexample{The example below shows how object elements can be read and written using `at()`. It also demonstrates the different exceptions that can be thrown.,at__object_t_key_type} */ reference at(const typename object_t::key_type& key) { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_TRY { return m_value.object->at(key); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified object element with bounds checking Returns a const reference to the element at with specified key @a key, with bounds checking. @param[in] key key of the element to access @return const reference to the element at key @a key @throw type_error.304 if the JSON value is not an object; in this case, calling `at` with a key makes no sense. See example below. @throw out_of_range.403 if the key @a key is is not stored in the object; that is, `find(key) == end()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Logarithmic in the size of the container. @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @since version 1.0.0 @liveexample{The example below shows how object elements can be read using `at()`. It also demonstrates the different exceptions that can be thrown., at__object_t_key_type_const} */ const_reference at(const typename object_t::key_type& key) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_TRY { return m_value.object->at(key); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified array element Returns a reference to the element at specified location @a idx. @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), then the array is silently filled up with `null` values to make `idx` a valid reference to the last stored element. @param[in] idx index of the element to access @return reference to the element at index @a idx @throw type_error.305 if the JSON value is not an array or null; in that cases, using the [] operator with an index makes no sense. @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @liveexample{The example below shows how array elements can be read and written using `[]` operator. Note the addition of `null` values.,operatorarray__size_type} @since version 1.0.0 */ reference operator[](size_type idx) { // implicitly convert null value to an empty array if (is_null()) { m_type = value_t::array; m_value.array = create(); assert_invariant(); } // operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // fill up array with null values if given idx is outside range if (idx >= m_value.array->size()) { m_value.array->insert(m_value.array->end(), idx - m_value.array->size() + 1, basic_json()); } return m_value.array->operator[](idx); } JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); } /*! @brief access specified array element Returns a const reference to the element at specified location @a idx. @param[in] idx index of the element to access @return const reference to the element at index @a idx @throw type_error.305 if the JSON value is not an array; in that case, using the [] operator with an index makes no sense. @complexity Constant. @liveexample{The example below shows how array elements can be read using the `[]` operator.,operatorarray__size_type_const} @since version 1.0.0 */ const_reference operator[](size_type idx) const { // const operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { return m_value.array->operator[](idx); } JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); } /*! @brief access specified object element Returns a reference to the element at with specified key @a key. @note If @a key is not found in the object, then it is silently added to the object and filled with a `null` value to make `key` a valid reference. In case the value was `null` before, it is converted to an object. @param[in] key key of the element to access @return reference to the element at key @a key @throw type_error.305 if the JSON value is not an object or null; in that cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the `[]` operator.,operatorarray__key_type} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.0.0 */ reference operator[](const typename object_t::key_type& key) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } // operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->operator[](key); } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief read-only access specified object element Returns a const reference to the element at with specified key @a key. No bounds checking is performed. @warning If the element with key @a key does not exist, the behavior is undefined. @param[in] key key of the element to access @return const reference to the element at key @a key @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the `[]` operator.,operatorarray__key_type_const} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.0.0 */ const_reference operator[](const typename object_t::key_type& key) const { // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief access specified object element Returns a reference to the element at with specified key @a key. @note If @a key is not found in the object, then it is silently added to the object and filled with a `null` value to make `key` a valid reference. In case the value was `null` before, it is converted to an object. @param[in] key key of the element to access @return reference to the element at key @a key @throw type_error.305 if the JSON value is not an object or null; in that cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the `[]` operator.,operatorarray__key_type} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.1.0 */ template JSON_HEDLEY_NON_NULL(2) reference operator[](T* key) { // implicitly convert null to object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->operator[](key); } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief read-only access specified object element Returns a const reference to the element at with specified key @a key. No bounds checking is performed. @warning If the element with key @a key does not exist, the behavior is undefined. @param[in] key key of the element to access @return const reference to the element at key @a key @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the `[]` operator.,operatorarray__key_type_const} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.1.0 */ template JSON_HEDLEY_NON_NULL(2) const_reference operator[](T* key) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief access specified object element with default value Returns either a copy of an object's element at the specified key @a key or a given default value if no element with key @a key exists. The function is basically equivalent to executing @code {.cpp} try { return at(key); } catch(out_of_range) { return default_value; } @endcode @note Unlike @ref at(const typename object_t::key_type&), this function does not throw if the given key @a key was not found. @note Unlike @ref operator[](const typename object_t::key_type& key), this function does not implicitly add an element to the position defined by @a key. This function is furthermore also applicable to const objects. @param[in] key key of the element to access @param[in] default_value the value to return if @a key is not found @tparam ValueType type compatible to JSON values, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. Note the type of the expected value at @a key and the default value @a default_value must be compatible. @return copy of the element at key @a key or @a default_value if @a key is not found @throw type_error.302 if @a default_value does not match the type of the value at @a key @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be queried with a default value.,basic_json__value} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @since version 1.0.0 */ // using std::is_convertible in a std::enable_if will fail when using explicit conversions template < class ValueType, typename std::enable_if < detail::is_getable::value && !std::is_same::value, int >::type = 0 > ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { // if key is found, return value and given default value otherwise const auto it = find(key); if (it != end()) { return it->template get(); } return default_value; } JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); } /*! @brief overload for a default value of type const char* @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const */ string_t value(const typename object_t::key_type& key, const char* default_value) const { return value(key, string_t(default_value)); } /*! @brief access specified object element via JSON Pointer with default value Returns either a copy of an object's element at the specified key @a key or a given default value if no element with key @a key exists. The function is basically equivalent to executing @code {.cpp} try { return at(ptr); } catch(out_of_range) { return default_value; } @endcode @note Unlike @ref at(const json_pointer&), this function does not throw if the given key @a key was not found. @param[in] ptr a JSON pointer to the element to access @param[in] default_value the value to return if @a ptr found no value @tparam ValueType type compatible to JSON values, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. Note the type of the expected value at @a key and the default value @a default_value must be compatible. @return copy of the element at key @a key or @a default_value if @a key is not found @throw type_error.302 if @a default_value does not match the type of the value at @a ptr @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be queried with a default value.,basic_json__value_ptr} @sa @ref operator[](const json_pointer&) for unchecked access by reference @since version 2.0.2 */ template::value, int>::type = 0> ValueType value(const json_pointer& ptr, const ValueType& default_value) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { // if pointer resolves a value, return it or use default value JSON_TRY { return ptr.get_checked(this).template get(); } JSON_INTERNAL_CATCH (out_of_range&) { return default_value; } } JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); } /*! @brief overload for a default value of type const char* @copydoc basic_json::value(const json_pointer&, ValueType) const */ JSON_HEDLEY_NON_NULL(3) string_t value(const json_pointer& ptr, const char* default_value) const { return value(ptr, string_t(default_value)); } /*! @brief access the first element Returns a reference to the first element in the container. For a JSON container `c`, the expression `c.front()` is equivalent to `*c.begin()`. @return In case of a structured type (array or object), a reference to the first element is returned. In case of number, string, boolean, or binary values, a reference to the value is returned. @complexity Constant. @pre The JSON value must not be `null` (would throw `std::out_of_range`) or an empty array or object (undefined behavior, **guarded by assertions**). @post The JSON value remains unchanged. @throw invalid_iterator.214 when called on `null` value @liveexample{The following code shows an example for `front()`.,front} @sa @ref back() -- access the last element @since version 1.0.0 */ reference front() { return *begin(); } /*! @copydoc basic_json::front() */ const_reference front() const { return *cbegin(); } /*! @brief access the last element Returns a reference to the last element in the container. For a JSON container `c`, the expression `c.back()` is equivalent to @code {.cpp} auto tmp = c.end(); --tmp; return *tmp; @endcode @return In case of a structured type (array or object), a reference to the last element is returned. In case of number, string, boolean, or binary values, a reference to the value is returned. @complexity Constant. @pre The JSON value must not be `null` (would throw `std::out_of_range`) or an empty array or object (undefined behavior, **guarded by assertions**). @post The JSON value remains unchanged. @throw invalid_iterator.214 when called on a `null` value. See example below. @liveexample{The following code shows an example for `back()`.,back} @sa @ref front() -- access the first element @since version 1.0.0 */ reference back() { auto tmp = end(); --tmp; return *tmp; } /*! @copydoc basic_json::back() */ const_reference back() const { auto tmp = cend(); --tmp; return *tmp; } /*! @brief remove element given an iterator Removes the element specified by iterator @a pos. The iterator @a pos must be valid and dereferenceable. Thus the `end()` iterator (which is valid, but is not dereferenceable) cannot be used as a value for @a pos. If called on a primitive type other than `null`, the resulting JSON value will be `null`. @param[in] pos iterator to the element to remove @return Iterator following the last removed element. If the iterator @a pos refers to the last element, the `end()` iterator is returned. @tparam IteratorType an @ref iterator or @ref const_iterator @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. @throw type_error.307 if called on a `null` value; example: `"cannot use erase() with null"` @throw invalid_iterator.202 if called on an iterator which does not belong to the current JSON value; example: `"iterator does not fit current value"` @throw invalid_iterator.205 if called on a primitive type with invalid iterator (i.e., any iterator which is not `begin()`); example: `"iterator out of range"` @complexity The complexity depends on the type: - objects: amortized constant - arrays: linear in distance between @a pos and the end of the container - strings and binary: linear in the length of the member - other types: constant @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType} @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @sa @ref erase(const size_type) -- removes the element from an array at the given index @since version 1.0.0 */ template < class IteratorType, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > IteratorType erase(IteratorType pos) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } IteratorType result = end(); switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: case value_t::binary: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { JSON_THROW(invalid_iterator::create(205, "iterator out of range")); } if (is_string()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.string); std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } else if (is_binary()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.binary); std::allocator_traits::deallocate(alloc, m_value.binary, 1); m_value.binary = nullptr; } m_type = value_t::null; assert_invariant(); break; } case value_t::object: { result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); break; } case value_t::array: { result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); break; } default: JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } return result; } /*! @brief remove elements given an iterator range Removes the element specified by the range `[first; last)`. The iterator @a first does not need to be dereferenceable if `first == last`: erasing an empty range is a no-op. If called on a primitive type other than `null`, the resulting JSON value will be `null`. @param[in] first iterator to the beginning of the range to remove @param[in] last iterator past the end of the range to remove @return Iterator following the last removed element. If the iterator @a second refers to the last element, the `end()` iterator is returned. @tparam IteratorType an @ref iterator or @ref const_iterator @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. @throw type_error.307 if called on a `null` value; example: `"cannot use erase() with null"` @throw invalid_iterator.203 if called on iterators which does not belong to the current JSON value; example: `"iterators do not fit current value"` @throw invalid_iterator.204 if called on a primitive type with invalid iterators (i.e., if `first != begin()` and `last != end()`); example: `"iterators out of range"` @complexity The complexity depends on the type: - objects: `log(size()) + std::distance(first, last)` - arrays: linear in the distance between @a first and @a last, plus linear in the distance between @a last and end of the container - strings and binary: linear in the length of the member - other types: constant @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType_IteratorType} @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @sa @ref erase(const size_type) -- removes the element from an array at the given index @since version 1.0.0 */ template < class IteratorType, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > IteratorType erase(IteratorType first, IteratorType last) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); } IteratorType result = end(); switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: case value_t::binary: { if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range")); } if (is_string()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.string); std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } else if (is_binary()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.binary); std::allocator_traits::deallocate(alloc, m_value.binary, 1); m_value.binary = nullptr; } m_type = value_t::null; assert_invariant(); break; } case value_t::object: { result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, last.m_it.object_iterator); break; } case value_t::array: { result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, last.m_it.array_iterator); break; } default: JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } return result; } /*! @brief remove element from a JSON object given a key Removes elements from a JSON object with the key value @a key. @param[in] key value of the elements to remove @return Number of elements removed. If @a ObjectType is the default `std::map` type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). @post References and iterators to the erased elements are invalidated. Other references and iterators are not affected. @throw type_error.307 when called on a type other than JSON object; example: `"cannot use erase() with null"` @complexity `log(size()) + count(key)` @liveexample{The example shows the effect of `erase()`.,erase__key_type} @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range @sa @ref erase(const size_type) -- removes the element from an array at the given index @since version 1.0.0 */ size_type erase(const typename object_t::key_type& key) { // this erase only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->erase(key); } JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } /*! @brief remove element from a JSON array given an index Removes element from a JSON array at the index @a idx. @param[in] idx index of the element to remove @throw type_error.307 when called on a type other than JSON object; example: `"cannot use erase() with null"` @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 is out of range"` @complexity Linear in distance between @a idx and the end of the container. @liveexample{The example shows the effect of `erase()`.,erase__size_type} @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @since version 1.0.0 */ void erase(const size_type idx) { // this erase only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } m_value.array->erase(m_value.array->begin() + static_cast(idx)); } else { JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } } /// @} //////////// // lookup // //////////// /// @name lookup /// @{ /*! @brief find an element in a JSON object Finds an element in a JSON object with key equivalent to @a key. If the element is not found or the JSON value is not an object, end() is returned. @note This method always returns @ref end() when executed on a JSON type that is not an object. @param[in] key key value of the element to search for. @return Iterator to an element with key equivalent to @a key. If no such element is found or the JSON value is not an object, past-the-end (see @ref end()) iterator is returned. @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how `find()` is used.,find__key_type} @sa @ref contains(KeyT&&) const -- checks whether a key exists @since version 1.0.0 */ template iterator find(KeyT&& key) { auto result = end(); if (is_object()) { result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; } /*! @brief find an element in a JSON object @copydoc find(KeyT&&) */ template const_iterator find(KeyT&& key) const { auto result = cend(); if (is_object()) { result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; } /*! @brief returns the number of occurrences of a key in a JSON object Returns the number of elements with key @a key. If ObjectType is the default `std::map` type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). @note This method always returns `0` when executed on a JSON type that is not an object. @param[in] key key value of the element to count @return Number of elements with key @a key. If the JSON value is not an object, the return value will be `0`. @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how `count()` is used.,count} @since version 1.0.0 */ template size_type count(KeyT&& key) const { // return 0 for all nonobject types return is_object() ? m_value.object->count(std::forward(key)) : 0; } /*! @brief check the existence of an element in a JSON object Check whether an element exists in a JSON object with key equivalent to @a key. If the element is not found or the JSON value is not an object, false is returned. @note This method always returns false when executed on a JSON type that is not an object. @param[in] key key value to check its existence. @return true if an element with specified @a key exists. If no such element with such key is found or the JSON value is not an object, false is returned. @complexity Logarithmic in the size of the JSON object. @liveexample{The following code shows an example for `contains()`.,contains} @sa @ref find(KeyT&&) -- returns an iterator to an object element @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer @since version 3.6.0 */ template < typename KeyT, typename std::enable_if < !std::is_same::type, json_pointer>::value, int >::type = 0 > bool contains(KeyT && key) const { return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); } /*! @brief check the existence of an element in a JSON object given a JSON pointer Check whether the given JSON pointer @a ptr can be resolved in the current JSON value. @note This method can be executed on any JSON value type. @param[in] ptr JSON pointer to check its existence. @return true if the JSON pointer can be resolved to a stored value, false otherwise. @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @complexity Logarithmic in the size of the JSON object. @liveexample{The following code shows an example for `contains()`.,contains_json_pointer} @sa @ref contains(KeyT &&) const -- checks the existence of a key @since version 3.7.0 */ bool contains(const json_pointer& ptr) const { return ptr.contains(this); } /// @} /////////////// // iterators // /////////////// /// @name iterators /// @{ /*! @brief returns an iterator to the first element Returns an iterator to the first element. @image html range-begin-end.svg "Illustration from cppreference.com" @return iterator to the first element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. @liveexample{The following code shows an example for `begin()`.,begin} @sa @ref cbegin() -- returns a const iterator to the beginning @sa @ref end() -- returns an iterator to the end @sa @ref cend() -- returns a const iterator to the end @since version 1.0.0 */ iterator begin() noexcept { iterator result(this); result.set_begin(); return result; } /*! @copydoc basic_json::cbegin() */ const_iterator begin() const noexcept { return cbegin(); } /*! @brief returns a const iterator to the first element Returns a const iterator to the first element. @image html range-begin-end.svg "Illustration from cppreference.com" @return const iterator to the first element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).begin()`. @liveexample{The following code shows an example for `cbegin()`.,cbegin} @sa @ref begin() -- returns an iterator to the beginning @sa @ref end() -- returns an iterator to the end @sa @ref cend() -- returns a const iterator to the end @since version 1.0.0 */ const_iterator cbegin() const noexcept { const_iterator result(this); result.set_begin(); return result; } /*! @brief returns an iterator to one past the last element Returns an iterator to one past the last element. @image html range-begin-end.svg "Illustration from cppreference.com" @return iterator one past the last element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. @liveexample{The following code shows an example for `end()`.,end} @sa @ref cend() -- returns a const iterator to the end @sa @ref begin() -- returns an iterator to the beginning @sa @ref cbegin() -- returns a const iterator to the beginning @since version 1.0.0 */ iterator end() noexcept { iterator result(this); result.set_end(); return result; } /*! @copydoc basic_json::cend() */ const_iterator end() const noexcept { return cend(); } /*! @brief returns a const iterator to one past the last element Returns a const iterator to one past the last element. @image html range-begin-end.svg "Illustration from cppreference.com" @return const iterator one past the last element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).end()`. @liveexample{The following code shows an example for `cend()`.,cend} @sa @ref end() -- returns an iterator to the end @sa @ref begin() -- returns an iterator to the beginning @sa @ref cbegin() -- returns a const iterator to the beginning @since version 1.0.0 */ const_iterator cend() const noexcept { const_iterator result(this); result.set_end(); return result; } /*! @brief returns an iterator to the reverse-beginning Returns an iterator to the reverse-beginning; that is, the last element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `reverse_iterator(end())`. @liveexample{The following code shows an example for `rbegin()`.,rbegin} @sa @ref crbegin() -- returns a const reverse iterator to the beginning @sa @ref rend() -- returns a reverse iterator to the end @sa @ref crend() -- returns a const reverse iterator to the end @since version 1.0.0 */ reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } /*! @copydoc basic_json::crbegin() */ const_reverse_iterator rbegin() const noexcept { return crbegin(); } /*! @brief returns an iterator to the reverse-end Returns an iterator to the reverse-end; that is, one before the first element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `reverse_iterator(begin())`. @liveexample{The following code shows an example for `rend()`.,rend} @sa @ref crend() -- returns a const reverse iterator to the end @sa @ref rbegin() -- returns a reverse iterator to the beginning @sa @ref crbegin() -- returns a const reverse iterator to the beginning @since version 1.0.0 */ reverse_iterator rend() noexcept { return reverse_iterator(begin()); } /*! @copydoc basic_json::crend() */ const_reverse_iterator rend() const noexcept { return crend(); } /*! @brief returns a const reverse iterator to the last element Returns a const iterator to the reverse-beginning; that is, the last element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).rbegin()`. @liveexample{The following code shows an example for `crbegin()`.,crbegin} @sa @ref rbegin() -- returns a reverse iterator to the beginning @sa @ref rend() -- returns a reverse iterator to the end @sa @ref crend() -- returns a const reverse iterator to the end @since version 1.0.0 */ const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); } /*! @brief returns a const reverse iterator to one before the first Returns a const reverse iterator to the reverse-end; that is, one before the first element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).rend()`. @liveexample{The following code shows an example for `crend()`.,crend} @sa @ref rend() -- returns a reverse iterator to the end @sa @ref rbegin() -- returns a reverse iterator to the beginning @sa @ref crbegin() -- returns a const reverse iterator to the beginning @since version 1.0.0 */ const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); } public: /*! @brief wrapper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref iterator::value() during range-based for loops. In these loops, a reference to the JSON values is returned, so there is no access to the underlying iterator. For loop without iterator_wrapper: @code{cpp} for (auto it = j_object.begin(); it != j_object.end(); ++it) { std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; } @endcode Range-based for loop without iterator proxy: @code{cpp} for (auto it : j_object) { // "it" is of type json::reference and has no key() member std::cout << "value: " << it << '\n'; } @endcode Range-based for loop with iterator proxy: @code{cpp} for (auto it : json::iterator_wrapper(j_object)) { std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; } @endcode @note When iterating over an array, `key()` will return the index of the element as string (see example). @param[in] ref reference to a JSON value @return iteration proxy object wrapping @a ref with an interface to use in range-based for loops @liveexample{The following code shows how the wrapper is used,iterator_wrapper} @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @note The name of this function is not yet final and may change in the future. @deprecated This stream operator is deprecated and will be removed in future 4.0.0 of the library. Please use @ref items() instead; that is, replace `json::iterator_wrapper(j)` with `j.items()`. */ JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(reference ref) noexcept { return ref.items(); } /*! @copydoc iterator_wrapper(reference) */ JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(const_reference ref) noexcept { return ref.items(); } /*! @brief helper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref iterator::value() during range-based for loops. In these loops, a reference to the JSON values is returned, so there is no access to the underlying iterator. For loop without `items()` function: @code{cpp} for (auto it = j_object.begin(); it != j_object.end(); ++it) { std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; } @endcode Range-based for loop without `items()` function: @code{cpp} for (auto it : j_object) { // "it" is of type json::reference and has no key() member std::cout << "value: " << it << '\n'; } @endcode Range-based for loop with `items()` function: @code{cpp} for (auto& el : j_object.items()) { std::cout << "key: " << el.key() << ", value:" << el.value() << '\n'; } @endcode The `items()` function also allows to use [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding) (C++17): @code{cpp} for (auto& [key, val] : j_object.items()) { std::cout << "key: " << key << ", value:" << val << '\n'; } @endcode @note When iterating over an array, `key()` will return the index of the element as string (see example). For primitive types (e.g., numbers), `key()` returns an empty string. @warning Using `items()` on temporary objects is dangerous. Make sure the object's lifetime exceeds the iteration. See for more information. @return iteration proxy object wrapping @a ref with an interface to use in range-based for loops @liveexample{The following code shows how the function is used.,items} @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 3.1.0, structured bindings support since 3.5.0. */ iteration_proxy items() noexcept { return iteration_proxy(*this); } /*! @copydoc items() */ iteration_proxy items() const noexcept { return iteration_proxy(*this); } /// @} ////////////// // capacity // ////////////// /// @name capacity /// @{ /*! @brief checks whether the container is empty. Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). @return The return value depends on the different types and is defined as follows: Value type | return value ----------- | ------------- null | `true` boolean | `false` string | `false` number | `false` binary | `false` object | result of function `object_t::empty()` array | result of function `array_t::empty()` @liveexample{The following code uses `empty()` to check if a JSON object contains any elements.,empty} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `empty()` functions have constant complexity. @iterators No changes. @exceptionsafety No-throw guarantee: this function never throws exceptions. @note This function does not return whether a string stored as JSON value is empty - it returns whether the JSON container itself is empty which is false in the case of a string. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `begin() == end()`. @sa @ref size() -- returns the number of elements @since version 1.0.0 */ bool empty() const noexcept { switch (m_type) { case value_t::null: { // null values are empty return true; } case value_t::array: { // delegate call to array_t::empty() return m_value.array->empty(); } case value_t::object: { // delegate call to object_t::empty() return m_value.object->empty(); } default: { // all other types are nonempty return false; } } } /*! @brief returns the number of elements Returns the number of elements in a JSON value. @return The return value depends on the different types and is defined as follows: Value type | return value ----------- | ------------- null | `0` boolean | `1` string | `1` number | `1` binary | `1` object | result of function object_t::size() array | result of function array_t::size() @liveexample{The following code calls `size()` on the different value types.,size} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their size() functions have constant complexity. @iterators No changes. @exceptionsafety No-throw guarantee: this function never throws exceptions. @note This function does not return the length of a string stored as JSON value - it returns the number of elements in the JSON value which is 1 in the case of a string. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `std::distance(begin(), end())`. @sa @ref empty() -- checks whether the container is empty @sa @ref max_size() -- returns the maximal number of elements @since version 1.0.0 */ size_type size() const noexcept { switch (m_type) { case value_t::null: { // null values are empty return 0; } case value_t::array: { // delegate call to array_t::size() return m_value.array->size(); } case value_t::object: { // delegate call to object_t::size() return m_value.object->size(); } default: { // all other types have size 1 return 1; } } } /*! @brief returns the maximum possible number of elements Returns the maximum number of elements a JSON value is able to hold due to system or library implementation limitations, i.e. `std::distance(begin(), end())` for the JSON value. @return The return value depends on the different types and is defined as follows: Value type | return value ----------- | ------------- null | `0` (same as `size()`) boolean | `1` (same as `size()`) string | `1` (same as `size()`) number | `1` (same as `size()`) binary | `1` (same as `size()`) object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` @liveexample{The following code calls `max_size()` on the different value types. Note the output is implementation specific.,max_size} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `max_size()` functions have constant complexity. @iterators No changes. @exceptionsafety No-throw guarantee: this function never throws exceptions. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of returning `b.size()` where `b` is the largest possible JSON value. @sa @ref size() -- returns the number of elements @since version 1.0.0 */ size_type max_size() const noexcept { switch (m_type) { case value_t::array: { // delegate call to array_t::max_size() return m_value.array->max_size(); } case value_t::object: { // delegate call to object_t::max_size() return m_value.object->max_size(); } default: { // all other types have max_size() == size() return size(); } } } /// @} /////////////// // modifiers // /////////////// /// @name modifiers /// @{ /*! @brief clears the contents Clears the content of a JSON value and resets it to the default value as if @ref basic_json(value_t) would have been called with the current value type from @ref type(): Value type | initial value ----------- | ------------- null | `null` boolean | `false` string | `""` number | `0` binary | An empty byte vector object | `{}` array | `[]` @post Has the same effect as calling @code {.cpp} *this = basic_json(type()); @endcode @liveexample{The example below shows the effect of `clear()` to different JSON types.,clear} @complexity Linear in the size of the JSON value. @iterators All iterators, pointers and references related to this container are invalidated. @exceptionsafety No-throw guarantee: this function never throws exceptions. @sa @ref basic_json(value_t) -- constructor that creates an object with the same value than calling `clear()` @since version 1.0.0 */ void clear() noexcept { switch (m_type) { case value_t::number_integer: { m_value.number_integer = 0; break; } case value_t::number_unsigned: { m_value.number_unsigned = 0; break; } case value_t::number_float: { m_value.number_float = 0.0; break; } case value_t::boolean: { m_value.boolean = false; break; } case value_t::string: { m_value.string->clear(); break; } case value_t::binary: { m_value.binary->clear(); break; } case value_t::array: { m_value.array->clear(); break; } case value_t::object: { m_value.object->clear(); break; } default: break; } } /*! @brief add an object to an array Appends the given element @a val to the end of the JSON value. If the function is called on a JSON null value, an empty array is created before appending @a val. @param[in] val the value to add to the JSON array @throw type_error.308 when called on a type other than JSON array or null; example: `"cannot use push_back() with number"` @complexity Amortized constant. @liveexample{The example shows how `push_back()` and `+=` can be used to add elements to a JSON array. Note how the `null` value was silently converted to a JSON array.,push_back} @since version 1.0.0 */ void push_back(basic_json&& val) { // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array (move semantics) m_value.array->push_back(std::move(val)); // if val is moved from, basic_json move constructor marks it null so we do not call the destructor } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ reference operator+=(basic_json&& val) { push_back(std::move(val)); return *this; } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ void push_back(const basic_json& val) { // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array m_value.array->push_back(val); } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ reference operator+=(const basic_json& val) { push_back(val); return *this; } /*! @brief add an object to an object Inserts the given element @a val to the JSON object. If the function is called on a JSON null value, an empty object is created before inserting @a val. @param[in] val the value to add to the JSON object @throw type_error.308 when called on a type other than JSON object or null; example: `"cannot use push_back() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @liveexample{The example shows how `push_back()` and `+=` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a JSON object.,push_back__object_t__value} @since version 1.0.0 */ void push_back(const typename object_t::value_type& val) { // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); } // transform null object into an object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // add element to array m_value.object->insert(val); } /*! @brief add an object to an object @copydoc push_back(const typename object_t::value_type&) */ reference operator+=(const typename object_t::value_type& val) { push_back(val); return *this; } /*! @brief add an object to an object This function allows to use `push_back` with an initializer list. In case 1. the current value is an object, 2. the initializer list @a init contains only two elements, and 3. the first element of @a init is a string, @a init is converted into an object element and added using @ref push_back(const typename object_t::value_type&). Otherwise, @a init is converted to a JSON value and added using @ref push_back(basic_json&&). @param[in] init an initializer list @complexity Linear in the size of the initializer list @a init. @note This function is required to resolve an ambiguous overload error, because pairs like `{"key", "value"}` can be both interpreted as `object_t::value_type` or `std::initializer_list`, see https://github.com/nlohmann/json/issues/235 for more information. @liveexample{The example shows how initializer lists are treated as objects when possible.,push_back__initializer_list} */ void push_back(initializer_list_t init) { if (is_object() && init.size() == 2 && (*init.begin())->is_string()) { basic_json&& key = init.begin()->moved_or_copied(); push_back(typename object_t::value_type( std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); } else { push_back(basic_json(init)); } } /*! @brief add an object to an object @copydoc push_back(initializer_list_t) */ reference operator+=(initializer_list_t init) { push_back(init); return *this; } /*! @brief add an object to an array Creates a JSON value from the passed parameters @a args to the end of the JSON value. If the function is called on a JSON null value, an empty array is created before appending the value created from @a args. @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object @return reference to the inserted element @throw type_error.311 when called on a type other than JSON array or null; example: `"cannot use emplace_back() with number"` @complexity Amortized constant. @liveexample{The example shows how `push_back()` can be used to add elements to a JSON array. Note how the `null` value was silently converted to a JSON array.,emplace_back} @since version 2.0.8, returns reference since 3.7.0 */ template reference emplace_back(Args&& ... args) { // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array (perfect forwarding) #ifdef JSON_HAS_CPP_17 return m_value.array->emplace_back(std::forward(args)...); #else m_value.array->emplace_back(std::forward(args)...); return m_value.array->back(); #endif } /*! @brief add an object to an object if key does not exist Inserts a new element into a JSON object constructed in-place with the given @a args if there is no element with the key in the container. If the function is called on a JSON null value, an empty object is created before appending the value created from @a args. @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object @return a pair consisting of an iterator to the inserted element, or the already-existing element if no insertion happened, and a bool denoting whether the insertion took place. @throw type_error.311 when called on a type other than JSON object or null; example: `"cannot use emplace() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @liveexample{The example shows how `emplace()` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a JSON object. Further note how no value is added if there was already one value stored with the same key.,emplace} @since version 2.0.8 */ template std::pair emplace(Args&& ... args) { // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); } // transform null object into an object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // add element to array (perfect forwarding) auto res = m_value.object->emplace(std::forward(args)...); // create result iterator and set iterator to the result of emplace auto it = begin(); it.m_it.object_iterator = res.first; // return pair of iterator and boolean return {it, res.second}; } /// Helper for insertion of an iterator /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template iterator insert_iterator(const_iterator pos, Args&& ... args) { iterator result(this); JSON_ASSERT(m_value.array != nullptr); auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); result.m_it.array_iterator = m_value.array->begin() + insert_pos; // This could have been written as: // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); // but the return value of insert is missing in GCC 4.8, so it is written this way instead. return result; } /*! @brief inserts element Inserts element @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] val element to insert @return iterator pointing to the inserted @a val. @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @complexity Constant plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert} @since version 1.0.0 */ iterator insert(const_iterator pos, const basic_json& val) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator return insert_iterator(pos, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } /*! @brief inserts element @copydoc insert(const_iterator, const basic_json&) */ iterator insert(const_iterator pos, basic_json&& val) { return insert(pos, val); } /*! @brief inserts elements Inserts @a cnt copies of @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] cnt number of copies of @a val to insert @param[in] val element to insert @return iterator pointing to the first element inserted, or @a pos if `cnt==0` @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @complexity Linear in @a cnt plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__count} @since version 1.0.0 */ iterator insert(const_iterator pos, size_type cnt, const basic_json& val) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } /*! @brief inserts elements Inserts elements from range `[first, last)` before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @throw invalid_iterator.211 if @a first or @a last are iterators into container for which insert is called; example: `"passed iterators may not belong to container"` @return iterator pointing to the first element inserted, or @a pos if `first==last` @complexity Linear in `std::distance(first, last)` plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__range} @since version 1.0.0 */ iterator insert(const_iterator pos, const_iterator first, const_iterator last) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); } // insert to array and return iterator return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); } /*! @brief inserts elements Inserts elements from initializer list @a ilist before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] ilist initializer list to insert the values from @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty @complexity Linear in `ilist.size()` plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__ilist} @since version 1.0.0 */ iterator insert(const_iterator pos, initializer_list_t ilist) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator return insert_iterator(pos, ilist.begin(), ilist.end()); } /*! @brief inserts elements Inserts elements from range `[first, last)`. @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert @throw type_error.309 if called on JSON values other than objects; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if iterator @a first or @a last does does not point to an object; example: `"iterators first and last must point to objects"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number of elements to insert. @liveexample{The example shows how `insert()` is used.,insert__range_object} @since version 3.0.0 */ void insert(const_iterator first, const_iterator last) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } /*! @brief updates a JSON object from another object, overwriting existing keys Inserts all values from JSON object @a j and overwrites existing keys. @param[in] j JSON object to read values from @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @complexity O(N*log(size() + N)), where N is the number of elements to insert. @liveexample{The example shows how `update()` is used.,update} @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update @since version 3.0.0 */ void update(const_reference j) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); } if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); } for (auto it = j.cbegin(); it != j.cend(); ++it) { m_value.object->operator[](it.key()) = it.value(); } } /*! @brief updates a JSON object from another object, overwriting existing keys Inserts all values from from range `[first, last)` and overwrites existing keys. @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @throw invalid_iterator.202 if iterator @a first or @a last does does not point to an object; example: `"iterators first and last must point to objects"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @complexity O(N*log(size() + N)), where N is the number of elements to insert. @liveexample{The example shows how `update()` is used__range.,update} @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update @since version 3.0.0 */ void update(const_iterator first, const_iterator last) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() || !last.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } for (auto it = first; it != last; ++it) { m_value.object->operator[](it.key()) = it.value(); } } /*! @brief exchanges the values Exchanges the contents of the JSON value with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other JSON value to exchange the contents with @complexity Constant. @liveexample{The example below shows how JSON values can be swapped with `swap()`.,swap__reference} @since version 1.0.0 */ void swap(reference other) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { std::swap(m_type, other.m_type); std::swap(m_value, other.m_value); assert_invariant(); } /*! @brief exchanges the values Exchanges the contents of the JSON value from @a left with those of @a right. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. implemented as a friend function callable via ADL. @param[in,out] left JSON value to exchange the contents with @param[in,out] right JSON value to exchange the contents with @complexity Constant. @liveexample{The example below shows how JSON values can be swapped with `swap()`.,swap__reference} @since version 1.0.0 */ friend void swap(reference left, reference right) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { left.swap(right); } /*! @brief exchanges the values Exchanges the contents of a JSON array with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other array to exchange the contents with @throw type_error.310 when JSON value is not an array; example: `"cannot use swap() with string"` @complexity Constant. @liveexample{The example below shows how arrays can be swapped with `swap()`.,swap__array_t} @since version 1.0.0 */ void swap(array_t& other) { // swap only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { std::swap(*(m_value.array), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /*! @brief exchanges the values Exchanges the contents of a JSON object with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other object to exchange the contents with @throw type_error.310 when JSON value is not an object; example: `"cannot use swap() with string"` @complexity Constant. @liveexample{The example below shows how objects can be swapped with `swap()`.,swap__object_t} @since version 1.0.0 */ void swap(object_t& other) { // swap only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { std::swap(*(m_value.object), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /*! @brief exchanges the values Exchanges the contents of a JSON string with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other string to exchange the contents with @throw type_error.310 when JSON value is not a string; example: `"cannot use swap() with boolean"` @complexity Constant. @liveexample{The example below shows how strings can be swapped with `swap()`.,swap__string_t} @since version 1.0.0 */ void swap(string_t& other) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_string())) { std::swap(*(m_value.string), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /*! @brief exchanges the values Exchanges the contents of a JSON string with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other binary to exchange the contents with @throw type_error.310 when JSON value is not a string; example: `"cannot use swap() with boolean"` @complexity Constant. @liveexample{The example below shows how strings can be swapped with `swap()`.,swap__binary_t} @since version 3.8.0 */ void swap(binary_t& other) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_binary())) { std::swap(*(m_value.binary), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /// @copydoc swap(binary_t) void swap(typename binary_t::container_type& other) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_binary())) { std::swap(*(m_value.binary), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /// @} public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// /// @name lexicographical comparison operators /// @{ /*! @brief comparison: equal Compares two JSON values for equality according to the following rules: - Two JSON values are equal if (1) they are from the same type and (2) their stored values are the same according to their respective `operator==`. - Integer and floating-point numbers are automatically converted before comparison. Note that two NaN values are always treated as unequal. - Two JSON null values are equal. @note Floating-point inside JSON values numbers are compared with `json::number_float_t::operator==` which is `double::operator==` by default. To compare floating-point while respecting an epsilon, an alternative [comparison function](https://github.com/mariokonrad/marnav/blob/master/include/marnav/math/floatingpoint.hpp#L34-#L39) could be used, for instance @code {.cpp} template::value, T>::type> inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept { return std::abs(a - b) <= epsilon; } @endcode Or you can self-defined operator equal function like this: @code {.cpp} bool my_equal(const_reference lhs, const_reference rhs) { const auto lhs_type lhs.type(); const auto rhs_type rhs.type(); if (lhs_type == rhs_type) { switch(lhs_type) // self_defined case case value_t::number_float: return std::abs(lhs - rhs) <= std::numeric_limits::epsilon(); // other cases remain the same with the original ... } ... } @endcode @note NaN values never compare equal to themselves or to other NaN values. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether the values @a lhs and @a rhs are equal @exceptionsafety No-throw guarantee: this function never throws exceptions. @complexity Linear. @liveexample{The example demonstrates comparing several JSON types.,operator__equal} @since version 1.0.0 */ friend bool operator==(const_reference lhs, const_reference rhs) noexcept { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_t::array: return *lhs.m_value.array == *rhs.m_value.array; case value_t::object: return *lhs.m_value.object == *rhs.m_value.object; case value_t::null: return true; case value_t::string: return *lhs.m_value.string == *rhs.m_value.string; case value_t::boolean: return lhs.m_value.boolean == rhs.m_value.boolean; case value_t::number_integer: return lhs.m_value.number_integer == rhs.m_value.number_integer; case value_t::number_unsigned: return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; case value_t::number_float: return lhs.m_value.number_float == rhs.m_value.number_float; case value_t::binary: return *lhs.m_value.binary == *rhs.m_value.binary; default: return false; } } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) { return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) { return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); } return false; } /*! @brief comparison: equal @copydoc operator==(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept { return lhs == basic_json(rhs); } /*! @brief comparison: equal @copydoc operator==(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) == rhs; } /*! @brief comparison: not equal Compares two JSON values for inequality by calculating `not (lhs == rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether the values @a lhs and @a rhs are not equal @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__notequal} @since version 1.0.0 */ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { return !(lhs == rhs); } /*! @brief comparison: not equal @copydoc operator!=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept { return lhs != basic_json(rhs); } /*! @brief comparison: not equal @copydoc operator!=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) != rhs; } /*! @brief comparison: less than Compares whether one JSON value @a lhs is less than another JSON value @a rhs according to the following rules: - If @a lhs and @a rhs have the same type, the values are compared using the default `<` operator. - Integer and floating-point numbers are automatically converted before comparison - In case @a lhs and @a rhs have different types, the values are ignored and the order of the types is considered, see @ref operator<(const value_t, const value_t). @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is less than @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__less} @since version 1.0.0 */ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_t::array: // note parentheses are necessary, see // https://github.com/nlohmann/json/issues/1530 return (*lhs.m_value.array) < (*rhs.m_value.array); case value_t::object: return (*lhs.m_value.object) < (*rhs.m_value.object); case value_t::null: return false; case value_t::string: return (*lhs.m_value.string) < (*rhs.m_value.string); case value_t::boolean: return (lhs.m_value.boolean) < (rhs.m_value.boolean); case value_t::number_integer: return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); case value_t::number_unsigned: return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); case value_t::number_float: return (lhs.m_value.number_float) < (rhs.m_value.number_float); case value_t::binary: return (*lhs.m_value.binary) < (*rhs.m_value.binary); default: return false; } } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) { return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) { return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; } // We only reach this line if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. return operator<(lhs_type, rhs_type); } /*! @brief comparison: less than @copydoc operator<(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept { return lhs < basic_json(rhs); } /*! @brief comparison: less than @copydoc operator<(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) < rhs; } /*! @brief comparison: less than or equal Compares whether one JSON value @a lhs is less than or equal to another JSON value by calculating `not (rhs < lhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is less than or equal to @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__greater} @since version 1.0.0 */ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { return !(rhs < lhs); } /*! @brief comparison: less than or equal @copydoc operator<=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept { return lhs <= basic_json(rhs); } /*! @brief comparison: less than or equal @copydoc operator<=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) <= rhs; } /*! @brief comparison: greater than Compares whether one JSON value @a lhs is greater than another JSON value by calculating `not (lhs <= rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is greater than to @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__lessequal} @since version 1.0.0 */ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { return !(lhs <= rhs); } /*! @brief comparison: greater than @copydoc operator>(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept { return lhs > basic_json(rhs); } /*! @brief comparison: greater than @copydoc operator>(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) > rhs; } /*! @brief comparison: greater than or equal Compares whether one JSON value @a lhs is greater than or equal to another JSON value by calculating `not (lhs < rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is greater than or equal to @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__greaterequal} @since version 1.0.0 */ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { return !(lhs < rhs); } /*! @brief comparison: greater than or equal @copydoc operator>=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept { return lhs >= basic_json(rhs); } /*! @brief comparison: greater than or equal @copydoc operator>=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) >= rhs; } /// @} /////////////////// // serialization // /////////////////// /// @name serialization /// @{ /*! @brief serialize to stream Serialize the given JSON value @a j to the output stream @a o. The JSON value will be serialized using the @ref dump member function. - The indentation of the output can be controlled with the member variable `width` of the output stream @a o. For instance, using the manipulator `std::setw(4)` on @a o sets the indentation level to `4` and the serialization result is the same as calling `dump(4)`. - The indentation character can be controlled with the member variable `fill` of the output stream @a o. For instance, the manipulator `std::setfill('\\t')` sets indentation to use a tab character rather than the default space character. @param[in,out] o stream to serialize to @param[in] j JSON value to serialize @return the stream @a o @throw type_error.316 if a string stored inside the JSON value is not UTF-8 encoded @complexity Linear. @liveexample{The example below shows the serialization with different parameters to `width` to adjust the indentation level.,operator_serialize} @since version 1.0.0; indentation character added in version 3.0.0 */ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { // read width member and use it as indentation parameter if nonzero const bool pretty_print = o.width() > 0; const auto indentation = pretty_print ? o.width() : 0; // reset width to 0 for subsequent calls to this stream o.width(0); // do the actual serialization serializer s(detail::output_adapter(o), o.fill()); s.dump(j, pretty_print, false, static_cast(indentation)); return o; } /*! @brief serialize to stream @deprecated This stream operator is deprecated and will be removed in future 4.0.0 of the library. Please use @ref operator<<(std::ostream&, const basic_json&) instead; that is, replace calls like `j >> o;` with `o << j;`. @since version 1.0.0; deprecated since version 3.0.0 */ JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) friend std::ostream& operator>>(const basic_json& j, std::ostream& o) { return o << j; } /// @} ///////////////////// // deserialization // ///////////////////// /// @name deserialization /// @{ /*! @brief deserialize from a compatible input @tparam InputType A compatible input, for instance - an std::istream object - a FILE pointer - a C-style array of characters - a pointer to a null-terminated string of single byte characters - an object obj for which begin(obj) and end(obj) produces a valid pair of iterators. @param[in] i input to read from @param[in] cb a parser callback function of type @ref parser_callback_t which is used to control the deserialization by filtering unwanted values (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.101 if a parse error occurs; example: `""unexpected end of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb or reading from the input @a i has a super-linear complexity. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below demonstrates the `parse()` function reading from an array.,parse__array__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__string__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__istream__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function reading from a contiguous container.,parse__contiguouscontainer__parser_callback_t} @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to ignore comments. */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } /*! @brief deserialize from a pair of character iterators The value_type of the iterator must be a integral type with size of 1, 2 or 4 bytes, which will be interpreted respectively as UTF-8, UTF-16 and UTF-32. @param[in] first iterator to start of character range @param[in] last iterator to end of character range @param[in] cb a parser callback function of type @ref parser_callback_t which is used to control the deserialization by filtering unwanted values (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.101 if a parse error occurs; example: `""unexpected end of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } /*! @brief check if the input is valid JSON Unlike the @ref parse(InputType&&, const parser_callback_t,const bool) function, this function neither throws an exception in case of invalid JSON input (i.e., a parse error) nor creates diagnostic information. @tparam InputType A compatible input, for instance - an std::istream object - a FILE pointer - a C-style array of characters - a pointer to a null-terminated string of single byte characters - an object obj for which begin(obj) and end(obj) produces a valid pair of iterators. @param[in] i input to read from @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default) @return Whether the input read from @a i is valid JSON. @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below demonstrates the `accept()` function reading from a string.,accept__string} */ template static bool accept(InputType&& i, const bool ignore_comments = false) { return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); } template static bool accept(IteratorType first, IteratorType last, const bool ignore_comments = false) { return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) static bool accept(detail::span_input_adapter&& i, const bool ignore_comments = false) { return parser(i.get(), nullptr, false, ignore_comments).accept(true); } /*! @brief generate SAX events The SAX event lister must follow the interface of @ref json_sax. This function reads from a compatible input. Examples are: - an std::istream object - a FILE pointer - a C-style array of characters - a pointer to a null-terminated string of single byte characters - an object obj for which begin(obj) and end(obj) produces a valid pair of iterators. @param[in] i input to read from @param[in,out] sax SAX event listener @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) @param[in] strict whether the input has to be consumed completely @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default); only applies to the JSON file format. @return return value of the last processed SAX event @throw parse_error.101 if a parse error occurs; example: `""unexpected end of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the SAX consumer @a sax has a super-linear complexity. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below demonstrates the `sax_parse()` function reading from string and processing the events with a user-defined SAX event consumer.,sax_parse} @since version 3.2.0 */ template JSON_HEDLEY_NON_NULL(2) static bool sax_parse(InputType&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } template JSON_HEDLEY_NON_NULL(3) static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } template JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) JSON_HEDLEY_NON_NULL(2) static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = i.get(); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } /*! @brief deserialize from stream @deprecated This stream operator is deprecated and will be removed in version 4.0.0 of the library. Please use @ref operator>>(std::istream&, basic_json&) instead; that is, replace calls like `j << i;` with `i >> j;`. @since version 1.0.0; deprecated since version 3.0.0 */ JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) friend std::istream& operator<<(basic_json& j, std::istream& i) { return operator>>(i, j); } /*! @brief deserialize from stream Deserializes an input stream to a JSON value. @param[in,out] i input stream to read a serialized JSON value from @param[in,out] j JSON value to write the deserialized input to @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below shows how a JSON value is constructed by reading a serialization from a stream.,operator_deserialize} @sa parse(std::istream&, const parser_callback_t) for a variant with a parser callback function to filter values while parsing @since version 1.0.0 */ friend std::istream& operator>>(std::istream& i, basic_json& j) { parser(detail::input_adapter(i)).parse(false, j); return i; } /// @} /////////////////////////// // convenience functions // /////////////////////////// /*! @brief return the type as string Returns the type name as string to be used in error messages - usually to indicate that a function was called on a wrong JSON type. @return a string representation of a the @a m_type member: Value type | return value ----------- | ------------- null | `"null"` boolean | `"boolean"` string | `"string"` number | `"number"` (for all number types) object | `"object"` array | `"array"` binary | `"binary"` discarded | `"discarded"` @exceptionsafety No-throw guarantee: this function never throws exceptions. @complexity Constant. @liveexample{The following code exemplifies `type_name()` for all JSON types.,type_name} @sa @ref type() -- return the type of the JSON value @sa @ref operator value_t() -- return the type of the JSON value (implicit) @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` since 3.0.0 */ JSON_HEDLEY_RETURNS_NON_NULL const char* type_name() const noexcept { { switch (m_type) { case value_t::null: return "null"; case value_t::object: return "object"; case value_t::array: return "array"; case value_t::string: return "string"; case value_t::boolean: return "boolean"; case value_t::binary: return "binary"; case value_t::discarded: return "discarded"; default: return "number"; } } } private: ////////////////////// // member variables // ////////////////////// /// the type of the current element value_t m_type = value_t::null; /// the value of the current element json_value m_value = {}; ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// /// @name binary serialization/deserialization support /// @{ public: /*! @brief create a CBOR serialization of a given JSON value Serializes a given JSON value @a j to a byte vector using the CBOR (Concise Binary Object Representation) serialization format. CBOR is a binary serialization format which aims to be more compact than JSON itself, yet more efficient to parse. The library uses the following mapping from JSON values types to CBOR types according to the CBOR specification (RFC 7049): JSON value type | value/range | CBOR type | first byte --------------- | ------------------------------------------ | ---------------------------------- | --------------- null | `null` | Null | 0xF6 boolean | `true` | True | 0xF5 boolean | `false` | False | 0xF4 number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 number_integer | -24..-1 | Negative integer | 0x20..0x37 number_integer | 0..23 | Integer | 0x00..0x17 number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B number_unsigned | 0..23 | Integer | 0x00..0x17 number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B number_float | *any value representable by a float* | Single-Precision Float | 0xFA number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB string | *length*: 0..23 | UTF-8 string | 0x60..0x77 string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B array | *size*: 0..23 | array | 0x80..0x97 array | *size*: 23..255 | array (1 byte follow) | 0x98 array | *size*: 256..65535 | array (2 bytes follow) | 0x99 array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B object | *size*: 0..23 | map | 0xA0..0xB7 object | *size*: 23..255 | map (1 byte follow) | 0xB8 object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB binary | *size*: 0..23 | byte string | 0x40..0x57 binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. @note If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the @ref dump() function which serializes NaN or Infinity to `null`. @note The following CBOR types are not used in the conversion: - UTF-8 strings terminated by "break" (0x7F) - arrays terminated by "break" (0x9F) - maps terminated by "break" (0xBF) - byte strings terminated by "break" (0x5F) - date/time (0xC0..0xC1) - bignum (0xC2..0xC3) - decimal fraction (0xC4) - bigfloat (0xC5) - expected conversions (0xD5..0xD7) - simple values (0xE0..0xF3, 0xF8) - undefined (0xF7) - half-precision floats (0xF9) - break (0xFF) @param[in] j JSON value to serialize @return CBOR serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in CBOR format.,to_cbor} @sa http://cbor.io @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the analogous deserialization @sa @ref to_msgpack(const basic_json&) for the related MessagePack format @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the related UBJSON format @since version 2.0.9; compact representation of floating-point numbers since version 3.8.0 */ static std::vector to_cbor(const basic_json& j) { std::vector result; to_cbor(j, result); return result; } static void to_cbor(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_cbor(j); } static void to_cbor(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_cbor(j); } /*! @brief create a MessagePack serialization of a given JSON value Serializes a given JSON value @a j to a byte vector using the MessagePack serialization format. MessagePack is a binary serialization format which aims to be more compact than JSON itself, yet more efficient to parse. The library uses the following mapping from JSON values types to MessagePack types according to the MessagePack specification: JSON value type | value/range | MessagePack type | first byte --------------- | --------------------------------- | ---------------- | ---------- null | `null` | nil | 0xC0 boolean | `true` | true | 0xC3 boolean | `false` | false | 0xC2 number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 number_integer | -2147483648..-32769 | int32 | 0xD2 number_integer | -32768..-129 | int16 | 0xD1 number_integer | -128..-33 | int8 | 0xD0 number_integer | -32..-1 | negative fixint | 0xE0..0xFF number_integer | 0..127 | positive fixint | 0x00..0x7F number_integer | 128..255 | uint 8 | 0xCC number_integer | 256..65535 | uint 16 | 0xCD number_integer | 65536..4294967295 | uint 32 | 0xCE number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF number_unsigned | 0..127 | positive fixint | 0x00..0x7F number_unsigned | 128..255 | uint 8 | 0xCC number_unsigned | 256..65535 | uint 16 | 0xCD number_unsigned | 65536..4294967295 | uint 32 | 0xCE number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF number_float | *any value representable by a float* | float 32 | 0xCA number_float | *any value NOT representable by a float* | float 64 | 0xCB string | *length*: 0..31 | fixstr | 0xA0..0xBF string | *length*: 32..255 | str 8 | 0xD9 string | *length*: 256..65535 | str 16 | 0xDA string | *length*: 65536..4294967295 | str 32 | 0xDB array | *size*: 0..15 | fixarray | 0x90..0x9F array | *size*: 16..65535 | array 16 | 0xDC array | *size*: 65536..4294967295 | array 32 | 0xDD object | *size*: 0..15 | fix map | 0x80..0x8F object | *size*: 16..65535 | map 16 | 0xDE object | *size*: 65536..4294967295 | map 32 | 0xDF binary | *size*: 0..255 | bin 8 | 0xC4 binary | *size*: 256..65535 | bin 16 | 0xC5 binary | *size*: 65536..4294967295 | bin 32 | 0xC6 @note The mapping is **complete** in the sense that any JSON value type can be converted to a MessagePack value. @note The following values can **not** be converted to a MessagePack value: - strings with more than 4294967295 bytes - byte strings with more than 4294967295 bytes - arrays with more than 4294967295 elements - objects with more than 4294967295 elements @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. @note If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the @ref dump() function which serializes NaN or Infinity to `null`. @param[in] j JSON value to serialize @return MessagePack serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in MessagePack format.,to_msgpack} @sa http://msgpack.org @sa @ref from_msgpack for the analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the related UBJSON format @since version 2.0.9 */ static std::vector to_msgpack(const basic_json& j) { std::vector result; to_msgpack(j, result); return result; } static void to_msgpack(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_msgpack(j); } static void to_msgpack(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_msgpack(j); } /*! @brief create a UBJSON serialization of a given JSON value Serializes a given JSON value @a j to a byte vector using the UBJSON (Universal Binary JSON) serialization format. UBJSON aims to be more compact than JSON itself, yet more efficient to parse. The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification: JSON value type | value/range | UBJSON type | marker --------------- | --------------------------------- | ----------- | ------ null | `null` | null | `Z` boolean | `true` | true | `T` boolean | `false` | false | `F` number_integer | -9223372036854775808..-2147483649 | int64 | `L` number_integer | -2147483648..-32769 | int32 | `l` number_integer | -32768..-129 | int16 | `I` number_integer | -128..127 | int8 | `i` number_integer | 128..255 | uint8 | `U` number_integer | 256..32767 | int16 | `I` number_integer | 32768..2147483647 | int32 | `l` number_integer | 2147483648..9223372036854775807 | int64 | `L` number_unsigned | 0..127 | int8 | `i` number_unsigned | 128..255 | uint8 | `U` number_unsigned | 256..32767 | int16 | `I` number_unsigned | 32768..2147483647 | int32 | `l` number_unsigned | 2147483648..9223372036854775807 | int64 | `L` number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` number_float | *any value* | float64 | `D` string | *with shortest length indicator* | string | `S` array | *see notes on optimized format* | array | `[` object | *see notes on optimized format* | map | `{` @note The mapping is **complete** in the sense that any JSON value type can be converted to a UBJSON value. @note The following values can **not** be converted to a UBJSON value: - strings with more than 9223372036854775807 bytes (theoretical) @note The following markers are not used in the conversion: - `Z`: no-op values are not created. - `C`: single-byte strings are serialized with `S` markers. @note Any UBJSON output created @ref to_ubjson can be successfully parsed by @ref from_ubjson. @note If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the @ref dump() function which serializes NaN or Infinity to `null`. @note The optimized formats for containers are supported: Parameter @a use_size adds size information to the beginning of a container and removes the closing marker. Parameter @a use_type further checks whether all elements of a container have the same type and adds the type marker to the beginning of the container. The @a use_type parameter must only be used together with @a use_size = true. Note that @a use_size = true alone may result in larger representations - the benefit of this parameter is that the receiving side is immediately informed on the number of elements of the container. @note If the JSON data contains the binary type, the value stored is a list of integers, as suggested by the UBJSON documentation. In particular, this means that serialization and the deserialization of a JSON containing binary values into UBJSON and back will result in a different JSON object. @param[in] j JSON value to serialize @param[in] use_size whether to add size annotations to container types @param[in] use_type whether to add type annotations to container types (must be combined with @a use_size = true) @return UBJSON serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in UBJSON format.,to_ubjson} @sa http://ubjson.org @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format @sa @ref to_msgpack(const basic_json&) for the related MessagePack format @since version 3.1.0 */ static std::vector to_ubjson(const basic_json& j, const bool use_size = false, const bool use_type = false) { std::vector result; to_ubjson(j, result, use_size, use_type); return result; } static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { binary_writer(o).write_ubjson(j, use_size, use_type); } static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { binary_writer(o).write_ubjson(j, use_size, use_type); } /*! @brief Serializes the given JSON object `j` to BSON and returns a vector containing the corresponding BSON-representation. BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a so-called document). The library uses the following mapping from JSON values types to BSON types: JSON value type | value/range | BSON type | marker --------------- | --------------------------------- | ----------- | ------ null | `null` | null | 0x0A boolean | `true`, `false` | boolean | 0x08 number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 number_integer | -2147483648..2147483647 | int32 | 0x10 number_integer | 2147483648..9223372036854775807 | int64 | 0x12 number_unsigned | 0..2147483647 | int32 | 0x10 number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 number_unsigned | 9223372036854775808..18446744073709551615| -- | -- number_float | *any value* | double | 0x01 string | *any value* | string | 0x02 array | *any value* | document | 0x04 object | *any value* | document | 0x03 binary | *any value* | binary | 0x05 @warning The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON. Also, integers larger than 9223372036854775807 cannot be serialized to BSON, and the keys may not contain U+0000, since they are serialized a zero-terminated c-strings. @throw out_of_range.407 if `j.is_number_unsigned() && j.get() > 9223372036854775807` @throw out_of_range.409 if a key in `j` contains a NULL (U+0000) @throw type_error.317 if `!j.is_object()` @pre The input `j` is required to be an object: `j.is_object() == true`. @note Any BSON output created via @ref to_bson can be successfully parsed by @ref from_bson. @param[in] j JSON value to serialize @return BSON serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in BSON format.,to_bson} @sa http://bsonspec.org/spec.html @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the analogous deserialization @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the related UBJSON format @sa @ref to_cbor(const basic_json&) for the related CBOR format @sa @ref to_msgpack(const basic_json&) for the related MessagePack format */ static std::vector to_bson(const basic_json& j) { std::vector result; to_bson(j, result); return result; } /*! @brief Serializes the given JSON object `j` to BSON and forwards the corresponding BSON-representation to the given output_adapter `o`. @param j The JSON object to convert to BSON. @param o The output adapter that receives the binary BSON representation. @pre The input `j` shall be an object: `j.is_object() == true` @sa @ref to_bson(const basic_json&) */ static void to_bson(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_bson(j); } /*! @copydoc to_bson(const basic_json&, detail::output_adapter) */ static void to_bson(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_bson(j); } /*! @brief create a JSON value from an input in CBOR format Deserializes a given input @a i to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. The library maps CBOR types to JSON value types as follows: CBOR type | JSON value type | first byte ---------------------- | --------------- | ---------- Integer | number_unsigned | 0x00..0x17 Unsigned integer | number_unsigned | 0x18 Unsigned integer | number_unsigned | 0x19 Unsigned integer | number_unsigned | 0x1A Unsigned integer | number_unsigned | 0x1B Negative integer | number_integer | 0x20..0x37 Negative integer | number_integer | 0x38 Negative integer | number_integer | 0x39 Negative integer | number_integer | 0x3A Negative integer | number_integer | 0x3B Byte string | binary | 0x40..0x57 Byte string | binary | 0x58 Byte string | binary | 0x59 Byte string | binary | 0x5A Byte string | binary | 0x5B UTF-8 string | string | 0x60..0x77 UTF-8 string | string | 0x78 UTF-8 string | string | 0x79 UTF-8 string | string | 0x7A UTF-8 string | string | 0x7B UTF-8 string | string | 0x7F array | array | 0x80..0x97 array | array | 0x98 array | array | 0x99 array | array | 0x9A array | array | 0x9B array | array | 0x9F map | object | 0xA0..0xB7 map | object | 0xB8 map | object | 0xB9 map | object | 0xBA map | object | 0xBB map | object | 0xBF False | `false` | 0xF4 True | `true` | 0xF5 Null | `null` | 0xF6 Half-Precision Float | number_float | 0xF9 Single-Precision Float | number_float | 0xFA Double-Precision Float | number_float | 0xFB @warning The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors (parse_error.112): - date/time (0xC0..0xC1) - bignum (0xC2..0xC3) - decimal fraction (0xC4) - bigfloat (0xC5) - expected conversions (0xD5..0xD7) - simple values (0xE0..0xF3, 0xF8) - undefined (0xF7) @warning CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected (parse_error.113). @note Any CBOR output created @ref to_cbor can be successfully parsed by @ref from_cbor. @param[in] i an input in CBOR format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @param[in] tag_handler how to treat CBOR tags (optional, error by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.110 if the given input ends prematurely or the end of file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from CBOR were used in the given input @a v or if the input is not valid CBOR @throw parse_error.113 if a string was expected as map key, but not found @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in CBOR format to a JSON value.,from_cbor} @sa http://cbor.io @sa @ref to_cbor(const basic_json&) for the analogous serialization @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the related MessagePack format @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the related UBJSON format @since version 2.0.9; parameter @a start_index since 2.1.1; changed to consume input adapters, removed start_index parameter, and added @a strict parameter since 3.0.0; added @a allow_exceptions parameter since 3.2.0; added @a tag_handler parameter since 3.9.0. */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_cbor(InputType&& i, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_cbor(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) static basic_json from_cbor(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) static basic_json from_cbor(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } /*! @brief create a JSON value from an input in MessagePack format Deserializes a given input @a i to a JSON value using the MessagePack serialization format. The library maps MessagePack types to JSON value types as follows: MessagePack type | JSON value type | first byte ---------------- | --------------- | ---------- positive fixint | number_unsigned | 0x00..0x7F fixmap | object | 0x80..0x8F fixarray | array | 0x90..0x9F fixstr | string | 0xA0..0xBF nil | `null` | 0xC0 false | `false` | 0xC2 true | `true` | 0xC3 float 32 | number_float | 0xCA float 64 | number_float | 0xCB uint 8 | number_unsigned | 0xCC uint 16 | number_unsigned | 0xCD uint 32 | number_unsigned | 0xCE uint 64 | number_unsigned | 0xCF int 8 | number_integer | 0xD0 int 16 | number_integer | 0xD1 int 32 | number_integer | 0xD2 int 64 | number_integer | 0xD3 str 8 | string | 0xD9 str 16 | string | 0xDA str 32 | string | 0xDB array 16 | array | 0xDC array 32 | array | 0xDD map 16 | object | 0xDE map 32 | object | 0xDF bin 8 | binary | 0xC4 bin 16 | binary | 0xC5 bin 32 | binary | 0xC6 ext 8 | binary | 0xC7 ext 16 | binary | 0xC8 ext 32 | binary | 0xC9 fixext 1 | binary | 0xD4 fixext 2 | binary | 0xD5 fixext 4 | binary | 0xD6 fixext 8 | binary | 0xD7 fixext 16 | binary | 0xD8 negative fixint | number_integer | 0xE0-0xFF @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. @param[in] i an input in MessagePack format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.110 if the given input ends prematurely or the end of file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from MessagePack were used in the given input @a i or if the input is not valid MessagePack @throw parse_error.113 if a string was expected as map key, but not found @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in MessagePack format to a JSON value.,from_msgpack} @sa http://msgpack.org @sa @ref to_msgpack(const basic_json&) for the analogous serialization @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the related CBOR format @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the related UBJSON format @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for the related BSON format @since version 2.0.9; parameter @a start_index since 2.1.1; changed to consume input adapters, removed start_index parameter, and added @a strict parameter since 3.0.0; added @a allow_exceptions parameter since 3.2.0 */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_msgpack(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @brief create a JSON value from an input in UBJSON format Deserializes a given input @a i to a JSON value using the UBJSON (Universal Binary JSON) serialization format. The library maps UBJSON types to JSON value types as follows: UBJSON type | JSON value type | marker ----------- | --------------------------------------- | ------ no-op | *no value, next value is read* | `N` null | `null` | `Z` false | `false` | `F` true | `true` | `T` float32 | number_float | `d` float64 | number_float | `D` uint8 | number_unsigned | `U` int8 | number_integer | `i` int16 | number_integer | `I` int32 | number_integer | `l` int64 | number_integer | `L` high-precision number | number_integer, number_unsigned, or number_float - depends on number string | 'H' string | string | `S` char | string | `C` array | array (optimized values are supported) | `[` object | object (optimized values are supported) | `{` @note The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value. @param[in] i an input in UBJSON format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.110 if the given input ends prematurely or the end of file was not reached when @a strict was set to true @throw parse_error.112 if a parse error occurs @throw parse_error.113 if a string could not be parsed successfully @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in UBJSON format to a JSON value.,from_ubjson} @sa http://ubjson.org @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the analogous serialization @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the related CBOR format @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the related MessagePack format @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for the related BSON format @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0 */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_ubjson(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @brief Create a JSON value from an input in BSON format Deserializes a given input @a i to a JSON value using the BSON (Binary JSON) serialization format. The library maps BSON record types to JSON value types as follows: BSON type | BSON marker byte | JSON value type --------------- | ---------------- | --------------------------- double | 0x01 | number_float string | 0x02 | string document | 0x03 | object array | 0x04 | array binary | 0x05 | still unsupported undefined | 0x06 | still unsupported ObjectId | 0x07 | still unsupported boolean | 0x08 | boolean UTC Date-Time | 0x09 | still unsupported null | 0x0A | null Regular Expr. | 0x0B | still unsupported DB Pointer | 0x0C | still unsupported JavaScript Code | 0x0D | still unsupported Symbol | 0x0E | still unsupported JavaScript Code | 0x0F | still unsupported int32 | 0x10 | number_integer Timestamp | 0x11 | still unsupported 128-bit decimal float | 0x13 | still unsupported Max Key | 0x7F | still unsupported Min Key | 0xFF | still unsupported @warning The mapping is **incomplete**. The unsupported mappings are indicated in the table above. @param[in] i an input in BSON format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.114 if an unsupported BSON record type is encountered @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in BSON format to a JSON value.,from_bson} @sa http://bsonspec.org/spec.html @sa @ref to_bson(const basic_json&) for the analogous serialization @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the related CBOR format @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the related MessagePack format @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the related UBJSON format */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_bson(detail::input_adapter&&, const bool, const bool) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_bson(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @} ////////////////////////// // JSON Pointer support // ////////////////////////// /// @name JSON Pointer functions /// @{ /*! @brief access specified element via JSON Pointer Uses a JSON pointer to retrieve a reference to the respective JSON value. No bound checking is performed. Similar to @ref operator[](const typename object_t::key_type&), `null` values are created in arrays and objects if necessary. In particular: - If the JSON pointer points to an object key that does not exist, it is created an filled with a `null` value before a reference to it is returned. - If the JSON pointer points to an array index that does not exist, it is created an filled with a `null` value before a reference to it is returned. All indices between the current maximum and the given index are also filled with `null`. - The special value `-` is treated as a synonym for the index past the end. @param[in] ptr a JSON pointer @return reference to the element pointed to by @a ptr @complexity Constant. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved @liveexample{The behavior is shown in the example.,operatorjson_pointer} @since version 2.0.0 */ reference operator[](const json_pointer& ptr) { return ptr.get_unchecked(this); } /*! @brief access specified element via JSON Pointer Uses a JSON pointer to retrieve a reference to the respective JSON value. No bound checking is performed. The function does not change the JSON value; no `null` values are created. In particular, the special value `-` yields an exception. @param[in] ptr JSON pointer to the desired element @return const reference to the element pointed to by @a ptr @complexity Constant. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} @since version 2.0.0 */ const_reference operator[](const json_pointer& ptr) const { return ptr.get_unchecked(this); } /*! @brief access specified element via JSON Pointer Returns a reference to the element at with specified JSON pointer @a ptr, with bounds checking. @param[in] ptr JSON pointer to the desired element @return reference to the element pointed to by @a ptr @throw parse_error.106 if an array index in the passed JSON pointer @a ptr begins with '0'. See example below. @throw parse_error.109 if an array index in the passed JSON pointer @a ptr is not a number. See example below. @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr is out of range. See example below. @throw out_of_range.402 if the array index '-' is used in the passed JSON pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. @throw out_of_range.403 if the JSON pointer describes a key of an object which cannot be found. See example below. @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 2.0.0 @liveexample{The behavior is shown in the example.,at_json_pointer} */ reference at(const json_pointer& ptr) { return ptr.get_checked(this); } /*! @brief access specified element via JSON Pointer Returns a const reference to the element at with specified JSON pointer @a ptr, with bounds checking. @param[in] ptr JSON pointer to the desired element @return reference to the element pointed to by @a ptr @throw parse_error.106 if an array index in the passed JSON pointer @a ptr begins with '0'. See example below. @throw parse_error.109 if an array index in the passed JSON pointer @a ptr is not a number. See example below. @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr is out of range. See example below. @throw out_of_range.402 if the array index '-' is used in the passed JSON pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. @throw out_of_range.403 if the JSON pointer describes a key of an object which cannot be found. See example below. @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 2.0.0 @liveexample{The behavior is shown in the example.,at_json_pointer_const} */ const_reference at(const json_pointer& ptr) const { return ptr.get_checked(this); } /*! @brief return flattened JSON value The function creates a JSON object whose keys are JSON pointers (see [RFC 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all primitive. The original JSON value can be restored using the @ref unflatten() function. @return an object that maps JSON pointers to primitive values @note Empty objects and arrays are flattened to `null` and will not be reconstructed correctly by the @ref unflatten() function. @complexity Linear in the size the JSON value. @liveexample{The following code shows how a JSON object is flattened to an object whose keys consist of JSON pointers.,flatten} @sa @ref unflatten() for the reverse function @since version 2.0.0 */ basic_json flatten() const { basic_json result(value_t::object); json_pointer::flatten("", *this, result); return result; } /*! @brief unflatten a previously flattened JSON value The function restores the arbitrary nesting of a JSON value that has been flattened before using the @ref flatten() function. The JSON value must meet certain constraints: 1. The value must be an object. 2. The keys must be JSON pointers (see [RFC 6901](https://tools.ietf.org/html/rfc6901)) 3. The mapped values must be primitive JSON types. @return the original JSON from a flattened version @note Empty objects and arrays are flattened by @ref flatten() to `null` values and can not unflattened to their original type. Apart from this example, for a JSON value `j`, the following is always true: `j == j.flatten().unflatten()`. @complexity Linear in the size the JSON value. @throw type_error.314 if value is not an object @throw type_error.315 if object values are not primitive @liveexample{The following code shows how a flattened JSON object is unflattened into the original nested JSON object.,unflatten} @sa @ref flatten() for the reverse function @since version 2.0.0 */ basic_json unflatten() const { return json_pointer::unflatten(*this); } /// @} ////////////////////////// // JSON Patch functions // ////////////////////////// /// @name JSON Patch functions /// @{ /*! @brief applies a JSON patch [JSON Patch](http://jsonpatch.com) defines a JSON document structure for expressing a sequence of operations to apply to a JSON) document. With this function, a JSON Patch is applied to the current JSON value by executing all operations from the patch. @param[in] json_patch JSON patch document @return patched document @note The application of a patch is atomic: Either all operations succeed and the patched document is returned or an exception is thrown. In any case, the original value is not changed: the patch is applied to a copy of the value. @throw parse_error.104 if the JSON patch does not consist of an array of objects @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory attributes are missing); example: `"operation add must have member path"` @throw out_of_range.401 if an array index is out of range. @throw out_of_range.403 if a JSON pointer inside the patch could not be resolved successfully in the current JSON value; example: `"key baz not found"` @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", "move") @throw other_error.501 if "test" operation was unsuccessful @complexity Linear in the size of the JSON value and the length of the JSON patch. As usually only a fraction of the JSON value is affected by the patch, the complexity can usually be neglected. @liveexample{The following code shows how a JSON patch is applied to a value.,patch} @sa @ref diff -- create a JSON patch by comparing two JSON values @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) @since version 2.0.0 */ basic_json patch(const basic_json& json_patch) const { // make a working copy to apply the patch to basic_json result = *this; // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; const auto get_op = [](const std::string & op) { if (op == "add") { return patch_operations::add; } if (op == "remove") { return patch_operations::remove; } if (op == "replace") { return patch_operations::replace; } if (op == "move") { return patch_operations::move; } if (op == "copy") { return patch_operations::copy; } if (op == "test") { return patch_operations::test; } return patch_operations::invalid; }; // wrapper for "add" operation; add value at ptr const auto operation_add = [&result](json_pointer & ptr, basic_json val) { // adding to the root of the target document means replacing it if (ptr.empty()) { result = val; return; } // make sure the top element of the pointer exists json_pointer top_pointer = ptr.top(); if (top_pointer != ptr) { result.at(top_pointer); } // get reference to parent of JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result[ptr]; switch (parent.m_type) { case value_t::null: case value_t::object: { // use operator[] to add value parent[last_path] = val; break; } case value_t::array: { if (last_path == "-") { // special case: append to back parent.push_back(val); } else { const auto idx = json_pointer::array_index(last_path); if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } // default case: insert add offset parent.insert(parent.begin() + static_cast(idx), val); } break; } // if there exists a parent it cannot be primitive default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } }; // wrapper for "remove" operation; remove value at ptr const auto operation_remove = [&result](json_pointer & ptr) { // get reference to parent of JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result.at(ptr); // remove child if (parent.is_object()) { // perform range check auto it = parent.find(last_path); if (JSON_HEDLEY_LIKELY(it != parent.end())) { parent.erase(it); } else { JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); } } else if (parent.is_array()) { // note erase performs range check parent.erase(json_pointer::array_index(last_path)); } }; // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); } // iterate and apply the operations for (const auto& val : json_patch) { // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); // context-sensitive error message const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; // check if desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) { JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); } // check if result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); } // no error: return value return it->second; }; // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); } // collect mandatory members const auto op = get_value("op", "op", true).template get(); const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) { case patch_operations::add: { operation_add(ptr, get_value("add", "value", false)); break; } case patch_operations::remove: { operation_remove(ptr); break; } case patch_operations::replace: { // the "path" location must exist - use at() result.at(ptr) = get_value("replace", "value", false); break; } case patch_operations::move: { const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() basic_json v = result.at(from_ptr); // The move operation is functionally identical to a // "remove" operation on the "from" location, followed // immediately by an "add" operation at the target // location with the value that was just removed. operation_remove(from_ptr); operation_add(ptr, v); break; } case patch_operations::copy: { const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() basic_json v = result.at(from_ptr); // The copy is functionally identical to an "add" // operation at the target location using the value // specified in the "from" member. operation_add(ptr, v); break; } case patch_operations::test: { bool success = false; JSON_TRY { // check if "value" matches the one at "path" // the "path" location must exist - use at() success = (result.at(ptr) == get_value("test", "value", false)); } JSON_INTERNAL_CATCH (out_of_range&) { // ignore out of range errors: success remains false } // throw an exception if test fails if (JSON_HEDLEY_UNLIKELY(!success)) { JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); } break; } default: { // op must be "add", "remove", "replace", "move", "copy", or // "test" JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); } } } return result; } /*! @brief creates a diff as a JSON patch Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can be changed into the value @a target by calling @ref patch function. @invariant For two JSON values @a source and @a target, the following code yields always `true`: @code {.cpp} source.patch(diff(source, target)) == target; @endcode @note Currently, only `remove`, `add`, and `replace` operations are generated. @param[in] source JSON value to compare from @param[in] target JSON value to compare against @param[in] path helper value to create JSON pointers @return a JSON patch to convert the @a source to @a target @complexity Linear in the lengths of @a source and @a target. @liveexample{The following code shows how a JSON patch is created as a diff for two JSON values.,diff} @sa @ref patch -- apply a JSON patch @sa @ref merge_patch -- apply a JSON Merge Patch @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) @since version 2.0.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, const std::string& path = "") { // the patch basic_json result(value_t::array); // if the values are the same, return empty patch if (source == target) { return result; } if (source.type() != target.type()) { // different types: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} }); return result; } switch (source.type()) { case value_t::array: { // first pass: traverse common elements std::size_t i = 0; while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } // i now reached the end of at least one array // in a second pass, traverse the remaining elements // remove my remaining elements const auto end_index = static_cast(result.size()); while (i < source.size()) { // add operations in reverse order to avoid invalid // indices result.insert(result.begin() + end_index, object( { {"op", "remove"}, {"path", path + "/" + std::to_string(i)} })); ++i; } // add other remaining elements while (i < target.size()) { result.push_back( { {"op", "add"}, {"path", path + "/-"}, {"value", target[i]} }); ++i; } break; } case value_t::object: { // first pass: traverse this object's elements for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch const auto key = json_pointer::escape(it.key()); if (target.find(it.key()) != target.end()) { // recursive call to compare object values at key it auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); } else { // found a key that is not in o -> remove it result.push_back(object( { {"op", "remove"}, {"path", path + "/" + key} })); } } // second pass: traverse other object's elements for (auto it = target.cbegin(); it != target.cend(); ++it) { if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it const auto key = json_pointer::escape(it.key()); result.push_back( { {"op", "add"}, {"path", path + "/" + key}, {"value", it.value()} }); } } break; } default: { // both primitive type: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} }); break; } } return result; } /// @} //////////////////////////////// // JSON Merge Patch functions // //////////////////////////////// /// @name JSON Merge Patch functions /// @{ /*! @brief applies a JSON Merge Patch The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of modifications to a target resource's content. This function applies a merge patch to the current JSON value. The function implements the following algorithm from Section 2 of [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): ``` define MergePatch(Target, Patch): if Patch is an Object: if Target is not an Object: Target = {} // Ignore the contents and set it to an empty Object for each Name/Value pair in Patch: if Value is null: if Name exists in Target: remove the Name/Value pair from Target else: Target[Name] = MergePatch(Target[Name], Value) return Target else: return Patch ``` Thereby, `Target` is the current object; that is, the patch is applied to the current value. @param[in] apply_patch the patch to apply @complexity Linear in the lengths of @a patch. @liveexample{The following code shows how a JSON Merge Patch is applied to a JSON document.,merge_patch} @sa @ref patch -- apply a JSON patch @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) @since version 3.0.0 */ void merge_patch(const basic_json& apply_patch) { if (apply_patch.is_object()) { if (!is_object()) { *this = object(); } for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) { if (it.value().is_null()) { erase(it.key()); } else { operator[](it.key()).merge_patch(it.value()); } } } else { *this = apply_patch; } } /// @} }; /*! @brief user-defined to_string function for JSON values This function implements a user-defined to_string for JSON objects. @param[in] j a JSON object @return a std::string object */ NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) { return j.dump(); } } // namespace nlohmann /////////////////////// // nonmember support // /////////////////////// // specialization of std::swap, and std::hash namespace std { /// hash value for JSON objects template<> struct hash { /*! @brief return a hash value for a JSON object @since version 1.0.0 */ std::size_t operator()(const nlohmann::json& j) const { return nlohmann::detail::hash(j); } }; /// specialization for std::less /// @note: do not remove the space after '<', /// see https://github.com/nlohmann/json/pull/679 template<> struct less<::nlohmann::detail::value_t> { /*! @brief compare two value_t enum values @since version 3.0.0 */ bool operator()(nlohmann::detail::value_t lhs, nlohmann::detail::value_t rhs) const noexcept { return nlohmann::detail::operator<(lhs, rhs); } }; // C++20 prohibit function specialization in the std namespace. #ifndef JSON_HAS_CPP_20 /*! @brief exchanges the values of two JSON objects @since version 1.0.0 */ template<> inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( is_nothrow_move_constructible::value&& is_nothrow_move_assignable::value ) { j1.swap(j2); } #endif } // namespace std /*! @brief user-defined string literal for JSON values This operator implements a user-defined string literal for JSON objects. It can be used by adding `"_json"` to a string literal and returns a JSON object if no parse error occurred. @param[in] s a string representation of a JSON object @param[in] n the length of string @a s @return a JSON object @since version 1.0.0 */ JSON_HEDLEY_NON_NULL(1) inline nlohmann::json operator "" _json(const char* s, std::size_t n) { return nlohmann::json::parse(s, s + n); } /*! @brief user-defined string literal for JSON pointer This operator implements a user-defined string literal for JSON Pointers. It can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer object if no parse error occurred. @param[in] s a string representation of a JSON Pointer @param[in] n the length of string @a s @return a JSON pointer object @since version 2.0.0 */ JSON_HEDLEY_NON_NULL(1) inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) { return nlohmann::json::json_pointer(std::string(s, n)); } // #include // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #endif #if defined(__clang__) #pragma GCC diagnostic pop #endif // clean up #undef JSON_ASSERT #undef JSON_INTERNAL_CATCH #undef JSON_CATCH #undef JSON_THROW #undef JSON_TRY #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT // #include #undef JSON_HEDLEY_ALWAYS_INLINE #undef JSON_HEDLEY_ARM_VERSION #undef JSON_HEDLEY_ARM_VERSION_CHECK #undef JSON_HEDLEY_ARRAY_PARAM #undef JSON_HEDLEY_ASSUME #undef JSON_HEDLEY_BEGIN_C_DECLS #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_BUILTIN #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_EXTENSION #undef JSON_HEDLEY_CLANG_HAS_FEATURE #undef JSON_HEDLEY_CLANG_HAS_WARNING #undef JSON_HEDLEY_COMPCERT_VERSION #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK #undef JSON_HEDLEY_CONCAT #undef JSON_HEDLEY_CONCAT3 #undef JSON_HEDLEY_CONCAT3_EX #undef JSON_HEDLEY_CONCAT_EX #undef JSON_HEDLEY_CONST #undef JSON_HEDLEY_CONSTEXPR #undef JSON_HEDLEY_CONST_CAST #undef JSON_HEDLEY_CPP_CAST #undef JSON_HEDLEY_CRAY_VERSION #undef JSON_HEDLEY_CRAY_VERSION_CHECK #undef JSON_HEDLEY_C_DECL #undef JSON_HEDLEY_DEPRECATED #undef JSON_HEDLEY_DEPRECATED_FOR #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #undef JSON_HEDLEY_DIAGNOSTIC_POP #undef JSON_HEDLEY_DIAGNOSTIC_PUSH #undef JSON_HEDLEY_DMC_VERSION #undef JSON_HEDLEY_DMC_VERSION_CHECK #undef JSON_HEDLEY_EMPTY_BASES #undef JSON_HEDLEY_EMSCRIPTEN_VERSION #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK #undef JSON_HEDLEY_END_C_DECLS #undef JSON_HEDLEY_FLAGS #undef JSON_HEDLEY_FLAGS_CAST #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_BUILTIN #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_EXTENSION #undef JSON_HEDLEY_GCC_HAS_FEATURE #undef JSON_HEDLEY_GCC_HAS_WARNING #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #undef JSON_HEDLEY_GCC_VERSION #undef JSON_HEDLEY_GCC_VERSION_CHECK #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_BUILTIN #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_EXTENSION #undef JSON_HEDLEY_GNUC_HAS_FEATURE #undef JSON_HEDLEY_GNUC_HAS_WARNING #undef JSON_HEDLEY_GNUC_VERSION #undef JSON_HEDLEY_GNUC_VERSION_CHECK #undef JSON_HEDLEY_HAS_ATTRIBUTE #undef JSON_HEDLEY_HAS_BUILTIN #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_HAS_EXTENSION #undef JSON_HEDLEY_HAS_FEATURE #undef JSON_HEDLEY_HAS_WARNING #undef JSON_HEDLEY_IAR_VERSION #undef JSON_HEDLEY_IAR_VERSION_CHECK #undef JSON_HEDLEY_IBM_VERSION #undef JSON_HEDLEY_IBM_VERSION_CHECK #undef JSON_HEDLEY_IMPORT #undef JSON_HEDLEY_INLINE #undef JSON_HEDLEY_INTEL_VERSION #undef JSON_HEDLEY_INTEL_VERSION_CHECK #undef JSON_HEDLEY_IS_CONSTANT #undef JSON_HEDLEY_IS_CONSTEXPR_ #undef JSON_HEDLEY_LIKELY #undef JSON_HEDLEY_MALLOC #undef JSON_HEDLEY_MESSAGE #undef JSON_HEDLEY_MSVC_VERSION #undef JSON_HEDLEY_MSVC_VERSION_CHECK #undef JSON_HEDLEY_NEVER_INLINE #undef JSON_HEDLEY_NON_NULL #undef JSON_HEDLEY_NO_ESCAPE #undef JSON_HEDLEY_NO_RETURN #undef JSON_HEDLEY_NO_THROW #undef JSON_HEDLEY_NULL #undef JSON_HEDLEY_PELLES_VERSION #undef JSON_HEDLEY_PELLES_VERSION_CHECK #undef JSON_HEDLEY_PGI_VERSION #undef JSON_HEDLEY_PGI_VERSION_CHECK #undef JSON_HEDLEY_PREDICT #undef JSON_HEDLEY_PRINTF_FORMAT #undef JSON_HEDLEY_PRIVATE #undef JSON_HEDLEY_PUBLIC #undef JSON_HEDLEY_PURE #undef JSON_HEDLEY_REINTERPRET_CAST #undef JSON_HEDLEY_REQUIRE #undef JSON_HEDLEY_REQUIRE_CONSTEXPR #undef JSON_HEDLEY_REQUIRE_MSG #undef JSON_HEDLEY_RESTRICT #undef JSON_HEDLEY_RETURNS_NON_NULL #undef JSON_HEDLEY_SENTINEL #undef JSON_HEDLEY_STATIC_ASSERT #undef JSON_HEDLEY_STATIC_CAST #undef JSON_HEDLEY_STRINGIFY #undef JSON_HEDLEY_STRINGIFY_EX #undef JSON_HEDLEY_SUNPRO_VERSION #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK #undef JSON_HEDLEY_TINYC_VERSION #undef JSON_HEDLEY_TINYC_VERSION_CHECK #undef JSON_HEDLEY_TI_ARMCL_VERSION #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK #undef JSON_HEDLEY_TI_CL2000_VERSION #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK #undef JSON_HEDLEY_TI_CL430_VERSION #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK #undef JSON_HEDLEY_TI_CL6X_VERSION #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK #undef JSON_HEDLEY_TI_CL7X_VERSION #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK #undef JSON_HEDLEY_TI_CLPRU_VERSION #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK #undef JSON_HEDLEY_TI_VERSION #undef JSON_HEDLEY_TI_VERSION_CHECK #undef JSON_HEDLEY_UNAVAILABLE #undef JSON_HEDLEY_UNLIKELY #undef JSON_HEDLEY_UNPREDICTABLE #undef JSON_HEDLEY_UNREACHABLE #undef JSON_HEDLEY_UNREACHABLE_RETURN #undef JSON_HEDLEY_VERSION #undef JSON_HEDLEY_VERSION_DECODE_MAJOR #undef JSON_HEDLEY_VERSION_DECODE_MINOR #undef JSON_HEDLEY_VERSION_DECODE_REVISION #undef JSON_HEDLEY_VERSION_ENCODE #undef JSON_HEDLEY_WARNING #undef JSON_HEDLEY_WARN_UNUSED_RESULT #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #undef JSON_HEDLEY_FALL_THROUGH #endif // INCLUDE_NLOHMANN_JSON_HPP_ proj-9.8.1/include/proj/util.hpp000664 001750 001750 00000063241 15166171715 016544 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef UTIL_HH_INCLUDED #define UTIL_HH_INCLUDED #if !(__cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900)) #error Must have C++11 or newer. #endif // windows.h can conflict with Criterion::STRICT #ifdef STRICT #undef STRICT #endif #include #include #include #include #include #include #ifndef NS_PROJ /** osgeo namespace */ namespace osgeo { /** osgeo.proj namespace */ namespace proj {} } // namespace osgeo #endif //! @cond Doxygen_Suppress #ifndef PROJ_DLL #if defined(_MSC_VER) #ifdef PROJ_MSVC_DLL_EXPORT #define PROJ_DLL __declspec(dllexport) #else #define PROJ_DLL __declspec(dllimport) #endif #elif defined(__GNUC__) #define PROJ_DLL __attribute__((visibility("default"))) #else #define PROJ_DLL #endif #endif #ifndef PROJ_MSVC_DLL #if defined(_MSC_VER) #define PROJ_MSVC_DLL PROJ_DLL #define PROJ_GCC_DLL #define PROJ_INTERNAL #elif defined(__GNUC__) #define PROJ_MSVC_DLL #define PROJ_GCC_DLL PROJ_DLL #if !defined(__MINGW32__) #define PROJ_INTERNAL __attribute__((visibility("hidden"))) #else #define PROJ_INTERNAL #endif #else #define PROJ_MSVC_DLL #define PROJ_GCC_DLL #define PROJ_INTERNAL #endif #define PROJ_FOR_TEST PROJ_DLL #endif #include "nn.hpp" /* To allow customizing the base namespace of PROJ */ #ifdef PROJ_INTERNAL_CPP_NAMESPACE #define NS_PROJ osgeo::internalproj #define NS_PROJ_START \ namespace osgeo { \ namespace internalproj { #define NS_PROJ_END \ } \ } #else #ifndef NS_PROJ #define NS_PROJ osgeo::proj #define NS_PROJ_START \ namespace osgeo { \ namespace proj { #define NS_PROJ_END \ } \ } #endif #endif // Private-implementation (Pimpl) pattern #define PROJ_OPAQUE_PRIVATE_DATA \ private: \ struct PROJ_INTERNAL Private; \ std::unique_ptr d; \ \ protected: \ PROJ_INTERNAL Private *getPrivate() noexcept { return d.get(); } \ PROJ_INTERNAL const Private *getPrivate() const noexcept { \ return d.get(); \ } \ \ private: // To include in the protected/private section of a class definition, // to be able to call make_shared on a protected/private constructor #define INLINED_MAKE_SHARED \ template \ static std::shared_ptr make_shared(Args &&...args) { \ return std::shared_ptr(new T(std::forward(args)...)); \ } \ template \ static util::nn_shared_ptr nn_make_shared(Args &&...args) { \ return util::nn_shared_ptr( \ util::i_promise_i_checked_for_null, \ std::shared_ptr(new T(std::forward(args)...))); \ } // To include in the protected/private section of a class definition, // to be able to call make_unique on a protected/private constructor #define INLINED_MAKE_UNIQUE \ template \ static std::unique_ptr make_unique(Args &&...args) { \ return std::unique_ptr(new T(std::forward(args)...)); \ } #ifdef DOXYGEN_ENABLED #define PROJ_FRIEND(mytype) #define PROJ_FRIEND_OPTIONAL(mytype) #else #define PROJ_FRIEND(mytype) friend class mytype #define PROJ_FRIEND_OPTIONAL(mytype) friend class util::optional #endif #ifndef PROJ_PRIVATE #define PROJ_PRIVATE public #endif #if defined(__GNUC__) #define PROJ_NO_INLINE __attribute__((noinline)) #define PROJ_NO_RETURN __attribute__((noreturn)) // Applies to a function that has no side effect. #define PROJ_PURE_DECL const noexcept __attribute__((pure)) #else #define PROJ_NO_RETURN #define PROJ_NO_INLINE #define PROJ_PURE_DECL const noexcept #endif #define PROJ_PURE_DEFN const noexcept //! @endcond NS_PROJ_START //! @cond Doxygen_Suppress namespace io { class DatabaseContext; using DatabaseContextPtr = std::shared_ptr; } // namespace io //! @endcond /** osgeo.proj.util namespace. * * \brief A set of base types from ISO 19103, \ref GeoAPI and other PROJ * specific classes. */ namespace util { //! @cond Doxygen_Suppress // Import a few classes from nn.hpp to expose them under our ::util namespace // for conveniency. using ::dropbox::oxygen::i_promise_i_checked_for_null; using ::dropbox::oxygen::nn; using ::dropbox::oxygen::nn_dynamic_pointer_cast; using ::dropbox::oxygen::nn_make_shared; // For return statements, to convert from derived type to base type using ::dropbox::oxygen::nn_static_pointer_cast; template using nn_shared_ptr = nn>; // Possible implementation of C++14 std::remove_reference_t // (cf https://en.cppreference.com/w/cpp/types/remove_cv) template using remove_reference_t = typename std::remove_reference::type; // Possible implementation of C++14 std::remove_cv_t // (cf https://en.cppreference.com/w/cpp/types/remove_cv) template using remove_cv_t = typename std::remove_cv::type; // Possible implementation of C++20 std::remove_cvref // (cf https://en.cppreference.com/w/cpp/types/remove_cvref) template struct remove_cvref { typedef remove_cv_t> type; }; #define NN_NO_CHECK(p) \ ::dropbox::oxygen::nn< \ typename ::NS_PROJ::util::remove_cvref::type>( \ ::dropbox::oxygen::i_promise_i_checked_for_null, (p)) //! @endcond // To avoid formatting differences between clang-format 3.8 and 7 #define PROJ_NOEXCEPT noexcept //! @cond Doxygen_Suppress // isOfExactType(*p) checks that the type of *p is exactly MyType template inline bool isOfExactType(const ObjectT &o) { return typeid(TemplateT).hash_code() == typeid(o).hash_code(); } //! @endcond /** \brief Loose transposition of [std::optional] * (https://en.cppreference.com/w/cpp/utility/optional) available from C++17. */ template class optional { public: //! @cond Doxygen_Suppress inline optional() : hasVal_(false) {} inline explicit optional(const T &val) : hasVal_(true), val_(val) {} inline explicit optional(T &&val) : hasVal_(true), val_(std::forward(val)) {} inline optional(const optional &) = default; inline optional(optional &&other) PROJ_NOEXCEPT : hasVal_(other.hasVal_), // cppcheck-suppress functionStatic val_(std::forward(other.val_)) { other.hasVal_ = false; } inline optional &operator=(const T &val) { hasVal_ = true; val_ = val; return *this; } inline optional &operator=(T &&val) noexcept { hasVal_ = true; val_ = std::forward(val); return *this; } inline optional &operator=(const optional &) = default; inline optional &operator=(optional &&other) noexcept { hasVal_ = other.hasVal_; val_ = std::forward(other.val_); other.hasVal_ = false; return *this; } inline T *operator->() { return &val_; } inline T &operator*() { return val_; } //! @endcond /** Returns a pointer to the contained value. */ inline const T *operator->() const { return &val_; } /** Returns a reference to the contained value. */ inline const T &operator*() const { return val_; } /** Return whether the optional has a value */ inline explicit operator bool() const noexcept { return hasVal_; } /** Return whether the optional has a value */ inline bool has_value() const noexcept { return hasVal_; } private: bool hasVal_; T val_{}; }; // --------------------------------------------------------------------------- class BaseObject; /** Shared pointer of BaseObject. */ using BaseObjectPtr = std::shared_ptr; #if 1 /** Non-null shared pointer of BaseObject. */ struct BaseObjectNNPtr : public util::nn { // This trick enables to avoid inlining of the destructor. // This is mostly an alias of the base class. //! @cond Doxygen_Suppress template // cppcheck-suppress noExplicitConstructor BaseObjectNNPtr(const util::nn> &x) : util::nn(x) {} template // cppcheck-suppress noExplicitConstructor BaseObjectNNPtr(util::nn> &&x) noexcept : util::nn(NN_NO_CHECK(std::move(x.as_nullable()))) {} explicit BaseObjectNNPtr(::dropbox::oxygen::i_promise_i_checked_for_null_t, BaseObjectPtr &&arg) noexcept : util::nn(i_promise_i_checked_for_null, std::move(arg)) {} BaseObjectNNPtr(const BaseObjectNNPtr &) = default; BaseObjectNNPtr &operator=(const BaseObjectNNPtr &) = default; PROJ_DLL ~BaseObjectNNPtr(); //! @endcond }; #else using BaseObjectNNPtr = util::nn; #endif /** \brief Class that can be derived from, to emulate Java's Object behavior. */ class PROJ_GCC_DLL BaseObject { public: //! @cond Doxygen_Suppress virtual PROJ_DLL ~BaseObject(); //! @endcond PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL BaseObjectNNPtr shared_from_this() const; //! @endcond protected: PROJ_INTERNAL BaseObject(); PROJ_INTERNAL void assignSelf(const BaseObjectNNPtr &self); PROJ_INTERNAL BaseObject &operator=(BaseObject &&other); private: PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Interface for an object that can be compared to another. */ class PROJ_GCC_DLL IComparable { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~IComparable(); //! @endcond /** \brief Comparison criterion. */ enum class PROJ_MSVC_DLL Criterion { /** All properties are identical. */ STRICT, /** The objects are equivalent for the purpose of coordinate * operations. They can differ by the name of their objects, * identifiers, other metadata. * Parameters may be expressed in different units, provided that the * value is (with some tolerance) the same once expressed in a * common unit. */ EQUIVALENT, /** Same as EQUIVALENT, relaxed with an exception that the axis order * of the base CRS of a DerivedCRS/ProjectedCRS or the axis order of * a GeographicCRS is ignored. Only to be used * with DerivedCRS/ProjectedCRS/GeographicCRS */ EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, }; PROJ_DLL bool isEquivalentTo(const IComparable *other, Criterion criterion = Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL virtual bool _isEquivalentTo( const IComparable *other, Criterion criterion = Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const = 0; //! @endcond }; // --------------------------------------------------------------------------- /** \brief Encapsulate standard datatypes in an object. */ class BoxedValue final : public BaseObject { public: //! @cond Doxygen_Suppress /** Type of data stored in the BoxedValue. */ enum class Type { /** a std::string */ STRING, /** an integer */ INTEGER, /** a boolean */ BOOLEAN }; //! @endcond // cppcheck-suppress noExplicitConstructor PROJ_DLL BoxedValue(const char *stringValueIn); // needed to avoid the bool // constructor to be taken ! // cppcheck-suppress noExplicitConstructor PROJ_DLL BoxedValue(const std::string &stringValueIn); // cppcheck-suppress noExplicitConstructor PROJ_DLL BoxedValue(int integerValueIn); // cppcheck-suppress noExplicitConstructor PROJ_DLL BoxedValue(bool booleanValueIn); PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL BoxedValue(const BoxedValue &other); PROJ_DLL ~BoxedValue() override; PROJ_INTERNAL const Type &type() const; PROJ_INTERNAL const std::string &stringValue() const; PROJ_INTERNAL int integerValue() const; PROJ_INTERNAL bool booleanValue() const; //! @endcond private: PROJ_OPAQUE_PRIVATE_DATA BoxedValue &operator=(const BoxedValue &) = delete; PROJ_INTERNAL BoxedValue(); }; /** Shared pointer of BoxedValue. */ using BoxedValuePtr = std::shared_ptr; /** Non-null shared pointer of BoxedValue. */ using BoxedValueNNPtr = util::nn; // --------------------------------------------------------------------------- class ArrayOfBaseObject; /** Shared pointer of ArrayOfBaseObject. */ using ArrayOfBaseObjectPtr = std::shared_ptr; /** Non-null shared pointer of ArrayOfBaseObject. */ using ArrayOfBaseObjectNNPtr = util::nn; /** \brief Array of BaseObject. */ class ArrayOfBaseObject final : public BaseObject { public: //! @cond Doxygen_Suppress PROJ_DLL ~ArrayOfBaseObject() override; //! @endcond PROJ_DLL void add(const BaseObjectNNPtr &obj); PROJ_DLL static ArrayOfBaseObjectNNPtr create(); PROJ_PRIVATE : //! @cond Doxygen_Suppress std::vector::const_iterator begin() const; std::vector::const_iterator end() const; bool empty() const; //! @endcond protected: ArrayOfBaseObject(); INLINED_MAKE_SHARED private: ArrayOfBaseObject(const ArrayOfBaseObject &other) = delete; ArrayOfBaseObject &operator=(const ArrayOfBaseObject &other) = delete; PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- /** \brief Wrapper of a std::map */ class PropertyMap { public: PROJ_DLL PropertyMap(); //! @cond Doxygen_Suppress PROJ_DLL PropertyMap(const PropertyMap &other); PROJ_DLL ~PropertyMap(); //! @endcond PROJ_DLL PropertyMap &set(const std::string &key, const BaseObjectNNPtr &val); //! @cond Doxygen_Suppress template inline PropertyMap &set(const std::string &key, const nn_shared_ptr &val) { return set( key, BaseObjectNNPtr(i_promise_i_checked_for_null, BaseObjectPtr(val.as_nullable(), val.get()))); } //! @endcond // needed to avoid the bool constructor to be taken ! PROJ_DLL PropertyMap &set(const std::string &key, const char *val); PROJ_DLL PropertyMap &set(const std::string &key, const std::string &val); PROJ_DLL PropertyMap &set(const std::string &key, int val); PROJ_DLL PropertyMap &set(const std::string &key, bool val); PROJ_DLL PropertyMap &set(const std::string &key, const std::vector &array); PROJ_PRIVATE : //! @cond Doxygen_Suppress const BaseObjectNNPtr * get(const std::string &key) const; // throw(InvalidValueTypeException) bool getStringValue(const std::string &key, std::string &outVal) const; bool getStringValue(const std::string &key, optional &outVal) const; void unset(const std::string &key); static PropertyMap createAndSetName(const char *name); static PropertyMap createAndSetName(const std::string &name); //! @endcond private: PropertyMap &operator=(const PropertyMap &) = delete; PROJ_OPAQUE_PRIVATE_DATA }; // --------------------------------------------------------------------------- class LocalName; /** Shared pointer of LocalName. */ using LocalNamePtr = std::shared_ptr; /** Non-null shared pointer of LocalName. */ using LocalNameNNPtr = util::nn; class NameSpace; /** Shared pointer of NameSpace. */ using NameSpacePtr = std::shared_ptr; /** Non-null shared pointer of NameSpace. */ using NameSpaceNNPtr = util::nn; class GenericName; /** Shared pointer of GenericName. */ using GenericNamePtr = std::shared_ptr; /** Non-null shared pointer of GenericName. */ using GenericNameNNPtr = util::nn; // --------------------------------------------------------------------------- /** \brief A sequence of identifiers rooted within the context of a namespace. * * \remark Simplified version of [GenericName] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/util/GenericName.html) * from \ref GeoAPI */ class GenericName : public BaseObject { public: //! @cond Doxygen_Suppress PROJ_DLL virtual ~GenericName() override; //! @endcond /** \brief Return the scope of the object, possibly a global one. */ PROJ_DLL virtual const NameSpacePtr scope() const = 0; /** \brief Return the LocalName as a string. */ PROJ_DLL virtual std::string toString() const = 0; /** \brief Return a fully qualified name corresponding to the local name. * * The namespace of the resulting name is a global one. */ PROJ_DLL virtual GenericNameNNPtr toFullyQualifiedName() const = 0; protected: GenericName(); GenericName(const GenericName &other); private: PROJ_OPAQUE_PRIVATE_DATA GenericName &operator=(const GenericName &other) = delete; }; // --------------------------------------------------------------------------- /** \brief A domain in which names given by strings are defined. * * \remark Simplified version of [NameSpace] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/util/NameSpace.html) * from \ref GeoAPI */ class NameSpace { public: //! @cond Doxygen_Suppress PROJ_DLL ~NameSpace(); //! @endcond PROJ_DLL bool isGlobal() const; PROJ_DLL const GenericNamePtr &name() const; protected: PROJ_FRIEND(NameFactory); PROJ_FRIEND(LocalName); explicit NameSpace(const GenericNamePtr &name); NameSpace(const NameSpace &other); NameSpaceNNPtr getGlobalFromThis() const; const std::string &separator() const; static const NameSpaceNNPtr GLOBAL; INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA NameSpace &operator=(const NameSpace &other) = delete; static NameSpaceNNPtr createGLOBAL(); }; // --------------------------------------------------------------------------- /** \brief Identifier within a NameSpace for a local object. * * Local names are names which are directly accessible to and maintained by a * NameSpace within which they are local, indicated by the scope. * * \remark Simplified version of [LocalName] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/util/LocalName.html) * from \ref GeoAPI */ class LocalName : public GenericName { public: //! @cond Doxygen_Suppress PROJ_DLL ~LocalName() override; //! @endcond PROJ_DLL const NameSpacePtr scope() const override; PROJ_DLL std::string toString() const override; PROJ_DLL GenericNameNNPtr toFullyQualifiedName() const override; protected: PROJ_FRIEND(NameFactory); PROJ_FRIEND(NameSpace); explicit LocalName(const std::string &nameIn); LocalName(const LocalName &other); LocalName(const NameSpacePtr &ns, const std::string &name); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA LocalName &operator=(const LocalName &other) = delete; }; // --------------------------------------------------------------------------- /** \brief Factory for generic names. * * \remark Simplified version of [NameFactory] * (http://www.geoapi.org/3.0/javadoc/org.opengis.geoapi/org/opengis/util/NameFactory.html) * from \ref GeoAPI */ class NameFactory { public: PROJ_DLL static NameSpaceNNPtr createNameSpace(const GenericNameNNPtr &name, const PropertyMap &properties); PROJ_DLL static LocalNameNNPtr createLocalName(const NameSpacePtr &scope, const std::string &name); PROJ_DLL static GenericNameNNPtr createGenericName(const NameSpacePtr &scope, const std::vector &parsedNames); }; // --------------------------------------------------------------------------- /** \brief Abstract class to define an enumeration of values. */ class CodeList { public: //! @cond Doxygen_Suppress PROJ_DLL ~CodeList(); //! @endcond /** Return the CodeList item as a string. */ // cppcheck-suppress functionStatic inline const std::string &toString() PROJ_PURE_DECL { return name_; } /** Return the CodeList item as a string. */ inline operator std::string() PROJ_PURE_DECL { return toString(); } //! @cond Doxygen_Suppress inline bool operator==(const CodeList &other) PROJ_PURE_DECL { return name_ == other.name_; } inline bool operator!=(const CodeList &other) PROJ_PURE_DECL { return name_ != other.name_; } //! @endcond protected: explicit CodeList(const std::string &nameIn) : name_(nameIn) {} CodeList(const CodeList &) = default; CodeList &operator=(const CodeList &other); private: std::string name_{}; }; // --------------------------------------------------------------------------- /** \brief Root exception class. */ class PROJ_GCC_DLL Exception : public std::exception { std::string msg_; public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit Exception(const char *message); PROJ_INTERNAL explicit Exception(const std::string &message); PROJ_DLL Exception(const Exception &other); PROJ_DLL ~Exception() override; //! @endcond PROJ_DLL virtual const char *what() const noexcept override; }; // --------------------------------------------------------------------------- /** \brief Exception thrown when an invalid value type is set as the value of * a key of a PropertyMap. */ class PROJ_GCC_DLL InvalidValueTypeException : public Exception { public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit InvalidValueTypeException(const char *message); PROJ_INTERNAL explicit InvalidValueTypeException( const std::string &message); PROJ_DLL InvalidValueTypeException(const InvalidValueTypeException &other); PROJ_DLL ~InvalidValueTypeException() override; //! @endcond }; // --------------------------------------------------------------------------- /** \brief Exception Thrown to indicate that the requested operation is not * supported. */ class PROJ_GCC_DLL UnsupportedOperationException : public Exception { public: //! @cond Doxygen_Suppress PROJ_INTERNAL explicit UnsupportedOperationException(const char *message); PROJ_INTERNAL explicit UnsupportedOperationException( const std::string &message); PROJ_DLL UnsupportedOperationException(const UnsupportedOperationException &other); PROJ_DLL ~UnsupportedOperationException() override; //! @endcond }; } // namespace util NS_PROJ_END #endif // UTIL_HH_INCLUDED proj-9.8.1/include/proj/coordinates.hpp000664 001750 001750 00000010131 15166171715 020067 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2023, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef COORDINATES_HH_INCLUDED #define COORDINATES_HH_INCLUDED #include #include "common.hpp" #include "crs.hpp" #include "io.hpp" #include "util.hpp" NS_PROJ_START /** osgeo.proj.coordinates namespace \brief Coordinates package */ namespace coordinates { class CoordinateMetadata; /** Shared pointer of CoordinateMetadata */ using CoordinateMetadataPtr = std::shared_ptr; /** Non-null shared pointer of CoordinateMetadata */ using CoordinateMetadataNNPtr = util::nn; // --------------------------------------------------------------------------- /** \brief Associates a CRS with a coordinate epoch. * * \remark Implements CoordinateMetadata from \ref ISO_19111_2019 * \since 9.2 */ class PROJ_GCC_DLL CoordinateMetadata : public util::BaseObject, public io::IWKTExportable, public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~CoordinateMetadata() override; //! @endcond PROJ_DLL const crs::CRSNNPtr &crs() PROJ_PURE_DECL; PROJ_DLL const util::optional & coordinateEpoch() PROJ_PURE_DECL; PROJ_DLL double coordinateEpochAsDecimalYear() PROJ_PURE_DECL; PROJ_DLL static CoordinateMetadataNNPtr create(const crs::CRSNNPtr &crsIn); PROJ_DLL static CoordinateMetadataNNPtr create(const crs::CRSNNPtr &crsIn, double coordinateEpochAsDecimalYear); PROJ_DLL static CoordinateMetadataNNPtr create(const crs::CRSNNPtr &crsIn, double coordinateEpochAsDecimalYear, const io::DatabaseContextPtr &dbContext); PROJ_DLL CoordinateMetadataNNPtr promoteTo3D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const; PROJ_PRIVATE : //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) //! @endcond protected: PROJ_INTERNAL explicit CoordinateMetadata(const crs::CRSNNPtr &crsIn); PROJ_INTERNAL CoordinateMetadata(const crs::CRSNNPtr &crsIn, double coordinateEpochAsDecimalYear); INLINED_MAKE_SHARED private: PROJ_OPAQUE_PRIVATE_DATA CoordinateMetadata &operator=(const CoordinateMetadata &other) = delete; }; // --------------------------------------------------------------------------- } // namespace coordinates NS_PROJ_END #endif // COORDINATES_HH_INCLUDED proj-9.8.1/ChangeLog000664 001750 001750 00000175101 15166171715 014232 0ustar00eveneven000000 000000 2015-12-13: jswhit * : Add inverse hammer transform (pull request #329). 2015-09-10 sisyphus * : Rename PVALUE in pj_param.cto prevent Windows variable name clash 2015-09-10 Bas Couwenberg * : Don't include files in proj dist, also included in proj-datumgrids dist #301 2015-09-10 Ture Pålsson * : PTHREAD_MUTEX_RECURSIVE detection issue on FreeBSD #303 2015-09-10 Martin Raspaud * : Don't return values when doing inverse projections outside of the mollweide map #304 2015-09-08 Charles Karney * : Update Geodesic library from GeographicLib * Improve accuracy of calculations by evaluating trigonometric functions more carefully and replacing the series for the reduced length with one with a smaller truncation error. * The allowed ranges for longitudes and azimuths is now unlimited; it used to be [-540d, 540d). * Enforce the restriction of latitude to [-90d, 90d] by returning NaNs if the latitude is outside this range. * The inverse calculation sets s12 to zero for coincident points at pole (instead of returning a tiny quantity). * This commit also includes a work-around for an inaccurate value for pi/180 in dmstor.c (see the definitions of DEG_IN and DEG_OUT in geod_interface.c). 2015-09-06 Even Rouault * re-add proj_def.dat which was missing from source distribution https://github.com/OSGeo/proj.4/issues/274 https://github.com/OSGeo/proj.4/issues/296 and https://github.com/OSGeo/proj.4/issues/297 2015-07-27 Even Rouault * : Remove setlocale() use in pj_init_ctx(), and replace uses of atof() & strtod() by their locale safe variants pj_atof() and pj_strtod(). Proj versions from now advertise #define PJ_LOCALE_SAFE 1 in proj_api.h and export pj_atof() & pj_strtod() (#226) 2015-06-01 Charles Karney Make PJ_aeqd.c use geodesics for inverse and forward projection modification so that the geodesic structure is not global https://github.com/OSGeo/proj.4/pull/281 2015-05-25 Elliott Sales de Andrade * : Fix inverse stereo projection on an ellipsoid https://github.com/OSGeo/proj.4/pull/277 2015-02-21 Even Rouault * nad/epsg: regenerate nad/epsg with GDAL r28536 to avoid precision loss in TOWGS84 parameters, e.g. on Amersfoort / RD EPSG:4289 (#260) 2015-02-21 Howard Butler * cmake/Proj4Version.cmake src\lib_proj.cmake: Align SOVERSION CMake configuration with autotools #263 2015-02-21 Howard Butler * src/lib_proj.cmake: define PROJ_LIB as part of the compilation defines #261 2015-02-21 Even Rouault * src/lib_proj.cmake nad/CMakeLists.txt: cmake build: install nad.lst, geodesic.h. But not emess.h and pj_list.h (from Charles Karney) 2015-02-21 Even Rouault * src/pj_gridinfo.c: remove trailing / from preprocessor line (from Charles Karney) 2015-02-21 Even Rouault * src/PJ_aitoff.c: define M_PI and M_PI_2 (needed for Windows) (from Charles Karney) 2015-02-21 Even Rouault * src/lib_proj.cmake: remove space from variable name to suppress policy warning. (from Charles Karney) 2015-02-21 Even Rouault * src/bin_nad2bin.cmake: backward test for nad2nad warning. bad directory specified for emess (from Charles Karney) 2015-02-21 Even Rouault * man/man1/proj.1 man/man1/cs2cs.1 man/man1/geod.1 man/man3/pj_init.3: fix various issues (#259) 2015-02-21 Even Rouault * nad/Makefile.am: compatibility with proj-datumgrids-1.6RC1 (patch by sebastic, #249) 2015-02-21 Even Rouault * nad/Makefile.am: fix install target when no .lla files are in nad subdirectory. 2015-02-21 Even Rouault * cmake/Makefile.am man/Makefile.am: install missing CMake support files for dist-all target 2015-02-20 Howard Butler * CMakeLists.txt cmake/Proj4Mac.cmake man/CMakeLists.txt src/bin_cs2cs.cmake src/lib_proj.cmake: Adapt Charles Karney CMake patches for smoother build #258 2015-02-20 Howard Butler * config.guess config.sub: #257 update very old config.guess and config.sub 2015-02-17 Howard Butler * src/PJ_aitoff.c: #250 Inverse solution for Winkel Tripel from Drazan Tutic 2015-02-17 Howard Butler * CMakeLists.txt cmake/policies.cmake src/lib_proj.cmake: #256 CMake tweaks to shut off some noisy policies, fix installation of proj_config header, and shut off Framework building by default on OSX 2015-02-17 Howard Butler * src/lib_proj.cmake CMakeLists: Fix #248 healpix compilation typo 2015-02-16 Howard Butler * src/pj_init.c: Fix #237 warning about initialization ordering due to setlocale 2015-02-16 Howard Butler * nad/Makefile.am nad/Makefile.in and others in nad/: Fix #247 to allow out-of-tree autoconf builds 2014-09-17 Even Rouault * src/pj_datums.c, src/pj_ellps.c: Add clrk80ign ellipsoid and use it in carthage datum def (#245) 2014-09-16 Frank Warmerdam * Generate 4.9.0 RC2. * nad/epsg: updated with Pulkova 1942(58) reverted, and vertical coordinate system names coming through properly. * src/pj_gridinfo.c, pj_apply_vgridshift.c, pj_apply_gridshift.c: Fix problems with NTv2 files with improper parent structure (#177). 2014-09-13 Frank Warmerdam * Generate 4.9.0 release. 2014-19-13 Howard Butler * CMake: Implement CMake build system for proj.4 #243 2014-09-13 Frank Warmerdam * src/pj_datums.c: fix spelling of clrk80 in carthage datum def (#245) 2014-19-13 Howard Butler * pj_gridinfo.c: Don't crash when nad_ctable_init doesn't return a ctx. #231 2014-09-13 Frank Warmerdam * nad/epsg: Updated to EPSG 8.5 2014-19-08 Even Rouault * src/pj_gridinfo.c: Make pj_gridinfo_load() thread-safe (#228) 2014-19-08 Howard Butler * src/pj_init.c: apply fix specified in #229 -- pj_init_plus() with init and other parms fails in 4.9.0beta 2014-06-06 Even Rouault * src/PJ_omerc.c: mark no_off/no_uoff as used for round-tripping pj_init_ctxt()/pj_get_def() (#239) 2014-05-14 Even Rouault * nad/epsg: Upgraded to EPSG 8.4 2013-12-09 Frank Warmerdam * src/PJ_geos.c, testvarious: reverse sense of sweep flag. (#146) 2013-12-05 Frank Warmerdam * src/PJ_qsc.c: Add QSC projection (#179) 2013-10-27 Frank Warmerdam * Prepare 4.9.0beta2 release. 2013-10-21 Frank Warmerdam * src/PJ_omerc.c: Change handling of values nearly 90degrees away from the origin (#114). * src/pj_datums.c: Switch to using EPSG:1618 COORD_OP_CODE to transform hermannskogel to WGS84 (same as used to ETRS89) (#207). 2013-10-20 Frank Warmerdam * src/Makefile.am: Given up on restricting access to projects.h, and move it back into the list of files installed normally. * configure.in: Add C_WFLAGS support, in particular use -Wdeclaration-after-statement to warn about code that won't work with MSVC (#224). * src/cs2cs.c: Support -I when there is no +to projection. * src/PJ_ob_tran.c: Propagate ctx into sub-projection (#225). 2013-10-03 Frank Warmerdam * src/PJ_healpix.c: Fix healpix build on msvc. (#223) 2013-10-01 Frank Warmerdam * nad/epsg: Upgraded to EPSG 8.2. 2013-07-21 Frank Warmerdam * src/proj_etmerc.c: Fix two errors in the n**5 coefficients. Add sixth order coefficients. Fix rounding problems (#222) 2013-07-19 Frank Warmerdam * src/PJ_healpix.c: major update for polar scaling and parms (#219) 2013-07-12 Frank Warmerdam * src/geodesic.{c,h}: allow polygon vertices to be specified incrementally for geodesic area (#221). 2013-07-08 Frank Warmerdam * src/PJ_calcofi.c: Add Cal Coop Ocean Fish Invest Lines/Stations projections (calcofi) (#135) 2013-07-02 Frank Warmerdam * nad/testvarious, nad/tv_out.dist: add new robinson forward test, and backwards tests. * src/PJ_robin.c: Applied new coefficients supplied by Ed Campbell pretty much on faith. (#113) 2013-06-26 Frank Warmerdam * src/pj_open_lib.c: change filename and access args to const. 2013-06-25 Frank Warmerdam * nad/Makefile.am: add CH to pkgdata_DATA (#145). * src/PJ_putp3.c: Fix putp3p usage line to remove "no inv" (#167). * src/PJ_aitoff.c: note that aitoff and wintri projections have no inverse (#160, #168). * src/PJ_urm5.c: Note that there is no inverse, fix spelling of alpha in the short description (#169). * src/pj_ell_set.c: Ensure thread context is forwarded. * src/multistresstest.c: add windows support (#199) * src/pj_ctx.c: avoid race condition on setting of default_context_initialized. (#199) * config.guess, config.sub: updated to newer versions (#208). * src/proj.def: add pj_get_spheroid_defn to proj.def. (#214) * install-sh: upgrade to support multiple files (#217) 2013-06-24 Frank Warmerdam * src/projects.h, src/proj_api.h: move pj_open_lib() into proj_api.h. * src/projects.h: Do not define PROJ_LIB to "PROJ_LIB". 2013-06-22 Frank Warmerdam * Preparing for 4.9.0 beta release. * src/geodesic.{c,h}: sync relative to GeographicLib 1.31. (#216) * src/pj_fileapi.c, etc: Implement a virtual file api accessible through the context for init file and grid shift file access. * src/mk_cheby.c: reformat, add braces to avoid warnings. 2013-06-19 Frank Warmerdam * src/PJ_healpix.c: correct various warnings about unused variables. 2013-06-19 Frank Warmerdam * src/pj_mutex.c, configure.in: Ensure that the core mutex lock is created in recursive mode. Results in -lpthread being required. 2013-06-18 Frank Warmerdam * src/PJ_healpix.c: rename sign() to pj_sign() and make it static. No need to risk conflicting with sign() in other packages like gctpc. 2012-12-17 Frank Warmerdam * src/pj_init.c: Recover gracefully if setlocale() returns NULL like on Android (#204). 2012-12-07 Frank Warmerdam * src/geod*: Replace geodesic implementation with one from Charles Karney, add public interface (#197). 2012-12-05 Frank Warmerdam * nad/epsg: Upgraded to EPSG 8.0. 2012-07-24 Frank Warmerdam * src/pj_gridcatalog.c, src/makefile.vc: fixes for visual studio builds (#182). 2012-07-04 Frank Warmerdam * src/PJ_healpix.c: Incorporate a polar fix (#176). 2012-06-27 Frank Warmerdam * src/nad2bin.c: Fix byte swapping for bigendian platforms (#157) 2012-06-07 Frank Warmerdam * src/pj_init.c: avoid leaking vgridlist_geoid (#175). 2012-06-01 Martin Desruisseaux * Removed the old JNI wrappers from trunk. Those wrappers are still present on the 4.8 branch as deprecated classes. 2012-05-31 Martin Desruisseaux * Replaced usages of NAN C/C++ constant by the java.lang.Double.NaN constant. This was done because not all C/C++ compilers define the NAN constant, and for making sure that the bits pattern is exactly the one expected by Java. 2012-03-25 Frank Warmerdam * src/Makefile.am: Add org_proj4_PJ.h to files to distribute. 2012-03-13 Frank Warmerdam * src/projects.h, src/pj_list.c: avoid using #include directly on a macro expansion - it is unnecessary and makes for problems in my work environment. 2012-03-06 Frank Warmerdam * Preparing 4.8.0 release candidate. * nad/epsg: regenerate with +no_uoff for hotine oblique mercator (#104) * src/PJ_sconics.c: Fix missing P->sig term in pconic forward projection equation (#148). 2012-03-03 Frank Warmerdam * src/PJ_omerc.c: Support +no_uoff and +no_off (#128) * src/PJ_stere.c: Cleanup odd code construct (#147) 2012-02-26 Frank Warmerdam * src/PJ_geos.c, nad/testvarious: Added GEOS +sweep and add GEOS to the test suite (#146) * nad/CH: added swiss datum related definitions from strk (#145) * src/Makefile.am, src/mutltistresstest.c: provide for building multistresstest in the makefile, and slightly improve it. 2012-02-25 Frank Warmerdam * nad/epsg: regenerate with +datum (#122) 2012-02-20 Frank Warmerdam * Prepare 4.8.0 Beta1. * src/PJ_isea.c: Add Icosahedral Snyder Equal Area projection (#111) * src/nad2nad.c: completely removed as part of the ctable2 overhaul. * src/cs2cs.c, src/pj_init.c, src/geod_set.c, src/nad2nad.c, src/geod.c: Use parenthesis around assignments in if statements (#123). * src/nad2bin.c: improve io error checking (#140). * src/PJ_healpix.c: fix windows build issues (#133) 2012-02-15 Frank Warmerdam * src/pj_utils.c: Add pj_get_spheroid_defn() (#142) 2012-02-08 Frank Warmerdam * src/pj_apply_gridshift.c: Ensure that one among many points falling outside the grid areas will not cause the remainder to not be datum shifted in a way that is hard to diagnose. (#45) 2012-02-01 Frank Warmerdam * src/pj_apply_gridshift.c: ensure we try to use grids as long as we are within epsilon of the edge (#141). 2012-01-31 Frank Warmerdam * src/nad2bin.c: fix comparison test for -f flag (#139). 2011-12-22 Frank Warmerdam * src/pj_init.c; Only split arguments on pluses following spaces in pj_init_plus() (#132) 2011-12-14 Frank Warmerdam * src/pj_open_lib.c: make sure we check errno before logging messages (#131). 2011-12-13 Frank Warmerdam * src/PJ_healpix.c, etc: added healpix support contributed by Landcare in New Zealand. 2011-11-22 Frank Warmerdam * src/nad_init.c, src/pj_gridinfo.c, src/nad2bin.c: Implement support for "ctable2" format grid shift files. 2011-11-18 Frank Warmerdam * src/pj_mutex.c, src/pj_apply_vgridshift.c: avoid unused warnings. 2011-11-13 Frank Warmerdam * src/nad2bin.c: Modified to write NTv2 format files. * src/pj_init.c: avoid casting warning with old_locale. 2011-09-28 Frank Warmerdam * nad/epsg: Upgrade to EPSG 7.9. Ideal datum selection rules also changed a bit upstream. 2011-09-01 Martin Desruisseaux * Updated jniwrap/build.xml Ant script and README file. 2011-08-27 Martin Desruisseaux * Fixed some (but not all) memory leaks in org.proj4.Projections JNI bindings * Deprecated org.proj4.Projections JNI bindings * Added org.proj4.PJ JNI bindings in replacement of org.proj4.Projections 2011-08-27 Frank Warmerdam * pj_pr_list.c, pj_sterrno.c: doc typo fixes from Martin. 2011-08-07 Frank Warmerdam * src/pj_datums.c: Updated Potsdam (DHDN) towgs84 parameters to match EPSG 7 parameter list for EPSG:4314 (#115). * src/pj_mutex.c: alter name of core_lock to avoid conflict on AIX (#55) 2011-07-23 * configure.in, Makefile.am, proj.pc.in: Added pkg-config support (#3) 2011-07-05 Frank Warmerdam * src/pj_init.c, src/pj_gridinfo.c: Correct error handling for missing grid shift files and defaults files (#116) 2011-06-09 Frank Warmerdam * src/PJ_robin.c: fix mistaken constant value (#113). * src/pj_init.c: fix for +axis validation (#87) * nad/IGNF: addition/fix of Kerguelen, Amsterdam and St Paul, Terre Adélie, INSPIRE CRSes in IGNF catalogue (#88) 2011-05-31 Frank Warmerdam * src/PJ_igh.c: use project free instead of free() in FREEUP (#112). * src/projects.h: memset PJ structure to zeros after allocation to avoid problems getting everything initialized properly (#112). 2011-05-23 Frank Warmerdam * nad/esri.extra, nad/other.extra: moved 900913 definition from esri.extra to other.extra since it has nothing to do with esri. * nad/epsg: updated to EPSG 7.6. 2011-05-20 Frank Warmerdam * src/PJ_sterea.c: ensure P->en is properly initialized (#109) 2011-05-10 Frank Warmerdam * src/projects.h, src/pj_init.c, src/pj_transform.c: Implement support for vto_meter and vunits vertical units transformation. 2011-05-04 Frank Warmerdam * src/PJ_igh.c: Added goodes interrupted homolosine (#106). 2011-03-28 Frank Warmerdam * src/pj_gridlist.c: avoid possible buffer overflow. https://bugs.meego.com/show_bug.cgi?id=14963 2011-03-23 Frank Warmerdam * src/pj_initcache.c: Fix reversed memcpy that causes a crash on the 16th item put in the initcache. (#100). 2011-02-21 Frank Warmerdam * src/pj_init.c: fix serious bug in locale handling, wasn't copying the old locale so it would sometimes get corrupted. * src/proj_etmerc.c: added extended transverse mercator impl. (#97) * Rerun autogen.sh with the latest versions of automake, autoconf and libtool. 2011-02-10 Frank Warmerdam * src/pj_gridinfo.c: fix debug bounds reported (#95). 2011-02-08 Frank Warmerdam * src/PJ_cea.c: Fix particular CEA case (#94). * src/pj_auth.c: correct precision of constants (#93) * src/pj_init.c, pj_malloc.c, jniproj.c: avoid C++ comments (#92) 2011-01-11 Frank Warmerdam * src/PJ_goode.c: fix propagation of es and ctx to sub-projections. 2010-10-19 Frank Warmerdam * src/proj_api.h, src/projects.h: move pj_clear_initcache() to public api and update to 4.8.0 PJ_VERSION to identify when this is available. 2010-08-31 Frank Warmerdam * src/pj_gridinfo.c: Move grids in 180 to 360 region to -180 to 0. Improve error/debug reporting. 2010-08-21 Frank Warmerdam * nad/test*: default to using ../src/cs2cs 2010-07-31 Frank Warmerdam * nad/epsg: updated from GDAL. Adds TMSO projection definitions, and replaces all named datums with fully defined datums. 2010-07-05 Frank Warmerdam * src/projects.h: I_ERROR macro must set context errno. 2010-06-10 Frank Warmerdam * src/*: Preliminary implementation of projCtx multithreading change. 2010-05-11 Frank Warmerdam * src/pj_apply_vgridshift.c (+more): preliminary addition of vertical grid shifting support. 2010-03-16 Frank Warmerdam * src/pj_transform.c, src/pj_init.c, src/projects.h, src/pj_gridlist.c, src/pj_apply_gridshift.c: rework the translation of nadgrids parameters into a list of gridshift files to avoid use of static "lastnadgrids" information which screws up multithreading. Changes the PJ structure. * src/multistresstest.c: new harnass for multithreaded testing. 2010-03-03 Frank Warmerdam * src/*: fix a variety of warnings when -Wall is used. Mostly unused variables, and use of assignment results in an if statement without extra brackets. * src/*: treat most grid shift errors as not-transient, with the exception of not having a grid shift file for the area of interest. This is done by adding a new error code for no grid shift file for target area. Also ensure that cs2cs reports pj_transform() errors via emess so we have a chance of seeing the error message. 2010-02-28 Frank Warmerdam * src/pj_init.c, src/pj_transform.c: added support for +axis setting to control axis orientation (#18). * nad/epsg: Regenerated from EPSG 7.4.1 with the big datum selection upgrade. 2010-02-20 Frank Warmerdam * src/PJ_omerc.c: wholesale update from libproj4.3 (20081120) (#62) 2010-01-25 Frank Warmerdam * src/pj_mutex.c: avoid conflict between pthread and win32 mutex implementations on unix-like build environments on windows. (#56) * src/pj_init,src/projects.h,src/pj_transform.c,nad/testvarious: Correct seriously broken +lon_wrap implementation. (#62) * src/pj_mutex.c: fix creation of mutex on win32 to be in unacquired state in pj_init_lock to avoid an extra reference. (#63) 2009-10-19 Frank Warmerdam * nad/ntf_r93.gsb: updated with file from IGN (#52). * docs/*: files moved out of source tree (still in svn) 2009-09-29 Frank Warmerdam * nmake.opt: Update so that various items can be externally overridden (#54). 2009-09-24 Frank Warmerdam * nad/Makefile.am: add ntv2 and ignf testing if grid shift files are available. 2009-09-23 Frank Warmerdam * Preparing for 4.7.0 release. * nad/makefile.vc: do not attempt to install ntf_r93.gsb by default. * src/pj_init.c: Temporarily set locale to "C" to avoid locale specific number parsing (#49). * src/pj_rho.c: move rho out of structure, threadsafety issue (#41). * nmake.opt: improve comments (#50). * nad/epsg: regenerated - use more symbolic ellipsoid/datum names, and fix EPSG 3857 and 3785 (#51). * src/pj_gridlist.c: Implement mutex protection for grid loader/cacher. * src/pj_mutex.c: fix up windows support. * nad/ntf_r93.gsb: set mime-type to binary so it isn't corrupted on windows systems. 2009-06-17 Frank Warmerdam * src/pj_mutex.c: Implement win32 and pthread mutex support. * configure, src/Makefile.am: add --without-mutex support to configure 2009-06-16 Frank Warmerdam * README: Update windows build instructions (#30). * nad/epsg: Upgraded to EPSG 7.1. 2009-05-19 Frank Warmerdam * nad/testvarious,nad/testdatumfile: split datum file specific stuff into testdatumfile, and add kav5 test in testvarious (#40). 2009-05-18 Frank Warmerdam * src/PJ_sts.c: Remove duplicate division o lp.phi by P->C_p (#40). 2009-05-13 Frank Warmerdam * src/PJ_imw_p.c: Correct handling of yc in loc_for() (#39). 2009-04-02 Frank Warmerdam * nad/Makefile.am: Changes to ensure grid shift files are processed before running check-local, and to use the local grid shift files if available, and to avoid testvarious if grid shift files are not available. * src: Fix various warnings. 2009-03-11 Frank Warmerdam * man/man1: fix Snyder reference (#29) 2009-03-10 Howard Butler * autogen.sh: Use autogen.sh from libLAS for wider platform (OSX, Solaris) compatibility * config.guess config.log: remove autoconf temporary files 2009-03-10 Mateusz Loskot * makefile.vc: Added new files pj_mutex.c, pj_initcache.c. 2009-03-09 Frank Warmerdam * pj_init.c, pj_mutex.c, pj_initcache.c: Introduced in-memory caching of init file search results. 2009-03-08 IGNF * src/PJ_gstmerc.c: Correction of a bug in inv() function : the projected origin coordinates where descaled. * nad/testIGNF: Add a comment on the mandatory existence of the world grid in order to make the test. * ChangeLog: this comments 2009-01-26 Frank Warmerdam * src/*.c: Remove SCCSID and lint stuff from all source files. 2009-01-23 Frank Warmerdam * src/biveval.c: Avoid use of static variables which interfere with re-entrancy (#24)" 2009-01-05 Frank Warmerdam * src: Removed CVS log messages from various files since they are not maintained by subversion. 2008-09-16 Frank Warmerdam * src/{Makefile.am, Makefile.in}: Added '-no-undefined' option to LDFLAGS. This is required to properly build a library in some environments, MinGW in particular. 2008-08-21 Frank Warmerdam * Prepare 4.6.1RC2 * nad/td_out.dist: backed out erroneous changes in 4.6.0 that lost datum shifts with grid shift files. Added stere (#12) test. * nmake.opt: Added /Op to avoid stere errors per ticket #12. 2008-08-07 Frank Warmerdam * nmake.opt, nad/makefile.vc: Make sure we use PROJ_LIB_DIR when installing nad directory support files on windows. 2008-07-28 IGNF * PJ_glabsgm.c : refactoring for better understanding of the projection's formula. * copy of PJ_glabsgm.c to PJ_gstmerc.c and make changes accordingly in src and nad directories. 2008-07-21 Frank Warmerdam * Prepare 4.6.1 release. * rename INSTALL.TXT to INSTALL since the damn distribution generator won't stand for the alternate naming. Change makefile.vc to use install-all target instead of install. What are the chances anyone will think of trying that? Not high. * nad/epsg: regenerated from EPSG 6.17. This should also correct the odd precision problems in the last version or two caused by GDAL numeric processing issues. 2008-06-17 Frank Warmerdam * src/PJ_tmerc.c: Ensure that tmerc forward projection inputs are within 90 degrees of the central meridian. This should be considered a preliminary patch until such time as Gerald comes up with a better solution. http://trac.osgeo.org/proj/ticket/5 2008-04-24 Frank Warmerdam * src/cs2cs.c: Fix process() so it passes through extra text as the docs claim. 2008-03-15 Frank Warmerdam * rename INSTALL to INSTALL.TXT to avoid screwing up "make install" * Rework win32 makefiles to support "make install", and better knowledge of grid shift files, 2008-01-18 IGNF * PJ_eqc.c : Merged eqr and eqc after advise from Gerald. eqc is now generalized (supports latitude of origin). Cleaned files including eqr. * IGNF catalogue : changed accordingly. Added proj_outIGN.dist-real in nad directory to get real coordinates for unit tests. 2008-01-05 IGNF * PJ_eqr.c: src/PJ_eqr.c added. src/pj_list.h modified (added eqr). src/Makefile.am, src/makefile.vc modified (added PJ_eqr.c and al). As automake 1.10 is missing, src/Makefile.in modified by hand. * PJ_glabsgm.c: src/PJ_glabsgm.c added. src/pj_list.h modified (added glabsgm). src/Makefile.am, src/makefile.vc modified (added PJ_glabgsm.c and al). As automake 1.10 is missing, src/Makefile.in modified by hand. * IGNF catalogue: nad/IGNF added. nad/ntf_r93.gsb added, nad/Makefile.am modified (added IGNF, ntf_r93.gsb little endian release) nad/README modified (added IGNF, ntf_r93.gsb). As automake 1.10 is missing, nad/Makefile.in modified by hand. * Specific IGN release : configure.in ChangeLog 2007-12-21 Frank Warmerdam * Prepare 4.6.0 final release. 2007-12-21 Andrey Kiselv * PJ_wag3.c: Added missed "lat_ts" parameter to projection description string. 2007-12-20 Frank Warmerdam * pj_list.h, Makefile.am, PJ_mpoly.c: Removed mpoly projection. It was just a dummy (no actual transformation). 2007-12-06 Frank Warmerdam * pj_factors.c: in the case of phi=90, the derived should be calculated at [90-delta,90] instead of at [90,90+delta] (the same is true for -90) http://bugzilla.remotesensing.org/show_bug.cgi?id=1605 2007-12-03 Frank Warmerdam * pj_transform.c: Small improvement in WGS84_ES precision to avoid an unnecessary trip through geocentric space (eg bug 1531). 2007-11-30 Frank Warmerdam * add latlon and lonlat as aliases. 2007-11-29 Frank Warmerdam * Prepare 4.6.0beta1 release. * nad/epsg: Upgrade to EPSG 6.13 2007-11-25 Frank Warmerdam * pj_transform.c: Do ellipsoid comparisons using the _orig ellipse values rather than the adjusted one. Use these original values for any conversion to/from geocentric coordinates. Also, only do pj_datum_transform if neither the source nor destination is PJD_UNKNOWN. This means we will no longer attempt via-geocentric adjustments for coordinate systems lacking a datum definition (having only an ellipsoid. * projects.h, pj_init.c: added a_orig and es_orig values in the PJ structure so we can distinguish between the originally requested ellipsoid, and the ellipsoid after adjustment for spherical projections Todays changes courtesy of bug 1602. 2007-09-28 Frank Warmerdam * nad/esri.extra: Add "900913" code for google mercator. 2007-09-11 Frank Warmerdam * src/gencent.c/h, src/pj_transform.c: Restructure so geocentric code does not use static variables - reentrancy fix. * src/nad_init.c: Improve error recovery if ctable datum shift files fails to load. 2007-08-20 Frank Warmerdam * src/proj_api.h: include void in arg list for prototypes with no arguments to avoid warning about not being a function declaration. 2007-07-06 Frank Warmerdam * src/pj_open_lib.c: Per suggestion from Janne, ensure pj_set_searchpath(0,NULL) clears the search path cleanly. 2007-06-04 Frank Warmerdam * src/proj.c: pj_free() the definition to simplify leak testing. 2007-04-04 Frank Warmerdam * src/PJ_laea.c: Fix memory leak of apa field. 2007-04-03 Frank Warmerdam * src/PJ_gn_sinu.c: remove duplicate call to pj_enfn() (bug #1536) 2007-03-12 Frank Warmerdam * src/pj_utils.c: Removed duplicate appending of towgs84 parameter. 2007-03-11 Frank Warmerdam * src/projects.h: Ensure that WIN32 is defined on win32 systems. * src/pj_open_lib.c: support drive letter prefixes on absolute paths. Support either \ or / as a dir delimiter on windows (bug 1499) 2007-03-07 Frank Warmerdam * src/PJ_krovak.c: info string change to report ellipsoidal instead of spherical per email from Markus. 2007-01-31 Frank Warmerdam * src/pj_datum_set.cpp: Don't parse more datum shift parameters than we have space to store in datum_params[]. 2006-11-02 Frank Warmerdam * src/rtodms.c: Fix computation of degree per bug described on the mailing list. 2006-10-22 Frank Warmerdam * Prepare for 4.5.0 final release. 2006-10-18 Frank Warmerdam * nad/epsg: added polish zones (2172-2175) manually per request from Maciek on the mailing list. * Preparing 4.5.0 beta4 release. 2006-10-17 Frank Warmerdam * src/proj_mdist.c, proj_rouss.c: Incorporated these from libproj4 for http://bugzilla.remotesensing.org/show_bug.cgi?id=967. * nad/epsg: Regenerated from EPSG 6.11.1 with a few other fixes (datum shift values) from several bug reports. 2006-10-12 Frank Warmerdam * Added experimental +lon_wrap argument to set a "center point" for longitude wrapping of longitude values coming out of pj_transform(). 2006-10-10 Frank Warmerdam * src/proj.c,nad2nad.c,cs2cs.c: Increase MAX_LINE to 1000 per request from Dan Scheirer. 2006-10-01 Frank Warmerdam * nad/Makefile.am: added test target. 2006-09-23 Frank Warmerdam * nad/epsg: upgraded to EPSG 6.11 2006-09-22 Frank Warmerdam * src/pj_init.c: removed static "start" variable to resolve thread-safety problems (bug 1283). 2006-09-14 Frank Warmerdam * Produce 4.5.0beta2 release. * src/PJ_krovak.c: Add +czech flag to apply non-useful sign reversal that someone once apparently thought was a good idea. By default work like folks want. Contributed by Martin Landa and Radim Blazek. Bug 1133, and 147. 2006-07-07 Frank Warmerdam * Added esri.extra and other.extra to distributed and installed files in nad/Makefile.am. * autotools update. 2006-06-23 Andrey Kiselev * src/PJ_eqdc.c: Do not call pj_enfn() twice avoiding memory leak. 2006-05-01 Frank Warmerdam * src/pj_transform.c: Ensure that out-of-range lat/long values in geodetic_to_geocentric are considered transient errors. Rel. 4.5.0 2006-04-21 ------------------------------------------------------------------------- 2006-04-21 Frank Warmerdam * nad/epsg: Upgraded using GDAL 1.3.2 with prime meridian fixes, and reporting of deprecated PCSes. 2006-04-20 Frank Warmerdam * Fixed direction of Bogota meridian (west not east). 2006-04-19 Frank Warmerdam * Preparing 4.5.0 release. 2006-03-30 Frank Warmerdam * projects.h, cs2cs.c, pj_strerrno.c, p_series.c, gen_cheb.c: Added _CRT_SECURE_NO_DEPRECATE declaration for VC8+, and ensure projects.h gets included first where needed. Avoids loud warnings on VC8. http://bugzilla.remotesensing.org/show_bug.cgi?id=1145 2006-03-29 Frank Warmerdam * pj_krovak.c: Removed MessageBox() DEBUG stuff. 2006-03-20 Frank Warmerdam * src/pj_transform.c: Return error -14 (latitude or longitude exceeds bounds) for failed geodetic to geocentric (lat out of +-90). 2006-03-10 Frank Warmerdam * nad/epsg: updated to EPSG 6.9. 2006-02-16 Frank Warmerdam * src/pj_transform.c: Treat errno=33 (EDOM) and errno=34 (ERANGE) as transient errors, and continue trying to transform the rest of the points. 2006-01-19 Frank Warmerdam * nad/world: Fixed definition of as per: http://bugzilla.remotesensing.org/show_bug.cgi?id=1041 2006-01-12 Frank Warmerdam * geocent.c: Make global variables static. Among other things this avoids conflicts for apps that link in geotrans. 2005-12-04 Frank Warmerdam * src/pj_transform.c: improve code with some symbolic names. 2005-11-08 Frank Warmerdam * src/pj_datums.c: Added OSGB36 transformation to list. 2005-07-06 Frank Warmerdam * nad/Makefile.am: added .gsb installation logic to capture nz file. * pj_gridinfo.c: fixed debug format string per: http://bugzilla.remotesensing.org/show_bug.cgi?id=886 * pj_utils.c: fixed precision of es encoding in pj_latlong_from_proj. http://bugzilla.remotesensing.org/show_bug.cgi?id=881 2005-04-20 Frank Warmerdam * pj_apply_gridshift.c: Fixed problem that was resulted in points after the first apparently succeeding to shift when a gridshift file wasn't found. Bug 834. 2004-11-05 Frank Warmerdam * src/pj_transform.c: Fixed pj_geocentric_to_geodetic() to not try and process HUGE_VAL values (those that have failed some previous transform step). Related to bug: http://bugzilla.remotesensing.org/show_bug.cgi?id=642 2004-10-30 Frank Warmerdam * Improved --with-jni support in configure to allow specification of an include directory. Rel. 4.4.9 2004-10-29 ------------------------------------------------------------------------- 2004-10-29 Frank Warmerdam * Preparing 4.4.9 release. * src/pj_gridinfo.c: Fixed reported information in ctable debug msg. * src/nad_cvt.c: Fixed problem with domai of tb.lam that caused failure of eastern hemisphere locations to transform with null grid (which is world sized). 2004-10-28 Frank Warmerdam * src/makefile.vc: Changed to build executables against a proj.dll by default. * proj.def: added lots of methods, including some private ones used only by proj.c, and geod.c. * Added pj_get_*_ref() accessors for all the definition lists. * Makefile.am: added jniwrap make support. * configure.in: various updates, including use of AC_MAINTAINER_MODE, and setting version to 4.4.9. Fixes annoying .so problem. * updated to latest libtoolish stuff. 2004-10-25 Frank Warmerdam * fixtimes.sh: Run this after a CVS checkout to setup times of various build files to avoid re-running automake and friends. * src/geocent.c,geocent.h,pj_transform.c: Added pj_ prefix to all Geotrans functions to avoid name conflict if both linked in. * configure.in: added --with-jni option. * Added src/jniproj.c, src/org_proj4_Projections.h. * Added jniwrap subtree (actually Andrea Antonello). 2004-10-21 Frank Warmerdam * src/makefile.vc: added support for new files. 2004-10-19 Frank Warmerdam * src/pj_gauss.c, src/PJ_geos.c, src/PJ_sterea.c: Incorporated geos and sterea projections from Gerald's libproj4. 2004-09-16 Frank Warmerdam * src/pj_open_lib.c: added pj_set_searchpath() provided by Eric Miller. 2004-09-14 Frank Warmerdam * src/pj_pr_list.c: Ensure unused parameters are not included in the returned string (provided by Eric Miller). 2004-05-17 Frank Warmerdam * proj.spec: Change PACKAGE_NAME from "PROJ" to "proj". 2004-05-12 Frank Warmerdam * nad/epsg: update translation for potsdam datum. http://bugzilla.remotesensing.org/show_bug.cgi?id=566 2004-05-04 Frank Warmerdam * src/pj_init.c: Made sword[] larger in get_opt() so long +towgs84 parameters or long +nadgrids parameters aren't truncated. Rel. 4.4.8 2004-05-04 ------------------------------------------------------------------------- 2004-05-04 Frank Warmerdam * 4.4.8 release re-issued. * nad/epsg: regenerated with prime meridian problems corrected. http://bugzilla.remotesensing.org/show_bug.cgi?id=510 2004-05-03 Frank Warmerdam * Preparing 4.4.8 release. * src/pj_datums.c: added nzgd49 datum definition http://bugzilla.remotesensing.org/show_bug.cgi?id=339 * nad/epsg: updated to EPSG 6.5. * src/pj_transform.c: fixed so that raw ellipsoids are handled in datum shifting as if they had a +towgs84=0,0,0. * src/pj_transform.c: Fixed so that prime meridian offsets are applied even if the coordinate system is not lat/long. http://bugzilla.remotesensing.org/show_bug.cgi?id=510 * src/geocent.c: Updated Geocentric_To_Geodetic computation to be iterative to reduce error as per Wenzel, H.-G.(1985): Hochauflösende Kugelfunktionsmodelle für das Gravitationspotential der Erde. Wiss. Arb. Univ. Hannover Nr. 137, p. 130-131. Fix adapted to geocent.c and submitted by Lothar Gorling. http://bugzilla.remotesensing.org/show_bug.cgi?id=563 2004-04-15 Frank Warmerdam * src/makefile.vc: Define HAVE_STRERROR. * src/projects.h: PJD_ERR_GEOCENTRIC now -45, and added to pj_strerrno.c. * src/pj_release.c: added pj_get_release() function. 2004-02-19 Frank Warmerdam * nad/other.extra: updated from some WKT definition Daniel got from CubeWerx. 2004-01-24 Frank Warmerdam * src/pj_transform.c: Ensure pj_transform() will try to transform all points in provided list if even some might transform properly. 2003-08-18 Frank Warmerdam * src/PJ_aea.c: fixed initialization of en variable. http://bugzilla.remotesensing.org/show_bug.cgi?id=380 2003-06-27 Frank Warmerdam * src/pj_init.c: changed tokenizing in pj_init_plus() so that if a value has an exponent with a plus sign this won't trigger a brand new token. See bug 355 in bugzilla. 2003-06-09 Frank Warmerdam * src/pj_init.c: ensure start is initialized at the very beginning of the function to avoid crashes in case where the input arg list is empty. 2003-04-24 Frank Warmerdam * src/geod.c: Don't emit an error message after listing ellipsoids or units, as per request from Dan Jacobson. 2003-04-09 Frank Warmerdam * man/man1/{proj,cs2cs}.1: moved -m option from cs2cs.1 to proj.1 since it is only supported by proj. * nad/Makefile.am: added DESTDIR in three missing places as per bug report from Peter Galbraith - proj debian package manager. Rel. 4.4.7 2003-03-31 ------------------------------------------------------------------------- 2003-03-31 Frank Warmerdam * Prepare 4.4.7 Release. * nad/esri: incorporated Paul Ramsey's update. ESRI specific coordinate systems in nad/esri.extra. * nad/epsg: Regenerated with towgs84 parameters properly generated for non-greenwich prime meridians. http://bugzilla.remotesensing.org/show_bug.cgi?id=304 2003-03-28 Frank Warmerdam * config.guess, config.sub: updated from ftp://ftp.gnu.org/pub/gnu/config/ in order to resolve Debian build problems on MIPS architecture. http://bugs.debian.org/cgi-bin/bugreport.cgi?archive=no&bug=186586 * src/pj_datums.c: fixed ire65 definition to refer to mod_airy, not modif_airy as per: http://bugzilla.remotesensing.org/show_bug.cgi?id=312 2003-03-26 Frank Warmerdam * src/pj_transform.c: Added check that srcdefn->inv actually exists! Per http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=301 2003-03-25 Frank Warmerdam * src/cs2cs.c: modified so that -f formats are used for Z as well as x and y values. As per http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=169056 * src/man/man1/cs2cs.1: removed -V flag ... it is not supported. As per http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=162331 2003-03-17 Frank Warmerdam * src/pj_datums.c: changed NAD27 definition to make everything optional, and to include alaska, and ntv2_0.gsb. nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat * src/pj_grid*, src/pj_apply_gridshift.c, src/nad_init.c: Lots of changes introducing the PJ_GRIDINFO structure, support for skippable grids ('@' prefix), delayed grid data loading and support for NTv2 grids. 2003-03-16 Frank Warmerdam * Modified get_opt() to terminate reading the definition when a new definition (a word starting with '<') is encountered, in addition to when the definition terminator '<>' is encountered, so that unterminated definitions like those in the distributed esri file will work properly. Patch provided by Carl Anderson. http://bugzilla.remotesensing.org/show_bug.cgi?id=302 2003-03-03 Frank Warmerdam * Prepare 4.4.6 Release. * nad/epsg: updated to EPSG 6.2.2. * src/Makefile.am, nad/Makefile.am: a few fixes for Cygwin compatibility, ensure /usr/local/share/proj get pre-created. * Incorporate src/PJ_lcca.c, the new "alternate" LCC implementation provided by Gerald for some old maps. See his site for details. * Rebuild dependent files with automake 1.6.3, libtool 1.4.2 and autoconf 2.53. 2003-01-15 Frank Warmerdam * src/pj_datums.c: added some datums as suggested by the GRASS team. 2002-12-14 Frank Warmerdam * src/projects.h, various others: updated header style in some files. * src/pj_geocent.c, src/pj_transform.c, src/pj_list.h, src/projects.h: added support for geocentric coordinates in pj_transform() api. * src/pj_utils.c: Fixed pj_get_def() to return info on prime meridian. 2002-12-08 Frank Warmerdam * src/cs2cs.c: added support for the -lm switch to report all prime meridians. * src/pj_init.c, pj_transform.c, pj_datum.c: added preliminary support for the +pm switch to set the prime meridian. 2002-12-01 Frank Warmerdam * src/pj_transform.c: Applied fix for 7 parameter shifts in pj_geocentric_from_wgs84() as per suggestion from Harald Lernbeiss in: http://bugzilla.remotesensing.org/show_bug.cgi?id=194 2002-11-19 Frank Warmerdam * src/cs2cs.c: cleanup memory at end to facility memory leak testing. 2002-07-29 Frank Warmerdam * nad/esri: applied gradian related patches as per bug 184: http://bugzilla.remotesensing.org/show_bug.cgi?id=184 2002-07-25 Frank Warmerdam * nad/esri: added new ESRI translation file. Includes EPSG values plus various ESRI extensions. 2002-07-07 Frank Warmerdam * src/*.c, src/*.h, src/makefile.vc: *Many* changes to support compiling all of the PROJ.4 source as C++ source. Add /TP to CFLAGS in makefile.vc to test this on Windows. projects.h, and proj_api.h attempt to export all externally visible functions with C linkage but all code should now compile as C++. Currently only tested with VC++ 6. 2002-06-11 Frank Warmerdam * src/pj_pr_list.c, proj.def, proj_api.h: Added the pj_get_def() function to return an expanded definition from a projPJ handle, including having the +init= section expanded. 2002-05-30 Frank Warmerdam * src/geod/{geod.c,geod_for.c,geod_inv.c,geod_set.c,geodesic.h}: Renamed a, S and f to geod_a, geod_S and geod_f to slightly reduce the horrible naming conflict situations with geodesic.h. http://bugzilla.remotesensing.org/show_bug.cgi?id=148 2002-04-30 Frank Warmerdam * html/faq.html: new * src/pj_apply_gridshift.c,pj_open_lib.c,nad_init.c: try to improve debug output when datum shifting fails. 2002-04-16 Frank Warmerdam * src/pj_list.c,src/PJ_krovak.c: Incorporated support for Krovak projection as per submission by Thomas Fleming and Markus Neteler. 2002-03-01 Frank Warmerdam * src/geod.c: Moved ctype.h up to avoid compile failure on MacOS X. 2002-02-15 Frank Warmerdam * pj_transform.c: Provide zerod Z array in pj_datum_transform() if none passed in. 2002-01-23 Frank Warmerdam * Added proj.spec file provided by Intevation (FreeGIS CD). Rel. 4.4.5 2002/01/09 ------------------------------------------------------------------------- 2002-01-09 Frank Warmerdam * src/geocent.c: Fixed serious bug in Convert_Geodetic_To_Geocentric() that essentially ruins all datum shifting (except NAD tables). This bug was introduced just in time for the PROJ 4.4.4 release. 2001-11-05 Frank Warmerdam * src/proj.def: added pj_strerrno and pj_errno as per request from Bernhard Herzog. Rel. 4.4.4 2001/09/15 ------------------------------------------------------------------------- 2001-09-15 Frank Warmerdam * src/geocent.c: I have modified the Convert_Geodetic_To_Geocentric() function to clamp Latitudes just a little out of the range -PI/2 to PI/2 and to no longer do error checks on Longitudes since they will be auto-wrapped by sin() and cos(). See http://bugzilla.remotesensing.org/show_bug.cgi?id=17 * nad/epsg: committed new updates with fixed units for us state plane zones in feet, as reported by Marc-Andre. 2001-08-23 Frank Warmerdam * src/makefile.vc: improved the setting of PROJ_LIB defaults. * src/pj_open_lib.c: added the pj_set_finder() entry point. * nad/epsg: fixed all LCC projections. The parameters were badly mixed up. 2001-08-11 Frank Warmerdam * src/proj.c: Generate an error message if +proj=latlong is used with this program. As per bugzilla bug 70. 2001-06-01 Frank Warmerdam * makefile.vc: emess.c directly linked into mainline programs. * pj_errno.c: added pj_get_errno_ref(). 2001-05-14 Frank Warmerdam * upgraded config.sub and config.guess as per debian bug report 97374. Rel. 4.4.3 2001/04/20 ------------------------------------------------------------------------- 2001-04-20 Frank Warmerdam * Don't install test files in /usr/local/share/proj. * Made WGS84 the default in proj_def.dat * nad/test27,test83: Use -b flag for diff to avoid differences on Windows due to CR/LF issues. * src/makefile.vc: default to building "all". * src/pj_init.c: call pj_open_lib() with mode of "rt" to ensure correct handling of def files on DOS based systems. * Updated for 4.4.3 release (pj_release.c, Makefile.am, etc). 2001-04-05 Frank Warmerdam * Introduce proj_api.h as a public include file with projects.h now intended to be private. * pj_datums.c: added ntv1_can.dat to list for NAD27 datum. * nad_init(): added support for loading NTv1 style datum shift files. * cs2cs.c: use pj_latlong_from_proj() * pj_init.c: added pj_init_plus(). * pj_utils.c: new with pj_is_latlong(), and pj_latlong_from_proj() functions. * pj_strerror.c: added error -43. 2001-04-04 Frank Warmerdam * rewrote 7 param datum shift to match EPSG:9606, now works with example. 2001-03-20 Frank Warmerdam * Added -DPROJ_LIB=\"C:/PROJ/\" in src/makefile.vc to provide for a default proj data file search directory. * Added HOWTO-RELEASE document in CVS. 2001-03-15 Frank Warmerdam * src/pj_apply_gridshift.c: fixed bug in pj_load_nadgrids() which would sometimes result in the load function failing because of a buffer overrun in the grid list string. 2001-03-14 Frank Warmerdam * added nad/epsg database of translations between EPSG PCS/GCS codes and PROJ.4 definitions. 2001-02-24 Frank Warmerdam * Include +ellps in proj example as per suggestion from Michael DeChaine. 2001-02-07 Frank Warmerdam * Cleaned up various warnings when compiled with -Wall. 2001-02-03 Frank Warmerdam * Added cs2cs.1 man page, and minor updates to nad2nad.1 and proj.1. * Added pj_transform docs to pj_init.3. 2001-01-25 Frank Warmerdam * Fixed pj_init() check for WGS84 match as per Bart Adriaanse bug rep. 2000-12-15 Frank Warmerdam * src/makefile.vc: only delete proj.lib if it exists. 2000-12-01 Frank Warmerdam * Added proj.def to extra_dist in src/Makefile.am. 2000-11-29 Frank Warmerdam * Changed strtod() to proj_strtod() in strtod.c, and make use of it in dmstor() to avoid having stuff like "5d10" interpreted as exponential notation on MSVC. 2000-11-18 Frank Warmerdam * Patch from Craig Bruce to adjlon.c to avoid wrong results, and near-hangs when adjusting very large numbers. http://bugzilla.remotesensing.org/show_bug.cgi?id=27 Rel. 4.4.2 2000/09/22 ------------------------------------------------------------------------- 2000-09-22 Frank Warmerdam * Fixed src/Makefile.am install-exec-local target, and added geocent.h, and emess.h. Reissued 4.4.2 distribution files. * Update version to 4.4.2, in preparation for 4.4.2 release. * Ensure makefile.vc is distributed, and mention windows building in README. * Cast args to freev2() in bch2bps.c, and mk_cheby.c to avoid errors on the Cray. 2000-09-21 Frank Warmerdam * Added "sphere" to pj_ellps.c. 2000-07-06 Frank Warmerdam * Fixed bug in nad_init() with path for datum shifting files. * Implemented cs2cs program for transforming between coordinate systems including datum shifts. * Implemented proj=latlong pseudo-projection. * Implemented pj_transform() to transform from one coordinate system to another, including applying geocentric datum shifts, and NAD27 grid shifts. * Implemented 3/7 parameter geocentric datum shift support. * Added support for +datum, +towgs84, and +nadgrids parameters when defining PJ's (for pj_init()). Added datum_type, and datum_params to PJ structure. 2000-07-04 Frank Warmerdam * Patched proj.c to handle binary io properly on Windows and DOS. Patch submitted by Thomas Knudsen . 2000-04-26 Frank Warmerdam * Added #define USE_PROJUV to projects.h to allow apps to work properly against old and new version. 2000-04-04 Frank Warmerdam * Patch from Craig Bruce (cbruce@cubewerx.com) for PJ_ortho.c to make INVERSE() work well for points near zero. 2000-03-29 Frank Warmerdam * Added hard links for invproj->proj and invgeod->geod in src/Makefile.{am,in}. Rel. 4.4.1 2000/03/27 ------------------------------------------------------------------------- 2000-03-27 Frank Warmerdam * Issued V4.4.1 Release. * Re-added install target for NADCON data files when available. * At the suggestion of John Evans, I have rolled the nad conversion functions into the core library. * Updated COPYING file to MIT style license. Added man_proj.html in html directory. * Add rules to install nad data files in $(prefix)/share/proj. 2000-03-21 Frank Warmerdam * Converted to use libtool. * Wrote new configure.in, and use automake to generate makefiles. * Renamed UV to projUV to avoid conflicts on windows. * Reorganize ChangeLog, and start work on 4.4. Rel. 4.3.2 94/10/30 Base-line ------------------------------------------------------------------------- 95/4/27 Corrected rf factor for GRS67. Thanks to: Peter Shih tyshih@cc.nctu.edu.tw 95/6/3 Gave an initializing value for pj_errno. Someone's compiler ignored the whole module because nothing happened(!!!). Thanks to: Mark Crispin . 95/7/6 Corrected function pj_inv_mlfn for improper derivative code. Previous computations not in error but convergence was slower. Thanks to: Tony Fisher fisher@minster.york.ac.uk. 95/8/8 Added Swiss Oblique Mercator projection. CH1903 Swiss grid system parameters added to nad/world. added to nad/world file and N-somerc.ps.Z added to documentation notes. Thanks to: Daniel Ebneter, ebneter@iap.unibe.ch. 95/9/5 Changed declaration of "char c" to "int c" to more properly monitor error return value in pj_init.c. Thanks to: Alejo Hausner (ah@cs.princeton.edu) 95/9/10 Some minor file/internal name changes to facilitate xport to primitive systems. Documented entries unchanged. Rel. 4.3.1 94/2/16 Base-line ------------------------------------------------------------------------- 94/6/2 Transverse Mercator, spherical inverse fixed. Misplaced parenthesis. 94/10/5 Dropped dependency on FILENAME_MAX---too poorly defined in both POSIX and ANSI standards. Adopted MAX_PATH_FILENAME which is set to 1024 (should be enough for most cases). This should solve problem with HP installations. 94/10/29 Problems with ellipsoidal for of azimuthal equidistant (PJ_aeqd.c). Some discrepancies remain on comparison with Snyder's examples but felt due to his use of TI calculator. Procedure should be replaced with better geodesic routine. 94/10/29 Corrected and added examples to geod.1 documentation. 94/10/30 Added mkdir in nad/install otherwise nad2783 install may fail. Rel. 4.3 94/2/16 Base-line ------------------------------------------------------------------------- 94/3/13 Equidistant Conic forced es to 0, thus previous ellipsoid usage flawed. Correction to sign of convergence angle and other details in pj_factors.c. Lambert Conf. conic corrected for +lat_0=90. Convergence sign in pj_factors.c corrected to conform to Bomford's definition. Also procedure corrected for usage when projection returns some of its own factors. 94/3/17 Added procedure pj_phi12 to support library. It gets and checks standard parallels for some of the conics. Added SPECIAL entry to conics Lambert, Albers and Equidistant. Corrected nad/install.in test so as to only look for conus.lla.Z as test for installation of NADCON datum matrices. 94/3/19 Problems with MAPGEN's mapdef choking on call to proj. Fixed with PROJ.4.3-patch-01. 94/3/22 Bump mode of handling memory allocation for 2D arrays, so that execution of -L may not work on some systems. Interim corrections distributed with PROJ.4.3-patch-02. Patched Make.2 to properly use $(LIBS). Not in patch. Apple's Unix libc has problems---no strerror and no %n in ?format. 94/5/22 Added several simple conics but not totally verified. Corrected proj.c so that resultant earth figure comments in -V are prefixed with # and do not blow *mapdef*. Releasing current code without documentation on new conics pending communications with Snyder on their veracity. Release mainly to clean up patches. Rel. 4.2.2 93/9/30 Base-line ------------------------------------------------------------------------- 93/11/14 1. Minor change to projects.h to correct prototype. 2. Changes to pj_init.c regarding ignoring failure to open proj_def.dat. 3. Alternate method of initializing automatic array. 93/11/16 DOS distribution. 93/11/28 Added "Final" figure line to beginning of -V option output. Allows user to see results of +ellps and +R_V, etc. arguments. "Feature," not an error. Mod to proj.c. 93/12/03 Removed non-ANSI usage of errno from PJ_laea. Added test for previous definition of NULL in strtod.c. 93/12/12 Made aatan2 (compensates for 0,0 args) global. 93/12/30 Removed proj "error" message at end of -l option list. 94/1 Major revision to projection structure to facilitate maintenance. Introduced PROJ_HEAD macro that is defined in several ways dependent upon use. Allows generation of pj_list table from `grep'ed projection files. Structure PJ now contains pointer to const string giving ascii description of projection. Limited application projection list much easier to generate with this system. Many new pseudocylindrical projections added as well as a few new miscellaneous projections. Total projection count now 110. Rel. 4.2.1 93/9/30 Base-line ------------------------------------------------------------------------- 93/10/3 Geod incorrectly computed some forward values when geodesic on the merdian. 93/11/2 Projection stere fails for polar cases because of 0 lat_ts. Fixed by testing for lat_ts specification and setting to 90 degrees when lat_ts not specified. UPS not affected. 93/11/5 Inverse polar stereographic also failed on 0 x xor y. Corrected. 93/11/10 Changed "install" to include "plain" system type for systems that do not require special consideration. Rel. 4.2 93/8/25 Base-line ------------------------------------------------------------------------- 93/9/13 Improved bch2bps.c code. Old code not in error. Still problems with DEC native C compiler. 93/9/28 Modified install script for DEC entry, forcing gcc for compilation. 93/9/29 Problem with due South forward in geod. Current version will not be fixed as it is to be replaced with Vincente algorithm. 93/9/30 Two corrections in src/Makefile. Rel. 4.1.3 93/4/15 Base-line ------------------------------------------------------------------------- 93/5/22 Extensively revised Chebychev approximation procedures and added conversion to power series. 93/6/8 Changed type of pj_param, plus mods to some other internal procedures. 93/6/13 Modified pj_factors. Principle mod was that calling program must provide base for structure FACTORS. Additional mods reflect optional analytic input from projection modules (see next entry). Modified base of PJ structure for projections to supply analytic values of partial derivatives, scale factors and/or convergence when formulary available. Added -V option for proj so as to provide more complete, verbose analysis of projection characteristics at selected geographic or cartesian point. 93/6/14 Pj_errno given its own module and projects.h declares it external. To cover ANSI standards related to global variable. SG linker should stop complaining. 93/7/15 Several additions and a couple of minor corrections to elliptical tables. 93/8/4 PJ_ocea.c error in applying k0. 93/8/19 Minor general corrections. Added nadcon conversion procedures and nad2nad program. Projects.h modified to reflect nadcon prototypes and structures. pj_open_lib extracted from pj_init and made global for use in nad_init. 93/8/25 Corrected pj_open_lib open for both binary and text modes. Mostly for brain damaged DOS. Also affected calls in pj_init.c and nad_init.c Installs and other scripts updated. Rel. 4.1.2 93/4/4 Base-line ------------------------------------------------------------------------- 93/4/8 Corrected pj_inv so that errno and pj_errno are reset on entry. 93/4/14 Added elliptical forms to Azimuthal Equidistant (aeqd). 93/4/15 Corrected positive error return to negative in PJ_lcc.c . Added Indian units conversions to pj_units. Rel. 4.1.1 93/3/26 Base-line ------------------------------------------------------------------------- 93/4/2 gen_cheby.c - added header. 93/4/3-4 gen_cheby.c, projects.h - corrected gen_cheby argument declarations related to 'proj' argument and prototype. Often signalled warnings, but still managed to execute OK. pj_init.c - local function get_init had insufficient storage defined for copy of file name and id. Added id define. Strncat replaced with correct strncpy (amazingly did not cause problems except of one system). Proj now compiles on DOS Microsoft 5.0 C compiler. MS suffers same brain-damage as DEC, so requires local strtod function. pj_strerrno prototype added to projects.h DOS option in strtod.c for MS C's lack of standard macros in headers. Rel. 4.1 93/3/8 Base-line --- @(#)CHANGE-LOG 4.14 95/09/23 GIE REL ------------------------------------------------------------------------- 93/3/20 pj_init -- added +k_0 as alternative to +k so as to match documentation. 93/3/21 Laborde projection added. Primarily for Madagascar grid. Considered BETA at moment until info obtained to give adequate documentation. 93/3/26 Oblique Mercator modified to allow processing of Malasian Grid. +no_uoff and +rot_conv options added. 93/3/26 Corrected text in Interim Report: p. 12 - +phi's changed to +lat's p. 12 - added updated Oblique Mercator documentation Unresolved: Reports of errno 25 persist. Do not know what platform. Reviewed code and can't see problem. Unknown platform has problem with pj_errno global and linker storage allocation. Seems similar to SG problem that was over come with -common switch. proj-9.8.1/man/000775 001750 001750 00000000000 15166171735 013230 5ustar00eveneven000000 000000 proj-9.8.1/man/CMakeLists.txt000664 001750 001750 00000000243 15166171715 015765 0ustar00eveneven000000 000000 install(FILES man1/proj.1 man1/cs2cs.1 man1/geod.1 man1/cct.1 man1/gie.1 man1/projinfo.1 man1/projsync.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) proj-9.8.1/man/man1/000775 001750 001750 00000000000 15166171735 014064 5ustar00eveneven000000 000000 proj-9.8.1/man/man1/gie.1000664 001750 001750 00000035056 15166171715 014721 0ustar00eveneven000000 000000 .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "GIE" "1" "10 avril 2026" "9.8" "PROJ" .SH NAME gie \- The Geospatial Integrity Investigation Environment .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 \fBgie\fP [ \fB\-hovql\fP [ args ] ] file[s] .UNINDENT .UNINDENT .SH DESCRIPTION .sp gie, the Geospatial Integrity Investigation Environment, is a regression testing environment for the PROJ transformation library. Its primary design goal is to be able to perform regression testing of code that are a part of PROJ, while not requiring any other kind of tooling than the same C compiler already employed for compiling the library. .INDENT 0.0 .TP .B \-h, \-\-help Print usage information .UNINDENT .INDENT 0.0 .TP .B \-o , \-\-output Specify output file name .UNINDENT .INDENT 0.0 .TP .B \-v, \-\-verbose Verbose: Provide non\-essential informational output. Repeat \fB\-v\fP for more verbosity (e.g. \fB\-vv\fP) .UNINDENT .INDENT 0.0 .TP .B \-q, \-\-quiet Quiet: Opposite of verbose. In quiet mode not even errors are reported. Only interaction is through the return code (0 on success, non\-zero indicates number of FAILED tests) .UNINDENT .INDENT 0.0 .TP .B \-l, \-\-list List the PROJ internal system error codes .UNINDENT .INDENT 0.0 .TP .B \-\-version Print version number .UNINDENT .sp Tests for gie are defined in simple text files. Usually having the extension \fB\&.gie\fP\&. Test for gie are written in the purpose\-build command language for gie. The basic functionality of the gie command language is implemented through just 3 command verbs: \fBoperation\fP, which defines the PROJ operation to test, \fBaccept\fP, which defines the input coordinate to read, and \fBexpect\fP, which defines the result to expect. .sp A sample test file for gie that uses the three above basic commands looks like: .INDENT 0.0 .INDENT 3.5 .sp .EX \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- Test output of the UTM projection \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- operation +proj=utm +zone=32 +ellps=GRS80 \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- accept 12 55 expect 691_875.632_14 6_098_907.825_05 .EE .UNINDENT .UNINDENT .sp Parsing of a gie file starts at \fB\fP and ends when \fB\fP is reached. Anything before \fB\fP and after \fB\fP is not considered. Test cases are created by defining an \fBoperation\fP which \fBaccept\fP an input coordinate and \fBexpect\fP an output coordinate. .sp Because gie tests are wrapped in the \fB\fP/\fB\fP tags it is also possible to add test cases to custom made init files \%<#\:init-files>\&. The tests will be ignore by PROJ when reading the init file with \fI+init\fP and gie ignores anything not wrapped in \fB\fP/\fB\fP\&. .sp gie tests are defined by a set of commands like \fBoperation\fP, \fBaccept\fP and \fBexpect\fP in the example above. Together the commands make out the gie command language. Any line in a gie file that does not start with a command is ignored. In the example above it is seen how this can be used to add comments and styling to gie test files in order to make them more readable as well as documenting what the purpose of the various tests are. .sp Below the gie command language is explained in details. .SH EXAMPLES .INDENT 0.0 .IP 1. 3 Run all tests in a file with all debug information turned on .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX gie \-vvvv corner\-cases.gie .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP 2. 3 Run all tests in several files .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX gie foo bar .EE .UNINDENT .UNINDENT .SH GIE COMMAND LANGUAGE .INDENT 0.0 .TP .B operation <+args> Define a PROJ operation to test. Example: .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=utm zone=32 ellps=GRS80 # test 4D function accept 12 55 0 0 expect 691875.63214 6098907.82501 0 0 # test 2D function accept 12 56 expect 687071.4391 6210141.3267 .EE .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B accept Define the input coordinate to read. Takes test coordinate. The coordinate can be defined by either 2, 3 or 4 values, where the first two values are the x\- and y\-components, the 3rd is the z\-component and the 4th is the time component. The number of components in the coordinate determines which version of the operation is tested (2D, 3D or 4D). Many coordinates can be accepted for one \fBoperation\fP\&. For each \fBaccept\fP an accompanying \fBexpect\fP is needed. .sp Note that gie accepts the underscore (\fB_\fP) as a thousands separator. It is not required (in fact, it is entirely ignored by the input routine), but it significantly improves the readability of the very long strings of numbers typically required in projected coordinates. .sp See \fBoperation\fP for an example. .UNINDENT .INDENT 0.0 .TP .B expect | Define the expected coordinate that will be returned from accepted coordinate passed though an operation. The expected coordinate can be defined by either 2, 3 or 4 components, similarly to \fBaccept\fP\&. Many coordinates can be expected for one \fBoperation\fP\&. For each \fBexpect\fP an accompanying \fBaccept\fP is needed. .sp See \fBoperation\fP for an example. .sp In addition to expecting a coordinate it is also possible to expect a PROJ error code in case an operation can\(aqt be created. This is useful when testing that errors are caught and handled correctly. Below is an example of that tests that the pipeline operator fails correctly when a non\-invertible pipeline is constructed. .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=pipeline step proj=urm5 n=0.5 inv expect failure pjd_err_malformed_pipeline .EE .UNINDENT .UNINDENT .sp See \fBgie \-\-list\fP for a list of error codes that can be expected. .UNINDENT .INDENT 0.0 .TP .B tolerance The \fBtolerance\fP command controls how much accepted coordinates can deviate from the expected coordinate. This is handy to test that an operation meets a certain numerical tolerance threshold. Some operations are expected to be accurate within millimeters where others might only be accurate within a few meters. \fBtolerance\fP should .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=merc # test coordinate as returned by \(ga\(ga\(gaecho 12 55 | proj +proj=merc\(ga\(ga tolerance 1 cm accept 12 55 expect 1335833.89 7326837.72 # test that the same coordinate with a 50 m false easting as determined # by \(ga\(gaecho 12 55 |proj +proj=merc +x_0=50\(ga\(ga is still within a 100 m # tolerance of the unaltered coordinate from proj=merc tolerance 100 m accept 12 55 expect 1335883.89 7326837.72 .EE .UNINDENT .UNINDENT .sp The default tolerance is 0.5 mm. See \fBproj \-lu\fP \%<#\:cmdoption-proj-lu> for a list of possible units. .UNINDENT .INDENT 0.0 .TP .B roundtrip Do a roundtrip test of an operation. \fBroundtrip\fP needs a \fBoperation\fP and a \fBaccept\fP command to function. The accepted coordinate is passed to the operation first in it\(aqs forward mode, then the output from the forward operation is passed back to the inverse operation. This procedure is done \fBn\fP times. If the resulting coordinate is within the set tolerance of the initial coordinate, the test is passed. .sp Example with the default 100 iterations and the default tolerance: .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=merc accept 12 55 roundtrip .EE .UNINDENT .UNINDENT .sp Example with count and default tolerance: .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=merc accept 12 55 roundtrip 10000 .EE .UNINDENT .UNINDENT .sp Example with count and tolerance: .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=merc accept 12 55 roundtrip 10000 5 mm .EE .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B direction The \fBdirection\fP command specifies in which direction an operation is performed. This can either be \fBforward\fP or \fBinverse\fP\&. An example of this is seen below where it is tested that a symmetrical transformation pipeline returns the same results in both directions. .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=pipeline zone=32 step proj=utm ellps=GRS80 step proj=utm ellps=GRS80 inv tolerance 0.1 mm accept 12 55 0 0 expect 12 55 0 0 # Now the inverse direction (still same result: the pipeline is symmetrical) direction inverse expect 12 55 0 0 .EE .UNINDENT .UNINDENT .sp The default direction is \(dqforward\(dq. .UNINDENT .INDENT 0.0 .TP .B ignore This is especially useful in test cases that rely on a grid that is not guaranteed to be available. Below is an example of that situation. .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=hgridshift +grids=nzgd2kgrid0005.gsb ellps=GRS80 tolerance 1 mm ignore pjd_err_failed_to_load_grid accept 172.999892181021551 \-45.001620431954613 expect 173 \-45 .EE .UNINDENT .UNINDENT .sp See \fBgie \-\-list\fP for a list of error codes that can be ignored. .UNINDENT .INDENT 0.0 .TP .B require_grid Checks the availability of the grid . If it is not found, then all \fBaccept\fP/\fBexpect\fP pairs until the next \fBoperation\fP will be skipped. \fBrequire_grid\fP can be repeated several times to specify several grids whose presence is required. .UNINDENT .INDENT 0.0 .TP .B echo Add user defined text to the output stream. See the example below. .INDENT 7.0 .INDENT 3.5 .sp .EX echo ** Mercator projection tests ** operation +proj=merc accept 0 0 expect 0 0 .EE .UNINDENT .UNINDENT .sp which returns .INDENT 7.0 .INDENT 3.5 .sp .EX \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- Reading file \(aqtest.gie\(aq ** Mercator projection test ** \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- total: 1 tests succeeded, 0 tests skipped, 0 tests failed. \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- .EE .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B skip Skip any test after the first occurrence of \fBskip\fP\&. In the example below only the first test will be performed. The second test is skipped. This feature is mostly relevant for debugging when writing new test cases. .INDENT 7.0 .INDENT 3.5 .sp .EX operation proj=merc accept 0 0 expect 0 0 skip accept 0 1 expect 0 110579.9 .EE .UNINDENT .UNINDENT .UNINDENT .SH STRICT MODE .sp Added in version 7.1. .sp A stricter variant of normal gie syntax can be used by wrapping gie commands between \fB\fP and \fB\fP\&. In strict mode, comment lines must start with a sharp character. Unknown commands will be considered as an error. A command can still be split on several lines, but intermediate lines must end with the space character followed by backslash to mark the continuation. .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .INDENT 3.5 .sp .EX # This is a comment. The following line with multiple repeated characters too \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- # A command on several lines must use \(dq \e\(dq continuation operation proj=hgridshift +grids=nzgd2kgrid0005.gsb \e ellps=GRS80 tolerance 1 mm ignore pjd_err_failed_to_load_grid accept 172.999892181021551 \-45.001620431954613 expect 173 \-45 .EE .UNINDENT .UNINDENT .UNINDENT .UNINDENT .SH BACKGROUND .sp More importantly than being an acronym for \(dqGeospatial Integrity Investigation Environment\(dq, gie were also the initials, user id, and USGS email address of Gerald Ian Evenden (1935\-\-2016), the geospatial visionary, who, already in the 1980s, started what was to become the PROJ of today. .sp Gerald\(aqs clear vision was that map projections are \fIjust special functions\fP\&. Some of them rather complex, most of them of two variables, but all of them \fIjust special functions\fP, and not particularly more special than the \fBsin()\fP, \fBcos()\fP, \fBtan()\fP, and \fBhypot()\fP already available in the C standard library. .sp And hence, according to Gerald, \fIthey should not be particularly much harder to use\fP, for a programmer, than the \fBsin()\fP\(aqs, \fBtan()\fP\(aqs and \fBhypot()\fP\(aqs so readily available. .sp Gerald\(aqs ingenuity also showed in the implementation of the vision, where he devised a comprehensive, yet simple, system of key\-value pairs for parameterising a map projection, and the highly flexible \fBPJ\fP \%<#\:c\:.PJ> struct, storing run\-time compiled versions of those key\-value pairs, hence making a map projection function call, \fBpj_fwd(PJ, point)\fP, as easy as a traditional function call like \fBhypot(x,y)\fP\&. .sp While today, we may have more formally well defined metadata systems (most prominent the OGC WKT2 representation), nothing comes close being as easily readable (\(dqhuman compatible\(dq) as Gerald\(aqs key\-value system. This system in particular, and the PROJ system in general, was Gerald\(aqs great gift to anyone using and/or communicating about geodata. .sp It is only reasonable to name a program, keeping an eye on the integrity of the PROJ system, in honour of Gerald. .sp So in honour, and hopefully also in the spirit, of Gerald Ian Evenden (1935\-\-2016), this is the Geospatial Integrity Investigation Environment. .SH SEE ALSO .sp \fBproj(1)\fP, \fBcs2cs(1)\fP, \fBcct(1)\fP, \fBgeod(1)\fP, \fBprojinfo(1)\fP, \fBprojsync(1)\fP .SH BUGS .sp A list of known bugs can be found at \% where new bug reports can be submitted to. .SH HOME PAGE .sp \% .SH Author Thomas Knudsen .SH Copyright 1983-2026, PROJ contributors .\" End of generated man page. proj-9.8.1/man/man1/projinfo.1000664 001750 001750 00000070074 15166171715 016002 0ustar00eveneven000000 000000 .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "PROJINFO" "1" "10 avril 2026" "9.8" "PROJ" .SH NAME projinfo \- Geodetic object and coordinate operation queries .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .nf \fBprojinfo\fP .in +2 [\-o formats] [\-k crs|operation|datum|ensemble|ellipsoid] [\-\-summary] [\-q] [[\-\-area name_or_code] | [\-\-bbox west_long,south_lat,east_long,north_lat]] [\-\-spatial\-test contains|intersects] [\-\-crs\-extent\-use none|both|intersection|smallest] [\-\-grid\-check none|discard_missing|sort|known_available] [\-\-pivot\-crs always|if_no_direct_transformation|never|{auth:code[,auth:code]*}] [\-\-show\-superseded] [\-\-hide\-ballpark] [\-\-accuracy {accuracy}] [\-\-allow\-ellipsoidal\-height\-as\-vertical\-crs] [\-\-boundcrs\-to\-wgs84] [\-\-authority name] [\-\-main\-db\-path path] [\-\-aux\-db\-path path]* [\-\-dump\-db\-structure] [\-\-identify] [\-\-3d] [\-\-output\-id AUTH:CODE] [\-\-c\-ify] [\-\-single\-line] \-\-searchpaths | \-\-remote\-data | \-\-list\-crs [list\-crs\-filter] | \-\-dump\-db\-structure [{object_definition} | {object_reference}] | {object_definition} | {object_reference} | (\-s {srs_def} [\-\-s_epoch {epoch}] \-t {srs_def} [\-\-t_epoch {epoch}]) | ({srs_def} {srs_def}) .in -2 .fi .sp .sp where {object_definition} or {srs_def} is one of the possibilities accepted by \fBproj_create()\fP \%<#\:c\:.proj_create> .INDENT 0.0 .IP \(bu 2 a proj\-string, .IP \(bu 2 a WKT string, .IP \(bu 2 an object code (like \(dqEPSG:4326\(dq, \(dqurn:ogc:def:crs:EPSG::4326\(dq, \(dqurn:ogc:def:coordinateOperation:EPSG::1671\(dq), .IP \(bu 2 an Object name. e.g \(dqWGS 84\(dq, \(dqWGS 84 / UTM zone 31N\(dq. In that case as uniqueness is not guaranteed, heuristics are applied to determine the appropriate best match. .IP \(bu 2 a CRS name and a coordinate epoch, separated with \(aq@\(aq. For example \(dq\%\(dq. (\fIadded in 9.2\fP) .IP \(bu 2 a OGC URN combining references for compound coordinate reference systems (e.g \(dq\%\(dq or custom abbreviated syntax \(dqEPSG:2393+5717\(dq), .IP \(bu 2 a OGC URN combining references for references for projected or derived CRSs e.g. for Projected 3D CRS \(dqUTM zone 31N / WGS 84 (3D)\(dq: \(dq\%\(dq (\fIadded in 6.2\fP) .IP \(bu 2 Extension of OGC URN for CoordinateMetadata. e.g. \(dq\%\(dq .IP \(bu 2 a OGC URN combining references for concatenated operations (e.g. \(dq\%\(dq) .IP \(bu 2 a PROJJSON string. The jsonschema is at \% (\fIadded in 6.2\fP) .IP \(bu 2 a compound CRS made from two object names separated with \(dq + \(dq. e.g. \(dqWGS 84 + EGM96 height\(dq (\fIadded in 7.1\fP) .UNINDENT .sp {object_reference} is a filename preceded by the \(aq@\(aq character. The file referenced by the {object_reference} must contain a valid {object_definition}. .sp The usage of \(dq{srs_def} {srs_def}\(dq is equivalent to \(dq\-s {srs_def} \-t {srs_def}\(dq (\fIadded in 9.5\fP). .UNINDENT .UNINDENT .SH DESCRIPTION .sp projinfo is a program that can query information on a geodetic object, coordinate reference system (CRS) or coordinate operation, when the \fB\-s\fP and \fB\-t\fP options are specified, and display it under different formats (PROJ string, WKT string or PROJJSON string). .sp It can also be used to query coordinate operations available between two CRS. .sp The program is named with some reference to the GDAL gdalsrsinfo \% utility that offers partly similar services. .sp The following control parameters can appear in any order: .INDENT 0.0 .TP .B \-o formats formats is a comma separated combination of: \fBall\fP, \fBdefault\fP, \fBPROJ\fP, \fBWKT_ALL\fP, \fBWKT2:2015\fP, \fBWKT2:2019\fP, \fBWKT1:GDAL\fP, \fBWKT1:ESRI\fP, \fBPROJJSON\fP, \fBSQL\fP\&. .sp Except \fBall\fP and \fBdefault\fP, other formats can be preceded by \fB\-\fP to disable them. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 WKT2_2019 was previously called WKT2_2018. .UNINDENT .UNINDENT .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 Before PROJ 6.3.0, WKT1:GDAL was implicitly calling \-\-boundcrs\-to\-wgs84. This is no longer the case. .UNINDENT .UNINDENT .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 When SQL is specified, \fB\-\-output\-id\fP must be specified. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-k crs|operation|datum|ensemble|ellipsoid When used to query a single object with a AUTHORITY:CODE, determines the (k)ind of the object in case there are CRS, coordinate operations or ellipsoids with the same CODE. The default is crs. .UNINDENT .INDENT 0.0 .TP .B \-\-summary When listing coordinate operations available between 2 CRS, return the result in a summary format, mentioning only the name of the coordinate operation, its accuracy and its area of use. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-q Turn on quiet mode. Quiet mode is only available for queries on single objects, and only one output format is selected. In that mode, only the PROJ, WKT or PROJJSON string is displayed, without other introduction output. The output is then potentially compatible of being piped in other utilities. .UNINDENT .INDENT 0.0 .TP .B \-\-area name_or_code Specify an area of interest to restrict the results when researching coordinate operations between 2 CRS. The area of interest can be specified either as a name (e.g \(dqDenmark \- onshore\(dq) or a AUTHORITY:CODE (EPSG:3237) This option is exclusive of \fB\-\-bbox\fP\&. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-bbox west_long,south_lat,east_long,north_lat Specify an area of interest to restrict the results when researching coordinate operations between 2 CRS. The area of interest is specified as a bounding box with geographic coordinates, expressed in degrees in a unspecified geographic CRS. \fIwest_long\fP and \fIeast_long\fP should be in the [\-180,180] range, and \fIsouth_lat\fP and \fInorth_lat\fP in the [\-90,90]. \fIwest_long\fP is generally lower than \fIeast_long\fP, except in the case where the area of interest crosses the antimeridian. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-spatial\-test contains|intersects Specify how the area of use of coordinate operations found in the database are compared to the area of use specified explicitly with \fB\-\-area\fP or \fB\-\-bbox\fP, or derived implicitly from the area of use of the source and target CRS. By default, projinfo will only keep coordinate operations whose are of use is strictly within the area of interest (\fBcontains\fP strategy). If using the \fBintersects\fP strategy, the spatial test is relaxed, and any coordinate operation whose area of use at least partly intersects the area of interest is listed. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-crs\-extent\-use none|both|intersection|smallest Specify which area of interest to consider when no explicit one is specified with \fB\-\-area\fP or \fB\-\-bbox\fP options. By default (\fBsmallest\fP strategy), the area of use of the source or target CRS will be looked, and the one that is the smallest one in terms of area will be used as the area of interest. If using \fBnone\fP, no area of interest is used. If using \fBboth\fP, only coordinate operations that relate (contain or intersect depending of the \fB\-\-spatial\-test\fP strategy) to the area of use of both CRS are selected. If using \fBintersection\fP, the area of interest is the intersection of the bounding box of the area of use of the source and target CRS .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-grid\-check none|discard_missing|sort|known_available Specify how the presence or absence of a horizontal or vertical shift grid required for a coordinate operation affects the results returned when researching coordinate operations between 2 CRS. The default strategy is \fBsort\fP (if \fBPROJ_NETWORK\fP \%<#\:envvar-PROJ_NETWORK> is not defined). In that case, all candidate operations are returned, but the actual availability of the grids is used to determine the sorting order. That is, if a coordinate operation involves using a grid that is not available in the PROJ resource directories (determined by the \fBPROJ_DATA\fP \%<#\:envvar-PROJ_DATA> environment variable), it will be listed in the bottom of the results. The \fBnone\fP strategy completely disables the checks of presence of grids and this returns the results as if all the grids where available. The \fBdiscard_missing\fP strategy discards results that involve grids not present in the PROJ resource directories. The \fBknown_available\fP strategy discards results that involve grids not present in the PROJ resource directories and that are not known of the CDN. This is the default strategy is \fBPROJ_NETWORK\fP \%<#\:envvar-PROJ_NETWORK> is set to \fBON\fP\&. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-pivot\-crs always|if_no_direct_transformation|never|{auth:code[,auth:code]*} Determine if intermediate (pivot) CRS can be used when researching coordinate operation between 2 CRS. A typical example is the WGS84 pivot. By default, projinfo will consider any potential pivot if there is no direct transformation ( \fBif_no_direct_transformation\fP). If using the \fBnever\fP strategy, only direct transformations between the source and target CRS will be used. If using the \fBalways\fP strategy, intermediate CRS will be considered even if there are direct transformations. It is also possible to restrict the pivot CRS to consider by specifying one or several CRS by their AUTHORITY:CODE. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-show\-superseded When enabled, coordinate operations that are superseded by others will be listed. Note that supersession is not equivalent to deprecation: superseded operations are still considered valid although they have a better equivalent, whereas deprecated operations have been determined to be erroneous and are not considered at all. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-hide\-ballpark Added in version 7.1. .sp Hides any coordinate operation that is, or contains, a Ballpark transformation \%<#\:term-Ballpark-transformation> .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-accuracy {accuracy} Added in version 8.0. .sp Sets the minimum desired accuracy for returned coordinate operations. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for coordinate operation computation .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-allow\-ellipsoidal\-height\-as\-vertical\-crs Added in version 8.0. .sp Allows exporting a geographic or projected 3D CRS as a compound CRS whose vertical CRS represents the ellipsoidal height. .sp \fBNote:\fP .INDENT 7.0 .INDENT 3.5 only used for CRS, and with WKT1:GDAL output format .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-boundcrs\-to\-wgs84 When specified, this option researches a coordinate operation from the base geographic CRS of the single CRS, source or target CRS to the WGS84 geographic CRS, and if found, wraps those CRS into a BoundCRS object. This is mostly to be used for early\-binding approaches. .UNINDENT .INDENT 0.0 .TP .B \-\-authority name Specify the name of the authority into which to restrict looks up for objects, when specifying an object by name or when coordinate operations are computed. The default is to allow all authorities. .sp When used with SQL output, this restricts the authorities to which intermediate objects can belong to (the default is EPSG and PROJ). Note that the authority of the \fB\-\-output\-id\fP option will also be implicitly added. .UNINDENT .INDENT 0.0 .TP .B \-\-main\-db\-path path Specify the name and path of the database to be used by projinfo\&. The default is \fBproj.db\fP in the PROJ resource directories. .UNINDENT .INDENT 0.0 .TP .B \-\-aux\-db\-path path Specify the name and path of auxiliary databases, that are to be combined with the main database. Those auxiliary databases must have a table structure that is identical to the main database, but can be partly filled and their entries can refer to entries of the main database. The option may be repeated to specify several auxiliary databases. .UNINDENT .INDENT 0.0 .TP .B \-\-identify When used with an object definition, this queries the PROJ database to find known objects, typically CRS, that are close or identical to the object. Each candidate object is associated with an approximate likelihood percentage. This is useful when used with a WKT string that lacks a EPSG identifier, such as ESRI WKT1. This might also be used with PROJ strings. For example, \fI+proj=utm +zone=31 +datum=WGS84 +type=crs\fP will be identified with a likelihood of 70% to EPSG:32631 .UNINDENT .INDENT 0.0 .TP .B \-\-dump\-db\-structure Added in version 8.1. .sp Outputs the sequence of SQL statements to create a new empty valid auxiliary database. This option can be specified as the only switch of the utility. If also specifying a CRS object and the \fB\-\-output\-id\fP option, the definition of the object as SQL statements will be appended. .UNINDENT .INDENT 0.0 .TP .B \-\-list\-crs [list\-crs\-filter] Added in version 8.1. .sp Outputs a list (authority name:code and CRS name) of the filtered CRSs from the database. If no filter is provided all authority names and types of non deprecated CRSs are dumped. list\-crs\-filter is a comma separated combination of: allow_deprecated,geodetic,geocentric, geographic,geographic_2d,geographic_3d,vertical,projected,compound. Affected by options \fB\-\-authority\fP, \fB\-\-area\fP, \fB\-\-bbox\fP and \fB\-\-spatial\-test\fP .sp A visual alternative is the webpage CRS Explorer \% . .UNINDENT .INDENT 0.0 .TP .B \-\-3d Added in version 6.3. .sp \(dqPromote\(dq 2D CRS(s) to their 3D version, where the vertical axis is the ellipsoidal height in metres, using the ellipsoid of the base geodetic CRS. Depending on PROJ versions and the exact nature of the CRS involved, especially before PROJ 9.1, a mix of 2D and 3D CRS could lead to 2D or 3D transformations. Starting with PROJ 9.1, both CRS need to be 3D for vertical transformation to possibly happen. .UNINDENT .INDENT 0.0 .TP .B \-\-output\-id=AUTH:NAME Added in version 8.1. .sp Identifier to assign to the object (for SQL output). .sp It is strongly recommended that new objects should not be added in common registries, such as \fBEPSG\fP, \fBESRI\fP, \fBIAU\fP, etc. Users should use a custom authority name instead. If a new object should be added to the official EPSG registry, users are invited to follow the procedure explained at \%\&. .sp Combined with \fB\-\-dump\-db\-structure\fP, users can create auxiliary databases, instead of directly modifying the main \fBproj.db\fP database. See the example how to export to an auxiliary database\&. .sp Those auxiliary databases can be specified through \fBproj_context_set_database_path()\fP or the \fBPROJ_AUX_DB\fP \%<#\:envvar-PROJ_AUX_DB> environment variable. .UNINDENT .INDENT 0.0 .TP .B \-\-c\-ify For developers only. Modify the string output of the utility so that it is easy to put those strings in C/C++ code .UNINDENT .INDENT 0.0 .TP .B \-\-single\-line Output PROJ, WKT or PROJJSON strings on a single line, instead of multiple indented lines by default. .UNINDENT .INDENT 0.0 .TP .B \-\-searchpaths Added in version 7.0. .sp Output the directories into which PROJ resources will be looked for (if not using C API such as \fBproj_context_set_search_paths()\fP that will override them. .UNINDENT .INDENT 0.0 .TP .B \-\-remote\-data Added in version 7.0. .sp Display information regarding if Network capabilities \%<#\:network> is enabled, and the related URL. .UNINDENT .INDENT 0.0 .TP .B \-\-s_epoch Added in version 9.4. .sp Epoch of coordinates in the source CRS, as decimal year. Only applies to a dynamic CRS. .UNINDENT .INDENT 0.0 .TP .B \-\-t_epoch Added in version 9.4. .sp Epoch of coordinates in the target CRS, as decimal year. Only applies to a dynamic CRS. .UNINDENT .SH EXAMPLES .INDENT 0.0 .IP 1. 3 Query the CRS object corresponding to EPSG:4326 .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX projinfo EPSG:4326 .EE .UNINDENT .UNINDENT .sp Output: .INDENT 0.0 .INDENT 3.5 .sp .EX PROJ.4 string: +proj=longlat +datum=WGS84 +no_defs +type=crs WKT2:2019 string: GEOGCRS[\(dqWGS 84\(dq, DATUM[\(dqWorld Geodetic System 1984\(dq, ELLIPSOID[\(dqWGS 84\(dq,6378137,298.257223563, LENGTHUNIT[\(dqmetre\(dq,1]]], PRIMEM[\(dqGreenwich\(dq,0, ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], CS[ellipsoidal,2], AXIS[\(dqgeodetic latitude (Lat)\(dq,north, ORDER[1], ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], AXIS[\(dqgeodetic longitude (Lon)\(dq,east, ORDER[2], ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], USAGE[ SCOPE[\(dqunknown\(dq], AREA[\(dqWorld\(dq], BBOX[\-90,\-180,90,180]], ID[\(dqEPSG\(dq,4326]] .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP 2. 3 List the coordinate operations between NAD27 (designed with its CRS name) and NAD83 (designed with its EPSG code 4269) within an area of interest .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX projinfo \-s NAD27 \-t EPSG:4269 \-\-area \(dqUSA \- Missouri\(dq .EE .UNINDENT .UNINDENT .sp Output: .INDENT 0.0 .INDENT 3.5 .sp .EX DERIVED_FROM(EPSG):1241, NAD27 to NAD83 (1), 0.15 m, USA \- CONUS including EEZ PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert \e +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=conus \e +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION[\(dqNAD27 to NAD83 (1)\(dq, SOURCECRS[ GEOGCRS[\(dqNAD27\(dq, DATUM[\(dqNorth American Datum 1927\(dq, ELLIPSOID[\(dqClarke 1866\(dq,6378206.4,294.978698213898, LENGTHUNIT[\(dqmetre\(dq,1]]], PRIMEM[\(dqGreenwich\(dq,0, ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], CS[ellipsoidal,2], AXIS[\(dqgeodetic latitude (Lat)\(dq,north, ORDER[1], ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], AXIS[\(dqgeodetic longitude (Lon)\(dq,east, ORDER[2], ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]]]], TARGETCRS[ GEOGCRS[\(dqNAD83\(dq, DATUM[\(dqNorth American Datum 1983\(dq, ELLIPSOID[\(dqGRS 1980\(dq,6378137,298.257222101, LENGTHUNIT[\(dqmetre\(dq,1]]], PRIMEM[\(dqGreenwich\(dq,0, ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], CS[ellipsoidal,2], AXIS[\(dqgeodetic latitude (Lat)\(dq,north, ORDER[1], ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], AXIS[\(dqgeodetic longitude (Lon)\(dq,east, ORDER[2], ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]]]], METHOD[\(dqCTABLE2\(dq], PARAMETERFILE[\(dqLatitude and longitude difference file\(dq,\(dqconus\(dq], OPERATIONACCURACY[0.15], USAGE[ SCOPE[\(dqunknown\(dq], AREA[\(dqUSA \- CONUS including EEZ\(dq], BBOX[23.81,\-129.17,49.38,\-65.69]], ID[\(dqDERIVED_FROM(EPSG)\(dq,1241]] .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP 3. 3 Export an object as a PROJJSON string .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX projinfo GDA94 \-o PROJJSON \-q .EE .UNINDENT .UNINDENT .sp Output: .INDENT 0.0 .INDENT 3.5 .sp .EX { \(dqtype\(dq: \(dqGeographicCRS\(dq, \(dqname\(dq: \(dqGDA94\(dq, \(dqdatum\(dq: { \(dqtype\(dq: \(dqGeodeticReferenceFrame\(dq, \(dqname\(dq: \(dqGeocentric Datum of Australia 1994\(dq, \(dqellipsoid\(dq: { \(dqname\(dq: \(dqGRS 1980\(dq, \(dqsemi_major_axis\(dq: 6378137, \(dqinverse_flattening\(dq: 298.257222101 } }, \(dqcoordinate_system\(dq: { \(dqsubtype\(dq: \(dqellipsoidal\(dq, \(dqaxis\(dq: [ { \(dqname\(dq: \(dqGeodetic latitude\(dq, \(dqabbreviation\(dq: \(dqLat\(dq, \(dqdirection\(dq: \(dqnorth\(dq, \(dqunit\(dq: \(dqdegree\(dq }, { \(dqname\(dq: \(dqGeodetic longitude\(dq, \(dqabbreviation\(dq: \(dqLon\(dq, \(dqdirection\(dq: \(dqeast\(dq, \(dqunit\(dq: \(dqdegree\(dq } ] }, \(dqarea\(dq: \(dqAustralia \- GDA\(dq, \(dqbbox\(dq: { \(dqsouth_latitude\(dq: \-60.56, \(dqwest_longitude\(dq: 93.41, \(dqnorth_latitude\(dq: \-8.47, \(dqeast_longitude\(dq: 173.35 }, \(dqid\(dq: { \(dqauthority\(dq: \(dqEPSG\(dq, \(dqcode\(dq: 4283 } } .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP 4. 3 Exporting the SQL statements to insert a new CRS in an auxiliary database. .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX # Get the SQL statements for a custom CRS projinfo \(dq+proj=merc +lat_ts=5 +datum=WGS84 +type=crs +title=my_crs\(dq \-\-output\-id HOBU:MY_CRS \-o SQL \-q > my_crs.sql cat my_crs.sql # Initialize an auxiliary database with the schema of the reference database echo \(dq.schema\(dq | sqlite3 /path/to/proj.db | sqlite3 aux.db # Append the content of the definition of HOBU:MY_CRS sqlite3 aux.db < my_crs.db # Check that everything works OK projinfo \-\-aux\-db\-path aux.db HOBU:MY_CRS .EE .UNINDENT .UNINDENT .sp or more simply: .INDENT 0.0 .INDENT 3.5 .sp .EX # Create an auxiliary database with the definition of a custom CRS. projinfo \(dq+proj=merc +lat_ts=5 +datum=WGS84 +type=crs +title=my_crs\(dq \-\-output\-id HOBU:MY_CRS \-\-dump\-db\-structure | sqlite3 aux.db # Check that everything works OK projinfo \-\-aux\-db\-path aux.db HOBU:MY_CRS .EE .UNINDENT .UNINDENT .sp Output: .INDENT 0.0 .INDENT 3.5 .sp .EX INSERT INTO geodetic_crs VALUES(\(aqHOBU\(aq,\(aqGEODETIC_CRS_MY_CRS\(aq,\(aqunknown\(aq,\(aq\(aq,\(aqgeographic 2D\(aq,\(aqEPSG\(aq,\(aq6424\(aq,\(aqEPSG\(aq,\(aq6326\(aq,NULL,0); INSERT INTO usage VALUES(\(aqHOBU\(aq,\(aqUSAGE_GEODETIC_CRS_MY_CRS\(aq,\(aqgeodetic_crs\(aq,\(aqHOBU\(aq,\(aqGEODETIC_CRS_MY_CRS\(aq,\(aqPROJ\(aq,\(aqEXTENT_UNKNOWN\(aq,\(aqPROJ\(aq,\(aqSCOPE_UNKNOWN\(aq); INSERT INTO conversion VALUES(\(aqHOBU\(aq,\(aqCONVERSION_MY_CRS\(aq,\(aqunknown\(aq,\(aq\(aq,\(aqEPSG\(aq,\(aq9805\(aq,\(aqMercator (variant B)\(aq,\(aqEPSG\(aq,\(aq8823\(aq,\(aqLatitude of 1st standard parallel\(aq,5,\(aqEPSG\(aq,\(aq9122\(aq,\(aqEPSG\(aq,\(aq8802\(aq,\(aqLongitude of natural origin\(aq,0,\(aqEPSG\(aq,\(aq9122\(aq,\(aqEPSG\(aq,\(aq8806\(aq,\(aqFalse easting\(aq,0,\(aqEPSG\(aq,\(aq9001\(aq,\(aqEPSG\(aq,\(aq8807\(aq,\(aqFalse northing\(aq,0,\(aqEPSG\(aq,\(aq9001\(aq,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0); INSERT INTO usage VALUES(\(aqHOBU\(aq,\(aqUSAGE_CONVERSION_MY_CRS\(aq,\(aqconversion\(aq,\(aqHOBU\(aq,\(aqCONVERSION_MY_CRS\(aq,\(aqPROJ\(aq,\(aqEXTENT_UNKNOWN\(aq,\(aqPROJ\(aq,\(aqSCOPE_UNKNOWN\(aq); INSERT INTO projected_crs VALUES(\(aqHOBU\(aq,\(aqMY_CRS\(aq,\(aqmy_crs\(aq,\(aq\(aq,\(aqEPSG\(aq,\(aq4400\(aq,\(aqHOBU\(aq,\(aqGEODETIC_CRS_MY_CRS\(aq,\(aqHOBU\(aq,\(aqCONVERSION_MY_CRS\(aq,NULL,0); INSERT INTO usage VALUES(\(aqHOBU\(aq,\(aqUSAGE_PROJECTED_CRS_MY_CRS\(aq,\(aqprojected_crs\(aq,\(aqHOBU\(aq,\(aqMY_CRS\(aq,\(aqPROJ\(aq,\(aqEXTENT_UNKNOWN\(aq,\(aqPROJ\(aq,\(aqSCOPE_UNKNOWN\(aq); .EE .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX PROJ.4 string: +proj=merc +lat_ts=5 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs +type=crs WKT2:2019 string: PROJCRS[\(dqmy_crs\(dq, BASEGEOGCRS[\(dqunknown\(dq, ENSEMBLE[\(dqWorld Geodetic System 1984 ensemble\(dq, MEMBER[\(dqWorld Geodetic System 1984 (Transit)\(dq], MEMBER[\(dqWorld Geodetic System 1984 (G730)\(dq], MEMBER[\(dqWorld Geodetic System 1984 (G873)\(dq], MEMBER[\(dqWorld Geodetic System 1984 (G1150)\(dq], MEMBER[\(dqWorld Geodetic System 1984 (G1674)\(dq], MEMBER[\(dqWorld Geodetic System 1984 (G1762)\(dq], ELLIPSOID[\(dqWGS 84\(dq,6378137,298.257223563, LENGTHUNIT[\(dqmetre\(dq,1]], ENSEMBLEACCURACY[2.0]], PRIMEM[\(dqGreenwich\(dq,0, ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433]], ID[\(dqHOBU\(dq,\(dqGEODETIC_CRS_MY_CRS\(dq]], CONVERSION[\(dqunknown\(dq, METHOD[\(dqMercator (variant B)\(dq, ID[\(dqEPSG\(dq,9805]], PARAMETER[\(dqLatitude of 1st standard parallel\(dq,5, ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433], ID[\(dqEPSG\(dq,8823]], PARAMETER[\(dqLongitude of natural origin\(dq,0, ANGLEUNIT[\(dqdegree\(dq,0.0174532925199433], ID[\(dqEPSG\(dq,8802]], PARAMETER[\(dqFalse easting\(dq,0, LENGTHUNIT[\(dqmetre\(dq,1], ID[\(dqEPSG\(dq,8806]], PARAMETER[\(dqFalse northing\(dq,0, LENGTHUNIT[\(dqmetre\(dq,1], ID[\(dqEPSG\(dq,8807]]], CS[Cartesian,2], AXIS[\(dq(E)\(dq,east, ORDER[1], LENGTHUNIT[\(dqmetre\(dq,1]], AXIS[\(dq(N)\(dq,north, ORDER[2], LENGTHUNIT[\(dqmetre\(dq,1]], ID[\(dqHOBU\(dq,\(dqMY_CRS\(dq]] .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP 5. 3 Get the WKT representation of EPSG:25832 in the WKT1:GDAL output format and on a single line .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX projinfo \-o WKT1:GDAL \-\-single\-line EPSG:25832 .EE .UNINDENT .UNINDENT .sp Output: .INDENT 0.0 .INDENT 3.5 .sp .EX WKT1:GDAL string: PROJCS[\(dqETRS89 / UTM zone 32N\(dq,GEOGCS[\(dqETRS89\(dq,DATUM[\(dqEuropean_Terrestrial_Reference_System_1989\(dq,SPHEROID[\(dqGRS 1980\(dq,6378137,298.257222101,AUTHORITY[\(dqEPSG\(dq,\(dq7019\(dq]],AUTHORITY[\(dqEPSG\(dq,\(dq6258\(dq]],PRIMEM[\(dqGreenwich\(dq,0,AUTHORITY[\(dqEPSG\(dq,\(dq8901\(dq]],UNIT[\(dqdegree\(dq,0.0174532925199433,AUTHORITY[\(dqEPSG\(dq,\(dq9122\(dq]],AUTHORITY[\(dqEPSG\(dq,\(dq4258\(dq]],PROJECTION[\(dqTransverse_Mercator\(dq],PARAMETER[\(dqlatitude_of_origin\(dq,0],PARAMETER[\(dqcentral_meridian\(dq,9],PARAMETER[\(dqscale_factor\(dq,0.9996],PARAMETER[\(dqfalse_easting\(dq,500000],PARAMETER[\(dqfalse_northing\(dq,0],UNIT[\(dqmetre\(dq,1,AUTHORITY[\(dqEPSG\(dq,\(dq9001\(dq]],AXIS[\(dqEasting\(dq,EAST],AXIS[\(dqNorthing\(dq,NORTH],AUTHORITY[\(dqEPSG\(dq,\(dq25832\(dq]] .EE .UNINDENT .UNINDENT .SH SEE ALSO .sp \fBcs2cs(1)\fP, \fBcct(1)\fP, \fBgeod(1)\fP, \fBgie(1)\fP, \fBproj(1)\fP, \fBprojsync(1)\fP .SH BUGS .sp A list of known bugs can be found at \% where new bug reports can be submitted to. .SH HOME PAGE .sp \% .SH Author Even Rouault .SH Copyright 1983-2026, PROJ contributors .\" End of generated man page. proj-9.8.1/man/man1/geod.1000664 001750 001750 00000017540 15166171715 015071 0ustar00eveneven000000 000000 .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "GEOD" "1" "10 avril 2026" "9.8" "PROJ" .SH NAME geod \- Geodesic computations .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 \fBgeod\fP \fI+ellps=\fP [\fB\-afFIlptwW\fP [args]] [\fI+opt[=arg]\fP ...] file ... .sp \fBinvgeod\fP \fI+ellps=\fP [\fB\-afFIlptwW\fP [args]] [\fI+opt[=arg]\fP ...] file ... .UNINDENT .UNINDENT .SH DESCRIPTION .sp geod (direct) and invgeod (inverse) perform geodesic (Great Circle) computations for determining latitude, longitude and back azimuth of a terminus point given a initial point latitude, longitude, azimuth and distance (direct) or the forward and back azimuths and distance between an initial and terminus point latitudes and longitudes (inverse). The results are accurate to round off for |f| < 1/50, where f is flattening. .sp invgeod may not be available on all platforms; in this case use \fBgeod \-I\fP instead. .sp The following command\-line options can appear in any order: .INDENT 0.0 .TP .B \-I Specifies that the inverse geodesic computation is to be performed. May be used with execution of geod as an alternative to invgeod execution. .UNINDENT .INDENT 0.0 .TP .B \-a Latitude and longitudes of the initial and terminal points, forward and back azimuths and distance are output. .UNINDENT .INDENT 0.0 .TP .B \-t Where \fIa\fP specifies a character employed as the first character to denote a control line to be passed through without processing. .UNINDENT .INDENT 0.0 .TP .B \-le Gives a listing of all the ellipsoids that may be selected with the \fI+ellps=\fP option. .UNINDENT .INDENT 0.0 .TP .B \-lu Gives a listing of all the units that may be selected with the \fI+units=\fP option. (Default units are meters.) .UNINDENT .INDENT 0.0 .TP .B \-f Where \fIformat\fP is a printf format string to control the output form of the geographic coordinate and azimuth values. The default mode is DMS. .UNINDENT .INDENT 0.0 .TP .B \-F Where \fIformat\fP is a printf format string to control the output form of the distance value. The default mode is \fB\(dq%.3f\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-w Where \fIn\fP is the number of significant fractional digits to employ for seconds output (when the option is not specified, \fB\-w3\fP is assumed). .UNINDENT .INDENT 0.0 .TP .B \-W Where \fIn\fP is the number of significant fractional digits to employ for seconds output. When \fB\-W\fP is employed the fields will be constant width with leading zeroes. .UNINDENT .INDENT 0.0 .TP .B \-p This option causes the azimuthal values to be output as unsigned DMS numbers between 0 and 360 degrees. Also note \fB\-f\fP\&. .UNINDENT .sp The \fI+opt\fP command\-line options are associated with geodetic parameters for specifying the ellipsoidal or sphere to use. controls. The options are processed in left to right order from the command line. Reentry of an option is ignored with the first occurrence assumed to be the desired value. .sp See the PROJ documentation for a full list of these parameters and controls. .sp One or more files (processed in left to right order) specify the source of data to be transformed. A \fB\-\fP will specify the location of processing standard input. If no files are specified, the input is assumed to be from stdin. .sp For direct determinations input data must be in latitude, longitude, azimuth and distance order and output will be latitude, longitude and back azimuth of the terminus point. Latitude, longitude of the initial and terminus point are input for the inverse mode and respective forward and back azimuth from the initial and terminus points are output along with the distance between the points. .sp Input geographic coordinates (latitude and longitude) and azimuthal data must be in decimal degrees or DMS format and input distance data must be in units consistent with the ellipsoid major axis or sphere radius units. The latitude must lie in the range [\-90d,90d]. Output geographic coordinates and azimuths will be in DMS (if the \fB\-f\fP switch is not employed) to 0.001\(dq with trailing, zero\-valued minute\-second fields deleted. Output distance data will be in the same units as the ellipsoid or sphere radius. .sp The Earth\(aqs ellipsoidal figure may be selected in the same manner as program proj \%<#\:proj> by using \fI+ellps=\fP, \fI+a=\fP, \fI+es=\fP, etc. .sp geod may also be used to determine intermediate points along either a geodesic line between two points or along an arc of specified distance from a geographic point. In both cases an initial point must be specified with \fI+lat_1=lat\fP and \fI+lon_1=long\fP parameters and either a terminus point \fI+lat_2=lat\fP and \fI+lon_2=long\fP or a distance and azimuth from the initial point with \fI+S=distance\fP and \fI+A=azimuth\fP must be specified. .sp If points along a geodesic are to be determined then either \fI+n_S=integer\fP specifying the number of intermediate points and/or \fI+del_S=distance\fP specifying the incremental distance between points must be specified. .sp To determine points along an arc equidistant from the initial point both \fI+del_A=angle\fP and \fI+n_A=integer\fP must be specified which determine the respective angular increments and number of points to be determined. .SH EXAMPLES .sp The following script determines the geodesic azimuths and distance in U.S. statute miles from Boston, MA, to Portland, OR: .INDENT 0.0 .INDENT 3.5 .sp .EX geod +ellps=clrk66 \-I +units=us\-mi <\&. .IP 2. 3 C. F. F. Karney, Algorithms for Geodesics \%, J. Geodesy \fB87\fP(1), 43–55 (2013); addenda \%\&. .IP 3. 3 A geodesic bibliography \%\&. .UNINDENT .SH SEE ALSO .sp \fBproj(1)\fP, \fBcs2cs(1)\fP, \fBcct(1)\fP, \fBgie(1)\fP, \fBprojinfo(1)\fP, \fBprojsync(1)\fP .SH BUGS .sp A list of known bugs can be found at \% where new bug reports can be submitted to. .SH HOME PAGE .sp \% .SH Author Charles Karney .SH Copyright 1983-2026, PROJ contributors .\" End of generated man page. proj-9.8.1/man/man1/projsync.1000664 001750 001750 00000014234 15166171715 016017 0ustar00eveneven000000 000000 .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "PROJSYNC" "1" "10 avril 2026" "9.8" "PROJ" .SH NAME projsync \- Downloading tool of resource files .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .nf \fBprojsync\fP .in +2 [\-\-endpoint URL] [\-\-local\-geojson\-file FILENAME] ([\-\-user\-writable\-directory] | [\-\-system\-directory] | [\-\-target\-dir DIRNAME]) [\-\-bbox west_long,south_lat,east_long,north_lat] [\-\-spatial\-test contains|intersects] [\-\-source\-id ID] [\-\-area\-of\-use NAME] [\-\-file NAME] [\-\-all] [\-\-exclude\-world\-coverage] [\-\-quiet | \-\-verbose] [\-\-dry\-run] [\-\-list\-files] [\-\-no\-version\-filtering] .in -2 .fi .sp .UNINDENT .UNINDENT .SH DESCRIPTION .sp projsync is a program that downloads remote resource files into a local directory. This is an alternative to downloading a proj\-data\-X.Y.Z archive file, or using the on\-demand networking capabilities \%<#\:network> of PROJ. .sp The following control parameters can appear in any order: .INDENT 0.0 .TP .B \-\-endpoint URL Defines the URL where to download the master \fBfiles.geojson\fP file and then the resource files. Defaults to the value set in proj.ini \%<#\:proj-ini> .UNINDENT .INDENT 0.0 .TP .B \-\-local\-geojson\-file FILENAME Defines the filename for the master GeoJSON files that references resources. Defaults to \fB${endpoint}/files.geojson\fP .UNINDENT .INDENT 0.0 .TP .B \-\-user\-writable\-directory Specifies that resource files must be downloaded in the user writable directory \%<#\:user-writable-directory>\&. This is the default. .UNINDENT .INDENT 0.0 .TP .B \-\-system\-directory Specifies that resource files must be downloaded in the ${installation_prefix}/share/proj directory. The user launching projsync should make sure it has writing rights in that directory. .UNINDENT .INDENT 0.0 .TP .B \-\-target\-dir DIRNAME Directory into which resource files must be downloaded. .UNINDENT .INDENT 0.0 .TP .B \-\-bbox west_long,south_lat,east_long,north_lat Specify an area of interest to restrict the resources to download. The area of interest is specified as a bounding box with geographic coordinates, expressed in degrees in a unspecified geographic CRS. \fIwest_long\fP and \fIeast_long\fP should be in the [\-180,180] range, and \fIsouth_lat\fP and \fInorth_lat\fP in the [\-90,90]. \fIwest_long\fP is generally lower than \fIeast_long\fP, except in the case where the area of interest crosses the antimeridian. .UNINDENT .INDENT 0.0 .TP .B \-\-spatial\-test contains|intersects Specify how the extent of the resource files are compared to the area of use specified explicitly with \fB\-\-bbox\fP\&. By default, any resource files whose extent intersects the value specified by \fB\-\-bbox\fP will be selected. If using the \fBcontains\fP strategy, only resource files whose extent is contained in the value specified by \fB\-\-bbox\fP will be selected. .UNINDENT .INDENT 0.0 .TP .B \-\-source\-id ID Restrict resource files to be downloaded to those whose source_id property contains the ID value. Specifying \fB?\fP as ID will list all possible values. .UNINDENT .INDENT 0.0 .TP .B \-\-area\-of\-use NAME Restrict resource files to be downloaded to those whose area_of_use property contains the NAME value. Specifying \fB?\fP as NAME will list all possible values. .UNINDENT .INDENT 0.0 .TP .B \-\-file NAME Restrict resource files to be downloaded to those whose name property contains the NAME value. Specifying \fB?\fP as NAME will list all possible values. .UNINDENT .INDENT 0.0 .TP .B \-\-all Ask to download all files. .UNINDENT .INDENT 0.0 .TP .B \-\-exclude\-world\-coverage Exclude files which have world coverage. .UNINDENT .INDENT 0.0 .TP .B \-q / \-\-quiet Quiet mode .UNINDENT .INDENT 0.0 .TP .B \-\-verbose Added in version 8.1. .sp Verbose mode (more than default) .UNINDENT .INDENT 0.0 .TP .B \-\-dry\-run Simulate the behavior of the tool without downloading resource files. .UNINDENT .INDENT 0.0 .TP .B \-\-list\-files List file names, with the source_id and area_of_use properties. .UNINDENT .INDENT 0.0 .TP .B \-\-no\-version\-filtering Added in version 8.1. .sp By default, projsync only downloads files that are compatible of the PROJ_DATA.VERSION metadata of \fBproj.db\fP, taking into account the \fBversion_added\fP and \fBversion_removed\fP properties of entries in \fBfiles.geojson\fP\&. When specifying this switch, all files referenced in \fBfiles.geojson\fP will be candidate (combined with other filters). .UNINDENT .sp At least one of \fB\-\-list\-files\fP, \fB\-\-file\fP, \fB\-\-source\-id\fP, \fB\-\-area\-of\-use\fP, \fB\-\-bbox\fP or \fB\-\-all\fP must be specified. .sp Options \fB\-\-file\fP, \fB\-\-source\-id\fP, \fB\-\-area\-of\-use\fP and \fB\-\-bbox\fP are combined with a AND logic. .SH EXAMPLES .INDENT 0.0 .IP 1. 3 Download all resource files .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX projsync \-\-all .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP 2. 3 Download resource files covering specified point and attributed to an agency .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX projsync \-\-source\-id fr_ign \-\-bbox 2,49,2,49 .EE .UNINDENT .UNINDENT .SH SEE ALSO .sp \fBcs2cs(1)\fP, \fBcct(1)\fP, \fBgeod(1)\fP, \fBgie(1)\fP, \fBproj(1)\fP, \fBprojinfo(1)\fP .SH BUGS .sp A list of known bugs can be found at \% where new bug reports can be submitted to. Bugs specific to resource files should be submitted to \% .SH HOME PAGE .sp \% .SH Author Even Rouault .SH Copyright 1983-2026, PROJ contributors .\" End of generated man page. proj-9.8.1/man/man1/cs2cs.1000664 001750 001750 00000036774 15166171715 015202 0ustar00eveneven000000 000000 .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "CS2CS" "1" "10 avril 2026" "9.8" "PROJ" .SH NAME cs2cs \- Cartographic coordinate system filter .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .nf \fBcs2cs\fP [\fB\-eEfIlrstvwW\fP [args]] .in +2 [[\-\-area ] | [\-\-bbox ]] [\-\-authority ] [\-\-3d] [\-\-accuracy ] [\-\-only\-best[=yes|=no]] [\-\-no\-ballpark] [\-\-s_epoch {epoch}] [\-\-t_epoch {epoch}] ([\fI+opt[=arg]\fP ...] [+to \fI+opt[=arg]\fP ...] | {source_crs} {target_crs}) file ... .in -2 .fi .sp .sp where {source_crs} or {target_crs} is one of the possibilities accepted by \fBproj_create()\fP \%<#\:c\:.proj_create>, provided it expresses a CRS .INDENT 0.0 .IP \(bu 2 a proj\-string, .IP \(bu 2 a WKT string, .IP \(bu 2 an object code (like \(dqEPSG:4326\(dq, \(dqurn:ogc:def:crs:EPSG::4326\(dq, \(dqurn:ogc:def:coordinateOperation:EPSG::1671\(dq), .IP \(bu 2 an Object name. e.g \(dqWGS 84\(dq, \(dqWGS 84 / UTM zone 31N\(dq. In that case as uniqueness is not guaranteed, heuristics are applied to determine the appropriate best match. .IP \(bu 2 a CRS name and a coordinate epoch, separated with \(aq@\(aq. For example \(dq\%\(dq. (\fIadded in 9.2\fP) .IP \(bu 2 a OGC URN combining references for compound coordinate reference systems (e.g \(dq\%\(dq or custom abbreviated syntax \(dqEPSG:2393+5717\(dq), .IP \(bu 2 a OGC URN combining references for references for projected or derived CRSs e.g. for Projected 3D CRS \(dqUTM zone 31N / WGS 84 (3D)\(dq: \(dq\%\(dq (\fIadded in 6.2\fP) .IP \(bu 2 a OGC URN combining references for concatenated operations (e.g. \(dq\%\(dq) .IP \(bu 2 a PROJJSON string. The jsonschema is at \% (\fIadded in 6.2\fP) .IP \(bu 2 a compound CRS made from two object names separated with \(dq + \(dq. e.g. \(dqWGS 84 + EGM96 height\(dq (\fIadded in 7.1\fP) .UNINDENT .sp Added in version 6.0.0. .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 before 7.0.1, it was needed to add +to between {source_crs} and {target_crs} when adding a filename .UNINDENT .UNINDENT .UNINDENT .UNINDENT .SH DESCRIPTION .sp cs2cs performs transformation between the source and destination cartographic coordinate reference system on a set of input points. The coordinate reference system transformation can include translation between projected and geographic coordinates as well as the application of datum shifts. .sp The following control parameters can appear in any order: .INDENT 0.0 .TP .B \-I Perform the inverse transformation, that is from target CRS to source CRS. .UNINDENT .INDENT 0.0 .TP .B \-t Where \fIa\fP specifies a character employed as the first character to denote a control line to be passed through without processing. This option applicable to ASCII input only. (# is the default value). .UNINDENT .INDENT 0.0 .TP .B \-d Added in version 5.2.0. .sp Specify the number of decimals to round to in the output. .UNINDENT .INDENT 0.0 .TP .B \-e Where \fIstring\fP is an arbitrary string to be output if an error is detected during data transformations. The default value is a three character string: \fB*\et*\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-E Causes the input coordinates to be copied to the output line prior to printing the converted values. .UNINDENT .INDENT 0.0 .TP .B \-l<[=id]> List projection identifiers that can be selected with \fI+proj\fP\&. \fBcs2cs \-l=id\fP gives expanded description of projection \fIid\fP, e.g. \fBcs2cs \-l=merc\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-lp List of all projection id that can be used with the \fI+proj\fP parameter. Equivalent to \fBcs2cs \-l\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-lP Expanded description of all projections that can be used with the \fI+proj\fP parameter. .UNINDENT .INDENT 0.0 .TP .B \-le List of all ellipsoids that can be selected with the \fI+ellps\fP parameters. .UNINDENT .INDENT 0.0 .TP .B \-lm List of hard\-coded prime meridians that can be selected with the \fI+pm\fP parameter. Note that this list is no longer updated, and some values may conflict with other sources. .UNINDENT .INDENT 0.0 .TP .B \-lu List of all distance units that can be selected with the \fI+units\fP parameter. .UNINDENT .INDENT 0.0 .TP .B \-r This options reverses the order of the first two expected inputs from that specified by the CRS to the opposite order. The third coordinate, typically height, remains third. .UNINDENT .INDENT 0.0 .TP .B \-s This options reverses the order of the first two expected outputs from that specified by the CRS to the opposite order. The third coordinate, typically height, remains third. .UNINDENT .INDENT 0.0 .TP .B \-f Where \fIformat\fP is a printf format string to control the form of the output values. For inverse projections, the output will be in degrees when this option is employed. If a format is specified for inverse projection the output data will be in decimal degrees. The default format is \fB\(dq%.2f\(dq\fP for forward projection and DMS for inverse. .UNINDENT .INDENT 0.0 .TP .B \-w Where \fIn\fP is the number of significant fractional digits to employ for seconds output (when the option is not specified, \fB\-w3\fP is assumed). .UNINDENT .INDENT 0.0 .TP .B \-W Where \fIn\fP is the number of significant fractional digits to employ for seconds output. When \fB\-W\fP is employed the fields will be constant width with leading zeroes. Valid range: \-W0 through \-W8. .UNINDENT .INDENT 0.0 .TP .B \-v Causes a listing of cartographic control parameters tested for and used by the program to be printed prior to input data. .UNINDENT .INDENT 0.0 .TP .B \-\-area Added in version 8.0.0. .sp Specify an area of interest to restrict the results when researching coordinate operations between 2 CRS. The area of interest can be specified either as a name (e.g \(dqDenmark \- onshore\(dq) or a AUTHORITY:CODE (EPSG:3237) .sp This option is mutually exclusive with \fB\-\-bbox\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-bbox Added in version 8.0.0. .sp Specify an area of interest to restrict the results when researching coordinate operations between 2 CRS. The area of interest is specified as a bounding box with geographic coordinates, expressed in degrees in a unspecified geographic CRS. \fIwest_long\fP and \fIeast_long\fP should be in the [\-180,180] range, and \fIsouth_lat\fP and \fInorth_lat\fP in the [\-90,90]. \fIwest_long\fP is generally lower than \fIeast_long\fP, except in the case where the area of interest crosses the antimeridian. .UNINDENT .INDENT 0.0 .TP .B \-\-only\-best[=yes|=no] Added in version 9.2.0. .sp Force \fIcs2cs\fP to only use the best transformation known by PROJ. \fIcs2cs\fP will return an error if a grid needed for the best transformation is missing. .sp Best transformation should be understood as the most accurate transformation available among all relevant for the point to transform, and if all known grids required to perform such transformation were accessible (either locally or through network). .sp Note that the default value for this option can be also set with the \fBPROJ_ONLY_BEST_DEFAULT\fP environment variable, or with the \fBonly_best_default\fP setting of proj.ini \%<#\:proj-ini> (\fB\-\-only\-best\fP when specified overrides such default value). .UNINDENT .INDENT 0.0 .TP .B \-\-no\-ballpark Added in version 8.0.0. .sp Disallow any coordinate operation that is, or contains, a Ballpark transformation \%<#\:term-Ballpark-transformation> .UNINDENT .INDENT 0.0 .TP .B \-\-accuracy Added in version 8.0.0. .sp Sets the minimum desired accuracy for candidate coordinate operations. .UNINDENT .INDENT 0.0 .TP .B \-\-authority Added in version 8.0.0. .sp This option can be used to restrict the authority of coordinate operations looked up in the database. When not specified, coordinate operations from any authority will be searched, with the restrictions set in the \fBauthority_to_authority_preference\fP database table related to the authority of the source/target CRS themselves. If authority is set to \fBany\fP, then coordinate operations from any authority will be searched If authority is a non\-empty string different of \fBany\fP, then coordinate operations will be searched only in that authority namespace (e.g \fBEPSG\fP). .sp This option is mutually exclusive with \fB\-\-bbox\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-3d Added in version 9.1. .sp \(dqPromote\(dq 2D CRS(s) to their 3D version, where the vertical axis is the ellipsoidal height in metres, using the ellipsoid of the base geodetic CRS. Depending on PROJ versions and the exact nature of the CRS involved, especially before PROJ 9.1, a mix of 2D and 3D CRS could lead to 2D or 3D transformations. Starting with PROJ 9.1, both CRS need to be 3D for vertical transformation to possibly happen. .UNINDENT .INDENT 0.0 .TP .B \-\-s_epoch Added in version 9.4. .sp Epoch of coordinates in the source CRS, as decimal year. Only applies to a dynamic CRS. .UNINDENT .INDENT 0.0 .TP .B \-\-t_epoch Added in version 9.4. .sp Epoch of coordinates in the target CRS, as decimal year. Only applies to a dynamic CRS. .UNINDENT .sp The \fI+opt\fP run\-line arguments are associated with cartographic parameters. .sp The cs2cs program takes two coordinate reference system (CRS) definitions. These can be given in a variety of formats such as EPSG\-codes, WKT\-strings or legacy PROJ.4 CRS descriptions. .sp If there is no second CRS defined, a geographic CRS based on the datum and ellipsoid of the source CRS is assumed. Note that the source and destination CRS can both of same or different nature (geographic, projected, compound CRS), or one of each and may have the same or different datums. .sp When using a WKT definition or a AUTHORITY:CODE, the axis order of the CRS will be enforced. So for example if using EPSG:4326, the first value expected (or returned) will be a latitude. .sp Internally, cs2cs uses the \fBproj_create_crs_to_crs()\fP \%<#\:c\:.proj_create_crs_to_crs> function to compute the appropriate coordinate operation, so implementation details of this function directly impact the results returned by the program. .sp The environment parameter \fBPROJ_DATA\fP \%<#\:envvar-PROJ_DATA> establishes the directory for resource files (database, datum shift grids, etc.) .sp One or more files (processed in left to right order) specify the source of data to be transformed. A \fB\-\fP will specify the location of processing standard input. If no files are specified, the input is assumed to be from stdin. For input data the two data values must be in the first two white space separated fields and when both input and output are ASCII all trailing portions of the input line are appended to the output line. .sp Input geographic data (longitude and latitude) must be in DMS or decimal degrees format and input cartesian data must be in units consistent with the ellipsoid major axis or sphere radius units. Output geographic coordinates will normally be in DMS format (use \fB\-f %.12f\fP for decimal degrees with 12 decimal places), while projected (cartesian) coordinates will be in linear (meter, feet) units. .SS Use of remote grids .sp Added in version 7.0.0. .sp If the \fBPROJ_NETWORK\fP \%<#\:envvar-PROJ_NETWORK> environment variable is set to \fBON\fP, cs2cs will attempt to use remote grids stored on CDN (Content Delivery Network) storage, when they are not available locally. .sp More details are available in the Network capabilities \%<#\:network> section. .SH EXAMPLES .SS Using EPSG CRS codes .sp Transforming from WGS 84 latitude/longitude (in that order) to UTM Zone 31N/WGS 84 .INDENT 0.0 .INDENT 3.5 .sp .EX cs2cs EPSG:4326 EPSG:32631 < where new bug reports can be submitted to. .SH HOME PAGE .sp \% .SH Author Frank Warmerdam .SH Copyright 1983-2026, PROJ contributors .\" End of generated man page. proj-9.8.1/man/man1/cct.1000664 001750 001750 00000021777 15166171715 014733 0ustar00eveneven000000 000000 .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "CCT" "1" "10 avril 2026" "9.8" "PROJ" .SH NAME cct \- Coordinate Conversion and Transformation .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 \fBcct\fP [\fB\-cIostvz\fP [args]] \fI+opt[=arg]\fP ... file ... .UNINDENT .UNINDENT .sp or .INDENT 0.0 .INDENT 3.5 \fBcct\fP [\fB\-cIostvz\fP [args]] {object_definition} file ... .UNINDENT .UNINDENT .sp Where {object_definition}\ is one of the possibilities accepted by \fBproj_create()\fP \%<#\:c\:.proj_create>, provided it expresses a coordinate operation .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 a proj\-string, .IP \(bu 2 a WKT string, .IP \(bu 2 an object code (like \(dqEPSG:1671\(dq \(dqurn:ogc:def:coordinateOperation:EPSG::1671\(dq), .IP \(bu 2 an object name. e.g. \(dqITRF2014 to ETRF2014 (1)\(dq. In that case as uniqueness is not guaranteed, heuristics are applied to determine the appropriate best match. .IP \(bu 2 a OGC URN combining references for concatenated operations (e.g. \(dq\%\(dq) .IP \(bu 2 a PROJJSON string. The jsonschema is at \% .UNINDENT .sp Added in version 8.0.0. .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 Before version 8.0.0 only proj\-strings could be used to instantiate operations in cct\&. .UNINDENT .UNINDENT .UNINDENT .UNINDENT .sp or .INDENT 0.0 .INDENT 3.5 \fBcct\fP [\fB\-cIostvz\fP [args]] {object_reference} file ... .UNINDENT .UNINDENT .sp where {object_reference} is a filename preceded by the \(aq@\(aq character. The file referenced by the {object_reference} must contain a valid {object_definition}. .INDENT 0.0 .INDENT 3.5 Added in version 8.0.0. .UNINDENT .UNINDENT .SH DESCRIPTION .sp cct is a 4D equivalent to the proj \%<#\:proj> projection program, performs transformation coordinate systems on a set of input points. The coordinate system transformation can include translation between projected and geographic coordinates as well as the application of datum shifts. .sp Note however that unlike the proj \%<#\:proj>, angular input must be in decimal degrees. Any minutes and seconds given will be silently dropped. .sp The following control parameters can appear in any order: .INDENT 0.0 .TP .B \-c Specify input columns for (up to) 4 input parameters. Defaults to 1,2,3,4. .UNINDENT .INDENT 0.0 .TP .B \-d Added in version 5.2.0. .sp Specify the number of decimals to round to in the output. .UNINDENT .INDENT 0.0 .TP .B \-I Do the inverse transformation. .UNINDENT .INDENT 0.0 .TP .B \-o , \-\-output= Specify the name of the output file. .UNINDENT .INDENT 0.0 .TP .B \-t Where \fIa\fP specifies a character employed as the first character to denote a control line to be passed through without processing. This option applicable to ASCII input only. (# is the default value). .UNINDENT .INDENT 0.0 .TP .B \-e Where \fIstring\fP is an arbitrary string to be output if an error is detected during data transformations. The default value is a three character string: \fB*\et*\fP\&. Note that if the \fB\-b\fP, \fB\-i\fP or \fB\-o\fP options are employed, an error is returned as HUGE_VAL value for both return values. .UNINDENT .INDENT 0.0 .TP .B \-E Causes the input coordinates to be copied to the output line prior to printing the converted values. .UNINDENT .INDENT 0.0 .TP .B \-l<[=id]> List projection identifiers that can be selected with \fI+proj\fP\&. \fBproj \-l=id\fP gives expanded description of projection \fIid\fP, e.g. \fBproj \-l=merc\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-lp List of all projection id that can be used with the \fI+proj\fP parameter. Equivalent to \fBproj \-l\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-lP Expanded description of all projections that can be used with the \fI+proj\fP parameter. .UNINDENT .INDENT 0.0 .TP .B \-le List of all ellipsoids that can be selected with the \fI+ellps\fP parameters. .UNINDENT .INDENT 0.0 .TP .B \-lu List of all distance units that can be selected with the \fI+units\fP parameter. .UNINDENT .INDENT 0.0 .TP .B \-r This options reverses the order of the expected input from longitude\-latitude or x\-y to latitude\-longitude or y\-x. .UNINDENT .INDENT 0.0 .TP .B \-s This options reverses the order of the output from x\-y or longitude\-latitude to y\-x or latitude\-longitude. .UNINDENT .INDENT 0.0 .TP .B \-S Causes estimation of meridional and parallel scale factors, area scale factor and angular distortion, and maximum and minimum scale factors to be listed between <> for each input point. For conformal projections meridional and parallel scales factors will be equal and angular distortion zero. Equal area projections will have an area factor of 1. .UNINDENT .INDENT 0.0 .TP .B \-m The cartesian data may be scaled by the \fImult\fP parameter. When processing data in a forward projection mode the cartesian output values are multiplied by \fImult\fP otherwise the input cartesian values are divided by \fImult\fP before inverse projection. If the first two characters of \fImult\fP are 1/ or 1: then the reciprocal value of \fImult\fP is employed. .UNINDENT .INDENT 0.0 .TP .B \-f Where \fIformat\fP is a printf format string to control the form of the output values. For inverse projections, the output will be in degrees when this option is employed. The default format is \fB\(dq%.2f\(dq\fP for forward projection and DMS for inverse. .UNINDENT .INDENT 0.0 .TP .B \-w Where \fIn\fP is the number of significant fractional digits to employ for seconds output (when the option is not specified, \fB\-w3\fP is assumed). .UNINDENT .INDENT 0.0 .TP .B \-W Where \fIn\fP is the number of significant fractional digits to employ for seconds output. When \fB\-W\fP is employed the fields will be constant width with leading zeroes. .UNINDENT .INDENT 0.0 .TP .B \-v Causes a listing of cartographic control parameters tested for and used by the program to be printed prior to input data. .UNINDENT .INDENT 0.0 .TP .B \-V This option causes an expanded annotated listing of the characteristics of the projected point. \fB\-v\fP is implied with this option. .UNINDENT .sp The \fI+opt\fP run\-line arguments are associated with cartographic parameters. Additional projection control parameters may be contained in two auxiliary control files: the first is optionally referenced with the \fI+init=file:id\fP and the second is always processed after the name of the projection has been established from either the run\-line or the contents of +init file. The environment parameter \fBPROJ_DATA\fP \%<#\:envvar-PROJ_DATA> establishes the default directory for a file reference without an absolute path. This is also used for supporting files like datum shift files. .sp Added in version 9.3.0: \fI{crs}\fP is one of the possibilities accepted by :c:\fBproj_create()\fP, provided it expresses a projected CRS, like a WKT string, an object code (like \(dqEPSG:32632\(dq) a PROJJSON string, etc. The projection computed will be those of the map projection implied by the transformation from the base geographic CRS of the projected CRS to the projected CRS. .sp One or more files (processed in left to right order) specify the source of data to be converted. A \fB\-\fP will specify the location of processing standard input. If no files are specified, the input is assumed to be from stdin. For ASCII input data the two data values must be in the first two white space separated fields and when both input and output are ASCII all trailing portions of the input line are appended to the output line. .sp Input geographic data (longitude and latitude) must be in DMS or decimal degrees format and input cartesian data must be in units consistent with the ellipsoid major axis or sphere radius units. Output geographic coordinates will be in DMS (if the \fB\-w\fP switch is not employed) and precise to 0.001\(dq with trailing, zero\-valued minute\-second fields deleted. .SH EXAMPLE .sp The following script .INDENT 0.0 .INDENT 3.5 .sp .EX proj +proj=utm +zone=12 \-r < program operates similarly, but allows translation between any pair of definable coordinate reference systems, including support for datum translation. .SH SEE ALSO .sp \fBcs2cs(1)\fP, \fBcct(1)\fP, \fBgeod(1)\fP, \fBgie(1)\fP, \fBprojinfo(1)\fP, \fBprojsync(1)\fP .SH BUGS .sp A list of known bugs can be found at \% where new bug reports can be submitted to. .SH HOME PAGE .sp \% .SH Author Gerald I. Evenden .SH Copyright 1983-2026, PROJ contributors .\" End of generated man page. proj-9.8.1/examples/000775 001750 001750 00000000000 15166171735 014273 5ustar00eveneven000000 000000 proj-9.8.1/examples/CMakeLists.txt000664 001750 001750 00000000565 15166171715 017037 0ustar00eveneven000000 000000 add_executable(pj_obs_api_mini_demo pj_obs_api_mini_demo.c) target_link_libraries(pj_obs_api_mini_demo PRIVATE ${PROJ_LIBRARIES}) add_executable(crs_to_geodetic crs_to_geodetic.c) target_link_libraries(crs_to_geodetic PRIVATE ${PROJ_LIBRARIES}) add_executable(EPSG_4326_to_32631 EPSG_4326_to_32631.cpp) target_link_libraries(EPSG_4326_to_32631 PRIVATE ${PROJ_LIBRARIES}) proj-9.8.1/examples/EPSG_4326_to_32631.cpp000664 001750 001750 00000007002 15166171715 017450 0ustar00eveneven000000 000000 /******************************************************************************* Example code that illustrates how to convert between two CRS Note: This file is in-lined in the documentation. Any changes must be reflected in docs/source/development/quickstart.rst *******************************************************************************/ #include #include // for HUGE_VAL #include // for std::setprecision() #include #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/util.hpp" // for nn_dynamic_pointer_cast using namespace NS_PROJ::crs; using namespace NS_PROJ::io; using namespace NS_PROJ::operation; using namespace NS_PROJ::util; int main(void) { auto dbContext = DatabaseContext::create(); // Instantiate a generic authority factory, that is not tied to a particular // authority, to be able to get transformations registered by different // authorities. This can only be used for CoordinateOperationContext. auto authFactory = AuthorityFactory::create(dbContext, std::string()); // Create a coordinate operation context, that can be customized to amend // the way coordinate operations are computed. Here we ask for default // settings. auto coord_op_ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); // Instantiate a authority factory for EPSG related objects. auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); // Instantiate source CRS from EPSG code auto sourceCRS = authFactoryEPSG->createCoordinateReferenceSystem("4326"); // Instantiate target CRS from PROJ.4 string (commented out, the equivalent // from the EPSG code) // auto targetCRS = // authFactoryEPSG->createCoordinateReferenceSystem("32631"); auto targetCRS = NN_CHECK_THROW(nn_dynamic_pointer_cast(createFromUserInput( "+proj=utm +zone=31 +datum=WGS84 +type=crs", dbContext))); // List operations available to transform from EPSG:4326 // (WGS 84 latitude/longitude) to EPSG:32631 (WGS 84 / UTM zone 31N). auto list = CoordinateOperationFactory::create()->createOperations( sourceCRS, targetCRS, coord_op_ctxt); // Check that we got a non-empty list of operations // The list is sorted from the most relevant to the less relevant one. // Cf // https://proj.org/operations/operations_computation.html#filtering-and-sorting-of-coordinate-operations // for more details on the sorting of those operations. // For a transformation between a projected CRS and its base CRS, like // we do here, there will be only one operation. assert(!list.empty()); // Create an execution context (must only be used by one thread at a time) PJ_CONTEXT *ctx = proj_context_create(); // Create a coordinate transformer from the first operation of the list auto transformer = list[0]->coordinateTransformer(ctx); // Perform the coordinate transformation. PJ_COORD c = {{ 49.0, // latitude in degree 2.0, // longitude in degree 0.0, // z ordinate. unused HUGE_VAL // time ordinate. unused }}; c = transformer->transform(c); // Display result std::cout << std::fixed << std::setprecision(3); std::cout << "Easting: " << c.v[0] << std::endl; // should be 426857.988 std::cout << "Northing: " << c.v[1] << std::endl; // should be 5427937.523 // Destroy execution context proj_context_destroy(ctx); return 0; } proj-9.8.1/examples/pj_obs_api_mini_demo.c000664 001750 001750 00000003742 15166171715 020570 0ustar00eveneven000000 000000 /******************************************************************************* Simple example code demonstrating use of the proj.h API for 2D coordinate transformations. Note: This file is in-lined in the documentation. Any changes must be reflected in docs/source/development/quickstart.rst Thomas Knudsen, 2016-10-30/2017-07-06 *******************************************************************************/ #include #include int main(void) { PJ_CONTEXT *C; PJ *P; PJ *norm; PJ_COORD a, b; /* or you may set C=PJ_DEFAULT_CTX if you are sure you will */ /* use PJ objects from only one thread */ C = proj_context_create(); P = proj_create_crs_to_crs( C, "EPSG:4326", "+proj=utm +zone=32 +datum=WGS84", /* or EPSG:32632 */ NULL); if (0 == P) { fprintf(stderr, "Failed to create transformation object.\n"); return 1; } /* This will ensure that the order of coordinates for the input CRS */ /* will be longitude, latitude, whereas EPSG:4326 mandates latitude, */ /* longitude */ norm = proj_normalize_for_visualization(C, P); if (0 == norm) { fprintf(stderr, "Failed to normalize transformation object.\n"); return 1; } proj_destroy(P); P = norm; /* a coordinate union representing Copenhagen: 55d N, 12d E */ /* Given that we have used proj_normalize_for_visualization(), the order */ /* of coordinates is longitude, latitude, and values are expressed in */ /* degrees. */ a = proj_coord(12, 55, 0, 0); /* transform to UTM zone 32, then back to geographical */ b = proj_trans(P, PJ_FWD, a); printf("easting: %.3f, northing: %.3f\n", b.enu.e, b.enu.n); b = proj_trans(P, PJ_INV, b); printf("longitude: %g, latitude: %g\n", b.lp.lam, b.lp.phi); /* Clean up */ proj_destroy(P); proj_context_destroy(C); /* may be omitted in the single threaded case */ return 0; } proj-9.8.1/examples/val_def.demo000664 001750 001750 00000005051 15166171715 016540 0ustar00eveneven000000 000000 ----------------------------------------------------------------------- INTEGRATING DEFINITION AND VALIDATION OF GEODETIC SYSTEMS ----------------------------------------------------------------------- Thomas Knudsen, thokn@sdfe.dk, 2017-12-06 ----------------------------------------------------------------------- This demo shows how to use the free format definition strings, introduced in PROJ version 5.0.0, to integrate system definition information with system validation data. The system definition parts are used when doing actual transformations, e.g. using the cct 4D transformation program: echo 9 55 0 0 | cct +init=val_def.demo:DKTM1 The system validation parts are used when validating the systems defined in the file. This is done using the gie test program: Place val_def.demo in your PROJ_LIB directory (or set PROJ_LIB=) and say: gie val_def.demo This will result in a report detailing how many tests succeeded, resp. failed. The syntax of proj init files is orthogonal to the syntax of gie integrity evaluation files. This makes it possible to interleave init and gie blocks in the same file. #----------------------------------------------------------------------- #----------------------------------------------------------------------- # Danish Transverse Mercator, zone 1 #----------------------------------------------------------------------- proj = etmerc lat_0 = 0 lon_0 = 9 x_0 = 200000 y_0 = -5000000 k = 0.99998 ellps = GRS80 units = m no_defs #----------------------------------------------------------------------- operation init = val_def.demo:DKTM1 tolerance 100 um accept 9 55 expect 200000.0000 1097108.3684 roundtrip 1000 1 nm #----------------------------------------------------------------------- #----------------------------------------------------------------------- # Danish Transverse Mercator, zone 2 #----------------------------------------------------------------------- proj = etmerc lat_0 = 0 lon_0 = 10 x_0 = 400000 y_0 = -5000000 k = 0.99998 ellps = GRS80 units = m no_defs #----------------------------------------------------------------------- operation init = val_def.demo:DKTM2 tolerance 100 um accept 10 55 expect 400000.0000 1097108.3684 accept 10.5 55.5 expect 431597.1668 1152884.9398 roundtrip 1000 100 um #----------------------------------------------------------------------- proj-9.8.1/examples/crs_to_geodetic.c000664 001750 001750 00000004154 15166171715 017575 0ustar00eveneven000000 000000 /******************************************************************************* Example code that illustrates how to convert between a CRS and geodetic coordnates for that CRS. Note: This file is in-lined in the documentation. Any changes must be reflected in docs/source/development/quickstart.rst *******************************************************************************/ #include #include #include int main(void) { /* Create the context. */ /* You may set C=PJ_DEFAULT_CTX if you are sure you will */ /* use PJ objects from only one thread */ PJ_CONTEXT *C = proj_context_create(); /* Create a projection. */ PJ *P = proj_create(C, "+proj=utm +zone=32 +datum=WGS84 +type=crs"); if (0 == P) { fprintf(stderr, "Failed to create transformation object.\n"); return 1; } /* Get the geodetic CRS for that projection. */ PJ *G = proj_crs_get_geodetic_crs(C, P); /* Create the transform from geodetic to projected coordinates.*/ PJ_AREA *A = NULL; const char *const *options = NULL; PJ *G2P = proj_create_crs_to_crs_from_pj(C, G, P, A, options); /* Longitude and latitude of Copenhagen, in degrees. */ double lon = 12.0, lat = 55.0; /* Prepare the input */ PJ_COORD c_in; c_in.lpzt.z = 0.0; c_in.lpzt.t = HUGE_VAL; // important only for time-dependent projections c_in.lp.lam = lon; c_in.lp.phi = lat; printf("Input longitude: %g, latitude: %g (degrees)\n", c_in.lp.lam, c_in.lp.phi); /* Compute easting and northing */ PJ_COORD c_out = proj_trans(G2P, PJ_FWD, c_in); printf("Output easting: %g, northing: %g (meters)\n", c_out.enu.e, c_out.enu.n); /* Apply the inverse transform */ PJ_COORD c_inv = proj_trans(G2P, PJ_INV, c_out); printf("Inverse applied. Longitude: %g, latitude: %g (degrees)\n", c_inv.lp.lam, c_inv.lp.phi); /* Clean up */ proj_destroy(P); proj_destroy(G); proj_destroy(G2P); proj_context_destroy(C); /* may be omitted in the single threaded case */ return 0; } proj-9.8.1/examples/s45b.pol000664 001750 001750 00000021372 15166171715 015567 0ustar00eveneven000000 000000 ################################################################################ # # Horner polynomial evaluation demo data # ################################################################################ # # These are the polynomial coefficients used for transforming to and from # the Danish legacy system s45b "System 45 Bornholm" # ################################################################################ proj=pipeline step init=./s45b.pol:s45b_tc32 # step init=./s45b.pol:tc32_utm32 step proj=utm inv ellps=intl zone=32 step proj=cart ellps=intl step proj=helmert ellps=GRS80 x=-81.0703 y=-89.3603 z=-115.7526 rx=-484.88 ry=-24.36 rz=-413.21 s=-0.540645 step proj=cart inv ellps=GRS80 step proj=utm ellps=GRS80 zone=32 ################################################################################ # # S 4 5 B -> T C 3 2 # ################################################################################ proj=horner ellps=intl range=500000 fwd_origin=47022.563745,51779.260103 inv_origin=878354.943082,6125305.175366 deg=6 # static double C_ttb[] # tc32_ed50 -> s45b # m_lim_gen: 0.153 red = 0 OBS = 1074 # m = 1.51 cm my_loss = +3 y_enp = +8.4 # m = 1.53 cm mx_loss = +4 x_enp = +8.4 # mht C_ttb er # fwd-inv ombyttet ifht original Poder/Engsager-kode # For at opnå at to fwd transform fører fra s45b->tc32->utm32 (->ETRS89) inv_v= # Poly NORTH :: e-degree = 0 : n-degree = 6 5.1779004699e+04, 9.9508320295e-01, -2.9453823207e-10, 1.9995084102e-14, -1.4895751366e-18, -9.9734812211e-23, 1.1194218845e-26, # Poly NORTH :: e-degree = 1 : n-degree = 5 -8.4285679515e-02, -7.9623049286e-09, -3.7190046062e-14, -2.3324127411e-18, -1.1150449763e-22, 2.8703154270e-27, # Poly NORTH :: e-degree = 2 : n-degree = 4 8.7160434140e-10, -3.3634602927e-14, -5.5718245313e-18, 6.2611750909e-23, -2.1011243838e-26, # Poly NORTH :: e-degree = 3 : n-degree = 3 1.0905463989e-14, -4.3960034360e-18, 3.6121595001e-22, -1.3493066011e-27, # Poly NORTH :: e-degree = 4 : n-degree = 2 -1.3360171462e-18, 1.0780850646e-22, 4.5118286607e-26, # Poly NORTH :: e-degree = 5 : n-degree = 1 -1.3718883973e-22, 1.6263920750e-26, # Poly NORTH :: e-degree = 6 : n-degree = 0 -5.1004217526e-27 # tcy 6125305.175366 inv_u= # Poly EAST :: n-degree = 0 : e-degree = 6 4.7022495967e+04, -9.9508282498e-01, 3.2436283039e-09, -2.6276394334e-15, 8.6318533291e-18, -3.8327518550e-23, -2.5704924282e-26, # Poly EAST :: n-degree = 1 : e-degree = 5 -8.4285975934e-02, 5.7098765263e-10, -6.0863955939e-14, 2.3608788740e-18, 6.8899581969e-24, -1.1429511179e-26, # Poly EAST :: n-degree = 2 : e-degree = 4 -4.6079778412e-09, 1.5072604543e-14, 5.4063862378e-18, 1.2591327827e-22, 7.9336388691e-27, # Poly EAST :: n-degree = 3 : e-degree = 3 -2.9479268638e-14, 1.7090049434e-18, 2.8413337985e-22, -3.3577391552e-27, # Poly EAST :: n-degree = 4 : e-degree = 2 3.0434879273e-18, -1.8081673510e-22, -2.3651419850e-26, # Poly EAST :: n-degree = 5 : e-degree = 1 9.2060044804e-23, 3.7807953325e-27, # Poly EAST :: n-degree = 6 : e-degree = 0 -4.9415665221e-27 # tcx 878354.943082 # static double C_btt[] # s45b -> tc32_ed50 # m_lim_gen: 0.154 red = 0 OBS = 1074 # m = 1.50 cm my_loss = +3 y_enp = +8.5 # m = 1.54 cm mx_loss = +4 x_enp = +8.3 fwd_v= # Poly NORTH :: e-degree = 0 : n-degree = 6 6.1253054245e+06, 9.9778251908e-01, -7.7346152025e-10, -2.5359789369e-14, 1.5614918228e-18, 9.8091134295e-23, -1.1092581145e-26, # Poly NORTH :: e-degree = 1 : n-degree = 5 -8.4514352088e-02, -7.9847579284e-09, -2.6865560962e-14, -2.0731372756e-18, -1.3660341123e-22, 1.1244836340e-26, # Poly NORTH :: e-degree = 2 : n-degree = 4 8.0551988135e-11, 3.6661500679e-14, 5.4247705403e-18, 8.4494604807e-23, 1.3334858516e-26, # Poly NORTH :: e-degree = 3 : n-degree = 3 8.3889821184e-15, -4.8124202237e-18, 2.9088188830e-22, -2.0129874264e-26, # Poly NORTH :: e-degree = 4 : n-degree = 2 2.4716463766e-18, -2.1717177513e-22, -3.2828537638e-26, # Poly NORTH :: e-degree = 5 : n-degree = 1 -1.2080655753e-22, 2.5050435391e-26, # Poly NORTH :: e-degree = 6 : n-degree = 0 1.1383483826e-27 # tcy 51779.260103, fwd_u= # Poly EAST :: n-degree = 0 : e-degree = 6 8.7835485387e+05, -9.9778289691e-01, 3.2537215213e-09, 6.9217640616e-15, 8.6268883840e-18, 4.6748156909e-23, -2.6492402009e-26, # Poly EAST :: n-degree = 1 : e-degree = 5 -8.4514648771e-02, 1.4399520180e-09, -6.0423329711e-14, 6.9816167332e-20, 6.7729233542e-23, -5.3308251880e-27, # Poly EAST :: n-degree = 2 : e-degree = 4 -4.5697800099e-09, -1.5194038814e-14, 5.1112653016e-18, -2.0307532869e-22, 1.0374125432e-26, # Poly EAST :: n-degree = 3 : e-degree = 3 -2.8983003841e-14, -1.6414425785e-18, 1.7874983379e-22, 1.5492164174e-26, # Poly EAST :: n-degree = 4 : e-degree = 2 2.7919197366e-18, 1.9218613279e-22, -2.1007264634e-26, # Poly EAST :: n-degree = 5 : e-degree = 1 1.0032412389e-22, -5.9007997846e-27, # Poly EAST :: n-degree = 6 : e-degree = 0 -4.4410970979e-27 # tcx 47022.563745 ################################################################################ # # T C 3 2 -> U T M 3 2 # ################################################################################ proj=horner ellps=intl range=500000 fwd_origin=877605.269066,6125810.306769 inv_origin=877605.760036,6125811.281773 # tc32_ed50 -> utm32_ed50 : Bornholm deg=4 # ttu_n and ttu_e are based on static double C_ttu_b[] # m_lim_gen: 0.086 red = 0 OBS = 852 # m = 1.38 cm my_loss = +2 y_enp = +10.5 # m = 1.44 cm mx_loss = +2 x_enp = +10.4 # static double ttu_n[] fwd_v= # Poly NORTH :: e-degree = 0 : n-degree = 0..4 6.1258112678e+06, 9.9999971567e-01, 1.5372750011e-10, 5.9300860915e-15, 2.2609497633e-19, # Poly NORTH :: e-degree = 1 : n-degree = 0..3 4.3188227445e-05, 2.8225130416e-10, 7.8740007114e-16, -1.7453997279e-19, # Poly NORTH :: e-degree = 2 : n-degree = 0..2 1.6877465415e-10, -1.1234649773e-14, -1.7042333358e-18, # Poly NORTH :: e-degree = 3 : n-degree = 0..1 -7.9303467953e-15, -5.2906832535e-19, # Poly NORTH :: e-degree = 4 : n-degree = 0 3.9984284847e-19 # tcy 6125810.306769 # static double ttu_e[] fwd_u= # Poly EAST :: n-degree = 0 : e-degree = 0..4 8.7760574982e+05, 9.9999752475e-01, 2.8817299305e-10, 5.5641310680e-15, -1.5544700949e-18, # Poly EAST :: n-degree = 1 : e-degree = 3 -4.1357045890e-05, 4.2106213519e-11, 2.8525551629e-14, -1.9107771273e-18, # Poly EAST :: n-degree = 2 : e-degree = 2 3.3615590093e-10, 2.4380247154e-14, -2.0241230315e-18, # Poly EAST :: n-degree = 3 : e-degree = 1 1.2429019719e-15, 5.3886155968e-19, # Poly EAST :: n-degree = 4 : e-degree = 0 -1.0167505000e-18 # tcx 877605.760036 # utt_n and utt_e are based on static double C_utt_b[] # utm32_ed50 -> tc32_ed50 : Bornholm # m_lim_gen: 0.086 red = 0 OBS = 852 # m = 1.38 cm my_loss = +2 y_enp = +10.8 # m = 1.44 cm mx_loss = +2 x_enp = +10.7 # static double utt_n[] inv_v= # Poly NORTH :: e-degree = 0 : n-degree = 4 6.1258103208e+06, 1.0000002826e+00, -1.5372762184e-10, -5.9304261011e-15, -2.2612705361e-19, # Poly NORTH :: e-degree = 1 : n-degree = 3 -4.3188331419e-05, -2.8225549995e-10, -7.8529116371e-16, 1.7476576773e-19, # Poly NORTH :: e-degree = 2 : n-degree = 2 -1.6875687989e-10, 1.1236475299e-14, 1.7042518057e-18, # Poly NORTH :: e-degree = 3 : n-degree = 1 7.9300735257e-15, 5.2881862699e-19, # Poly NORTH :: e-degree = 4 : n-degree = 0 -3.9990736798e-19 # tcy 6125811.281773 # static double utt_e[] inv_u= # Poly EAST :: n-degree = 0 : e-degree = 0..4 8.7760527928e+05, 1.0000024735e+00, -2.8817540032e-10, -5.5627059451e-15, 1.5543637570e-18, # Poly EAST :: n-degree = 1 : e-degree = 0..3 4.1357152105e-05, -4.2114813612e-11, -2.8523713454e-14, 1.9109017837e-18, # Poly EAST :: n-degree = 2 : e-degree = 0..2 -3.3616407783e-10, -2.4382678126e-14, 2.0245020199e-18, # Poly EAST :: n-degree = 3 : e-degree = 0..1 -1.2441377565e-15, -5.3885232238e-19, # Poly EAST :: n-degree = 4 : e-degree = 0 1.0167203661e-18 # tcx 877605.760036 <> proj-9.8.1/COPYING000664 001750 001750 00000003370 15166171715 013511 0ustar00eveneven000000 000000 All source, data files and other contents of the PROJ package are available under the following terms. Note that the PROJ 4.3 and earlier was "public domain" as is common with US government work, but apparently this is not a well defined legal term in many countries. Frank Warmerdam placed everything under the following MIT style license because he believed it is effectively the same as public domain, allowing anyone to use the code as they wish, including making proprietary derivatives. Initial PROJ 4.3 public domain code was put as Frank Warmerdam as copyright holder, but he didn't mean to imply he did the work. Essentially all work was done by Gerald Evenden. Copyright information can be found in source files. -------------- 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. proj-9.8.1/scripts/000775 001750 001750 00000000000 15166171735 014144 5ustar00eveneven000000 000000 proj-9.8.1/scripts/CMakeLists.txt000664 001750 001750 00000002252 15166171715 016703 0ustar00eveneven000000 000000 find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(PC_BASH_COMPLETION QUIET bash-completion) if (PC_BASH_COMPLETION_FOUND) pkg_get_variable(BASH_COMPLETIONS_FULL_DIR bash-completion completionsdir) pkg_get_variable(BASH_COMPLETIONS_PREFIX bash-completion prefix) if (BASH_COMPLETIONS_FULL_DIR AND BASH_COMPLETIONS_PREFIX AND BASH_COMPLETIONS_FULL_DIR MATCHES "^${BASH_COMPLETIONS_PREFIX}/") string(REGEX REPLACE "^${BASH_COMPLETIONS_PREFIX}/" "" BASH_COMPLETIONS_DIR_DEFAULT ${BASH_COMPLETIONS_FULL_DIR}) endif () endif () endif () if (NOT DEFINED BASH_COMPLETIONS_DIR_DEFAULT) include(GNUInstallDirs) set(BASH_COMPLETIONS_DIR_DEFAULT ${CMAKE_INSTALL_DATADIR}/bash-completion/completions) endif () set(BASH_COMPLETIONS_DIR "${BASH_COMPLETIONS_DIR_DEFAULT}" CACHE PATH "Installation sub-directory for bash completion scripts") if (NOT BASH_COMPLETIONS_DIR STREQUAL "") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/install_bash_completions.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/install_bash_completions.cmake @ONLY) install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/install_bash_completions.cmake) endif () proj-9.8.1/scripts/build_db_from_iau.py000775 001750 001750 00000044717 15166171715 020161 0ustar00eveneven000000 000000 #!/usr/bin/env python # SPDX-License-Identifier: MIT # Copyright (c) 2021, Hobu Inc # Author: Even Rouault, import csv import os from pathlib import Path script_dir_name = os.path.dirname(os.path.realpath(__file__)) all_sql = [] AUTH_IAU2015 = 'IAU_2015' all_sql.append("INSERT INTO coordinate_system VALUES('PROJ','OCENTRIC_LAT_LON','spherical',2);") all_sql.append("INSERT INTO axis VALUES('PROJ','OCENTRIC_LAT_LON_LAT','Planetocentric latitude','U','north','PROJ','OCENTRIC_LAT_LON',1,'EPSG','9122');"); all_sql.append("INSERT INTO axis VALUES('PROJ','OCENTRIC_LAT_LON_LON','Planetocentric longitude','V','east','PROJ','OCENTRIC_LAT_LON',2,'EPSG','9122');"); all_sql.append("INSERT INTO coordinate_system VALUES('PROJ','OGRAPHIC_NORTH_WEST','ellipsoidal',2);") all_sql.append("INSERT INTO axis VALUES('PROJ','OGRAPHIC_NORTH_WEST_LAT','Geodetic latitude','Lat','north','PROJ','OGRAPHIC_NORTH_WEST',1,'EPSG','9122');"); all_sql.append("INSERT INTO axis VALUES('PROJ','OGRAPHIC_NORTH_WEST_LON','Geodetic longitude','Lon','west','PROJ','OGRAPHIC_NORTH_WEST',2,'EPSG','9122');"); all_sql.append("INSERT INTO coordinate_system VALUES('PROJ','PROJECTED_WEST_NORTH','Cartesian',2);") all_sql.append("INSERT INTO axis VALUES('PROJ','PROJECTED_WEST_NORTH_W','Westing','W','west','PROJ','PROJECTED_WEST_NORTH',1,'EPSG','9001');"); all_sql.append("INSERT INTO axis VALUES('PROJ','PROJECTED_WEST_NORTH_N','Northing','N','north','PROJ','PROJECTED_WEST_NORTH',2,'EPSG','9001');"); SOURCE_IAU = "Source of IAU Coordinate systems: https://doi.org/10.1007/s10569-017-9805-5" def get_longitude_positive_direction(Body, Naif_id, rotation): """Define the positive longitudes in ographic CRS based on the rotation sens. The general rule is the following: * Direct rotation has longitude positive to West * Retrograde rotation has longitude positive to East A special case is done for Sun/Earth/Moon for historical reasons for which longitudes are positive to East independently of the rotation sens Another special case is that longitude ographic is always to East for small bodies, comets, dwarf planets (Naif_id >= 900) """ if rotation == 'Direct': direction = 'west' elif rotation == 'Retrograde': direction = 'east' elif rotation == '': direction = '' else: assert False, rotation if Body in ('Sun', 'Moon', 'Earth'): direction = 'east' if Naif_id >= 900: direction = 'east' return direction def add_usage(table_name, code): if table_name == 'geodetic_datum': prefix = 'GD' elif table_name == 'geodetic_crs': prefix = 'GCRS' elif table_name == 'projected_crs': prefix = 'PCRS' elif table_name == 'conversion': prefix = 'CONV' else: assert False all_sql.append("INSERT INTO usage VALUES('%s','%s_%d','%s','%s',%d,'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');" % (AUTH_IAU2015, prefix, code, table_name, AUTH_IAU2015, code)) projection_data = [ [10, "Equirectangular, clon = 0", "Equidistant Cylindrical", "Latitude of 1st standard parallel", 0, "Longitude of natural origin", 0, "False easting", 0, "False northing", 0, None, None, None, None], [15, "Equirectangular, clon = 180", "Equidistant Cylindrical", "Latitude of 1st standard parallel", 0, "Longitude of natural origin", 180, "False easting", 0, "False northing", 0, None, None, None, None], [20, "Sinusoidal, clon = 0", "Sinusoidal", "Longitude of natural origin", 0, "False easting", 0, "False northing", 0,None, None, None, None, None, None], [25, "Sinusoidal, clon = 180", "Sinusoidal", "Longitude of natural origin", 180, "False easting", 0, "False northing", 0, None, None, None, None, None, None], [30, "North Polar", "Polar Stereographic (variant A)", "Latitude of natural origin", 90, "Longitude of natural origin", 0, "Scale factor at natural origin", 1, "False easting", 0, "False northing", 0, None, None], [35, "South Polar", "Polar Stereographic (variant A)", "Latitude of natural origin", -90, "Longitude of natural origin", 0, "Scale factor at natural origin", 1, "False easting", 0, "False northing", 0, None, None], [40, "Mollweide, clon = 0", "Mollweide", "Longitude of natural origin", 0, "False easting", 0, "False northing", 0, None, None, None, None, None, None], [45, "Mollweide, clon = 180", "Mollweide", "Longitude of natural origin", 180, "False easting", 0, "False northing", 0, None, None, None, None, None, None], [50, "Robinson, clon = 0", "Robinson", "Longitude of natural origin", 0, "False easting", 0, "False northing", 0, None, None, None, None, None, None], [55, "Robinson, clon = 180", "Robinson", "Longitude of natural origin", 180, "False easting", 0, "False northing", 0, None, None, None, None, None, None], [60, "Transverse Mercator", "Transverse Mercator", "Latitude of natural origin", 0, "Longitude of natural origin", 0, "Scale factor at natural origin", 1.0, "False easting", 0, "False northing", 0, None, None], [65, "Orthographic, clon = 0", "Orthographic", "Latitude of natural origin", 0, "Longitude of natural origin", 0, "False easting", 0, "False northing", 0, None, None, None, None], [70, "Orthographic, clon = 180", "Orthographic", "Latitude of natural origin", 0, "Longitude of natural origin", 180, "False easting", 0, "False northing", 0, None, None, None, None], [75, "Lambert Conic Conformal", "Lambert Conic Conformal (2SP)", "Latitude of false origin", 40, "Longitude of false origin", 0, "Latitude of 1st standard parallel", 20, "Latitude of 2nd standard parallel", 60, "Easting at false origin", 0, "Northing at false origin", 0], [80, "Lambert Azimuthal Equal Area", "Lambert Azimuthal Equal Area", "Latitude of natural origin", 40, "Longitude of natural origin", 0, "False easting", 0, "False northing", 0, None, None, None, None], [85, "Albers Equal Area", "Albers Equal Area", "Latitude of false origin", 40,"Longitude of false origin", 0, "Latitude of 1st standard parallel", 20, "Latitude of 2nd standard parallel", 60, "Easting at false origin", 0, "Northing at false origin", 0], [90, "Mercator", "Mercator (Spherical)", "Latitude of natural origin", 0, "Longitude of natural origin", 0, "False easting", 0, "False northing", 0, None, None, None, None], ] method_and_param_mapping = { "Lambert Azimuthal Equal Area (Spherical)" : ["EPSG", 1027], "Equidistant Cylindrical" : ["EPSG", 1028], "Equidistant Cylindrical (Spherical)": ["EPSG", 1029], "Scale factor at natural origin": ["EPSG", 8805, "SCALEUNIT[\"unity\",1.0, ID[\"EPSG\", 9201]]"], "False easting": ["EPSG", 8806, "LENGTHUNIT[\"metre\", 1, ID[\"EPSG\", 9001]]"], "False northing": ["EPSG", 8807, "LENGTHUNIT[\"metre\", 1, ID[\"EPSG\", 9001]]"], "Latitude of natural origin": ["EPSG", 8801, "ANGLEUNIT[\"degree\", 0.017453292519943295, ID[\"EPSG\", 9122]]"], "Longitude of natural origin": ["EPSG", 8802, "ANGLEUNIT[\"degree\", 0.017453292519943295, ID[\"EPSG\", 9122]]"], "Latitude of false origin": ["EPSG", 8821, "ANGLEUNIT[\"degree\", 0.017453292519943295, ID[\"EPSG\", 9122]]"], "Longitude of false origin": ["EPSG", 8822, "ANGLEUNIT[\"degree\", 0.017453292519943295, ID[\"EPSG\", 9122]]"], "Latitude of 1st standard parallel": ["EPSG", 8823, "ANGLEUNIT[\"degree\", 0.017453292519943295, ID[\"EPSG\", 9122]]"], "Latitude of 2nd standard parallel": ["EPSG", 8824, "ANGLEUNIT[\"degree\", 0.017453292519943295, ID[\"EPSG\", 9122]]"], "Easting at false origin": ["EPSG", 8826, "LENGTHUNIT[\"metre\", 1, ID[\"EPSG\", 9001]]"], "Northing at false origin": ["EPSG", 8827, "LENGTHUNIT[\"metre\", 1, ID[\"EPSG\", 9001]]"], "Sinusoidal": ["PROJ", 'SINUSOIDAL'], "Robinson" : ["PROJ", 'ROBINSON'], "Mollweide" : ["PROJ", 'MOLLWEIDE'], "Transverse Mercator" : ["EPSG",9807], "Lambert Conic Conformal (2SP)": ["EPSG", 9802], "Polar Stereographic (variant A)": ["EPSG", 9810], "Lambert Azimuthal Equal Area": ["EPSG", 9820], "Albers Equal Area" : ["EPSG", 9822], "Orthographic" : ["EPSG", 9840], "Mercator (Spherical)": ["EPSG", 1026] } def append_proj_paramater(name, value): if name is None: return ',NULL,NULL,NULL,NULL,NULL,NULL' sql = ',' + "'%s'" % method_and_param_mapping[name][0] sql += ',' + "'%s'" %str(method_and_param_mapping[name][1]) sql += ',' + "'%s'" % name sql += ',' + str(value) sql += ",'EPSG'" if 'unity' in method_and_param_mapping[name][2]: sql += ',9201' elif 'degree' in method_and_param_mapping[name][2]: sql += ',9122' elif 'metre' in method_and_param_mapping[name][2]: sql += ',9001' else: assert False return sql for row in projection_data: conv_code, conv_name, method_name, p1n, p1v, p2n, p2v, p3n, p3v, p4n, p4v, p5n, p5v, p6n, p6v = row method_auth = method_and_param_mapping[method_name][0] method_code = str(method_and_param_mapping[method_name][1]) sql = "INSERT INTO conversion VALUES ('%s',%d,'%s','','%s','%s','%s'" % (AUTH_IAU2015, conv_code, conv_name, method_auth, method_code, method_name) sql += append_proj_paramater(p1n, p1v) sql += append_proj_paramater(p2n, p2v) sql += append_proj_paramater(p3n, p3v) sql += append_proj_paramater(p4n, p4v) sql += append_proj_paramater(p5n, p5v) sql += append_proj_paramater(p6n, p6v) sql += append_proj_paramater(None, None) sql += ",0);" all_sql.append(sql) add_usage('conversion', conv_code) def generate_projected_crs(geod_crs_code, geod_crs_name, is_west): for row in projection_data: conv_code, conv_name, method_name, p1n, p1v, p2n, p2v, p3n, p3v, p4n, p4v, p5n, p5v, p6n, p6v = row pcrs_code = geod_crs_code + conv_code pcrs_name = geod_crs_name + ' / ' + conv_name if is_west: cs_auth = 'PROJ' cs_code = 'PROJECTED_WEST_NORTH' else: cs_auth = 'EPSG' if conv_name == "North Polar": cs_code = '4469' elif conv_name == "South Polar": cs_code = '4490' else: cs_code = '4400' all_sql.append("INSERT INTO projected_crs VALUES('%s',%d,'%s',NULL,'%s','%s','%s',%d,'%s',%d,NULL,0);" % (AUTH_IAU2015, pcrs_code, pcrs_name, cs_auth, cs_code, AUTH_IAU2015, geod_crs_code, AUTH_IAU2015, conv_code)) add_usage('projected_crs', pcrs_code) with open(Path(script_dir_name) / 'data/naifcodes_radii_m_wAsteroids_IAU2015.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) assert header == ['Naif_id', 'Body', 'IAU2015_Mean', 'IAU2015_Semimajor', 'IAU2015_Axisb', 'IAU2015_Semiminor', 'rotation', 'origin_long_name', 'origin_lon_pos'] nfields = len(header) while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row Naif_id, Body, IAU2015_Mean, IAU2015_Semimajor, IAU2015_Axisb, IAU2015_Semiminor, rotation, origin_long_name, origin_lon_pos = row Naif_id = int(Naif_id) IAU2015_Mean = float(IAU2015_Mean) IAU2015_Semimajor = float(IAU2015_Semimajor) IAU2015_Axisb = float(IAU2015_Axisb) IAU2015_Semiminor = float(IAU2015_Semiminor) all_axis_known_and_consistent = True if IAU2015_Mean <= 0: if IAU2015_Semimajor <= 0 or IAU2015_Axisb <= 0 or IAU2015_Semiminor <= 0: reason = 'Skip %s has it lacks all axis information' % Body print(reason) all_sql.append('-- ' + reason) continue elif IAU2015_Semimajor <= 0 or IAU2015_Axisb <= 0 or IAU2015_Semiminor <= 0: all_sql.append('-- %s lacks value for semimajor and/or axisb and/or semiminor. Only consider its mean value.' % Body) all_axis_known_and_consistent = False elif IAU2015_Semimajor < IAU2015_Axisb or IAU2015_Axisb < IAU2015_Semiminor: all_sql.append('-- %s has inconsistent values: semimajor < axisb or axis < semiminor. Only consider its mean value.' % Body) all_axis_known_and_consistent = False if all_axis_known_and_consistent: is_spherical = IAU2015_Semimajor == IAU2015_Axisb and IAU2015_Axisb == IAU2015_Semiminor is_triaxial = IAU2015_Semimajor != IAU2015_Axisb and IAU2015_Semiminor != IAU2015_Axisb else: is_spherical = False is_triaxial = True sphere_remark = '' if is_triaxial: if IAU2015_Mean <= 0: assert IAU2015_Semimajor > 0 assert IAU2015_Axisb > 0 assert IAU2015_Semiminor > 0 # Use R_m = (a+b+c)/3 as mean radius when mean radius is not defined ( = -1) sphere_radius = (IAU2015_Semimajor + IAU2015_Axisb + IAU2015_Semiminor) / 3 sphere_remark = "Use R_m = (a+b+c)/3 as mean radius. " else: sphere_radius = IAU2015_Mean sphere_remark += "Use mean radius as sphere radius for interoperability. " elif not is_spherical: # biaxial case assert IAU2015_Semimajor > 0 sphere_radius = IAU2015_Semimajor sphere_remark = "Use semi-major radius as sphere for interoperability. " else: assert IAU2015_Mean == IAU2015_Semimajor assert IAU2015_Semimajor > 0 sphere_radius = IAU2015_Mean sphere_remark += SOURCE_IAU all_sql.append("INSERT INTO celestial_body VALUES('%s', %d, '%s', %f);" % (AUTH_IAU2015, Naif_id, Body, sphere_radius)) prime_meridian_code = Naif_id * 100 all_sql.append("INSERT INTO prime_meridian VALUES('%s', %d, 'Reference Meridian', 0.0, 'EPSG', 9102, 0);" % (AUTH_IAU2015, prime_meridian_code)) anchor = 'NULL' if origin_long_name: if '.' not in origin_lon_pos: origin_lon_pos += '.0' anchor = "'%s: %s'" % (origin_long_name, origin_lon_pos) spherical_ellipsoid_code = Naif_id * 100 spherical_ellipsoid_name = '%s (2015) - Sphere' % Body all_sql.append("INSERT INTO ellipsoid VALUES('%s',%d,'%s',NULL,'%s',%d,%f,'EPSG','9001',NULL,%f,0);" % (AUTH_IAU2015, spherical_ellipsoid_code, spherical_ellipsoid_name, AUTH_IAU2015, Naif_id, sphere_radius, sphere_radius)) spherical_datum_code = spherical_ellipsoid_code spherical_datum_name = spherical_ellipsoid_name all_sql.append("INSERT INTO geodetic_datum VALUES('%s',%d,'%s','','%s',%d,'%s',%d,NULL,NULL,NULL,%s,NULL,0);" % (AUTH_IAU2015, spherical_datum_code, spherical_datum_name, AUTH_IAU2015, spherical_ellipsoid_code, AUTH_IAU2015, prime_meridian_code, anchor)) add_usage('geodetic_datum', spherical_datum_code) spherical_crs_code = Naif_id * 100 spherical_crs_name = '%s / Ocentric' % spherical_datum_name all_sql.append("INSERT INTO geodetic_crs VALUES('%s',%d,'%s','%s','geographic 2D','EPSG','6422','%s',%d,NULL,0);" % (AUTH_IAU2015, spherical_crs_code, spherical_crs_name, sphere_remark, AUTH_IAU2015, spherical_datum_code)) add_usage('geodetic_crs', spherical_crs_code) generate_projected_crs(spherical_crs_code, spherical_crs_name, False) if not all_axis_known_and_consistent: continue # Sun and Moon have only the Sphere Ocentric description if Body in ('Sun', 'Moon'): continue # Unsupported by PROJ if is_triaxial: continue direction = get_longitude_positive_direction(Body, Naif_id, rotation) has_ographic = direction != '' has_ocentric = True if is_spherical: # The ocentric CRS will be identical to the spherical ocentric one on a sphere has_ocentric = False # and if the ographic one is east oriented, it will also be redundant if direction == 'east': has_ographic = False if not has_ographic and not has_ocentric: continue ellipsoidal_ellipsoid_code = Naif_id * 100 + 1 ellipsoidal_ellipsoid_name = '%s (2015)' % Body all_sql.append("INSERT INTO ellipsoid VALUES('%s',%d,'%s',NULL,'%s',%d,%f,'EPSG','9001',NULL,%f,0);" % (AUTH_IAU2015, ellipsoidal_ellipsoid_code, ellipsoidal_ellipsoid_name, AUTH_IAU2015, Naif_id, IAU2015_Semimajor, IAU2015_Semiminor)) ellipsoidal_datum_code = ellipsoidal_ellipsoid_code ellipsoidal_datum_name = ellipsoidal_ellipsoid_name all_sql.append("INSERT INTO geodetic_datum VALUES('%s',%d,'%s','','%s',%d,'%s',%d,NULL,NULL,NULL,%s,NULL,0);" % (AUTH_IAU2015, ellipsoidal_datum_code, ellipsoidal_datum_name, AUTH_IAU2015, ellipsoidal_ellipsoid_code, AUTH_IAU2015, prime_meridian_code, anchor)) add_usage('geodetic_datum', ellipsoidal_datum_code) if has_ographic: # ographic crs_code = Naif_id * 100 + 1 crs_name = '%s / Ographic' % ellipsoidal_datum_name if direction == 'west': cs_auth = 'PROJ' cs_code = 'OGRAPHIC_NORTH_WEST' else: assert direction == 'east' cs_auth = 'EPSG' cs_code = '6422' all_sql.append("INSERT INTO geodetic_crs VALUES('%s',%d,'%s','%s','geographic 2D','%s','%s','%s',%d,NULL,0);" % (AUTH_IAU2015,crs_code, crs_name, SOURCE_IAU, cs_auth, cs_code, AUTH_IAU2015, ellipsoidal_datum_code)) add_usage('geodetic_crs', crs_code) generate_projected_crs(crs_code, crs_name, direction == 'west') if has_ocentric: # ocentric crs_code = Naif_id * 100 + 2 crs_name = '%s / Ocentric' % ellipsoidal_datum_name cs_auth = 'PROJ' cs_code = 'OCENTRIC_LAT_LON' all_sql.append("INSERT INTO geodetic_crs VALUES('%s',%d,'%s','%s','other','%s','%s','%s',%d,NULL,0);" % (AUTH_IAU2015, crs_code, crs_name, SOURCE_IAU, cs_auth, cs_code, AUTH_IAU2015, ellipsoidal_datum_code)) add_usage('geodetic_crs', crs_code) if not (has_ographic and direction == 'east'): # We don't need to generate projected CRS based on the ocentric one, if we have # generated them for the ographic CRS and that it is east oriented generate_projected_crs(crs_code, crs_name, False) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') f = open(os.path.join(sql_dir_name, 'iau') + '.sql', 'wb') f.write("--- This file has been generated by scripts/build_db_from_iau.py. DO NOT EDIT !\n\n".encode('UTF-8')) for sql in all_sql: f.write((sql + '\n').encode('UTF-8')) f.close() print('') print('Finished !') proj-9.8.1/scripts/build_db_create_ignf_from_xml.py000775 001750 001750 00000203203 15166171715 022514 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Build SRS and coordinate transform database from IGNF registry # Author: Even Rouault # ############################################################################### # Copyright (c) 2018, Even Rouault # # 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. ############################################################################### from lxml import etree import os import requests import sys # url = "http://librairies.ign.fr/geoportail/resources/IGNF.xml" url = "https://raw.githubusercontent.com/rouault/proj-resources/master/IGNF.v3.1.0.xml" if len(sys.argv) not in (1, 2) or (len(sys.argv) == 2 and sys.argv[1].startswith('-')): print('Usage: build_db_create_ignf.py [path_to_IGNF.xml]') print('') print('If local filename is not used, then %s is downloaded.' % url) sys.exit(1) def escape_literal(x): return x.replace("'", "''") def strip_ns_prefix(tree): for element in tree.iter('*'): element.tag = etree.QName(element).localname for att in element.attrib: newkey = att[att.find('}')+1:] val = element.attrib[att] del element.attrib[att] element.attrib[newkey] = val return tree all_sql = [] all_sql_concat = [] # for concatenated_operation if len(sys.argv) == 1: r = requests.get(url) root = etree.fromstring(r.content) else: IGNF_file = sys.argv[1] tree = etree.parse(open(IGNF_file, 'rt')) root = tree.getroot() root = strip_ns_prefix(root) # Retrieve and insert metada version = root.find('versionNumber').find('CharacterString').text date = root.find('versionDate').find('Date').text all_sql.append("""INSERT INTO "metadata" VALUES('IGNF.SOURCE', '%s');""" % escape_literal(url)) all_sql.append("""INSERT INTO "metadata" VALUES('IGNF.VERSION', '%s');""" % version) all_sql.append("""INSERT INTO "metadata" VALUES('IGNF.DATE', '%s');""" % date) def get_epsg_code(txt): assert ':EPSG:' in txt return txt[txt.rfind(':')+1:] def ingest_ellipsoids(root, all_sql): mapEllpsId = {} for ellps in root.iter('ellipsoid'): E = ellps.find('Ellipsoid') id = E.attrib['id'] names = [name.text for name in E.iter('name')] #print(id, names) if len(names) == 2: mapEllpsId[id] = ('EPSG', get_epsg_code(names[1])) else: assert len(names) == 1 assert E.find('secondDefiningParameter').find('SecondDefiningParameter').find('isSphere').text == 'sphere' mapEllpsId[id] = ('IGNF', id) all_sql.append("""INSERT INTO "ellipsoid" VALUES('IGNF','%s','%s',NULL,'PROJ', 'EARTH', %s,'EPSG','9001',0,NULL,0);""" % (id, names[0], E.find('semiMajorAxis').text)) return mapEllpsId def ingest_prime_meridians(root, all_sql): mapPmId = {} for pm in root.iter('primeMeridian'): PM = pm.find('PrimeMeridian') id = PM.attrib['id'] names = [name.text for name in PM.iter('name')] #print(id, names) assert len(names) == 2 mapPmId[id] = ('EPSG', get_epsg_code(names[1])) return mapPmId def extract_id_from_href(txt): assert '#' in txt return txt[txt.rfind('#')+1:] def ingest_datums(root, all_sql, mapEllpsId, mapPmId): mapDatumId = {} invalidDatumId = set() mapVerticalDatumId = {} for datum in root.iter('datum'): node = datum.find('GeodeticDatum') if node is not None: id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] if len(names) == 2: if id == 'REG0020002': assert get_epsg_code(names[1]) == '6275' print('Error in registry. it points to EPSG:6275 instead of EPSG:6807 for NTF Paris') mapDatumId[id] = ('EPSG', '6807') else: mapDatumId[id] = ('EPSG', get_epsg_code(names[1])) else: assert len(names) == 1, names pmNode = node.find('usesPrimeMeridian') if pmNode is None or 'href' not in pmNode.attrib: print('Invalid GeodeticDatum: ' + id) invalidDatumId.add(id) continue pmCode = extract_id_from_href(pmNode.attrib['href']) assert pmCode in mapPmId ellpsCode = extract_id_from_href(node.find('usesEllipsoid').attrib['href']) assert ellpsCode in mapEllpsId sql = """INSERT INTO "geodetic_datum" VALUES('IGNF','%s','%s',NULL,'%s','%s','%s','%s',NULL,NULL,NULL,NULL,NULL,0);""" % (id, names[0], mapEllpsId[ellpsCode][0], mapEllpsId[ellpsCode][1], mapPmId[pmCode][0], mapPmId[pmCode][1]) all_sql.append(sql) mapDatumId[id] = ('IGNF', id) else: node = datum.find('VerticalDatum') if node is not None: id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] sql = """INSERT INTO "vertical_datum" VALUES('IGNF','%s','%s',NULL,NULL,NULL,NULL,NULL,NULL,0);"""% (id, names[0]) all_sql.append(sql) mapVerticalDatumId[id] = ('IGNF', id) else: assert False return mapDatumId, mapVerticalDatumId, invalidDatumId mapEllpsId = ingest_ellipsoids(root, all_sql) mapPmId = ingest_prime_meridians(root, all_sql) mapDatumId, mapVerticalDatumId, invalidDatumId = ingest_datums(root, all_sql, mapEllpsId, mapPmId) extentMap = {} def get_extent_auth_name_code(domainOfValidity): extent = domainOfValidity.find('EX_Extent') desc = extent.find('description').find('CharacterString').text if desc is None: return 'EPSG', '1262' if desc in extentMap: return extentMap[desc] geographicElement = extent.find('geographicElement') if geographicElement is None: print('No geographicElement for area of use ' + desc) return 'EPSG', '1262' code = str(len(extentMap) + 1) extentMap[desc] = ['IGNF', code ] EX_GeographicBoundingBox = geographicElement.find('EX_GeographicBoundingBox') south = EX_GeographicBoundingBox.find('southBoundLatitude').find('Decimal').text west = EX_GeographicBoundingBox.find('westBoundLongitude').find('Decimal').text north = EX_GeographicBoundingBox.find('northBoundLatitude').find('Decimal').text east = EX_GeographicBoundingBox.find('eastBoundLongitude').find('Decimal').text all_sql.append("""INSERT INTO "extent" VALUES('IGNF','%s','%s','%s',%s,%s,%s,%s,0);""" % (code, escape_literal(desc), escape_literal(desc), south, north, west, east)) return extentMap[desc] scopeMap = {} def get_scope_auth_name_code(scope): if scope in scopeMap: return scopeMap[scope] code = str(len(scopeMap)+1) scopeMap[scope] = ['IGNF', code] all_sql.append("""INSERT INTO scope VALUES('IGNF','%s','%s',0);""" % (code, scope)) return scopeMap[scope] mapCrsId = {} mapGeocentricId = {} # This is a trick to find a GeocentricCRS and its related GeographicCRS # We could use the name, but if we use the datum code + area of use, it is # more reliable # We need this since the registry only exposes GeocentricCRS <--> GeocentricCRS # transformations, and we need to port them to GeographicCRS as well mapDatumAndAreaToGeocentricId = {} mapGeocentricIdToDatumAndArea = {} aliasOfCRS = {} for node in root.iterfind('.//GeocentricCRS'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] cartesianCS = extract_id_from_href(node.find('usesCartesianCS').attrib['href']) assert cartesianCS == 'TYP_CRG10' datumCode = extract_id_from_href(node.find('usesGeodeticDatum').attrib['href']) if datumCode in invalidDatumId: print('Skipping GeocentricCRS %s since its datum is unknown' % id) continue assert datumCode in mapDatumId, (id, name, datumCode) extent_auth_and_code = get_extent_auth_name_code(node.find('domainOfValidity')) scope = node.find('scope').text scope_auth_and_code = get_scope_auth_name_code(scope) #sql = """INSERT INTO "crs" VALUES('IGNF','%s','geocentric');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "geodetic_crs" VALUES('IGNF','%s','%s',NULL,'geocentric','EPSG','6500','%s','%s',NULL,0);""" % (id, name, mapDatumId[datumCode][0], mapDatumId[datumCode][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','geodetic_crs','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) mapCrsId[id] = ('IGNF', id) mapGeocentricId[id] = ('IGNF', id) if len(names) >= 2 and names[1].startswith('http://registre.ign.fr/ign/IGNF/crs/IGNF/'): alias = names[1][len('http://registre.ign.fr/ign/IGNF/crs/IGNF/'):] aliasOfCRS[id] = [('IGNF', alias)] if id == 'WGS84': aliasOfCRS[id].append(('EPSG', '4978')) #sql = """INSERT INTO "crs" VALUES('IGNF','%s','geocentric'); -- alias of %s""" % (alias, id) #all_sql.append(sql) sql = """INSERT INTO "geodetic_crs" VALUES('IGNF','%s','%s',NULL,'geocentric','EPSG','6500','%s','%s',NULL,0);""" % (alias, name, mapDatumId[datumCode][0], mapDatumId[datumCode][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','geodetic_crs','IGNF','%s','%s','%s','%s','%s');""" % (alias, alias, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) key = str((mapDatumId[datumCode], extent_auth_and_code)) assert key not in mapDatumAndAreaToGeocentricId, (id, name) mapDatumAndAreaToGeocentricId[key] = ('IGNF', id) mapGeocentricIdToDatumAndArea[id] = key mapGeographicId = {} mapDatumAndAreaToGeographicId = {} for node in root.iterfind('.//GeographicCRS'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] ellipsoidalCS = extract_id_from_href(node.find('usesEllipsoidalCS').attrib['href']) assert ellipsoidalCS in ('TYP_CRG24', 'TYP_CRG26', 'TYP_CRG22', 'TYP_CRG28', 'TYP_CRG29'), (id, name, ellipsoidalCS) datumCode = extract_id_from_href(node.find('usesGeodeticDatum').attrib['href']) if datumCode in invalidDatumId: print('Skipping GeographicCRS %s since its datum is unknown' % id) continue assert datumCode in mapDatumId, (id, name, datumCode) extent_auth_and_code = get_extent_auth_name_code(node.find('domainOfValidity')) scope = node.find('scope').text scope_auth_and_code = get_scope_auth_name_code(scope) csCode = None type = 'geographic 2D' if ellipsoidalCS in ('TYP_CRG24', 'TYP_CRG28'): # Long, Lat deg csCode = '6424' if ellipsoidalCS in ('TYP_CRG26', 'TYP_CRG29'): # Long, Lat deg, h m csCode = '6426' type = 'geographic 3D' if ellipsoidalCS == 'TYP_CRG22': # Long, Lat grad csCode = '6425' #sql = """INSERT INTO "crs" VALUES('IGNF','%s','%s');""" % (id, type) #all_sql.append(sql) sql = """INSERT INTO "geodetic_crs" VALUES('IGNF','%s','%s',NULL,'%s','EPSG','%s','%s','%s',NULL,0);""" % (id, name, type, csCode, mapDatumId[datumCode][0], mapDatumId[datumCode][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','geodetic_crs','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) if id == 'WGS84G': aliasOfCRS[id] = [('EPSG','4326')] if len(names) >= 2 and names[1].startswith('http://registre.ign.fr/ign/IGNF/crs/IGNF/'): alias = names[1][len('http://registre.ign.fr/ign/IGNF/crs/IGNF/'):] assert id != 'WGS84G' aliasOfCRS[id] = [('IGNF', alias)] #sql = """INSERT INTO "crs" VALUES('IGNF','%s','%s'); -- alias of %s""" % (alias, type, id) #all_sql.append(sql) sql = """INSERT INTO "geodetic_crs" VALUES('IGNF','%s','%s',NULL,'%s','EPSG','%s','%s','%s',NULL,0);""" % (alias, name, type, csCode, mapDatumId[datumCode][0], mapDatumId[datumCode][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','geodetic_crs','IGNF','%s','%s','%s','%s','%s');""" % (alias, alias, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) mapCrsId[id] = ('IGNF', id) mapGeographicId[id] = ('IGNF', id, type) key = str((mapDatumId[datumCode], extent_auth_and_code)) if key in mapDatumAndAreaToGeographicId: #print('Adding ' + id + ' to ' + str(mapDatumAndAreaToGeographicId[key])) mapDatumAndAreaToGeographicId[key].append(id) else: mapDatumAndAreaToGeographicId[key] = [id] # Create a 2D version to be able to create compoundCRS with it if id == 'RGWF96GEO': id = 'RGWF96G' csCode = '6424' type = 'geographic 2D' #sql = """INSERT INTO "crs" VALUES('IGNF','%s','%s');""" % (id, type) #all_sql.append(sql) sql = """INSERT INTO "geodetic_crs" VALUES('IGNF','%s','%s',NULL,'%s','EPSG','%s','%s','%s',NULL,0);""" % (id, name, type, csCode, mapDatumId[datumCode][0], mapDatumId[datumCode][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','geodetic_crs','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) mapCrsId[id] = ('IGNF', id) mapGeographicId[id] = ('IGNF', id, type) key = str((mapDatumId[datumCode], extent_auth_and_code)) if key in mapDatumAndAreaToGeographicId: #print('Adding ' + id + ' to ' + str(mapDatumAndAreaToGeographicId[key])) mapDatumAndAreaToGeographicId[key].append(id) else: mapDatumAndAreaToGeographicId[key] = [id] mapVerticalCrsId = {} for node in root.iterfind('.//VerticalCRS'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] verticalCS = extract_id_from_href(node.find('usesVerticalCS').attrib['href']) assert verticalCS in ('TYP_CRG92','TYP_CRG91'), verticalCS datumCode = extract_id_from_href(node.find('usesVerticalDatum').attrib['href']) assert datumCode in mapVerticalDatumId, (id, name, datumCode) # VerticalCRS and GeocentricCRS can have same IDs ! like 'STPM50' id_modified = id if id in mapCrsId: print('VerticalCRS %s conflicts with a Geodetic one of same name. Appending _V for disambiguation'% id) id_modified += '_V' #sql = """INSERT INTO "crs" VALUES('IGNF','%s','vertical');""" % (id_modified) #all_sql.append(sql) extent_auth_and_code = get_extent_auth_name_code(node.find('domainOfValidity')) scope = node.find('scope').text scope_auth_and_code = get_scope_auth_name_code(scope) sql = """INSERT INTO "vertical_crs" VALUES('IGNF','%s','%s',NULL,'EPSG','6499','%s','%s',0);""" % (id_modified, name, mapVerticalDatumId[datumCode][0], mapVerticalDatumId[datumCode][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','vertical_crs','IGNF','%s','%s','%s','%s','%s');""" % (id_modified, id_modified, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) if len(names) >= 2 and names[1].startswith('http://registre.ign.fr/ign/IGNF/crs/IGNF/'): assert False mapCrsId[id] = ('IGNF', id_modified) mapVerticalCrsId[id] = ('IGNF', id_modified) mapGridURLs = { # France metropole 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/RAF09.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/RAF09.mnt', # France metropole 'https://geodesie.ign.fr/contenu/fichiers/documentation/grilles/metropole/RAF18.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/RAF18.mnt', # Corse 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/RAC09.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/RAC09.mnt', # Guadeloupe RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_gtbt.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAGTBT2016.mnt', 'RAGTBT2016.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAGTBT2016.mnt', # Les Saintes RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_ls.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RALS2016.mnt', 'RALS2016.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RALS2016.mnt', # Martinique RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_mart.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAMART2016.mnt', 'RAMART2016.MNT': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAMART2016.mnt', # Marie Galante RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_mg.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAMG2016.mnt', 'RAMG2016.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAMG2016.mnt', # Saint Barthelemy RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_sb.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/gg10_sbv2.mnt', 'gg10_sbv2.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/gg10_sbv2.mnt', 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_sbv2.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/gg10_sbv2.mnt', # Saint Martin RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_sm.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/gg10_sbv2.mnt', 'gg10_smv2.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/gg10_smv2.mnt', 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_smv2.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/gg10_smv2.mnt', # La Desirade RGAF09 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/gg10_ld.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RALD2016.mnt', 'RALD2016.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RALD2016.mnt', # Guadeloupe WGS84 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggg00.txt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/ggg00v2.txt', # Les Saintes WGS84 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggg00_ls.txt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/ggg00_lsv2.txt', 'ggg00_lsv2.mnt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/ggg00_lsv2.txt', # Martinique WGS84 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggm00.txt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/ggm00v2.txt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggm04v1.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggm04v1.mnt', # Saint Barthelemy WGS84 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggg00_sb.txt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/ggg00_sbv2.txt', # Saint Martin WGS84 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggg00_sm.txt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/anciennes/ggg00_smv2.txt', # La Desirade WGS84 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggg00_ld.txt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RALDW842016.mnt', 'RALDW842016.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RALDW842016.mnt', # Guyane RGF95 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggguy00.txt': 'http://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggguy15.mnt', # Reunion grille RAR 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/RAR07_bl.gra': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/RAR07_bl.gra', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Bora.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Bora.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Huahine.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Huahine.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Maiao.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Maiao.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Maupiti.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Maupiti.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Raiatea.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Raiatea.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Tahaa.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf02-Tahaa.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggker08v2.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggker08v2.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf08-Fakarava.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf08-Fakarava.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf10-Moorea.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf10-Moorea.mnt', 'http://geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf10-Tahiti.mnt': 'https://fiches-geodesie.ign.fr/contenu/fichiers/documentation/grilles/outremer/ggpf10-Tahiti.mnt', } setVerticalGrids = set() for node in root.iterfind('.//Transformation'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] sourceCRS = extract_id_from_href(node.find('sourceCRS').attrib['href']) if not sourceCRS in mapCrsId: print('Skipping ' + name + ', missing sourceCRS') continue targetCRS = node.find('targetCRS') if targetCRS is None or 'href' not in targetCRS.attrib: print('Skipping ' + name + ', missing targetCRS') continue targetCRS = extract_id_from_href(targetCRS.attrib['href']) if not targetCRS in mapCrsId: print('Skipping ' + name + ', missing targetCRS') continue operation_version = node.find('operationVersion').text extent_auth_and_code = get_extent_auth_name_code(node.find('domainOfValidity')) scope_auth_and_code = get_scope_auth_name_code(node.find('scope').text) usesMethod = extract_id_from_href(node.find('usesMethod').attrib['href']) if usesMethod in ('Geographic3DtoGravityRelatedHeight_IGN'): #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','grid_transformation');""" % id #all_sql.append(sql) usesValue = node.find('usesValue') paramValue = usesValue.find('ParameterValue') filename = paramValue.find('valueFile').text if filename in mapGridURLs: print('Fixing URL of ' + filename + ' to ' + mapGridURLs[filename]) filename = mapGridURLs[filename] if not filename.endswith('ggspm06v1.mnt'): # no longer available r = requests.head(filename, allow_redirects = True ) if r.status_code not in (200, 302): assert False, (r.status_code, id, name, filename) setVerticalGrids.add(filename) assert sourceCRS in mapVerticalCrsId, (id, name, sourceCRS) assert targetCRS in mapGeographicId, (id, name, targetCRS) # Switching source and target to be consistent with the EPSG practice and the naming of the method name_components = name.split(' vers ') name_inverted = name_components[1] + ' vers ' + name_components[0] sql = """INSERT INTO "grid_transformation" VALUES('IGNF','%s','%s',NULL,'EPSG','9664','Geographic3D to GravityRelatedHeight (IGN1997)','%s','%s','%s','%s',NULL,'EPSG','8666','Geoid (height correction) model file','%s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'%s',0);""" % (id, name_inverted, mapCrsId[targetCRS][0], mapCrsId[targetCRS][1], mapCrsId[sourceCRS][0], mapCrsId[sourceCRS][1], filename, operation_version) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','grid_transformation','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) continue def get_alias_of(code): if code in aliasOfCRS: return [ ('IGNF', code) ] + aliasOfCRS[code] return [ ('IGNF', code) ] if id == 'TSG1240': # 'NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS)', 'from1Dto1D') #print('Skipping ' + str((id, name))) assert usesMethod == 'from1Dto1D', usesMethod for src in get_alias_of(sourceCRS): for target in get_alias_of(targetCRS): custom_id = id if not ((src == ('IGNF', sourceCRS) and target == ('IGNF', targetCRS))): custom_id = id + '_' + src[0] + '_' + src[1] + '_TO_' + target[0] + '_' + target[1] #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','other_transformation');""" % custom_id #all_sql.append(sql) sql = """INSERT INTO "other_transformation" VALUES('IGNF','%s','%s',NULL,'EPSG','9601','Longitude rotation','%s','%s','%s','%s',0.0,'EPSG','8602','Longitude offset',2.5969213,'EPSG','9105',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'%s',0);"""% (custom_id, name, src[0], src[1], target[0], target[1], operation_version) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','other_transformation','IGNF','%s','%s','%s','%s','%s');""" % (custom_id, custom_id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) continue if usesMethod == 'TSGM510': # geocentric interpolation id = 'NTFG_TO_RGF93G' assert sourceCRS == 'NTF' assert targetCRS == 'RGF93' sourceCRS = 'NTFG' targetCRS = 'RGF93G' for src in get_alias_of(sourceCRS): # As the transformation from RGF93G to WGS84 is a zero-translation helmert, # we can also use the grid for NTF->WGS84. This makes the coordinate # operation finder happier for target in get_alias_of(targetCRS) + [('EPSG','4326')]: custom_id = id if not ((src == ('IGNF', sourceCRS) and target == ('IGNF', targetCRS))): custom_id = src[0] + '_' + src[1] + '_TO_' + target[0] + '_' + target[1] #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','grid_transformation');""" % (custom_id) #all_sql.append(sql) sql = """INSERT INTO "grid_transformation" VALUES('IGNF','%s','%s',NULL,'EPSG','9615','NTv2','%s','%s','%s','%s',NULL,'EPSG','8656','Latitude and longitude difference file','ntf_r93.gsb',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'%s',0);""" % (custom_id, name, src[0], src[1], target[0], target[1], operation_version) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','grid_transformation','IGNF','%s','%s','%s','%s','%s');""" % (custom_id, custom_id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) continue if usesMethod == 'Vfrom1Dto1D': #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','other_transformation');""" % id #all_sql.append(sql) usesValue = node.find('usesValue') paramValue = usesValue.find('ParameterValue') value = paramValue.find('value').text uom = paramValue.find('value').attrib['uom'] assert uom == 'm' sql = """INSERT INTO "other_transformation" VALUES('IGNF','%s','%s',NULL,'EPSG','9616','Vertical Offset','%s','%s','%s','%s',NULL,'EPSG','8603','Vertical Offset',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'%s',0);"""% (id, name, mapCrsId[sourceCRS][0], mapCrsId[sourceCRS][1], mapCrsId[targetCRS][0], mapCrsId[targetCRS][1], value, operation_version) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','other_transformation','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) continue assert usesMethod in ('TSGM120', 'TSGM110', 'TSGM112', 'TSGM111'), (id, name, usesMethod) assert sourceCRS in mapGeocentricId assert targetCRS in mapGeocentricId vals =[val for val in node.iterfind('usesValue')] assert len(vals) in (3,7) x = vals[0].find('ParameterValue').find('value').text assert vals[0].find('ParameterValue').find('value').attrib['uom'] == 'm' y = vals[1].find('ParameterValue').find('value').text assert vals[1].find('ParameterValue').find('value').attrib['uom'] == 'm' z = vals[2].find('ParameterValue').find('value').text assert vals[2].find('ParameterValue').find('value').attrib['uom'] == 'm' if len(vals) == 3: rx = 'NULL' ry = 'NULL' rz = 'NULL' s = 'NULL' r_uom_auth_name = 'NULL' r_uom_code = 'NULL' s_uom_auth_name = 'NULL' s_uom_code = 'NULL' method_code = "'1031'" method_name = "'Geocentric translations (geocentric domain)'" method_geog_code = "'9603'" method_geog_name = "'Geocentric translations (geog2D domain)'" method_geog_3d_code = "'1035'" method_geog_3d_name = "'Geocentric translations (geog3D domain)'" else: s = vals[3].find('ParameterValue').find('value').text assert vals[3].find('ParameterValue').find('value').attrib['uom'] == 'UNITE' rx = vals[4].find('ParameterValue').find('value').text assert vals[4].find('ParameterValue').find('value').attrib['uom'] == 'sec' ry = vals[5].find('ParameterValue').find('value').text assert vals[5].find('ParameterValue').find('value').attrib['uom'] == 'sec' rz = vals[6].find('ParameterValue').find('value').text assert vals[6].find('ParameterValue').find('value').attrib['uom'] == 'sec' r_uom_auth_name = "'EPSG'" r_uom_code = "'9104'" s_uom_auth_name = "'EPSG'" s_uom_code = "'9202'" method_code = "'1033'" method_name = "'Position Vector transformation (geocentric domain)'" method_geog_code = "'9606'" method_geog_name = "'Position Vector transformation (geog2D domain)'" method_geog_3d_code = "'1037'" method_geog_3d_name = "'Geocentric translations (geog3D domain)'" for src in get_alias_of(sourceCRS): for target in get_alias_of(targetCRS): custom_id = id if not ((src == ('IGNF', sourceCRS) and target == ('IGNF', targetCRS))): custom_id += '_' + src[1] + '_' + target[1] #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','helmert_transformation');""" % (custom_id) #all_sql.append(sql) sql = """INSERT INTO "helmert_transformation" VALUES('IGNF','%s','%s',NULL,'EPSG',%s,%s,'%s','%s','%s','%s',NULL,%s,%s,%s,'EPSG','9001',%s,%s,%s,%s,%s, %s,%s,%s,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'%s',0);""" % (custom_id, name, method_code, method_name, src[0], src[1], target[0], target[1], x, y, z, rx, ry, rz, r_uom_auth_name, r_uom_code, s, s_uom_auth_name, s_uom_code, operation_version) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','helmert_transformation','IGNF','%s','%s','%s','%s','%s');""" % (custom_id, custom_id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) key = mapGeocentricIdToDatumAndArea[sourceCRS] assert key in mapDatumAndAreaToGeographicId sourceGeogIdAr = mapDatumAndAreaToGeographicId[key] key = mapGeocentricIdToDatumAndArea[targetCRS] assert key in mapDatumAndAreaToGeographicId targetGeogIdAr = mapDatumAndAreaToGeographicId[key] for sourceGeogId in sourceGeogIdAr: for targetGeogId in targetGeogIdAr: # Check type type = mapGeographicId[sourceGeogId][2] if type != mapGeographicId[targetGeogId][2]: continue for src in get_alias_of(sourceGeogId): for target in get_alias_of(targetGeogId): id_geog = id + '_' + src[1] + '_TO_' + target[1] #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','helmert_transformation');""" % (id_geog) #all_sql.append(sql) sql = """INSERT INTO "helmert_transformation" VALUES('IGNF','%s','%s',NULL,'EPSG',%s,%s,'%s','%s','%s','%s',NULL,%s,%s,%s,'EPSG','9001',%s,%s,%s,%s,%s,%s,%s, %s,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'%s',0);""" % (id_geog, name, method_geog_code if type == 'geographic 2D' else method_geog_3d_code, method_geog_name if type == 'geographic 2D' else method_geog_3d_name, src[0], src[1], target[0], target[1], x, y, z, rx, ry, rz, r_uom_auth_name, r_uom_code, s, s_uom_auth_name, s_uom_code, operation_version) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','helmert_transformation','IGNF','%s','%s','%s','%s','%s');""" % (id_geog, id_geog, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) if src[1] == 'NTFG': for NTFPalias, idFirstOp in (('NTFPGRAD', 'TSG1240'), ('NTFP', 'TSG1240_IGNF_NTFP_TO_IGNF_NTFG')): id_concat = id + '_' + NTFPalias + '_TO_' + target[1] #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','concatenated_operation');""" % (id_concat) #all_sql_concat.append(sql) sql = """INSERT INTO "concatenated_operation" VALUES('IGNF','%s','Nouvelle Triangulation Francaise Paris grades to %s',NULL,'IGNF','%s','%s','%s',NULL,'%s',0);""" % (id_concat, target[1], NTFPalias, target[0], target[1], operation_version) all_sql_concat.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','concatenated_operation','IGNF','%s','%s','%s','%s','%s');""" % (id_concat, id_concat, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql_concat.append(sql) sql = """INSERT INTO "concatenated_operation_step" VALUES('IGNF','%s',1,'IGNF','%s',NULL);""" % (id_concat, idFirstOp) all_sql_concat.append(sql) sql = """INSERT INTO "concatenated_operation_step" VALUES('IGNF','%s',2,'IGNF','%s',NULL);""" % (id_concat, id_geog) all_sql_concat.append(sql) mapConversionId = {} def getParameter(node, code, expected_uom): for val in node.iterfind('usesValue'): parameter_code = extract_id_from_href(val.find('ParameterValue').find('valueOfParameter').attrib['href']) if parameter_code == code: dms = val.find('ParameterValue').find('dmsAngleValue') if expected_uom == 'deg' and dms is not None: deg_val = float(dms.find('degrees').text) direction_deg = dms.find('degrees').attrib['direction'] assert direction_deg in ('E', 'W', 'S', 'N') min_val = float(dms.find('minutes').text) if dms.find('secondes') is not None: sec_val = float(dms.find('secondes').text) else: sec_val = 0 ret_val = deg_val + min_val / 60.0 + sec_val / 3600.0 if direction_deg in ('W', 'S'): ret_val = -ret_val return ret_val assert val.find('ParameterValue').find('value').attrib['uom'] == expected_uom return float(val.find('ParameterValue').find('value').text) raise Exception('cannot find value for parameter ' + code) for node in root.iterfind('.//Conversion'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] #print(id, name) reuse_epsg_conversion = False ### if reuse_epsg_conversion and len(names) == 2: if id == 'PRC9601581': # PSEUDO MERCATOR (POPULAR VISUALISATION) assert get_epsg_code(names[1]) == '3857' # this is wrong, this is the projectedCRS code, note the conversion one mapConversionId[id] = ('EPSG', '3856') else: mapConversionId[id] = ('EPSG', get_epsg_code(names[1])) continue usesMethod = extract_id_from_href(node.find('usesMethod').attrib['href']) d = {} if usesMethod == 'PVPM001From2Dto2D': # Popular Visualisation Pseudo-Mercator assert len([1 for val in node.iterfind('usesValue')]) == 4 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') assert d['x_0'] == 0 assert d['y_0'] == 0 assert d['lon_0'] == 0 assert d['lat_0'] == 0 #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','1024','Popular Visualisation Pseudo Mercator','EPSG','8801','Latitude of natural origin',0.0,'EPSG','9102','EPSG','8802','Longitude of natural origin',0.0,'EPSG','9102','EPSG','8806','False easting',0.0,'EPSG','9001','EPSG','8807','False northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'EQRC001from2Dto2D': # Equirectangular assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['lat_ts'] = getParameter(node, 'PRCP600', 'deg') assert d['lat_0'] == 0, (id, name, d) #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','1028','Equidistant Cylindrical','EPSG','8823','Latitude of 1st standard parallel',%s,'EPSG','9102','EPSG','8802','Longitude of natural origin',%s,'EPSG','9102','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_ts'], d['lon_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod in ('PRCM030from2Dto2D', 'PRCM020from2Dto2D', 'PRCM040from2Dto2D', 'PRCM030from3Dto2D'): # Transverse Mercator assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',%s,'EPSG','9102','EPSG','8802','Longitude of natural origin',%s,'EPSG','9102','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['k_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM060from2Dto2D': # Bonne assert len([1 for val in node.iterfind('usesValue')]) == 6 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'gr') d['lat_0'] = getParameter(node, 'PRCP400', 'gr') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') d['lat_1'] = getParameter(node, 'PRCP600', 'gr') assert d['lat_0'] == d['lat_1'] #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9827','Bonne','EPSG','8801','Latitude of natural origin',%s,'EPSG','9105','EPSG','8802','Longitude of natural origin',%s,'EPSG','9105','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM015from2Dto2D': # LAEA assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') assert d['k_0'] == 1 #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9820','Lambert Azimuthal Equal Area','EPSG','8801','Latitude of natural origin',%s,'EPSG','9102','EPSG','8802','Longitude of natural origin',%s,'EPSG','9102','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM013from2Dto2D': # LCC_2SP assert len([1 for val in node.iterfind('usesValue')]) == 6 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['lat_1'] = getParameter(node, 'PRCP600', 'deg') d['lat_2'] = getParameter(node, 'PRCP700', 'deg') #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9802','Lambert Conic Conformal (2SP)','EPSG','8821','Latitude of false origin',%s,'EPSG','9102','EPSG','8822','Longitude of false origin',%s,'EPSG','9102','EPSG','8823','Latitude of 1st standard parallel',%s,'EPSG','9102','EPSG','8824','Latitude of 2nd standard parallel',%s,'EPSG','9102','EPSG','8826','Easting at false origin',%s,'EPSG','9001','EPSG','8827','Northing at false origin',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['lat_1'], d['lat_2'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM070from2Dto2D': # Mercator (variant A) assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9804','Mercator (variant A)','EPSG','8801','Latitude of natural origin',%s,'EPSG','9102','EPSG','8802','Longitude of natural origin',%s,'EPSG','9102','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['k_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod in ('PRCM012from2Dto2D', 'PRCM012from3Dto2D'): # LCC_1SP assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'gr') d['lat_0'] = getParameter(node, 'PRCP400', 'gr') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9801','Lambert Conic Conformal (1SP)','EPSG','8801','Latitude of natural origin',%s,'EPSG','9105','EPSG','8802','Longitude of natural origin',%s,'EPSG','9105','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['k_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod in ('PRCM014from2Dto2D'): # LCC_1SP assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP110', 'm') d['y_0'] = getParameter(node, 'PRCP210', 'm') d['lon_0'] = getParameter(node, 'PRCP310', 'gr') d['lat_0'] = getParameter(node, 'PRCP410', 'gr') d['k_0'] = getParameter(node, 'PRCP510', 'UNITE') #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9801','Lambert Conic Conformal (1SP)','EPSG','8801','Latitude of natural origin',%s,'EPSG','9105','EPSG','8802','Longitude of natural origin',%s,'EPSG','9105','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['k_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM053from2Dto2D': # Gauss Schreiber Transverse Mercator assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'PROJ','gstm','Gauss Schreiber Transverse Mercator','EPSG','8801','Latitude of natural origin',%s,'EPSG','9102','EPSG','8802','Longitude of natural origin',%s,'EPSG','9102','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['k_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM094from2Dto2D': # Polar Stereographic assert len([1 for val in node.iterfind('usesValue')]) == 5 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') d['k_0'] = getParameter(node, 'PRCP500', 'UNITE') assert float(d['lat_0']) == -90 assert float(d['lon_0']) == 140 #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'EPSG','9810','Polar Stereographic (variant A)','EPSG','8801','Latitude of natural origin',%s,'EPSG','9102','EPSG','8802','Longitude of natural origin',%s,'EPSG','9102','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','9001','EPSG','8807','False northing',%s,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id, name, d['lat_0'], d['lon_0'], d['k_0'], d['x_0'], d['y_0']) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'MILL001from2Dto2D': # Miller assert len([1 for val in node.iterfind('usesValue')]) == 4 d['x_0'] = getParameter(node, 'PRCP100', 'm') d['y_0'] = getParameter(node, 'PRCP200', 'm') d['lon_0'] = getParameter(node, 'PRCP300', 'deg') d['lat_0'] = getParameter(node, 'PRCP400', 'deg') assert d['x_0'] == 0 assert d['y_0'] == 0 assert d['lon_0'] == 0 assert d['lat_0'] == 0 #sql = """INSERT INTO "coordinate_operation" VALUES('IGNF','%s','conversion');""" % (id) #all_sql.append(sql) sql = """INSERT INTO "conversion" VALUES('IGNF','%s','%s',NULL,'PROJ','mill','PROJ mill',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""" % (id,name) all_sql.append(sql) mapConversionId[id] = ('IGNF', id) elif usesMethod == 'PRCM095from2Dto2D': print('Unhandled conversion PRCM095from2Dto2D = Polar Sterographic (Variant C) %s' % (str((id, name, usesMethod)))) continue else: print('Unknown conversion %s' % (str((id, name, usesMethod)))) assert False mapProjectedId = {} for node in root.iterfind('.//ProjectedCRS'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] usesCartesianCS = extract_id_from_href(node.find('usesCartesianCS').attrib['href']) assert usesCartesianCS in ('TYP_CRG32', 'TYP_CRG70', 'TYP_CRG34'), (id, name, usesCartesianCS) baseGeographicCRS = extract_id_from_href(node.find('baseGeographicCRS').attrib['href']) if baseGeographicCRS not in mapGeographicId: print('Skipping ProjectedCRS %s since its baseGeographicCRS %s is unknown' % (id, baseGeographicCRS)) continue definedByConversion = extract_id_from_href(node.find('definedByConversion').attrib['href']) if definedByConversion in ('PRC0909577'): print('Skipping ProjectedCRS %s since its definedByConversion %s is unhandled' % (id, definedByConversion)) continue assert definedByConversion in mapConversionId, (id, name, definedByConversion) extent_auth_and_code = get_extent_auth_name_code(node.find('domainOfValidity')) scope = node.find('scope').text scope_auth_and_code = get_scope_auth_name_code(scope) #sql = """INSERT INTO "crs" VALUES('IGNF','%s','projected');""" % (id, ) #all_sql.append(sql) cs_code = 4499 # TYP_CRG32 if usesCartesianCS == 'TYP_CRG70': cs_code = 4400 if usesCartesianCS == 'TYP_CRG34': cs_code = 4530 sql = """INSERT INTO "projected_crs" VALUES('IGNF','%s','%s',NULL,'EPSG','%s','%s','%s','%s','%s',NULL,0);""" % (id,name,cs_code,mapGeographicId[baseGeographicCRS][0], mapGeographicId[baseGeographicCRS][1],mapConversionId[definedByConversion][0], mapConversionId[definedByConversion][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','projected_crs','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) if len(names) >= 2 and names[1].startswith('http://registre.ign.fr/ign/IGNF/crs/IGNF/'): alias = names[1][len('http://registre.ign.fr/ign/IGNF/crs/IGNF/'):] aliasOfCRS[id] = [('IGNF', alias)] #sql = """INSERT INTO "crs" VALUES('IGNF','%s','projected'); -- alias of %s""" % (alias, id) #all_sql.append(sql) sql = """INSERT INTO "projected_crs" VALUES('IGNF','%s','%s',NULL,'EPSG','%s','%s','%s','%s','%s',NULL,0);""" % (alias,name,cs_code,mapGeographicId[baseGeographicCRS][0], mapGeographicId[baseGeographicCRS][1],mapConversionId[definedByConversion][0], mapConversionId[definedByConversion][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','projected_crs','IGNF','%s','%s','%s','%s','%s');""" % (alias, alias, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) mapProjectedId[id] = ('IGNF', id) for node in root.iterfind('.//CompoundCRS'): id = node.attrib['id'] names = [_name.text for _name in node.iter('name')] name = names[0] singleCRS = [extract_id_from_href(includesSingleCRS.attrib['href']) for includesSingleCRS in node.iter('includesSingleCRS')] assert len(singleCRS) == 2 if singleCRS[0] == 'RGWF96GEO': singleCRS[0] = 'RGWF96G' assert singleCRS[0] in mapProjectedId or singleCRS[0] in mapGeographicId, (id, name) assert singleCRS[1] in mapVerticalCrsId, (id, name, singleCRS[1]) if singleCRS[0] in mapProjectedId: horiz = mapProjectedId[singleCRS[0]] else: horiz = mapGeographicId[singleCRS[0]] extent_auth_and_code = get_extent_auth_name_code(node.find('domainOfValidity')) scope = node.find('scope').text scope_auth_and_code = get_scope_auth_name_code(scope) #sql = """INSERT INTO "crs" VALUES('IGNF','%s','compound');""" % (id, ) #all_sql.append(sql) sql = """INSERT INTO "compound_crs" VALUES('IGNF','%s','%s',NULL,'%s','%s','%s','%s',0);""" % (id,name,horiz[0], horiz[1],mapVerticalCrsId[singleCRS[1]][0], mapVerticalCrsId[singleCRS[1]][1]) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('IGNF', '%s_USAGE','compound_crs','IGNF','%s','%s','%s','%s','%s');""" % (id, id, extent_auth_and_code[0], extent_auth_and_code[1], scope_auth_and_code[0], scope_auth_and_code[1]) all_sql.append(sql) if len(names) >= 2 and names[1].startswith('http://registre.ign.fr/ign/IGNF/crs/IGNF/'): assert False all_sql.append('') all_sql.append("""--- Grid alternatives""") all_sql.append('') all_sql.append("""INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('ntf_r93.gsb', -- as referenced by the IGNF registry 'fr_ign_ntf_r93.tif', 'ntf_r93.gsb', 'GTiff', 'hgridshift', 0, NULL, 'https://cdn.proj.org/fr_ign_ntf_r93.tif', 1, 1, NULL); """) for grid in sorted(setVerticalGrids): original_grid_name = grid old_proj_grid_name = grid[grid.rfind('/')+1:].replace('.txt', '.gtx').replace('.mnt', '.gtx').replace('.gra', '.gtx') gtiff_grid_name = 'fr_ign_' + old_proj_grid_name[0:-4] + '.tif' proj_cdn_url = 'https://cdn.proj.org/' + gtiff_grid_name r = requests.head(proj_cdn_url, allow_redirects = True) assert r.status_code in (200, 302), proj_cdn_url all_sql.append("""INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('%s', -- as referenced by the IGNF registry '%s', '%s', 'GTiff', 'geoid_like', 0, NULL, '%s', 1, 1, NULL);""" % (original_grid_name, gtiff_grid_name, old_proj_grid_name, proj_cdn_url)) all_sql.append('') all_sql.append("""--- Null transformations between RRAF and WGS84 adapted from EPSG""") all_sql.append('') area_of_use_name = 'ANTILLES FRANCAISES' assert area_of_use_name in extentMap extent_auth_and_code = extentMap[area_of_use_name] all_sql.append("""INSERT INTO "helmert_transformation" VALUES('PROJ','IGNF_RRAF_TO_EPSG_4978','RRAF to WGS 84',NULL,'EPSG','1031','Geocentric translations (geocentric domain)','IGNF','RRAF','EPSG','4978',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""") sql = """INSERT INTO "usage" VALUES('PROJ', '%s_USAGE','helmert_transformation','PROJ','%s','%s','%s','%s','%s');""" % ('IGNF_RRAF_TO_EPSG_4978', 'IGNF_RRAF_TO_EPSG_4978', extent_auth_and_code[0], extent_auth_and_code[1], 'EPSG', '1024') all_sql.append(sql) all_sql.append("""INSERT INTO "helmert_transformation" VALUES('PROJ','IGNF_RRAFG_TO_EPSG_4326','RRAFG to WGS 84',NULL,'EPSG','9603','Geocentric translations (geog2D domain)','IGNF','RRAFG','EPSG','4326',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""") sql = """INSERT INTO "usage" VALUES('PROJ', '%s_USAGE','helmert_transformation','PROJ','%s','%s','%s','%s','%s');""" % ('IGNF_RRAFG_TO_EPSG_4326', 'IGNF_RRAFG_TO_EPSG_4326', extent_auth_and_code[0], extent_auth_and_code[1], 'EPSG', '1024') all_sql.append(sql) all_sql.append("""INSERT INTO "helmert_transformation" VALUES('PROJ','IGNF_RRAFGDD_TO_EPSG_4326','RRAFGDD to WGS 84',NULL,'EPSG','9603','Geocentric translations (geog2D domain)','IGNF','RRAFGDD','EPSG','4326',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""") sql = """INSERT INTO "usage" VALUES('PROJ', '%s_USAGE','helmert_transformation','PROJ','%s','%s','%s','%s','%s');""" % ('IGNF_RRAFGDD_TO_EPSG_4326', 'IGNF_RRAFGDD_TO_EPSG_4326', extent_auth_and_code[0], extent_auth_and_code[1], 'EPSG', '1024') all_sql.append(sql) all_sql.append('') all_sql.append("""--- Null transformations between RGF93 and WGS84 adapted from EPSG""") all_sql.append('') area_of_use_name = 'FRANCE METROPOLITAINE (CORSE COMPRISE)' assert area_of_use_name in extentMap extent_auth_and_code = extentMap[area_of_use_name] all_sql.append("""INSERT INTO "helmert_transformation" VALUES('PROJ','IGNF_RGF93_TO_EPSG_4978','RGF93 to WGS 84',NULL,'EPSG','1031','Geocentric translations (geocentric domain)','IGNF','RGF93','EPSG','4978',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""") sql = """INSERT INTO "usage" VALUES('PROJ', '%s_USAGE','helmert_transformation','PROJ','%s','%s','%s','%s','%s');""" % ('IGNF_RGF93_TO_EPSG_4978', 'IGNF_RGF93_TO_EPSG_4978', extent_auth_and_code[0], extent_auth_and_code[1], 'EPSG', '1024') all_sql.append(sql) all_sql.append("""INSERT INTO "helmert_transformation" VALUES('PROJ','IGNF_RGF93G_TO_EPSG_4326','RGF93G to WGS 84',NULL,'EPSG','9603','Geocentric translations (geog2D domain)','IGNF','RGF93G','EPSG','4326',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""") sql = """INSERT INTO "usage" VALUES('PROJ', '%s_USAGE','helmert_transformation','PROJ','%s','%s','%s','%s','%s');""" % ('IGNF_RGF93G_TO_EPSG_4326', 'IGNF_RGF93G_TO_EPSG_4326', extent_auth_and_code[0], extent_auth_and_code[1], 'EPSG', '1024') all_sql.append(sql) all_sql.append("""INSERT INTO "helmert_transformation" VALUES('PROJ','IGNF_RGF93GDD_TO_EPSG_4326','RGF93GDD to WGS 84',NULL,'EPSG','9603','Geocentric translations (geog2D domain)','IGNF','RGF93GDD','EPSG','4326',1.0,0.0,0.0,0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);""") sql = """INSERT INTO "usage" VALUES('PROJ', '%s_USAGE','helmert_transformation','PROJ','%s','%s','%s','%s','%s');""" % ('IGNF_RGF93GDD_TO_EPSG_4326', 'IGNF_RGF93GDD_TO_EPSG_4326', extent_auth_and_code[0], extent_auth_and_code[1], 'EPSG', '1024') all_sql.append(sql) script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') f = open(os.path.join(sql_dir_name, 'ignf') + '.sql', 'wb') f.write("--- This file has been generated by scripts/build_db_create_ignf_from_xml.py from the http://librairies.ign.fr/geoportail/resources/IGNF.xml definition file. DO NOT EDIT !\n\n".encode('UTF-8')) for sql in all_sql: f.write((sql + '\n').encode('UTF-8')) comment = [] comment.append('') comment.append("""--- Concatenated operations""") comment.append('') for sql in comment: f.write((sql + '\n').encode('UTF-8')) for sql in all_sql_concat: f.write((sql + '\n').encode('UTF-8')) f.close() proj-9.8.1/scripts/build_db.py000775 001750 001750 00000256467 15166171715 016307 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Build SRS and coordinate transform database # Author: Even Rouault # ############################################################################### # Copyright (c) 2018, Even Rouault # # 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. ############################################################################### """ Steps for updating proj.db with the latest EPSG data: 1. Download the latest version of the EPSG registry from epsg.org. Choose the PostgreSQL scripts. You may need to register a user on the site. 2. Unzip the downloaded file into scripts/ 3. Run this script: > python build_db.py 4. Verify output of the script and fix any errors it may report 5. Update version number in `data/sql/metadata.sql` 6. Update the `PROJ_DB_SQL_EXPECTED_MD5` in `data/CmakeLists.txt`: Build PROJ with CMake to get the new hash. Update the hash and build again to verify that the hash is correct. 7. Run the test suite. Fix errors, if any. """ import os import re import sqlite3 import sys EPSG_AUTHORITY = 'EPSG' def ingest_sqlite_dump(cursor, filename): sql = '' f = open(filename, 'rb') # Skip UTF-8 BOM if f.read(3) != b'\xEF\xBB\xBF': f.seek(0, os.SEEK_SET) for line in f.readlines(): line = line.replace(b'\r\n', b'\n') if sys.version_info >= (3, 0, 0): line = line.decode('utf-8') #python3 else: line = str(line) # python2 # Historically this script was developed with code columns using TEXT # so keep it that way to minimized changes in it, and in the diff of # generated .sql files line = line.replace('INTEGER_OR_TEXT', 'TEXT') # Same for WITHOUT ROWID line = line.replace('WITHOUT ROWID', '') sql += line if sqlite3.complete_statement(sql): sql = sql.strip() if sql != 'COMMIT;': try: cursor.execute(sql) except: print(sql) raise sql = '' def ingest_epsg(): for f in ['PostgreSQL_Data_Script.sql', 'PostgreSQL_Table_Script.sql']: if not os.path.exists(f): raise Exception('Missing file: ' + f) epsg_tmp_db_filename = 'tmp_epsg.db' if os.path.exists(epsg_tmp_db_filename): os.unlink(epsg_tmp_db_filename) conn = sqlite3.connect(epsg_tmp_db_filename) cursor = conn.cursor() cursor.execute('PRAGMA journal_mode = OFF;') ingest_sqlite_dump(cursor, 'PostgreSQL_Table_Script.sql') ingest_sqlite_dump(cursor, 'PostgreSQL_Data_Script.sql') cursor.close() conn.commit() return (conn, epsg_tmp_db_filename) def fill_unit_of_measure(proj_db_cursor): proj_db_cursor.execute( "INSERT INTO unit_of_measure SELECT ?, uom_code, unit_of_meas_name, unit_of_meas_type, factor_b / factor_c, NULL, deprecated FROM epsg.epsg_unitofmeasure", (EPSG_AUTHORITY,)) def fill_ellipsoid(proj_db_cursor): proj_db_cursor.execute( "INSERT INTO ellipsoid SELECT ?, ellipsoid_code, ellipsoid_name, NULL, 'PROJ', 'EARTH', semi_major_axis, ?, uom_code, inv_flattening, semi_minor_axis, deprecated FROM epsg.epsg_ellipsoid", (EPSG_AUTHORITY, EPSG_AUTHORITY)) def fill_extent(proj_db_cursor): #proj_db_cursor.execute( # "INSERT INTO extent SELECT ?, extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecatedFROM epsg.epsg_extent", (EPSG_AUTHORITY,)) proj_db_cursor.execute( "SELECT extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecated FROM epsg.epsg_extent") res = proj_db_cursor.fetchall() for (extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecated) in res: try: # Some new records have longitudes outside [-180,180] if bbox_west_bound_lon and bbox_west_bound_lon < -180: # print( extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecated) bbox_west_bound_lon += 360 if bbox_east_bound_lon and bbox_east_bound_lon > 180: # print( extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecated) bbox_east_bound_lon -= 360 proj_db_cursor.execute( "INSERT INTO extent VALUES (?,?,?,?,?,?,?,?,?)", (EPSG_AUTHORITY, extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecated)) except sqlite3.IntegrityError as e: print(e) print(extent_code, extent_name, extent_description, bbox_south_bound_lat, bbox_north_bound_lat, bbox_west_bound_lon, bbox_east_bound_lon, deprecated) raise def fill_scope(proj_db_cursor): proj_db_cursor.execute( "INSERT INTO scope SELECT ?, scope_code, scope, deprecated FROM epsg.epsg_scope", (EPSG_AUTHORITY,)) def fill_usage(proj_db_cursor): proj_db_cursor.execute( "SELECT usage_code, object_table_name, object_code, extent_code, scope_code FROM epsg.epsg_usage") res = proj_db_cursor.fetchall() for (usage_code, object_table_name, object_code, extent_code, scope_code) in res: if object_table_name == 'epsg_coordinatereferencesystem': proj_db_cursor.execute('SELECT table_name FROM crs_view WHERE auth_name = ? AND code = ?', (EPSG_AUTHORITY, object_code)) proj_table_name = proj_db_cursor.fetchone() if proj_table_name is None: continue elif object_table_name == 'epsg_coordoperation': proj_db_cursor.execute("SELECT table_name FROM coordinate_operation_view WHERE auth_name = ? AND code = ? UNION ALL SELECT 'conversion' FROM conversion WHERE auth_name = ? AND code = ?", (EPSG_AUTHORITY, object_code, EPSG_AUTHORITY, object_code)) proj_table_name = proj_db_cursor.fetchone() if proj_table_name is None: continue elif object_table_name == 'epsg_datum': proj_db_cursor.execute("SELECT 'geodetic_datum' FROM geodetic_datum WHERE auth_name = ? AND code = ? UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE auth_name = ? AND code = ? UNION ALL SELECT 'engineering_datum' FROM engineering_datum WHERE auth_name = ? AND code = ?", (EPSG_AUTHORITY, object_code, EPSG_AUTHORITY, object_code, EPSG_AUTHORITY, object_code)) proj_table_name = proj_db_cursor.fetchone() if proj_table_name is None: continue proj_table_name = proj_table_name[0] proj_db_cursor.execute( "INSERT INTO usage VALUES (?,?,?,?,?,?,?,?,?)", (EPSG_AUTHORITY, usage_code, proj_table_name, EPSG_AUTHORITY, object_code, EPSG_AUTHORITY, extent_code, EPSG_AUTHORITY, scope_code)) def fill_prime_meridian(proj_db_cursor): proj_db_cursor.execute( "INSERT INTO prime_meridian SELECT ?, prime_meridian_code, prime_meridian_name, greenwich_longitude, ?, uom_code, deprecated FROM epsg.epsg_primemeridian", (EPSG_AUTHORITY, EPSG_AUTHORITY)) def compute_publication_date(datum_code, datum_name, frame_reference_epoch, publication_date): if frame_reference_epoch is not None: epoch = float(frame_reference_epoch) fractional = epoch - int(epoch) if fractional == 0: publication_date = '%04d-01-01' % int(epoch) elif abs(fractional - 0.4) < 1e-6: publication_date = '%04d-05-01' % int(epoch) elif abs(fractional - 0.5) < 1e-6: publication_date = '%04d-07-01' % int(epoch) else: assert False, (datum_code, datum_name, frame_reference_epoch, fractional) elif publication_date != '': if len(publication_date) == 4: publication_date += '-01-01' elif len(publication_date) == 7: publication_date += '-01' elif len(publication_date) == 4+1+4: m = re.search('([0-9]{4})-([0-9]{4})', publication_date) if m: publication_date = m.group(1) else: assert False, (datum_code, datum_name, publication_date) else: assert len(publication_date) == 10, (datum_code, datum_name, publication_date) else: publication_date = None return publication_date def fill_geodetic_datum(proj_db_cursor): proj_db_cursor.execute( "SELECT DISTINCT * FROM epsg.epsg_datum WHERE datum_type NOT IN ('geodetic', 'dynamic geodetic', 'ensemble', 'vertical', 'engineering')") res = proj_db_cursor.fetchall() if res: raise Exception('Found unexpected datum_type in epsg_datum: %s' % str(res)) proj_db_cursor.execute("SELECT datum_code, datum_name, ellipsoid_code, prime_meridian_code, publication_date, frame_reference_epoch, anchor_epoch, deprecated FROM epsg.epsg_datum WHERE datum_type IN ('geodetic', 'dynamic geodetic')") res = proj_db_cursor.fetchall() for (datum_code, datum_name, ellipsoid_code, prime_meridian_code, publication_date, frame_reference_epoch, anchor_epoch, deprecated) in res: publication_date = compute_publication_date(datum_code, datum_name, frame_reference_epoch, publication_date) proj_db_cursor.execute( "INSERT INTO geodetic_datum VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?)", (EPSG_AUTHORITY, datum_code, datum_name, EPSG_AUTHORITY, ellipsoid_code, EPSG_AUTHORITY, prime_meridian_code, publication_date, frame_reference_epoch, anchor_epoch, deprecated)) def fill_vertical_datum(proj_db_cursor): proj_db_cursor.execute("SELECT datum_code, datum_name, publication_date, frame_reference_epoch, anchor_epoch, deprecated FROM epsg.epsg_datum WHERE datum_type IN ('vertical')") res = proj_db_cursor.fetchall() for (datum_code, datum_name, publication_date, frame_reference_epoch, anchor_epoch, deprecated) in res: publication_date = compute_publication_date(datum_code, datum_name, frame_reference_epoch, publication_date) proj_db_cursor.execute( "INSERT INTO vertical_datum VALUES (?, ?, ?, NULL, ?, ?, NULL, NULL, ?, ?)", (EPSG_AUTHORITY, datum_code, datum_name, publication_date, frame_reference_epoch, anchor_epoch, deprecated)) def fill_engineering_datum(proj_db_cursor): proj_db_cursor.execute("SELECT datum_code, datum_name, publication_date, frame_reference_epoch, anchor_epoch, deprecated FROM epsg.epsg_datum WHERE datum_type IN ('engineering') AND datum_name NOT LIKE 'EPSG example%'") res = proj_db_cursor.fetchall() for (datum_code, datum_name, publication_date, frame_reference_epoch, anchor_epoch, deprecated) in res: publication_date = compute_publication_date(datum_code, datum_name, frame_reference_epoch, publication_date) proj_db_cursor.execute( "INSERT INTO engineering_datum VALUES (?, ?, ?, ?, NULL, ?, ?)", (EPSG_AUTHORITY, datum_code, datum_name, publication_date, anchor_epoch, deprecated)) def fill_datumensemble(proj_db_cursor): proj_db_cursor.execute("SELECT datum_code, datum_name, ensemble_accuracy, deprecated FROM epsg.epsg_datum JOIN epsg.epsg_datumensemble ON datum_code = datum_ensemble_code WHERE datum_type = 'ensemble'") rows = proj_db_cursor.fetchall() for (datum_code, datum_name, ensemble_accuracy, deprecated) in rows: assert ensemble_accuracy is not None proj_db_cursor.execute("SELECT DISTINCT replace(datum_type, 'dynamic ',''), ellipsoid_code, prime_meridian_code FROM epsg.epsg_datum WHERE datum_code IN (SELECT datum_code FROM epsg.epsg_datumensemblemember WHERE datum_ensemble_code = ?)", (datum_code,)) subrows = proj_db_cursor.fetchall() assert len(subrows) == 1, (datum_code, subrows) datum_type = subrows[0][0] if datum_type == 'vertical': datum_ensemble_member_table = 'vertical_datum_ensemble_member' proj_db_cursor.execute("INSERT INTO vertical_datum (auth_name, code, name, description, publication_date, frame_reference_epoch, ensemble_accuracy, deprecated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", (EPSG_AUTHORITY, datum_code, datum_name, None, None, None, ensemble_accuracy, deprecated)) else: datum_ensemble_member_table = 'geodetic_datum_ensemble_member' assert datum_type in ('dynamic geodetic', 'geodetic'), datum_code ellipsoid_code = subrows[0][1] prime_meridian_code = subrows[0][2] assert ellipsoid_code, datum_code assert prime_meridian_code, datum_code proj_db_cursor.execute( "INSERT INTO geodetic_datum (auth_name, code, name, description, ellipsoid_auth_name, ellipsoid_code, prime_meridian_auth_name, prime_meridian_code, publication_date, frame_reference_epoch, ensemble_accuracy, anchor, deprecated) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (EPSG_AUTHORITY, datum_code, datum_name, None, EPSG_AUTHORITY, ellipsoid_code, EPSG_AUTHORITY, prime_meridian_code, None, None, ensemble_accuracy, None, deprecated)) proj_db_cursor.execute("SELECT datum_code, datum_sequence FROM epsg.epsg_datumensemblemember WHERE datum_ensemble_code = ? ORDER by datum_sequence", (datum_code,)) for member_code, sequence in proj_db_cursor.fetchall(): proj_db_cursor.execute( "INSERT INTO " + datum_ensemble_member_table + " (ensemble_auth_name, ensemble_code, member_auth_name, member_code, sequence) VALUES (?, ?, ?, ?, ?)", (EPSG_AUTHORITY, datum_code, EPSG_AUTHORITY, member_code, sequence)) def find_crs_code_name_extent_from_geodetic_datum_code(proj_db_cursor, datum_code): proj_db_cursor.execute("SELECT coord_ref_sys_code, coord_ref_sys_name FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind = 'geographic 2D' AND deprecated = 0 AND datum_code = ? AND coord_ref_sys_name NOT LIKE '%(lon-lat)'", (datum_code,)) subrows = proj_db_cursor.fetchall() assert len(subrows) == 1, (subrows, datum_code) crs_code = subrows[0][0] crs_name = subrows[0][1] proj_db_cursor.execute("SELECT extent_code FROM epsg.epsg_usage WHERE object_table_name = 'epsg_coordinatereferencesystem' AND object_code = ?", (crs_code,)) subrows = proj_db_cursor.fetchall() assert len(subrows) == 1, (subrows, datum_code) crs_extent = subrows[0][0] return crs_code, crs_name, crs_extent def find_crs_code_name_extent_from_vertical_datum_code(proj_db_cursor, datum_code): proj_db_cursor.execute("SELECT coord_ref_sys_code, coord_ref_sys_name FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind = 'vertical' AND deprecated = 0 AND datum_code = ?", (datum_code,)) subrows = proj_db_cursor.fetchall() assert len(subrows) == 1, (subrows, datum_code) crs_code = subrows[0][0] crs_name = subrows[0][1] proj_db_cursor.execute("SELECT extent_code FROM epsg.epsg_usage WHERE object_table_name = 'epsg_coordinatereferencesystem' AND object_code = ?", (crs_code,)) subrows = proj_db_cursor.fetchall() assert len(subrows) == 1, (subrows, datum_code) crs_extent = subrows[0][0] return crs_code, crs_name, crs_extent def create_datumensemble_transformations(proj_db_cursor): proj_db_cursor.execute("SELECT datum_code, datum_name, ensemble_accuracy, deprecated FROM epsg.epsg_datum JOIN epsg.epsg_datumensemble ON datum_code = datum_ensemble_code WHERE datum_type = 'ensemble'") rows = proj_db_cursor.fetchall() for (datum_code, datum_name, ensemble_accuracy, deprecated) in rows: assert ensemble_accuracy is not None proj_db_cursor.execute("SELECT DISTINCT replace(datum_type, 'dynamic ',''), ellipsoid_code, prime_meridian_code FROM epsg.epsg_datum WHERE datum_code IN (SELECT datum_code FROM epsg.epsg_datumensemblemember WHERE datum_ensemble_code = ?)", (datum_code,)) subrows = proj_db_cursor.fetchall() assert len(subrows) == 1, (datum_code, subrows) datum_type = subrows[0][0] if datum_type == 'vertical': datum_ensemble_member_table = 'vertical_datum_ensemble_member' ensemble_crs_code, ensemble_crs_name, ensemble_crs_extent = find_crs_code_name_extent_from_vertical_datum_code(proj_db_cursor, datum_code) else: datum_ensemble_member_table = 'geodetic_datum_ensemble_member' assert datum_type in ('dynamic geodetic', 'geodetic'), datum_code ensemble_crs_code, ensemble_crs_name, ensemble_crs_extent = find_crs_code_name_extent_from_geodetic_datum_code(proj_db_cursor, datum_code) proj_db_cursor.execute("SELECT datum_code FROM epsg.epsg_datumensemblemember WHERE datum_ensemble_code = ? ORDER by datum_sequence", (datum_code,)) list_datums = list(proj_db_cursor.fetchall()) for member_code, in list_datums: if datum_ensemble_member_table == 'geodetic_datum_ensemble_member': # Insert a null transformation between the representative CRS of the datum ensemble # and each representative CRS of its members. crs_code, crs_name, crs_extent = find_crs_code_name_extent_from_geodetic_datum_code(proj_db_cursor, member_code) assert crs_extent == ensemble_crs_extent or (crs_extent in (2830, 1262) and ensemble_crs_extent in (2830, 1262)) or (ensemble_crs_code == 4258 and ensemble_crs_extent == 4755 and crs_extent in (1298, 1162, 4543, 1096, 1305, 1090, 1225, 1139, 1145, 3343, 1076, 1212, 1056, 4542, 1192, 1103, 1050, 1095, 1182, 1264, 1172, 1037, 1044, 1079, 1211, 1093, 1106, 1148, 4832, 1197, 4833, 1119, 1286, 1080, 1025, 1146, 4795)), (ensemble_crs_code, ensemble_crs_name, ensemble_crs_extent, crs_code, crs_name, crs_extent) # Check if there's already any transformation registered between # the member crs and the ensemble crs proj_db_cursor.execute(f"SELECT coord_op_name FROM epsg.epsg_coordoperation WHERE coord_op_name = '{crs_name} to {ensemble_crs_name} (1)' OR coord_op_name = '{ensemble_crs_name} to {crs_name} (1)'") v = proj_db_cursor.fetchone() if v: print(f"Skipping {ensemble_crs_name} to {crs_name} because of {v}") continue code = '%s_TO_%s' % (ensemble_crs_name, crs_name) code = code.replace(' ', '') code = code.replace('(', '_') code = code.replace(')', '') code = code.upper() name = '%s to %s' % (ensemble_crs_name, crs_name) remarks = 'Accuracy %s m, from datum ensemble definition' % ensemble_accuracy method_code = '9603' method_name = 'Geocentric translations (geog2D domain)' source_crs_code = ensemble_crs_code target_crs_code = crs_code coord_op_accuracy = ensemble_accuracy arg = ('PROJ', code, name, remarks, EPSG_AUTHORITY, method_code, method_name, EPSG_AUTHORITY, source_crs_code, EPSG_AUTHORITY, target_crs_code, coord_op_accuracy, 0,0,0,EPSG_AUTHORITY,'9001', None,None,None,None,None, None,None,None, None,None,None,None,None, None,None,None,None,None, None,None,None, None,None,None, None,None,None,None,None, '',0) proj_db_cursor.execute('INSERT INTO helmert_transformation VALUES (' + '?,?,?, ?, ?,?,?, ?,?, ?,?, ?, ?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?, ?,?,?,?,?, ?,?)', arg) proj_db_cursor.execute('INSERT INTO usage VALUES (?,?,?,?,?,?,?,?,?)', ('PROJ', code + '_USAGE', 'helmert_transformation', 'PROJ', code, EPSG_AUTHORITY, crs_extent, EPSG_AUTHORITY,'1024')) # unknown scope else: # Insert a null transformation between the representative CRS of the datum ensemble # and each representative CRS of its members. crs_code, crs_name, crs_extent = find_crs_code_name_extent_from_vertical_datum_code(proj_db_cursor, member_code) code = '%s_TO_%s' % (ensemble_crs_name, crs_name) code = code.replace(' ', '_') code = code.replace('St.', 'St') code = code.replace('(', '_') code = code.replace(')', '') code = code.replace('__', '_') code = code.upper() name = '%s to %s' % (ensemble_crs_name, crs_name) remarks = 'Accuracy %s m, from datum ensemble definition' % ensemble_accuracy method_code = '9616' method_name = 'Vertical Offset' source_crs_code = ensemble_crs_code target_crs_code = crs_code coord_op_accuracy = ensemble_accuracy arg = ('PROJ', code, name, remarks, EPSG_AUTHORITY, method_code, method_name, EPSG_AUTHORITY, source_crs_code, EPSG_AUTHORITY, target_crs_code, coord_op_accuracy, 'EPSG','8603','Vertical Offset',0,'EPSG','9001', None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None,None,None, None,None,None,None, None,None, '',0) proj_db_cursor.execute('INSERT INTO other_transformation VALUES (' + '?,?,?, ?, ?,?,?, ?,?, ?,?, ?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ' + '?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?, ?,?, ?,?)', arg) proj_db_cursor.execute('INSERT INTO usage VALUES (?,?,?,?,?,?,?,?,?)', ('PROJ', code + '_USAGE', 'other_transformation', 'PROJ', code, EPSG_AUTHORITY, crs_extent, EPSG_AUTHORITY,'1024')) # unknown scope handled_coord_sys_type = "('Cartesian', 'vertical', 'ellipsoidal', 'spherical', 'ordinal')" def fill_coordinate_system(proj_db_cursor): proj_db_cursor.execute( "INSERT INTO coordinate_system SELECT ?, coord_sys_code, coord_sys_type, dimension FROM epsg.epsg_coordinatesystem WHERE coord_sys_type IN " + handled_coord_sys_type, (EPSG_AUTHORITY,)) proj_db_cursor.execute("SELECT coord_sys_name, coord_sys_code, coord_sys_type, dimension FROM epsg.epsg_coordinatesystem WHERE coord_sys_type NOT IN " + handled_coord_sys_type) res = proj_db_cursor.fetchall() for row in res: print('Skipping coordinate system %s' % str(row)) def fill_axis(proj_db_cursor): proj_db_cursor.execute( "INSERT INTO axis " "SELECT ?, coord_axis_code, coord_axis_name, coord_axis_abbreviation, " "coord_axis_orientation, ?, ca.coord_sys_code, coord_axis_order, " "CASE WHEN uom_code IS NULL THEN NULL ELSE ? END, uom_code " "FROM epsg.epsg_coordinateaxis ca " "LEFT JOIN epsg.epsg_coordinateaxisname can " "ON ca.coord_axis_name_code = can.coord_axis_name_code " "JOIN epsg.epsg_coordinatesystem cs " "ON cs.coord_sys_code = ca.coord_sys_code " "WHERE coord_sys_type IN " + handled_coord_sys_type, (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) def fill_geodetic_crs(proj_db_cursor): # TODO?: address 'derived' proj_db_cursor.execute( "SELECT DISTINCT * FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind NOT IN ('projected', 'geographic 2D', 'geographic 3D', 'geocentric', 'vertical', 'compound', 'engineering', 'derived')") res = proj_db_cursor.fetchall() if res: raise Exception('Found unexpected coord_ref_sys_kind in epsg_coordinatereferencesystem: %s' % str(res)) #proj_db_cursor.execute( # "INSERT INTO crs SELECT ?, coord_ref_sys_code, coord_ref_sys_kind FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('geographic 2D', 'geographic 3D', 'geocentric') AND datum_code IS NOT NULL", (EPSG_AUTHORITY,)) # There are a few deprecated records of code 61 000 000 that we have never imported in versions <= 10.039 because # they lacked a datum code. We will continue to ignore them. proj_db_cursor.execute("INSERT INTO geodetic_crs SELECT ?, coord_ref_sys_code, coord_ref_sys_name, remarks, coord_ref_sys_kind, ?, coord_sys_code, ?, datum_code, NULL, deprecated FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('geographic 2D', 'geographic 3D', 'geocentric') AND datum_code IS NOT NULL AND NOT (coord_ref_sys_code > 61000000 AND deprecated)", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) def fill_vertical_crs(proj_db_cursor): #proj_db_cursor.execute( # "INSERT INTO crs SELECT ?, coord_ref_sys_code, coord_ref_sys_kind FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('vertical') AND datum_code IS NOT NULL", (EPSG_AUTHORITY,)) proj_db_cursor.execute("INSERT INTO vertical_crs SELECT ?, coord_ref_sys_code, coord_ref_sys_name, NULL, ?, coord_sys_code, ?, datum_code, deprecated FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('vertical') AND datum_code IS NOT NULL", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) proj_db_cursor.execute("SELECT * FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('vertical') AND datum_code IS NULL AND projection_conv_code NOT IN (7812, 7813)") res = proj_db_cursor.fetchall() for row in res: assert False, row proj_db_cursor.execute("SELECT * FROM epsg.epsg_coordinatereferencesystem crs1 WHERE crs1.coord_ref_sys_kind IN ('vertical') AND crs1.datum_code IS NULL AND NOT EXISTS (SELECT 1 FROM epsg.epsg_coordinatereferencesystem crs2 WHERE crs2.coord_ref_sys_code = crs1.base_crs_code AND crs2.coord_ref_sys_kind IN ('vertical'))") res = proj_db_cursor.fetchall() for row in res: assert False, row # Insert vertical_crs that are based on another one, such as EPSG:8228 that is based on EPSG:5703 proj_db_cursor.execute("INSERT INTO vertical_crs SELECT ?, crs1.coord_ref_sys_code, crs1.coord_ref_sys_name, NULL, ?, crs1.coord_sys_code, ?, crs2.datum_code, crs1.deprecated FROM epsg.epsg_coordinatereferencesystem crs1 JOIN epsg.epsg_coordinatereferencesystem crs2 ON crs1.base_crs_code = crs2.coord_ref_sys_code WHERE crs1.coord_ref_sys_kind IN ('vertical') AND crs1.datum_code IS NULL AND crs2.datum_code iS NOT NULL", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) # Extra punishment for EPSG:8051 that is based on EPSG:5715 which is based on EPSG:5714 proj_db_cursor.execute("INSERT INTO vertical_crs SELECT ?, crs1.coord_ref_sys_code, crs1.coord_ref_sys_name, NULL, ?, crs1.coord_sys_code, ?, crs3.datum_code, crs1.deprecated FROM epsg.epsg_coordinatereferencesystem crs1 JOIN epsg.epsg_coordinatereferencesystem crs2 ON crs1.base_crs_code = crs2.coord_ref_sys_code JOIN epsg.epsg_coordinatereferencesystem crs3 ON crs2.base_crs_code = crs3.coord_ref_sys_code WHERE crs1.coord_ref_sys_kind IN ('vertical') AND crs1.datum_code IS NULL AND crs2.datum_code IS NULL", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) def fill_engineering_crs(proj_db_cursor): proj_db_cursor.execute("SELECT ?, coord_ref_sys_code, coord_ref_sys_name, NULL, ?, coord_sys_code, ?, datum_code, deprecated FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('engineering') AND datum_code IS NOT NULL AND coord_ref_sys_name NOT LIKE 'EPSG%example%' AND coord_ref_sys_name NOT LIKE 'enter here name%'", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) res = proj_db_cursor.fetchall() for row in res: proj_db_cursor.execute("INSERT INTO engineering_crs VALUES (?,?,?,?,?,?,?,?,?)", (row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8])) proj_db_cursor.execute("SELECT * FROM epsg.epsg_coordinatereferencesystem crs1 WHERE crs1.coord_ref_sys_kind IN ('engineering') AND crs1.datum_code IS NULL AND NOT EXISTS (SELECT 1 FROM epsg.epsg_coordinatereferencesystem crs2 WHERE crs2.coord_ref_sys_code = crs1.base_crs_code AND crs2.coord_ref_sys_kind IN ('engineering'))") res = proj_db_cursor.fetchall() for row in res: assert False, row def fill_conversion(proj_db_cursor): # TODO? current we deal with point motion operation as transformation in grid_transformation table proj_db_cursor.execute( "SELECT DISTINCT * FROM epsg.epsg_coordoperation WHERE coord_op_type NOT IN ('conversion', 'transformation', 'concatenated operation', 'point motion operation')") res = proj_db_cursor.fetchall() if res: raise Exception('Found unexpected coord_op_type in epsg_coordoperation: %s' % str(res)) already_mapped_methods = set() trigger_sql = """ CREATE TRIGGER conversion_method_check_insert_trigger BEFORE INSERT ON conversion BEGIN """ # 1068 and 1069 are Height Depth Reversal and Change of Vertical Unit # In EPSG, there is one generic instance of those as 7812 and 7813 that # don't refer to particular CRS, and instances pointing to CRS names # The later are imported in the other_transformation table since we recover # the source/target CRS names from the transformation name. # Method EPSG:9666 'P6 I=J+90 seismic bin grid coordinate operation' requires more than 7 parameters. Not supported by PROJ for now # Idem for EPSG:1049 'P6 I=J-90 seismic bin grid coordinate operation' proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'conversion' AND coord_op_name NOT LIKE '%to DMSH' AND (coord_op_method_code NOT IN (1068, 1069, 9666, 1049) OR coord_op_code IN (7812,7813))") for (code, name, method_code, method_name, deprecated, remarks) in proj_db_cursor.fetchall(): # If skipping some projection methods is needed if method_code in (): print(f"Skipping conversion {code}, {name}, {method_code}, {method_name} as the map projection is not handled") continue expected_order = 1 max_n_params = 7 param_auth_name = [None for i in range(max_n_params)] param_code = [None for i in range(max_n_params)] param_name = [None for i in range(max_n_params)] param_value = [None for i in range(max_n_params)] param_uom_auth_name = [None for i in range(max_n_params)] param_uom_code = [None for i in range(max_n_params)] param_uom_type = [None for i in range(max_n_params)] iterator = proj_db_cursor.execute("SELECT sort_order, cop.parameter_code, parameter_name, parameter_value, uom_code, uom.unit_of_meas_type FROM epsg_coordoperationparam cop LEFT JOIN epsg_coordoperationparamvalue copv LEFT JOIN epsg_unitofmeasure uom USING (uom_code) LEFT JOIN epsg_coordoperationparamusage copu ON cop.parameter_code = copv.parameter_code AND copu.parameter_code = copv.parameter_code WHERE copu.coord_op_method_code = copv.coord_op_method_code AND coord_op_code = ? AND copv.coord_op_method_code = ? ORDER BY sort_order", (code, method_code)) for (order, parameter_code, parameter_name, parameter_value, uom_code, uom_type) in iterator: # Modified Krovak and Krovak North Oriented: keep only the 7 first parameters if order == max_n_params + 1 and method_code in (1042, 1043): break assert order <= max_n_params, (method_code, method_name, order) assert order == expected_order, (code, name, method_code, method_name) param_auth_name[order - 1] = EPSG_AUTHORITY param_code[order - 1] = parameter_code param_name[order - 1] = parameter_name param_value[order - 1] = parameter_value param_uom_auth_name[order - 1] = EPSG_AUTHORITY if uom_code else None param_uom_code[order - 1] = uom_code param_uom_type[order - 1] = uom_type expected_order += 1 if method_code not in already_mapped_methods: already_mapped_methods.add(method_code) trigger_sql += """ SELECT RAISE(ABORT, 'insert on conversion violates constraint: bad parameters for %(method_name)s') WHERE NEW.deprecated != 1 AND NEW.method_auth_name = 'EPSG' AND NEW.method_code = '%(method_code)s' AND (NEW.method_name != '%(method_name)s'""" % {'method_name': method_name, 'method_code' : method_code} for i in range(expected_order-1): trigger_sql += " OR NEW.param%(n)d_auth_name != 'EPSG' OR NEW.param%(n)d_code != '%(code)d' OR NEW.param%(n)d_name != '%(param_name)s'" % {'n': i+1, 'code': param_code[i], 'param_name': param_name[i]} if method_name in ('Change of Vertical Unit'): trigger_sql += " OR (NOT((NEW.param%(n)d_value IS NULL AND NEW.param%(n)d_uom_auth_name IS NULL AND NEW.param%(n)d_uom_code IS NULL) OR (NEW.param%(n)d_value IS NOT NULL AND (SELECT type FROM unit_of_measure WHERE auth_name = NEW.param%(n)s_uom_auth_name AND code = NEW.param%(n)s_uom_code) = 'scale')))" % {'n': i+1, 'param_name': param_name[i]} else: trigger_sql += " OR NEW.param%(n)d_value IS NULL OR NEW.param%(n)d_uom_auth_name IS NULL OR NEW.param%(n)d_uom_code IS NULL" % {'n': i+1, 'param_name': param_name[i]} if param_uom_type[i]: trigger_sql += " OR (SELECT type FROM unit_of_measure WHERE auth_name = NEW.param%(n)s_uom_auth_name AND code = NEW.param%(n)s_uom_code) != '%(uom_type)s'" % {'n': i+1, 'uom_type': param_uom_type[i]} for i in range(expected_order-1, max_n_params): trigger_sql += " OR NEW.param%(n)d_auth_name IS NOT NULL OR NEW.param%(n)d_code IS NOT NULL OR NEW.param%(n)d_name IS NOT NULL OR NEW.param%(n)d_value IS NOT NULL OR NEW.param%(n)d_uom_auth_name IS NOT NULL OR NEW.param%(n)d_uom_code IS NOT NULL" % {'n': i+1} trigger_sql += ");\n" arg = (EPSG_AUTHORITY, code, name, remarks, EPSG_AUTHORITY, method_code, method_name, param_auth_name[0], param_code[0], param_name[0], param_value[0], param_uom_auth_name[0], param_uom_code[0], param_auth_name[1], param_code[1], param_name[1], param_value[1], param_uom_auth_name[1], param_uom_code[1], param_auth_name[2], param_code[2], param_name[2], param_value[2], param_uom_auth_name[2], param_uom_code[2], param_auth_name[3], param_code[3], param_name[3], param_value[3], param_uom_auth_name[3], param_uom_code[3], param_auth_name[4], param_code[4], param_name[4], param_value[4], param_uom_auth_name[4], param_uom_code[4], param_auth_name[5], param_code[5], param_name[5], param_value[5], param_uom_auth_name[5], param_uom_code[5], param_auth_name[6], param_code[6], param_name[6], param_value[6], param_uom_auth_name[6], param_uom_code[6], deprecated) #proj_db_cursor.execute("INSERT INTO coordinate_operation VALUES (?,?,'conversion')", (EPSG_AUTHORITY, code)) proj_db_cursor.execute('INSERT INTO conversion VALUES (' + '?,?,?, ?, ?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ' + '?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?)', arg) trigger_sql += "END;"; #print(trigger_sql) proj_db_cursor.execute(trigger_sql) def fill_projected_crs(proj_db_cursor): #proj_db_cursor.execute( # "INSERT INTO crs SELECT 'EPSG', coord_ref_sys_code, coord_ref_sys_kind FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('projected')") #proj_db_cursor.execute("INSERT INTO projected_crs SELECT 'EPSG', coord_ref_sys_code, coord_ref_sys_name, 'EPSG', coord_sys_code, 'EPSG', base_crs_code, 'EPSG', projection_conv_code, deprecated FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('projected')") proj_db_cursor.execute("SELECT ?, coord_ref_sys_code, coord_ref_sys_name, NULL, ?, coord_sys_code, ?, base_crs_code, ?, projection_conv_code, crs.deprecated, co.coord_op_method_code, com.coord_op_method_name FROM epsg.epsg_coordinatereferencesystem crs LEFT JOIN epsg.epsg_coordoperation co ON crs.projection_conv_code = co.coord_op_code LEFT JOIN epsg.epsg_coordoperationmethod com USING (coord_op_method_code) WHERE coord_ref_sys_kind IN ('projected')", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) for (auth_name, code, name, description, coordinate_system_auth_name, coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, conversion_auth_name, conversion_code, deprecated, coord_op_method_code, coord_op_method_name) in proj_db_cursor.fetchall(): # If skipping some projection methods is needed if coord_op_method_code in (): print(f'Skipping EPSG:{code} {name} as we do not handle yet projection method EPSG:{coord_op_method_code} / {coord_op_method_name}') continue proj_db_cursor.execute("SELECT 1 FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_code = ? AND coord_ref_sys_kind IN ('geographic 2D', 'geographic 3D', 'geocentric')", (geodetic_crs_code,)) if proj_db_cursor.fetchone(): #proj_db_cursor.execute("INSERT INTO crs VALUES (?, ?, 'projected')", (EPSG_AUTHORITY, code)) try: proj_db_cursor.execute("INSERT INTO projected_crs VALUES (?,?,?,?,?,?,?,?,?,?,NULL,?)", (auth_name, code, name, description, coordinate_system_auth_name, coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, conversion_auth_name, conversion_code, deprecated)) except sqlite3.IntegrityError as e: print(e) print(row) raise def fill_compound_crs(proj_db_cursor): #proj_db_cursor.execute( # "INSERT INTO crs SELECT ?, coord_ref_sys_code, coord_ref_sys_kind FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('compound')", (EPSG_AUTHORITY,)) proj_db_cursor.execute("SELECT ?, coord_ref_sys_code, coord_ref_sys_name, NULL, ?, cmpd_horizcrs_code, ?, cmpd_vertcrs_code, deprecated FROM epsg.epsg_coordinatereferencesystem WHERE coord_ref_sys_kind IN ('compound') AND coord_ref_sys_name NOT LIKE 'EPSG%example%'", (EPSG_AUTHORITY, EPSG_AUTHORITY, EPSG_AUTHORITY)) for auth_name, code, name, description, horiz_auth_name, horiz_code, vert_auth_name, vert_code, deprecated in proj_db_cursor.fetchall(): try: proj_db_cursor.execute("INSERT INTO compound_crs VALUES (?,?,?,?,?,?,?,?,?)", (auth_name, code, name, description, horiz_auth_name, horiz_code, vert_auth_name, vert_code, deprecated)) except sqlite3.IntegrityError as e: print(e) print(auth_name, code, name, description, horiz_auth_name, horiz_code, vert_auth_name, vert_code, deprecated) raise def fill_helmert_transformation(proj_db_cursor): proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'transformation' AND coord_op_method_code IN (1031, 1032, 1033, 1034, 1035, 1037, 1038, 1039, 1053, 1054, 1055, 1056, 1057, 1058, 1061, 1062, 1063, 1065, 1066, 1132, 1133, 1140, 1149, 9603, 9606, 9607, 9636) ") for (code, name, method_code, method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, deprecated, remarks) in proj_db_cursor.fetchall(): expected_order = 1 max_n_params = 15 param_auth_name = [None for i in range(max_n_params)] param_code = [None for i in range(max_n_params)] param_name = [None for i in range(max_n_params)] param_value = [None for i in range(max_n_params)] param_uom_code = [None for i in range(max_n_params)] iterator = proj_db_cursor.execute("SELECT sort_order, cop.parameter_code, parameter_name, parameter_value, uom_code from epsg_coordoperationparam cop LEFT JOIN epsg_coordoperationparamvalue copv LEFT JOIN epsg_coordoperationparamusage copu ON cop.parameter_code = copv.parameter_code AND copu.parameter_code = copv.parameter_code WHERE copu.coord_op_method_code = copv.coord_op_method_code AND coord_op_code = ? AND copv.coord_op_method_code = ? ORDER BY sort_order", (code, method_code)) for (order, parameter_code, parameter_name, parameter_value, uom_code) in iterator: assert order <= max_n_params assert order == expected_order param_auth_name[order - 1] = EPSG_AUTHORITY param_code[order - 1] = parameter_code param_name[order - 1] = parameter_name param_value[order - 1] = parameter_value param_uom_code[order - 1] = uom_code expected_order += 1 n_params = expected_order - 1 if param_value[0] is None and deprecated: continue # silently discard non sense deprecated transforms (like EPSG:1076) assert param_code[0] == 8605 assert param_code[1] == 8606 assert param_code[2] == 8607 assert param_uom_code[0] == param_uom_code[1] assert param_uom_code[0] == param_uom_code[2] px = None py = None pz = None pivot_uom_code = None if n_params > 3: assert param_code[3] == 8608 assert param_code[4] == 8609 assert param_code[5] == 8610 assert param_code[6] == 8611 assert param_uom_code[3] == param_uom_code[4] assert param_uom_code[3] == param_uom_code[5] for i in range(7): assert param_uom_code[i] is not None if n_params == 8: # Time-specific transformation assert param_code[7] == 1049, (code, name, param_code[7]) param_value[14] = param_value[7] param_uom_code[14] = param_uom_code[7] param_value[7] = None param_uom_code[7] = None elif n_params == 10: # Molodensky-Badekas assert param_code[7] == 8617, (code, name, param_code[7]) assert param_code[8] == 8618, (code, name, param_code[8]) assert param_code[9] == 8667, (code, name, param_code[9]) assert param_uom_code[7] == param_uom_code[8] assert param_uom_code[7] == param_uom_code[9] px = param_value[7] py = param_value[8] pz = param_value[9] pivot_uom_code = param_uom_code[7] param_value[7] = None param_uom_code[7] = None param_value[8] = None param_uom_code[8] = None param_value[9] = None param_uom_code[9] = None elif n_params > 7: # Time-dependant transformation assert param_code[7] == 1040, (code, name, param_code[7]) assert param_code[8] == 1041 assert param_code[9] == 1042 assert param_code[10] == 1043 assert param_code[11] == 1044 assert param_code[12] == 1045 assert param_code[13] == 1046 assert param_code[14] == 1047 assert param_uom_code[7] == param_uom_code[8] assert param_uom_code[7] == param_uom_code[9] assert param_uom_code[10] == param_uom_code[11] assert param_uom_code[10] == param_uom_code[12] for i in range(15): assert param_uom_code[i] is not None, (code, name, i, param_name[i]) arg = (EPSG_AUTHORITY, code, name, remarks, EPSG_AUTHORITY, method_code, method_name, EPSG_AUTHORITY, source_crs_code, EPSG_AUTHORITY, target_crs_code, coord_op_accuracy, param_value[0], param_value[1], param_value[2], EPSG_AUTHORITY, param_uom_code[0], param_value[3], param_value[4], param_value[5], EPSG_AUTHORITY if param_uom_code[3] else None, param_uom_code[3], param_value[6], EPSG_AUTHORITY if param_uom_code[6] else None, param_uom_code[6], param_value[7], param_value[8], param_value[9], EPSG_AUTHORITY if param_uom_code[7] else None, param_uom_code[7], param_value[10], param_value[11], param_value[12], EPSG_AUTHORITY if param_uom_code[10] else None, param_uom_code[10], param_value[13], EPSG_AUTHORITY if param_uom_code[13] else None, param_uom_code[13], param_value[14], EPSG_AUTHORITY if param_uom_code[14] else None, param_uom_code[14], px, py, pz, EPSG_AUTHORITY if px else None, pivot_uom_code, coord_tfm_version, deprecated ) #proj_db_cursor.execute("INSERT INTO coordinate_operation VALUES (?,?,'helmert_transformation')", (EPSG_AUTHORITY, code)) try: proj_db_cursor.execute('INSERT INTO helmert_transformation VALUES (' + '?,?,?, ?, ?,?,?, ?,?, ?,?, ?, ?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?, ?,?,?,?,?, ?,?)', arg) except Exception: print(arg) raise def fill_grid_transformation(proj_db_cursor): proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type IN ('transformation', 'point motion operation') AND coord_op_method_code NOT IN (1131, 1136) AND (coord_op_method_name LIKE 'Geographic3D to%' OR coord_op_method_name LIKE 'Geog3D to%' OR coord_op_method_name LIKE 'Point motion % grid%' OR coord_op_method_name LIKE 'Vertical Offset %rid%' OR coord_op_method_name LIKE 'Geographic3D Offset % velocity %rid%' OR coord_op_method_name IN ('NADCON', 'NADCON5 (2D)', 'NADCON5 (3D)', 'NTv1', 'NTv2', 'VERTCON', 'Geocentric translations (geog2D domain) by grid (IGN)', 'Geocentric translations using NEU velocity grid (gtg)', 'Geocen translations by grid (gtg) & Geocen translations NEU velocities (gtg)', 'New Zealand Deformation Model', 'Cartesian Grid Offsets by TIN Interpolation (JSON)', 'Vertical Offset by TIN Interpolation (JSON)', 'Geographic2D Offsets by TIN Interpolation (JSON)', 'Vertical change by geoid grid difference (NRCan)'))") for (code, name, method_code, method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, deprecated, remarks) in proj_db_cursor.fetchall(): if code == 10929: # SOPAC deformation model for California v1 print(f"Skipping transformation {code} ({name})") continue expected_order = 1 if method_name == 'Geocentric translations (geog2D domain) by grid (IGN)': max_n_params = 3 elif method_name == 'Geocentric translations using NEU velocity grid (gtg)': max_n_params = 4 elif method_name == 'Geocen translations by grid (gtg) & Geocen translations NEU velocities (gtg)': max_n_params = 6 else: max_n_params = 2 param_auth_name = [None for i in range(max_n_params)] param_code = [None for i in range(max_n_params)] param_name = [None for i in range(max_n_params)] param_value = [None for i in range(max_n_params)] param_uom_code = [None for i in range(max_n_params)] iterator = proj_db_cursor.execute("SELECT sort_order, cop.parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code from epsg_coordoperationparam cop LEFT JOIN epsg_coordoperationparamvalue copv LEFT JOIN epsg_coordoperationparamusage copu ON cop.parameter_code = copv.parameter_code AND copu.parameter_code = copv.parameter_code WHERE copu.coord_op_method_code = copv.coord_op_method_code AND coord_op_code = ? AND copv.coord_op_method_code = ? ORDER BY sort_order", (code, method_code)) first = True order_inc = 0 for (order, parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code) in iterator: if first and order == 0: # Some new records have sort_order starting at 0 rather than 1 # print(code, name, method_code, method_name, param_code, param_name, order) order_inc = 1 order += order_inc first = False if method_name == "Point motion (geocen domain) using NEU velocity grid (Gravsoft)": # We skip the second and third grid if order in (2, 3): continue # but keep the interpolation CRS if order == 4: order = 2 # NADCON5 lists 3 grids (lat_shift, lon_shift, ellipsoidal_height_shift). Our database # can only list 2. Truncate. Not critical as we ultimately have one GeoTIFF # grid for the 3 original grids if method_name == "NADCON5 (3D)" and order > max_n_params: break assert order <= max_n_params, (code, name) assert order == expected_order, (code, name, method_code, method_name, param_code, param_name, order) if parameter_value is not None: assert param_value_file_ref is None or len(param_value_file_ref) == 0, (order, parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code) if param_value_file_ref is not None and len(param_value_file_ref) != 0: assert parameter_value is None, (order, parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code) param_auth_name[order - 1] = EPSG_AUTHORITY param_code[order - 1] = parameter_code param_name[order - 1] = parameter_name param_value[order - 1] = parameter_value if parameter_value else param_value_file_ref param_uom_code[order - 1] = uom_code expected_order += 1 n_params = expected_order - 1 assert param_code[0] in (1048, 1050, 1063, 1064, 1072, 8656, 8657, 8666, 8732, 8727), (code, param_code[0]) grid2_param_auth_name = None grid2_param_code = None grid2_param_name = None grid2_value = None interpolation_crs_auth_name = None interpolation_crs_code = None sql_param_auth_name = [None] * 2 sql_param_code = [None] * 2 sql_param_name = [None] * 2 sql_param_value = [None] * 2 sql_param_uom_auth_name = [None] * 2 sql_param_uom_code = [None] * 2 if method_code == 9613: # NADCON assert param_code[1] == 8658, param_code[1] grid2_param_auth_name = EPSG_AUTHORITY grid2_param_code = param_code[1] grid2_param_name = param_name[1] grid2_value = param_value[1] elif method_code in (1074, 1075): # NADCON5 (2D) and NADCON5 (3D) assert param_code[1] == 8658, param_code[1] grid2_param_auth_name = EPSG_AUTHORITY grid2_param_code = param_code[1] grid2_param_name = param_name[1] grid2_value = param_value[1] # 1071: Vertical Offset by Grid Interpolation (NZLVD) # 1080: Vertical Offset by Grid Interpolation (BEV AT) # 1081: Geographic3D to GravityRelatedHeight (BEV AT) # 1083: Geog3D to Geog2D+Vertical (AUSGeoid v2) # 1084: Vertical Offset by Grid Interpolation (gtx) # 1085: Vertical Offset by Grid Interpolation (asc) # 1086: Point motion (geocen domain) using XYZ velocity grid (INADEFORM) # 1088: Geog3D to Geog2D+GravityRelatedHeight (gtx) # 1089: Geog3D to Geog2D+GravityRelatedHeight (BEV AT) # 1090: Geog3D to Geog2D+GravityRelatedHeight (CGG 2013) # 1091: Geog3D to Geog2D+GravityRelatedHeight (CI) # 1092: Geog3D to Geog2D+GravityRelatedHeight (EGM2008) # 1093: Geog3D to Geog2D+GravityRelatedHeight (Gravsoft) # 1094: Geog3D to Geog2D+GravityRelatedHeight (IGN1997) # 1095: Geog3D to Geog2D+GravityRelatedHeight (IGN2009) # 1096: Geog3D to Geog2D+GravityRelatedHeight (OSGM15-Ire) # 1097: Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB) # 1098: Geog3D to Geog2D+GravityRelatedHeight (SA 2010) # 1100: Geog3D to Geog2D+GravityRelatedHeight (PL txt) # 1101: Vertical Offset by Grid Interpolation (PL txt) # 1103: Geog3D to Geog2D+GravityRelatedHeight (EGM) # 1105: Geog3D to Geog2D+GravityRelatedHeight (ITAL2005) # 1110: Geog3D to Geog2D+Depth (Gravsoft) # 1112: Vertical Offset by Grid Interpolation (NRCan byn) # 1113: Vertical Offset by velocity grid (NRCan byn) # 1114: Geographic3D Offset by velocity grid (NTv2_Vel) # 1115: Geog3D to Geog2D+Depth (txt) # 1118: Geog3D to Geog2D+GravityRelatedHeight (ISG) # 1120: Point motion (geocen domain) using XYZ velocity grid (BGN) # 1122: Geog3D to Geog2D+Depth (gtx) # 1124: Geog3D to Geog2D+GravityRelatedHeight (gtg) # 1126: Vertical change by geoid grid difference (NRCan) # 1135: Geog3D to Geog2D+GravityRelatedHeight (NGS bin) # 1137: Vertical Offset by TIN Interpolation (JSON) # 1138: Cartesian Grid Offsets by TIN Interpolation (JSON) # 1139: Point motion (geocen domain) using NEU velocity grid (Gravsoft) # 1141: Point motion by grid (NEU domain) (NTv2_Vel) # WARNING: update Transformation::isGeographic3DToGravityRelatedHeight() # in src/iso19111/operation/singleoperation.cpp if adding new methods elif method_code in (1071, 1080, 1081, 1083, 1084, 1085, 1086, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1100, 1101, 1103, 1105, 1110, 1112, 1113, 1114, 1115, 1118, 1120, 1122, 1124, 1126, 1128, 1129, 1135, 1137, 1138, 1139, 1141) and n_params == 2: assert param_code[1] == 1048, (code, method_code, param_code[1]) interpolation_crs_auth_name = EPSG_AUTHORITY interpolation_crs_code = str(int(param_value[1])) # needed to avoid codes like XXXX.0 # 1087: Geocentric translation by Grid Interpolation (IGN) elif method_code in (1087, ) and n_params == 3: assert param_code[1] == 1048, (code, method_code, param_code[1]) interpolation_crs_auth_name = EPSG_AUTHORITY interpolation_crs_code = str(int(param_value[1])) # needed to avoid codes like XXXX.0 # ignoring parameter 2 Standard CT code elif method_code == 1144 and n_params == 4: assert param_code[1] == 1048, (code, method_code, param_code[1]) interpolation_crs_auth_name = EPSG_AUTHORITY interpolation_crs_code = str(int(param_value[1])) # needed to avoid codes like XXXX.0 iout = 0 for i in (2, 3): if param_value[i] != -999: sql_param_auth_name[iout] = EPSG_AUTHORITY sql_param_code[iout] = param_code[i] sql_param_name[iout] = param_name[i] sql_param_value[iout] = param_value[i] sql_param_uom_auth_name[iout] = EPSG_AUTHORITY sql_param_uom_code[iout] = param_uom_code[i] iout += 1 elif method_code == 1142 and n_params == 6: assert param_code[1] == 1070, (code, method_code, param_code[1]) interpolation_crs_auth_name = EPSG_AUTHORITY interpolation_crs_code = str(int(param_value[1])) # needed to avoid codes like XXXX.0 assert param_code[3] == 1071, (code, method_code, param_code[3]) assert param_value[3] == param_value[1] grid2_param_auth_name = EPSG_AUTHORITY grid2_param_code = param_code[2] grid2_param_name = param_name[2] grid2_value = param_value[2] iout = 0 for i in (4, 5): if param_value[i] != -999: sql_param_auth_name[iout] = EPSG_AUTHORITY sql_param_code[iout] = param_code[i] sql_param_name[iout] = param_name[i] sql_param_value[iout] = param_value[i] sql_param_uom_auth_name[iout] = EPSG_AUTHORITY sql_param_uom_code[iout] = param_uom_code[i] iout += 1 else: assert n_params == 1, (code, name, method_code, n_params) arg = (EPSG_AUTHORITY, code, name, remarks, EPSG_AUTHORITY, method_code, method_name, EPSG_AUTHORITY, source_crs_code, EPSG_AUTHORITY, target_crs_code, coord_op_accuracy, EPSG_AUTHORITY, param_code[0], param_name[0], param_value[0], grid2_param_auth_name, grid2_param_code, grid2_param_name, grid2_value, sql_param_auth_name[0], sql_param_code[0], sql_param_name[0], sql_param_value[0], sql_param_uom_auth_name[0], sql_param_uom_code[0], sql_param_auth_name[1], sql_param_code[1], sql_param_name[1], sql_param_value[1], sql_param_uom_auth_name[1], sql_param_uom_code[1], interpolation_crs_auth_name, interpolation_crs_code, coord_tfm_version, deprecated ) #proj_db_cursor.execute("INSERT INTO coordinate_operation VALUES (?,?,'grid_transformation')", (EPSG_AUTHORITY, code)) try: proj_db_cursor.execute('INSERT INTO grid_transformation VALUES (' + '?,?,?, ?, ?,?,?, ?,?, ?,?, ?, ?,?,?,?, ?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?, ?,?)', arg) except sqlite3.IntegrityError: print(arg) raise def fill_other_transformation(proj_db_cursor): # 9601: Longitude rotation # 9616: Vertical offset # 9618: Geographic2D with Height offsets # 9619: Geographic2D offsets # 9624: Affine Parametric Transformation # 9660: Geographic3D offsets # 1131: Geog3D to Geog2D+GravityRelatedHeight # 1136: Geographic3D to GravityRelatedHeight # 1068: Height Depth Reversal # 1069: Change of Vertical Unit # 1046: Vertical Offset and Slope # 9621: Similarity transformation # 9656: Cartesian Grid Offsets # 1143: Position Vector (geocen) & Geocen translations NEU velocities (gtg) proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_method_code IN (9601, 9616, 9618, 9619, 9624, 9660, 1068, 1069, 1046, 1131, 1136, 9621, 9656, 1143)") for (code, name, method_code, method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, deprecated, remarks) in proj_db_cursor.fetchall(): # 1068 and 1069 are Height Depth Reversal and Change of Vertical Unit # In EPSG, there is one generic instance of those as 7812 and 7813 that # don't refer to particular CRS, and instances pointing to CRS names # The later are imported in the other_transformation table since we recover # the source/target CRS names from the transformation name. if method_code in (1068, 1069) and source_crs_code is None and target_crs_code is None: parts = name.split(" to ") if len(parts) != 2: continue proj_db_cursor.execute("SELECT coord_ref_sys_code FROM epsg_coordinatereferencesystem WHERE coord_ref_sys_name = ?", (parts[0],)) source_codes = proj_db_cursor.fetchall() proj_db_cursor.execute("SELECT coord_ref_sys_code FROM epsg_coordinatereferencesystem WHERE coord_ref_sys_name = ?", (parts[1],)) target_codes = proj_db_cursor.fetchall() if len(source_codes) != 1 and len(target_codes) != 1: continue source_crs_code = source_codes[0][0] target_crs_code = target_codes[0][0] expected_order = 1 max_n_params = 11 param_auth_name = [None for i in range(max_n_params)] param_code = [None for i in range(max_n_params)] param_name = [None for i in range(max_n_params)] param_value = [None for i in range(max_n_params)] param_uom_auth_name = [None for i in range(max_n_params)] param_uom_code = [None for i in range(max_n_params)] interpolation_crs_auth_name = None interpolation_crs_code = None grid_param_auth_name = None grid_param_code = None grid_param_name = None grid_name = None iterator = proj_db_cursor.execute("SELECT sort_order, cop.parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code from epsg_coordoperationparam cop LEFT JOIN epsg_coordoperationparamvalue copv LEFT JOIN epsg_coordoperationparamusage copu ON cop.parameter_code = copv.parameter_code AND copu.parameter_code = copv.parameter_code WHERE copu.coord_op_method_code = copv.coord_op_method_code AND coord_op_code = ? AND copv.coord_op_method_code = ? ORDER BY sort_order", (code, method_code)) for (order, parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code) in iterator: if parameter_value is not None: assert param_value_file_ref is None or len(param_value_file_ref) == 0, (order, parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code) if param_value_file_ref is not None and len(param_value_file_ref) != 0: assert parameter_value is None, (order, parameter_code, parameter_name, parameter_value, param_value_file_ref, uom_code) if method_code == 1143 and order >= 8: if order == 8 and parameter_code == 1050: grid_param_auth_name = EPSG_AUTHORITY grid_param_code = parameter_code grid_param_name = parameter_name grid_name = param_value_file_ref continue elif order == 9 and parameter_code == 1048: interpolation_crs_auth_name = EPSG_AUTHORITY interpolation_crs_code = str(int(parameter_value)) # needed to avoid codes like XXXX.0 continue else: order -= 2 elif method_code == 1046 and order == 6: # Vertical offset and slope assert parameter_code == 1037 # EPSG code for Horizontal CRS interpolation_crs_auth_name = EPSG_AUTHORITY interpolation_crs_code = str(int(parameter_value)) # needed to avoid codes like XXXX.0 break assert order <= max_n_params, (code, name, order, max_n_params) assert order == expected_order, (code, name, order, expected_order) param_auth_name[order - 1] = EPSG_AUTHORITY param_code[order - 1] = parameter_code param_name[order - 1] = parameter_name param_value[order - 1] = parameter_value param_uom_auth_name[order - 1] = EPSG_AUTHORITY param_uom_code[order - 1] = uom_code expected_order += 1 arg = (EPSG_AUTHORITY, code, name, remarks, EPSG_AUTHORITY, method_code, method_name, EPSG_AUTHORITY, source_crs_code, EPSG_AUTHORITY, target_crs_code, coord_op_accuracy, param_auth_name[0], param_code[0], param_name[0], param_value[0], param_uom_auth_name[0], param_uom_code[0], param_auth_name[1], param_code[1], param_name[1], param_value[1], param_uom_auth_name[1], param_uom_code[1], param_auth_name[2], param_code[2], param_name[2], param_value[2], param_uom_auth_name[2], param_uom_code[2], param_auth_name[3], param_code[3], param_name[3], param_value[3], param_uom_auth_name[3], param_uom_code[3], param_auth_name[4], param_code[4], param_name[4], param_value[4], param_uom_auth_name[4], param_uom_code[4], param_auth_name[5], param_code[5], param_name[5], param_value[5], param_uom_auth_name[5], param_uom_code[5], param_auth_name[6], param_code[6], param_name[6], param_value[6], param_uom_auth_name[6], param_uom_code[6], param_auth_name[7], param_code[7], param_name[7], param_value[7], param_uom_auth_name[7], param_uom_code[7], param_auth_name[8], param_code[8], param_name[8], param_value[8], param_uom_auth_name[8], param_uom_code[8], grid_param_auth_name, grid_param_code, grid_param_name, grid_name, interpolation_crs_auth_name, interpolation_crs_code, coord_tfm_version, deprecated) #proj_db_cursor.execute("INSERT INTO coordinate_operation VALUES (?,?,'other_transformation')", (EPSG_AUTHORITY, code)) #print(arg) proj_db_cursor.execute('INSERT INTO other_transformation VALUES (' + '?,?,?, ?, ?,?,?, ?,?, ?,?, ?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ' + '?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?, ?,?, ?,?)', arg) def fill_concatenated_operation(proj_db_cursor): proj_db_cursor.execute("SELECT coord_op_code, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, epsg_coordoperation.deprecated, epsg_coordoperation.remarks FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_type = 'concatenated operation'") for (code, name, method_code, method_name, source_crs_code, target_crs_code, coord_op_accuracy, coord_tfm_version, deprecated, remarks) in proj_db_cursor.fetchall(): expected_order = 1 steps_code = [] iterator = proj_db_cursor.execute("SELECT op_path_step, single_operation_code FROM epsg_coordoperationpath WHERE concat_operation_code = ? ORDER BY op_path_step", (code,)) for (order, single_operation_code) in iterator: assert order == expected_order steps_code.append(single_operation_code) expected_order += 1 n_params = expected_order - 1 if n_params == 0: # For example http://www.epsg-registry.org//export.htm?gml=urn:ogc:def:coordinateOperation:EPSG::8658 continue all_steps_exist = True for step_code in steps_code: proj_db_cursor.execute("SELECT 1 FROM coordinate_operation_with_conversion_view WHERE code = ?", (step_code,)) if proj_db_cursor.fetchone() is None: print('Step of code %d for concatenated_operation %d does not exist' % (step_code, code)) all_steps_exist = False break if all_steps_exist: arg = (EPSG_AUTHORITY, code, name, remarks, EPSG_AUTHORITY, source_crs_code, EPSG_AUTHORITY, target_crs_code, coord_op_accuracy, coord_tfm_version, deprecated ) #proj_db_cursor.execute("INSERT INTO coordinate_operation VALUES (?,?,'concatenated_operation')", (EPSG_AUTHORITY, code)) proj_db_cursor.execute('INSERT INTO concatenated_operation VALUES (' + '?,?,?, ?, ?,?, ?,?, ?, ?,?)', arg) for i in range(len(steps_code)): proj_db_cursor.execute('INSERT INTO concatenated_operation_step VALUES (?,?,?,?,?,NULL)', (EPSG_AUTHORITY, code, i+1, EPSG_AUTHORITY,steps_code[i])) def fill_alias(proj_db_cursor): proj_db_cursor.execute("SELECT DISTINCT object_code, alias FROM epsg.epsg_alias WHERE object_table_name = 'epsg_datum'") for row in proj_db_cursor.fetchall(): code, alt_name = row proj_db_cursor.execute('SELECT 1 FROM geodetic_datum WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is not None: proj_db_cursor.execute("INSERT INTO alias_name VALUES ('geodetic_datum','EPSG',?,?,'EPSG')", (code, alt_name)) else: proj_db_cursor.execute('SELECT 1 FROM vertical_datum WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is not None: proj_db_cursor.execute("INSERT INTO alias_name VALUES ('vertical_datum','EPSG',?,?,'EPSG')", (code, alt_name)) else: print('Cannot find datum %s in geodetic_datum or vertical_datum' % (code)) proj_db_cursor.execute("SELECT DISTINCT object_code, alias FROM epsg.epsg_alias WHERE object_table_name = 'epsg_coordinatereferencesystem'") for row in proj_db_cursor.fetchall(): code, alt_name = row if int(code) > 60000000: continue proj_db_cursor.execute('SELECT 1 FROM geodetic_crs WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is not None: proj_db_cursor.execute("INSERT INTO alias_name VALUES ('geodetic_crs','EPSG',?,?,'EPSG')", (code, alt_name)) continue proj_db_cursor.execute('SELECT 1 FROM projected_crs WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is not None: proj_db_cursor.execute("INSERT INTO alias_name VALUES ('projected_crs','EPSG',?,?,'EPSG')", (code, alt_name)) continue proj_db_cursor.execute('SELECT 1 FROM vertical_crs WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is not None: proj_db_cursor.execute("INSERT INTO alias_name VALUES ('vertical_crs','EPSG',?,?,'EPSG')", (code, alt_name)) continue proj_db_cursor.execute('SELECT 1 FROM compound_crs WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is not None: proj_db_cursor.execute("INSERT INTO alias_name VALUES ('compound_crs','EPSG',?,?,'EPSG')", (code, alt_name)) continue print('Cannot find CRS %s in geodetic_crs, projected_crs, vertical_crs or compound_crs' % (code)) def find_table(proj_db_cursor, code): for table_name in ('helmert_transformation', 'grid_transformation', 'concatenated_operation', 'geodetic_crs', 'projected_crs', 'vertical_crs', 'compound_crs'): proj_db_cursor.execute('SELECT name FROM %s WHERE code = ?' % table_name, (code,)) row = proj_db_cursor.fetchone() if row is not None: return row[0], table_name raise Exception(f"cannot find table for code {code}") def fill_supersession(proj_db_cursor): proj_db_cursor.execute("SELECT object_code, superseded_by FROM epsg.epsg_supersession WHERE object_table_name = 'epsg_coordoperation' AND object_code != superseded_by") for row in proj_db_cursor.fetchall(): code, superseded_by = row proj_db_cursor.execute('SELECT 1 FROM coordinate_operation_view WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is None: print('Skipping supersession of %d since it has not been imported' % code) continue proj_db_cursor.execute('SELECT 1 FROM coordinate_operation_view WHERE code = ?', (superseded_by,)) if proj_db_cursor.fetchone() is None: print('Skipping supersession of %d by %d since the later has not been imported' % (code, superseded_by)) continue src_name, superseded_table_name = find_table(proj_db_cursor, code) dst_name, replacement_table_name = find_table(proj_db_cursor, superseded_by) assert superseded_table_name, row assert replacement_table_name, row if superseded_table_name == 'grid_transformation' and replacement_table_name == 'grid_transformation' and src_name.startswith('NAD27 to NAD83'): print('Skipping supersession of %d (%s) by %d (%s) because of exception specific to NAD27 to NAD83' % (code, src_name, superseded_by, dst_name)) continue proj_db_cursor.execute("SELECT source_crs_code, crs1.coord_ref_sys_name, target_crs_code, crs2.coord_ref_sys_name FROM epsg_coordoperation LEFT JOIN epsg_coordinatereferencesystem crs1 ON source_crs_code = crs1.coord_ref_sys_code LEFT JOIN epsg_coordinatereferencesystem crs2 ON target_crs_code = crs2.coord_ref_sys_code WHERE coord_op_code = ?", (code,)) source_crs_code_superseded, source_crs_name_superseded, target_crs_code_superseded, target_crs_name_superseded = proj_db_cursor.fetchone() proj_db_cursor.execute("SELECT source_crs_code, crs1.coord_ref_sys_name, target_crs_code, crs2.coord_ref_sys_name FROM epsg_coordoperation LEFT JOIN epsg_coordinatereferencesystem crs1 ON source_crs_code = crs1.coord_ref_sys_code LEFT JOIN epsg_coordinatereferencesystem crs2 ON target_crs_code = crs2.coord_ref_sys_code WHERE coord_op_code = ?", (superseded_by,)) source_crs_code_replacement, source_crs_name_replacement, target_crs_code_replacement, target_crs_name_replacement = proj_db_cursor.fetchone() same_source_target_crs = (source_crs_code_superseded, target_crs_code_superseded) == (source_crs_code_replacement, target_crs_code_replacement) if not same_source_target_crs: for crs_name_prefix in ("ETRS89", ): if source_crs_code_superseded == source_crs_code_replacement and target_crs_name_superseded.startswith(crs_name_prefix) and target_crs_name_replacement.startswith(crs_name_prefix): same_source_target_crs = True elif target_crs_code_superseded == target_crs_code_replacement and source_crs_name_superseded.startswith(crs_name_prefix) and source_crs_name_replacement.startswith(crs_name_prefix): same_source_target_crs = True proj_db_cursor.execute("INSERT INTO supersession VALUES (?,'EPSG',?,?,'EPSG',?,'EPSG',?)", (superseded_table_name, code, replacement_table_name, superseded_by, same_source_target_crs)) def fill_deprecation(proj_db_cursor): proj_db_cursor.execute("SELECT object_code, replaced_by FROM epsg.epsg_deprecation WHERE object_table_name = 'epsg_coordinatereferencesystem' AND object_code != replaced_by") for row in proj_db_cursor.fetchall(): code, replaced_by = row proj_db_cursor.execute('SELECT 1 FROM crs_view WHERE code = ?', (code,)) if proj_db_cursor.fetchone() is None: print('Skipping deprecation of %d since it has not been imported' % code) continue src_name, deprecated_table_name = find_table(proj_db_cursor, code) dst_name, replacement_table_name = find_table(proj_db_cursor, replaced_by) assert deprecated_table_name, row assert replacement_table_name, row assert deprecated_table_name == replacement_table_name proj_db_cursor.execute("INSERT INTO deprecation VALUES (?,'EPSG',?,'EPSG',?,'EPSG')", (deprecated_table_name, code, replaced_by)) def report_non_imported_operations(proj_db_cursor): proj_db_cursor.execute("SELECT coord_op_code, coord_op_type, coord_op_name, coord_op_method_code, coord_op_method_name, source_crs_code, target_crs_code, coord_op_accuracy, epsg_coordoperation.deprecated FROM epsg.epsg_coordoperation LEFT JOIN epsg.epsg_coordoperationmethod USING (coord_op_method_code) WHERE coord_op_code NOT IN (SELECT code FROM coordinate_operation_with_conversion_view) AND NOT epsg_coordoperation.deprecated = 1") rows = [] first = True for row in proj_db_cursor.fetchall(): if first: print('Non-imported non-deprecated coordinate_operations:') first = False print(' ' + str(row)) rows.append(row) return rows epsg_db_conn, epsg_tmp_db_filename = ingest_epsg() script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') proj_db_filename = ':memory:' #proj_db_filename = 'tmp_proj.db' if os.path.exists(proj_db_filename): os.unlink(proj_db_filename) proj_db_conn = sqlite3.connect(proj_db_filename) proj_db_cursor = proj_db_conn.cursor() proj_db_cursor.execute('PRAGMA foreign_keys = 1;') ingest_sqlite_dump(proj_db_cursor, os.path.join(sql_dir_name, 'proj_db_table_defs.sql')) proj_db_cursor.execute("INSERT INTO celestial_body VALUES('PROJ', 'EARTH', 'Earth', 6378137.0);") # A bit messy, but to avoid churn in our existing .sql files, we temporarily # recreate the original conversion and helmert_transformation tables # instead of the view in the true database. proj_db_cursor.execute("""DROP VIEW conversion;""") proj_db_cursor.execute("""DROP TABLE conversion_table;""") proj_db_cursor.execute("""CREATE TABLE conversion( auth_name TEXT NOT NULL CHECK (length(auth_name) >= 1), code TEXT NOT NULL CHECK (length(code) >= 1), name TEXT NOT NULL CHECK (length(name) >= 2), description TEXT, method_auth_name TEXT CHECK (method_auth_name IS NULL OR length(method_auth_name) >= 1), method_code TEXT CHECK (method_code IS NULL OR length(method_code) >= 1), method_name TEXT, param1_auth_name TEXT, param1_code TEXT, param1_name TEXT, param1_value FLOAT, param1_uom_auth_name TEXT, param1_uom_code TEXT, param2_auth_name TEXT, param2_code TEXT, param2_name TEXT, param2_value FLOAT, param2_uom_auth_name TEXT, param2_uom_code TEXT, param3_auth_name TEXT, param3_code TEXT, param3_name TEXT, param3_value FLOAT, param3_uom_auth_name TEXT, param3_uom_code TEXT, param4_auth_name TEXT, param4_code TEXT, param4_name TEXT, param4_value FLOAT, param4_uom_auth_name TEXT, param4_uom_code TEXT, param5_auth_name TEXT, param5_code TEXT, param5_name TEXT, param5_value FLOAT, param5_uom_auth_name TEXT, param5_uom_code TEXT, param6_auth_name TEXT, param6_code TEXT, param6_name TEXT, param6_value FLOAT, param6_uom_auth_name TEXT, param6_uom_code TEXT, param7_auth_name TEXT, param7_code TEXT, param7_name TEXT, param7_value FLOAT, param7_uom_auth_name TEXT, param7_uom_code TEXT, deprecated BOOLEAN NOT NULL CHECK (deprecated IN (0, 1)), CONSTRAINT pk_conversion PRIMARY KEY (auth_name, code) );""") proj_db_cursor.execute("""DROP VIEW helmert_transformation;""") proj_db_cursor.execute("""DROP TABLE helmert_transformation_table;""") proj_db_cursor.execute("""CREATE TABLE helmert_transformation( auth_name TEXT NOT NULL CHECK (length(auth_name) >= 1), code TEXT NOT NULL CHECK (length(code) >= 1), name TEXT NOT NULL CHECK (length(name) >= 2), description TEXT, method_auth_name TEXT NOT NULL CHECK (length(method_auth_name) >= 1), method_code TEXT NOT NULL CHECK (length(method_code) >= 1), method_name NOT NULL CHECK (length(method_name) >= 2), source_crs_auth_name TEXT NOT NULL, source_crs_code TEXT NOT NULL, target_crs_auth_name TEXT NOT NULL, target_crs_code TEXT NOT NULL, accuracy FLOAT CHECK (accuracy >= 0), tx FLOAT NOT NULL, ty FLOAT NOT NULL, tz FLOAT NOT NULL, translation_uom_auth_name TEXT NOT NULL, translation_uom_code TEXT NOT NULL, rx FLOAT, ry FLOAT, rz FLOAT, rotation_uom_auth_name TEXT, rotation_uom_code TEXT, scale_difference FLOAT, scale_difference_uom_auth_name TEXT, scale_difference_uom_code TEXT, rate_tx FLOAT, rate_ty FLOAT, rate_tz FLOAT, rate_translation_uom_auth_name TEXT, rate_translation_uom_code TEXT, rate_rx FLOAT, rate_ry FLOAT, rate_rz FLOAT, rate_rotation_uom_auth_name TEXT, rate_rotation_uom_code TEXT, rate_scale_difference FLOAT, rate_scale_difference_uom_auth_name TEXT, rate_scale_difference_uom_code TEXT, epoch FLOAT, epoch_uom_auth_name TEXT, epoch_uom_code TEXT, px FLOAT, -- Pivot / evaluation point for Molodensky-Badekas py FLOAT, pz FLOAT, pivot_uom_auth_name TEXT, pivot_uom_code TEXT, operation_version TEXT, -- normally mandatory in OGC Topic 2 but optional here deprecated BOOLEAN NOT NULL CHECK (deprecated IN (0, 1)), CONSTRAINT pk_helmert_transformation PRIMARY KEY (auth_name, code) );""") proj_db_cursor.execute("SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name = 'projected_crs'") for (name, sql) in proj_db_cursor.fetchall(): proj_db_cursor.execute("DROP TABLE " + name) proj_db_cursor.execute(sql.replace('conversion_table', 'conversion')) proj_db_cursor.execute("SELECT name, sql FROM sqlite_master WHERE type = 'view'") for (name, sql) in proj_db_cursor.fetchall(): if 'conversion_table' in sql: proj_db_cursor.execute("DROP VIEW " + name) proj_db_cursor.execute(sql.replace('conversion_table', 'conversion')) elif 'helmert_transformation_table' in sql: proj_db_cursor.execute("DROP VIEW " + name) proj_db_cursor.execute(sql.replace('helmert_transformation_table', 'helmert_transformation')) proj_db_cursor = proj_db_conn.cursor() proj_db_cursor.execute("ATTACH DATABASE '%s' AS epsg;" % epsg_tmp_db_filename) fill_unit_of_measure(proj_db_cursor) fill_ellipsoid(proj_db_cursor) fill_extent(proj_db_cursor) fill_scope(proj_db_cursor) fill_prime_meridian(proj_db_cursor) fill_geodetic_datum(proj_db_cursor) fill_vertical_datum(proj_db_cursor) fill_engineering_datum(proj_db_cursor) fill_datumensemble(proj_db_cursor) fill_coordinate_system(proj_db_cursor) fill_axis(proj_db_cursor) fill_geodetic_crs(proj_db_cursor) fill_vertical_crs(proj_db_cursor) fill_engineering_crs(proj_db_cursor) create_datumensemble_transformations(proj_db_cursor) fill_conversion(proj_db_cursor) fill_projected_crs(proj_db_cursor) fill_compound_crs(proj_db_cursor) fill_helmert_transformation(proj_db_cursor) fill_grid_transformation(proj_db_cursor) fill_other_transformation(proj_db_cursor) fill_concatenated_operation(proj_db_cursor) fill_alias(proj_db_cursor) fill_supersession(proj_db_cursor) fill_deprecation(proj_db_cursor) fill_usage(proj_db_cursor) non_imported_operations = report_non_imported_operations(proj_db_cursor) proj_db_cursor.execute("""DROP TABLE builtin_authorities;""") proj_db_cursor.close() proj_db_conn.commit() files = {} # Dump the generated database and split it one .sql file per table # except for usage, that we append just after the record that the usage is for tables_with_usage = ('geodetic_datum', 'vertical_datum', 'engineering_datum', 'geodetic_crs', 'vertical_crs', 'projected_crs', 'compound_crs', 'engineering_crs', 'helmert_transformation', 'grid_transformation', 'other_transformation', 'conversion', 'concatenated_operation') usages_map = {} # INSERT INTO "usage" VALUES('EPSG','13089','geodetic_datum','EPSG','1037','EPSG','3340','EPSG','1028'); for line in proj_db_conn.iterdump(): if line.startswith('INSERT INTO "'): table_name = line[len('INSERT INTO "'):] table_name = table_name[0:table_name.find('"')] if table_name == 'usage': _, code, object_table_name, _, object_code, _, _, _, _ = line.split(',') object_table_name = object_table_name[1:-1] code = code[1:-1] if code[0] >= '0' and code[0] <= '9': code = int(code) object_code = object_code[1:-1] if object_code[0] >= '0' and object_code[0] <= '9': object_code = int(object_code) assert object_table_name in tables_with_usage, line key = (object_table_name, object_code) if key not in usages_map: usages_map[key] = [(code, line)] else: usages_map[key].append((code, line)) for line in proj_db_conn.iterdump(): if line.startswith('INSERT INTO "'): table_name = line[len('INSERT INTO "'):] table_name = table_name[0:table_name.find('"')] if table_name == 'usage': continue if table_name in files: f = files[table_name] else: f = open(os.path.join(sql_dir_name, table_name) + '.sql', 'wb') f.write("--- This file has been generated by scripts/build_db.py. DO NOT EDIT !\n\n".encode('UTF-8')) files[table_name] = f f.write((line + '\n').encode('UTF-8')) if table_name in tables_with_usage: pos = line.find("'PROJ','") if pos > 0: pos += len("'EPSG','") pos_end = line.find("'", pos) code = line[pos:pos_end] else: pos = line.find("'EPSG','") assert pos > 0 pos += len("'EPSG','") pos_end = line.find("'", pos) code = int(line[pos:pos_end]) usages = sorted(usages_map[(table_name, code)]) for _, l in usages: f.write((l + '\n').encode('UTF-8')) elif line.startswith('CREATE TRIGGER conversion_method_check_insert_trigger'): table_name = 'conversion_triggers' if table_name in files: f = files[table_name] else: f = open(os.path.join(sql_dir_name, table_name) + '.sql', 'wb') f.write("--- This file has been generated by scripts/build_db.py. DO NOT EDIT !\n\n".encode('UTF-8')) files[table_name] = f f.write((line + '\n').replace('BEFORE INSERT ON conversion', 'INSTEAD OF INSERT ON conversion').encode('UTF-8')) with open(os.path.join(sql_dir_name, 'non_imported_operations') + '.sql', 'wb') as f: f.write("--- This file has been generated by scripts/build_db.py. DO NOT EDIT !\n\n".encode('UTF-8')) for row in non_imported_operations: if 'example' not in row[2]: f.write(("-- Non-imported: " + str(row) + '\n').encode('UTF-8')) del files # Content already in proj_db_table_defs.sql os.unlink(os.path.join(sql_dir_name, 'celestial_body') + '.sql') proj_db_conn = None epsg_db_conn = None if os.path.exists(epsg_tmp_db_filename): os.unlink(epsg_tmp_db_filename) proj-9.8.1/scripts/build_nadcon5_concatenated_operations.py000775 001750 001750 00000014457 15166171715 024213 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Populate data/sql/nadcon5_concatenated_operations.sql # Author: Even Rouault # ############################################################################### # Copyright (c) 2022, Even Rouault # # 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. ############################################################################### import os script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') out_filename = os.path.join(sql_dir_name, 'nadcon5_concatenated_operations') + '.sql' #print(out_filename) def sanitize_crs_name_for_code(name): return name.replace('(', '_').replace(')', '').replace(' ','_').upper() def gen_transformations(sql, transformations, crs_dict, short_area_of_use, extent_code): for i in range(len(transformations)): for j in range(i,len(transformations)): if i >= j: continue source_crs = transformations[i][0] target_crs = transformations[j][1] total_acc = 0 for k in range(i,j+1): total_acc += transformations[k][3] ** 2 total_acc = total_acc ** 0.5 total_acc = round(total_acc * 100) / 100.0 transfm_code = sanitize_crs_name_for_code(source_crs) + "_TO_" + sanitize_crs_name_for_code(target_crs) + "_" + short_area_of_use.upper() transfm_name = f"{source_crs} to {target_crs} (NADCON5, {short_area_of_use})" source_crs_code = crs_dict[source_crs][0] target_crs_code = crs_dict[target_crs][0] sql += f"INSERT INTO concatenated_operation VALUES('PROJ','{transfm_code}','{transfm_name}','Transformation based on concatenation of NADCON5 transformations','EPSG','{source_crs_code}','EPSG','{target_crs_code}',{total_acc},NULL,0);\n" for k in range(i,j+1): step = k - i + 1 source_crs_name = transformations[k][0] target_crs_name = transformations[k][1] step_code = transformations[k][2] acc = transformations[k][3] sql += f"INSERT INTO concatenated_operation_step VALUES('PROJ','{transfm_code}',{step},'EPSG','{step_code}','forward'); -- {source_crs_name} to {target_crs_name} (EPSG:{step_code}), {acc} m\n" sql += f"INSERT INTO usage VALUES('PROJ','{transfm_code}_USAGE','concatenated_operation','PROJ','{transfm_code}',\n" sql += f" 'EPSG','{extent_code}', -- extent: {short_area_of_use}\n" sql += f" 'EPSG','1027' -- scope: Geodesy\n" sql += ");\n" sql += "\n" return sql crs_dict = { "NAD27" : (4267, 0), "Puerto Rico": (4139, 0), "Old Hawaiian": (4135, 0), "American Samoa 1962": (4169, 0), "Guam 1963": (4675, 0), "NAD83" : (4269, 0), "NAD83(HARN)": (4152, 4957), "NAD83(HARN Corrected)": (8545, 8544), # PRVI only "NAD83(FBN)": (8860, 8542), "NAD83(NSRS2007)": (4759, 4893), "NAD83(2011)": (6318, 6319), "NAD83(PA11)": (6322, 6321), "NAD83(MA11)": (6325, 6324), } f = open(out_filename, 'wb') f.write("--- This file has been generated by scripts/build_nadcon5_concatenated_operations.py. DO NOT EDIT !\n\n".encode('UTF-8')) f.write("-- Concatenated accuracy is sqrt(sum of squared accuracies) (confirmed by NGS to be a valid way)\n\n".encode('UTF-8')) sql = "" # CONUS transformations = [ ("NAD27", "NAD83", 8555, 0.15), ("NAD83", "NAD83(HARN)", 8556, 0.05), ("NAD83(HARN)", "NAD83(FBN)", 8861, 0.05), ("NAD83(FBN)", "NAD83(NSRS2007)", 8862, 0.05), ("NAD83(NSRS2007)", "NAD83(2011)", 8559, 0.05), ] sql = gen_transformations(sql, transformations, crs_dict, "CONUS", 4516) # Alaska transformations = [ ("NAD27", "NAD83", 8549, 0.5), ("NAD83", "NAD83(HARN)", 8550, 0.15), ("NAD83(HARN)", "NAD83(NSRS2007)", 8551, 0.05), ("NAD83(NSRS2007)", "NAD83(2011)", 8552, 0.05), ] sql = gen_transformations(sql, transformations, crs_dict, "Alaska", 1330) # Hawaii transformations = [ ("Old Hawaiian", "NAD83", 8561, 0.2), ("NAD83", "NAD83(HARN)", 8660, 0.05), ("NAD83(HARN)", "NAD83(PA11)", 8661, 0.05), ] sql = gen_transformations(sql, transformations, crs_dict, "Hawaii", 1334) # PRVI transformations = [ ("Puerto Rico", "NAD83", 8668, 0.15), ("NAD83", "NAD83(HARN)", 8669, 0.15), ("NAD83(HARN)", "NAD83(HARN Corrected)", 9181, 0.05), ("NAD83(HARN Corrected)", "NAD83(FBN)", 8867, 0.05), ("NAD83(FBN)", "NAD83(NSRS2007)", 8868, 0.05), ("NAD83(NSRS2007)", "NAD83(2011)", 8673, 0.05), ] sql = gen_transformations(sql, transformations, crs_dict, "PRVI", 3634) # American Samoa transformations = [ ("American Samoa 1962", "NAD83(HARN)", 8662, 5.0), ("NAD83(HARN)", "NAD83(FBN)", 8863, 0.05), ("NAD83(FBN)", "NAD83(PA11)", 8864, 0.05), ] sql = gen_transformations(sql, transformations, crs_dict, "AS", 3110) # Guam transformations = [ ("Guam 1963", "NAD83(HARN)", 8665, 5.0), ("NAD83(HARN)", "NAD83(FBN)", 8865, 0.05), ("NAD83(FBN)", "NAD83(MA11)", 8866, 0.05), ] sql = gen_transformations(sql, transformations, crs_dict, "GUAM", 4525) f.write(sql.encode('UTF-8')) f.close() proj-9.8.1/scripts/build_airocean_parameters.py000775 001750 001750 00000063512 15166171715 021711 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Build airocean parameters # Author: Pierre Louvart # ############################################################################### # Copyright (c) 2025, Pierre Louvart # # 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. ############################################################################### """ This python script can be launched "as is" with no additional argument. It will generate 2 plots (require geopandas and matplotlib): - one for the 20 faces unfolded net - one for the complete 23 faces unfolded net that makes up the final airocean map It will also print on the standard output the C code initializing vertices, faces and transform matrices (forward and inverse) required to conduct the airocean projection. """ import numpy as np from pathlib import Path import json np.set_printoptions(precision=22) """ Zap values close to 1s and 0s """ def zap_zero_or_one(vals, eps=1e-12, verbose=False): """Return copy of array with values very close to zero and one set exactly.""" vals = vals.copy() sel = (np.abs(vals) < eps) & (vals != 0.0) if sel.any(): vals[sel] = 0.0 if verbose: print(f"adjusted {sel.sum()} zeros") ones = np.abs(np.abs(vals) - 1.0) sel = (ones < eps) & (ones != 0.0) if sel.any(): vals[sel] = np.sign(vals[sel]) if verbose: print(f"adjusted {sel.sum()} ones") return vals """ Definition of the vertices of the unfolded icosahedron net: """ # Icosahedron properties circumscribed_radius = 1 inscribed_radius = 3**0.5 / 12 * (3 + 5**0.5) triangle_edge = float(circumscribed_radius / np.sin(2 * np.pi / 5)) triangle_height = 3**0.5 / 2 * triangle_edge # Icosahedron properties scaled to a specific radius. Left to 1.0 radius = 1.0 triangle_earth_edge = triangle_edge * radius triangle_earth_height = triangle_height * radius circumscribed_earth_radius = circumscribed_radius * radius inscribed_earth_radius = inscribed_radius * radius # Vertices of the faces in the airocean referential airocean_vertices = np.array( [ [2, 0, 0], # Vertex 0 [1, 1, 0], # Vertex 1 [3, 1, 0], # Vertex 2 [0, 2, 0], # Vertex 3 [2, 2, 0], # Vertex 4 [1, 3, 0], # Vertex 5 [3, 3, 0], # Vertex 6 [0, 4, 0], # Vertex 7 [2, 4, 0], # Vertex 8 [1, 5, 0], # Vertex 9 [3, 5, 0], # Vertex 10 [0, 6, 0], # Vertex 11 [2, 6, 0], # Vertex 12 [1, 7, 0], # Vertex 13 [3, 7, 0], # Vertex 14 [0, 8, 0], # Vertex 15 [2, 8, 0], # Vertex 16 [1, 9, 0], # Vertex 17 [3, 9, 0], # Vertex 18 [0, 10, 0], # Vertex 19 [2, 10, 0], # Vertex 20 [1, 11, 0], # Vertex 21 [0.5, 9.5, 0], # Vertex 22 : Split from Face 15 (ocean) [1, 0, 0], # Vertex 23 : Split from Face 15 (australia) [2 / 3, 8, 0], # Vertex 24 : split from face 8 (japan) [1 / 3, 7, 0], # Vertex 25 : new from face 8 (japan) [1, -1, 0], # Vertex 26 : bottom new for antartica ] ) * [triangle_earth_height, triangle_earth_edge / 2, 1] """ Definition of the airocean "base" (read non-split) faces and centers """ airocean_face_vertices = airocean_vertices[ [ [12, 16, 14], # Face 0 [12, 13, 16], # Face 1 [12, 9, 13], # Face 2 [12, 8, 9], # Face 3 [12, 14, 10], # Face 4 [14, 16, 18], # Face 5 [20, 16, 17], # Face 6 [17, 16, 13], # Face 7 [9, 11, 13], # Face 9 [9, 5, 7], # Face 10 [9, 8, 5], # Face 11 [5, 8, 4], # Face 12 [4, 8, 6], # Face 13 [0, 4, 2], # Face 14 [19, 17, 15], # Face 16 [3, 7, 5], # Face 17 [1, 5, 4], # Face 18 [1, 4, 0], # Face 19 [15, 17, 13], # Face 8 [19, 21, 17], # Face 15 [26, 1, 0], # Face 8' [11, 15, 13], # Face 15' ] ] airocean_face_vertices = np.concatenate( [ airocean_face_vertices, np.zeros((*airocean_face_vertices.shape[:-1], 1)), ], axis=2, ) airocean_22_centers = airocean_face_vertices.mean(axis=1) airocean_22_centers[:, 2] = 1 airocean_2_centers = airocean_22_centers[[18, 19]] """ Definition of the complete faces of the airocean unfolded net (23 faces total). """ airocean_23_face_vertices = airocean_vertices[ [ [12, 16, 14], # Face 0 [12, 13, 16], # Face 1 [12, 9, 13], # Face 2 [12, 8, 9], # Face 3 [12, 14, 10], # Face 4 [14, 16, 18], # Face 5 [20, 16, 17], # Face 6 [17, 16, 13], # Face 7 # [15, 17, 13], # Face 8, split [9, 11, 13], # Face 9 [9, 5, 7], # Face 10 [9, 8, 5], # Face 11 [5, 8, 4], # Face 12 [4, 8, 6], # Face 13 [0, 4, 2], # Face 14 # [19, 21, 17], # Face 15, split [19, 17, 15], # Face 16 [3, 7, 5], # Face 17 [1, 5, 4], # Face 18 [1, 4, 0], # Face 19 [17, 22, 21], # Face 20 - from 15 (Ocean) [1, 0, 23], # Face 21 - from 15 (Australia) [15, 17, 24], # Face 22 - from 8 (Japan) [17, 13, 24], # Face 23 - from 8 (Japan) [11, 25, 13], # Face 24 - from 8 (Japan) ] ] airocean_23_face_vertices = np.concatenate( [ airocean_23_face_vertices, np.ones((*airocean_23_face_vertices.shape[:-1], 1)), ], axis=2, ) airocean_23_face_vertices[:, :, 3] = 1.0 airocean_23_centers = airocean_23_face_vertices.mean(axis=1) airocean_23_centers[:, 2] = 1 """ Definition of the forward and inverse transform matrices to switch from vertical to horizontal layout """ def translation(dx=0, dy=0): res = np.eye(4, dtype=np.float64) res[[0, 1], 3] = dx, dy return res def rotation(angle): res = np.eye(4) res[:2, :2] = zap_zero_or_one( np.array( [ [np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)], ], dtype=np.float64, ) ) return res w, h = airocean_vertices[:, 0].max(), airocean_vertices[:, 1].max() orient_horizontal = zap_zero_or_one( translation(h, 0) @ rotation(np.pi / 2 * 1) ) inv_orient_horizontal = zap_zero_or_one(np.linalg.inv(orient_horizontal)) """ Definition of icosahedron vertices, faces and centers. Credit to Gray Fuller for the original values. Original source: http://www.rwgrayprojects.com/rbfnotes/maps/docs/graypr.c """ ico_faces = np.array( [ [ [ 0.420152426708710003, 0.078145249402782959, 0.904082550615019298, 1.0, ], [ 0.518836730327364437, 0.835420380378235850, 0.181331837557262454, 1.0, ], [ 0.995009439436241649, -0.091347795276427931, 0.040147175877166645, 1.0, ], ], [ [ 0.420152426708710003, 0.078145249402782959, 0.904082550615019298, 1.0, ], [ -0.414682225320335218, 0.655962405434800777, 0.630675807891475371, 1.0, ], [ 0.518836730327364437, 0.835420380378235850, 0.181331837557262454, 1.0, ], ], [ [ 0.420152426708710003, 0.078145249402782959, 0.904082550615019298, 1.0, ], [ -0.515455959944041808, -0.381716898287133011, 0.767200992517747538, 1.0, ], [ -0.414682225320335218, 0.655962405434800777, 0.630675807891475371, 1.0, ], ], [ [ 0.420152426708710003, 0.078145249402782959, 0.904082550615019298, 1.0, ], [ 0.355781402532944713, -0.843580002466178147, 0.402234226602925571, 1.0, ], [ -0.515455959944041808, -0.381716898287133011, 0.767200992517747538, 1.0, ], ], [ [ 0.420152426708710003, 0.078145249402782959, 0.904082550615019298, 1.0, ], [ 0.995009439436241649, -0.091347795276427931, 0.040147175877166645, 1.0, ], [ 0.355781402532944713, -0.843580002466178147, 0.402234226602925571, 1.0, ], ], [ [ 0.995009439436241649, -0.091347795276427931, 0.040147175877166645, 1.0, ], [ 0.518836730327364437, 0.835420380378235850, 0.181331837557262454, 1.0, ], [ 0.515455959944041808, 0.381716898287133011, -0.767200992517747538, 1.0, ], ], [ [ 0.515455959944041808, 0.381716898287133011, -0.767200992517747538, 1.0, ], [ 0.518836730327364437, 0.835420380378235850, 0.181331837557262454, 1.0, ], [ -0.355781402532944713, 0.843580002466178147, -0.402234226602925571, 1.0, ], ], [ [ -0.355781402532944713, 0.843580002466178147, -0.402234226602925571, 1.0, ], [ 0.518836730327364437, 0.835420380378235850, 0.181331837557262454, 1.0, ], [ -0.414682225320335218, 0.655962405434800777, 0.630675807891475371, 1.0, ], ], [ [ -0.515455959944041808, -0.381716898287133011, 0.767200992517747538, 1.0, ], [ -0.995009439436241649, 0.091347795276427931, -0.040147175877166645, 1.0, ], [ -0.414682225320335218, 0.655962405434800777, 0.630675807891475371, 1.0, ], ], [ [ -0.515455959944041808, -0.381716898287133011, 0.767200992517747538, 1.0, ], [ -0.518836730327364437, -0.835420380378235850, -0.181331837557262454, 1.0, ], [ -0.995009439436241649, 0.091347795276427931, -0.040147175877166645, 1.0, ], ], [ [ -0.515455959944041808, -0.381716898287133011, 0.767200992517747538, 1.0, ], [ 0.355781402532944713, -0.843580002466178147, 0.402234226602925571, 1.0, ], [ -0.518836730327364437, -0.835420380378235850, -0.181331837557262454, 1.0, ], ], [ [ -0.518836730327364437, -0.835420380378235850, -0.181331837557262454, 1.0, ], [ 0.355781402532944713, -0.843580002466178147, 0.402234226602925571, 1.0, ], [ 0.414682225320335218, -0.655962405434800777, -0.630675807891475371, 1.0, ], ], [ [ 0.414682225320335218, -0.655962405434800777, -0.630675807891475371, 1.0, ], [ 0.355781402532944713, -0.843580002466178147, 0.402234226602925571, 1.0, ], [ 0.995009439436241649, -0.091347795276427931, 0.040147175877166645, 1.0, ], ], [ [ 0.515455959944041808, 0.381716898287133011, -0.767200992517747538, 1.0, ], [ 0.414682225320335218, -0.655962405434800777, -0.630675807891475371, 1.0, ], [ 0.995009439436241649, -0.091347795276427931, 0.040147175877166645, 1.0, ], ], [ [ -0.420152426708710003, -0.078145249402782959, -0.904082550615019298, 1.0, ], [ -0.355781402532944713, 0.843580002466178147, -0.402234226602925571, 1.0, ], [ -0.995009439436241649, 0.091347795276427931, -0.040147175877166645, 1.0, ], ], [ [ -0.420152426708710003, -0.078145249402782959, -0.904082550615019298, 1.0, ], [ -0.995009439436241649, 0.091347795276427931, -0.040147175877166645, 1.0, ], [ -0.518836730327364437, -0.835420380378235850, -0.181331837557262454, 1.0, ], ], [ [ -0.420152426708710003, -0.078145249402782959, -0.904082550615019298, 1.0, ], [ -0.518836730327364437, -0.835420380378235850, -0.181331837557262454, 1.0, ], [ 0.414682225320335218, -0.655962405434800777, -0.630675807891475371, 1.0, ], ], [ [ -0.420152426708710003, -0.078145249402782959, -0.904082550615019298, 1.0, ], [ 0.414682225320335218, -0.655962405434800777, -0.630675807891475371, 1.0, ], [ 0.515455959944041808, 0.381716898287133011, -0.767200992517747538, 1.0, ], ], [ [ -0.355781402532944713, 0.843580002466178147, -0.402234226602925571, 1.0, ], [ -0.38796691462082733, 0.3827173765316976, -0.6531583886089725, 1.0, ], [ 0.515455959944041808, 0.381716898287133011, -0.767200992517747538, 1.0, ], ], [ [ -0.420152426708710003, -0.078145249402782959, -0.904082550615019298, 1.0, ], [ 0.515455959944041808, 0.381716898287133011, -0.767200992517747538, 1.0, ], [ -0.38796691462082733, 0.3827173765316976, -0.6531583886089725, 1.0, ], ], [ [ -0.995009439436241649, 0.091347795276427931, -0.040147175877166645, 1.0, ], [ -0.355781402532944713, 0.843580002466178147, -0.402234226602925571, 1.0, ], [ -0.5884910224298405, 0.5302967343924689, 0.06276480180379439, 1.0, ], ], [ [ -0.355781402532944713, 0.843580002466178147, -0.402234226602925571, 1.0, ], [ -0.414682225320335218, 0.655962405434800777, 0.630675807891475371, 1.0, ], [ -0.5884910224298405, 0.5302967343924689, 0.06276480180379439, 1.0, ], ], [ [ -0.995009439436241649, 0.091347795276427931, -0.040147175877166645, 1.0, ], [ -0.5884910224298405, 0.5302967343924689, 0.06276480180379439, 1.0, ], [ -0.414682225320335218, 0.655962405434800777, 0.630675807891475371, 1.0, ], ], ], dtype=np.float64, ) ico_20_centers = np.array( [ [0.6446661988241054, 0.27407261150153034, 0.37518718801648276], [0.17476897723857973, 0.5231760117386065, 0.5720300653545857], [-0.16999525285188902, 0.1174635855168169, 0.7673197836747474], [0.08682595643253761, -0.3823838837835094, 0.6911725899118975], [0.5903144228926321, -0.28559418277994103, 0.44882131769837047], [0.6764340432358825, 0.375263161129647, -0.18190732636110615], [0.22617042924615385, 0.6869057603771823, -0.3293677938544702], [-0.0838756325086385, 0.778320929426405, 0.13659113961527075], [-0.6417158749002062, 0.12186443414136523, 0.45257654151068544], [-0.6764340432358825, -0.375263161129647, 0.18190732636110615], [-0.22617042924615385, -0.6869057603771823, 0.32936779385447024], [0.0838756325086385, -0.778320929426405, -0.13659113961527075], [0.5884910224298405, -0.5302967343924689, -0.06276480180379439], [0.6417158749002062, -0.12186443414136523, -0.4525765415106855], [-0.5903144228926321, 0.28559418277994103, -0.44882131769837047], [-0.6446661988241054, -0.2740726115015303, -0.37518718801648276], [-0.17476897723857973, -0.5231760117386065, -0.5720300653545857], [0.16999525285188902, -0.11746358551681692, -0.7673197836747474], [-0.5884910224298405, 0.5302967343924689, 0.06276480180379439], [-0.08682595643253764, 0.3823838837835094, -0.6911725899118975], ], dtype=np.float64, ) """ Definition of the forward and inverse transform matrices from icosahedral faces to airocean unfolded net space """ A = ico_faces[:, 1] - ico_faces[:, 0] B = ico_faces[:, 2] - ico_faces[:, 0] ico_normals = -np.array( [ A[:, 1] * B[:, 2] - A[:, 2] * B[:, 1], A[:, 2] * B[:, 0] - A[:, 0] * B[:, 2], A[:, 0] * B[:, 1] - A[:, 1] * B[:, 0], ] ).T ico_normals /= ((ico_normals**2).sum(axis=1) ** 0.5).reshape(-1, 1) ico_centers = ico_faces.mean(axis=1) ico_elevated_centers = ico_centers.copy() ico_elevated_centers[:, :3] += ico_normals airocean_basis = np.zeros((23, 4, 4)) airocean_basis[:, :3, :] = airocean_23_face_vertices[:, :3, :] airocean_basis[:, 3, :] = airocean_23_centers ico_basis = np.zeros((23, 4, 4)) ico_basis[:, :3, :] = ico_faces ico_basis[:, 3, :] = ico_elevated_centers air_ico_trans = zap_zero_or_one( np.array( [ a.T @ np.linalg.inv(b.T) for i, (a, b) in enumerate(zip(ico_basis, airocean_basis)) ], dtype=np.float64, ) ) ico_air_trans = zap_zero_or_one( np.array( [ b.T @ np.linalg.inv(a.T) for i, (a, b) in enumerate(zip(ico_basis, airocean_basis)) ], dtype=np.float64, ) ) for i, (a, b, v1, v2) in enumerate( zip(ico_basis, airocean_basis, ico_centers, airocean_22_centers) ): m = b.T @ np.linalg.inv(a.T) # ico to air w = a.T @ np.linalg.inv(b.T) # air to ico for x, y in zip(a, b): v1 = m @ x.T v1 /= v1[3] v2 = w @ y.T v2 /= v2[3] assert np.isclose(v1, y, rtol=1e-7).all() assert np.isclose(v2, x, rtol=1e-7).all() # Assert that the transform matrices work adequately for i in range(100): r = np.random.rand(3) r /= r.sum() v = (a[:3, :] * r.reshape(3, 1)).sum(axis=0) v1 = m @ v.T v1 /= v1[3] v2 = w @ v1.T v2 /= v2[3] assert np.isclose(v, v2, rtol=1e-7).all() """ End of definitions. """ def generate_20_faces_airocean_net( outfile: Path, ) -> None: """ Save a picture of the "base" (non split) 20 faces of the unfolded icosahedron net """ try: import geopandas as gpd from shapely.geometry import Polygon gdf = gpd.GeoDataFrame( data={"id": range(22)}, geometry=[Polygon(shell=v[:, :2]) for v in airocean_face_vertices], crs=None, ) ax = gdf.plot() gdf.apply( lambda x: ax.annotate( text=x["id"], xy=x.geometry.centroid.coords[0], ha="center" ), axis=1, ) ax.get_figure().savefig(outfile) except ImportError as e: print( f"Geopandas and shapely must be installed to generate airocean plot! ({e})" ) def generate_23_faces_airocean_net( outfile: Path, ) -> None: """ Save a picture of the complete 23 faces of the unfolded icosahedron net """ try: import geopandas as gpd from shapely.geometry import Polygon gdf = gpd.GeoDataFrame( data={"id": range(len(airocean_23_face_vertices))}, geometry=[ Polygon(shell=v[:, :2]) for v in airocean_23_face_vertices ], crs=None, ) ax = gdf.plot() gdf.apply( lambda x: ax.annotate( text=x["id"], xy=x.geometry.centroid.coords[0], ha="center" ), axis=1, ) ax.get_figure().savefig(outfile) except ImportError as e: print( f"Geopandas and shapely must be installed to generate airocean plot! ({e})" ) def generate_airocean_parameters() -> str: """ Create a string of C code that initializes airocean parameters and that can be included directly into the source file of the airocean projection. """ return "\n".join( [ ( f"{name} = " + json.dumps(value.tolist()) .replace("[", "{") .replace("]", "}") + ";" ) for name, value in { "constexpr pj_face base_ico_faces[23]": ico_faces[:, :, :3], "constexpr PJ_XYZ base_ico_centers[23]": ico_centers[:, :3], "constexpr PJ_XYZ base_ico_normals[23]": ico_normals, "constexpr pj_face base_airocean_faces[23]": airocean_23_face_vertices[ :, :, [0, 1, 3] ], "constexpr double base_ico_air_trans[23][4][4]": ico_air_trans, "constexpr double base_air_ico_trans[23][4][4]": air_ico_trans, "constexpr double orient_horizontal_trans[4][4]": orient_horizontal, "constexpr double orient_horizontal_inv_trans[4][4]": inv_orient_horizontal, }.items() ] ) if __name__ == "__main__": generate_20_faces_airocean_net(Path("./20_faces_airocean_net.png")) generate_23_faces_airocean_net(Path("./23_faces_airocean_net.png")) parameters = generate_airocean_parameters() print(parameters) proj-9.8.1/scripts/build_nrcan.py000775 001750 001750 00000100525 15166171715 017002 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Build NRCan specific definitions # Author: Even Rouault # ############################################################################### # Copyright (c) 2023, Even Rouault # # 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. ############################################################################### import json import os def MTM_NAD83CSRSv7(zone): longitude = { 1: -53, 2: -56, 3: -58.5, 4: -61.5, 5: -64.5, 6: -67.5, 7: -70.5, 8: -73.5, 9: -76.5, 10: -79.5, 11: -82.5, 12: -81, 13: -84, 14: -87, 15: -90, 16: -93, 17: -96 } return { "type": "ProjectedCRS", "name": "NAD83(CSRS)v7 / MTM zone " + str(zone), "base_crs": { "name": "NAD83(CSRS)v7", "datum": { "type": "GeodeticReferenceFrame", "name": "North American Datum of 1983 (CSRS) version 7", "ellipsoid": { "name": "GRS 1980", "semi_major_axis": 6378137, "inverse_flattening": 298.257222101 } }, "coordinate_system": { "subtype": "ellipsoidal", "axis": [ { "name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree" }, { "name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree" } ] } }, "conversion": { "name": "MTM zone " + str(zone), "method": { "name": "Transverse Mercator", "id": { "authority": "EPSG", "code": 9807 } }, "parameters": [ { "name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": { "authority": "EPSG", "code": 8801 } }, { "name": "Longitude of natural origin", "value": longitude[zone], "unit": "degree", "id": { "authority": "EPSG", "code": 8802 } }, { "name": "Scale factor at natural origin", "value": 0.9999, "unit": "unity", "id": { "authority": "EPSG", "code": 8805 } }, { "name": "False easting", "value": 304800, "unit": "metre", "id": { "authority": "EPSG", "code": 8806 } }, { "name": "False northing", "value": 0, "unit": "metre", "id": { "authority": "EPSG", "code": 8807 } } ] }, "coordinate_system": { "subtype": "Cartesian", "axis": [ { "name": "Easting", "abbreviation": "E(X)", "direction": "east", "unit": "metre" }, { "name": "Northing", "abbreviation": "N(Y)", "direction": "north", "unit": "metre" } ] } } def UTM_NAD83CSRSv7(zone): return { "type": "ProjectedCRS", "name": "NAD83(CSRS)v7 / UTM zone " + str(zone), "base_crs": { "name": "NAD83(CSRS)v7", "datum": { "type": "GeodeticReferenceFrame", "name": "North American Datum of 1983 (CSRS) version 7", "ellipsoid": { "name": "GRS 1980", "semi_major_axis": 6378137, "inverse_flattening": 298.257222101 } }, "coordinate_system": { "subtype": "ellipsoidal", "axis": [ { "name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree" }, { "name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree" } ] } }, "conversion": { "name": "UTM zone " + str(zone) + "N", "method": { "name": "Transverse Mercator", "id": { "authority": "EPSG", "code": 9807 } }, "parameters": [ { "name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": { "authority": "EPSG", "code": 8801 } }, { "name": "Longitude of natural origin", "value": -183 + zone * 6, "unit": "degree", "id": { "authority": "EPSG", "code": 8802 } }, { "name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": { "authority": "EPSG", "code": 8805 } }, { "name": "False easting", "value": 500000, "unit": "metre", "id": { "authority": "EPSG", "code": 8806 } }, { "name": "False northing", "value": 0, "unit": "metre", "id": { "authority": "EPSG", "code": 8807 } } ] }, "coordinate_system": { "subtype": "Cartesian", "axis": [ { "name": "Easting", "abbreviation": "E(X)", "direction": "east", "unit": "metre" }, { "name": "Northing", "abbreviation": "N(Y)", "direction": "north", "unit": "metre" } ] } } def vert_crs_CGVD28(geoid_model_name, geoid_model_authority, geoid_model_code): return { "type": "VerticalCRS", "name": "CGVD28 height", "datum": { "type": "VerticalReferenceFrame", "name": "Canadian Geodetic Vertical Datum of 1928" }, "coordinate_system": { "subtype": "vertical", "axis": [ { "name": "Gravity-related height", "abbreviation": "H", "direction": "up", "unit": "metre" } ] }, "geoid_model": { "name": geoid_model_name, "id": { "authority": geoid_model_authority, "code": geoid_model_code } }, "id": { "authority": "EPSG", "code": 5713 } } def vert_crs_CGVD28_HT2_1997(): return vert_crs_CGVD28("HT2_1997", "NRCAN", "HT2_1997_NAD83CSRSV7") def vert_crs_CGVD28_HT2_2002(): return vert_crs_CGVD28("HT2_2002", "NRCAN", "HT2_2002_NAD83CSRSV7") def vert_crs_CGVD28_HT2_2010(): return vert_crs_CGVD28("HT2_2010", "EPSG", 9987) def vert_crs_CGVD2013a_1997(): return { "type": "VerticalCRS", "name": "CGVD2013a(1997) height", "datum": { "type": "VerticalReferenceFrame", "name": "Canadian Geodetic Vertical Datum of 2013 (CGG2013a) epoch 1997" }, "coordinate_system": { "subtype": "vertical", "axis": [ { "name": "Gravity-related height", "abbreviation": "H", "direction": "up", "unit": "metre" } ] }, "id": { "authority": "EPSG", "code": 20035 } } def vert_crs_CGVD2013a_2002(): return { "type": "VerticalCRS", "name": "CGVD2013a(2002) height", "datum": { "type": "VerticalReferenceFrame", "name": "Canadian Geodetic Vertical Datum of 2013 (CGG2013a) epoch 2002" }, "coordinate_system": { "subtype": "vertical", "axis": [ { "name": "Gravity-related height", "abbreviation": "H", "direction": "up", "unit": "metre" } ] }, "id": { "authority": "EPSG", "code": 20034 } } def vert_crs_CGVD2013a_2010(): return { "type": "VerticalCRS", "name": "CGVD2013a(2010) height", "datum": { "type": "VerticalReferenceFrame", "name": "Canadian Geodetic Vertical Datum of 2013 (CGG2013a) epoch 2010" }, "coordinate_system": { "subtype": "vertical", "axis": [ { "name": "Gravity-related height", "abbreviation": "H", "direction": "up", "unit": "metre" } ] }, "id": { "authority": "EPSG", "code": 9245 } } usages_MTM = { 1: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Newfoundland - onshore east of 54°30'W.", "bbox": { "south_latitude": 46.56, "west_longitude": -54.5, "north_latitude": 49.89, "east_longitude": -52.54 }, }, 2: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Newfoundland and Labrador between 57°30'W and 54°30'W.", "bbox": { "south_latitude": 46.81, "west_longitude": -57.5, "north_latitude": 54.71, "east_longitude": -54.49 }, }, 3: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Newfoundland west of 57°30'W.", "bbox": { "south_latitude": 47.5, "west_longitude": -59.48, "north_latitude": 50.54, "east_longitude": -57.5 }, }, 4: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Labrador between 63°W and 60°W.", "bbox": { "south_latitude": 52, "west_longitude": -63, "north_latitude": 58.92, "east_longitude": -60 }, }, 5: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Labrador - 66°W to 63°W.", "bbox": { "south_latitude": 51.58, "west_longitude": -66, "north_latitude": 60.52, "east_longitude": -63 }, }, 6: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Labrador - west of 66°W.", "bbox": { "south_latitude": 52.05, "west_longitude": -67.81, "north_latitude": 55.34, "east_longitude": -66 }, }, 7: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Quebec - between 72°W and 69°W.", "bbox": { "south_latitude": 45.01, "west_longitude": -72, "north_latitude": 61.8, "east_longitude": -69 }, }, 8: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - east of 75°W.", "bbox": { "south_latitude": 44.98, "west_longitude": -75, "north_latitude": 45.65, "east_longitude": -74.35 }, }, 9: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 78°W and 75°W.", "bbox": { "south_latitude": 43.63, "west_longitude": -78, "north_latitude": 46.25, "east_longitude": -75 }, }, 10: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 81°W and 78°W: south of 46°N in area to west of 80°15'W, south of 47°N in area between 80°15'W and 79°30'W, entire province between 79°30'W and 78°W.", "bbox": { "south_latitude": 42.26, "west_longitude": -81, "north_latitude": 47.33, "east_longitude": -77.99 }, }, 11: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - south of 46°N and west of 81°W.", "bbox": { "south_latitude": 41.67, "west_longitude": -83.6, "north_latitude": 46, "east_longitude": -81 }, }, 12: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 82°30'W and 79°30'W: north of 46°N in area between 82°30'W and 80°15'W, north of 47°N in area between 80°15'W and 79°30'W.", "bbox": { "south_latitude": 46, "west_longitude": -82.5, "north_latitude": 55.21, "east_longitude": -79.5 }, }, 13: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 85°30'W and 82°30'W and north of 46°N.", "bbox": { "south_latitude": 46, "west_longitude": -85.5, "north_latitude": 55.59, "east_longitude": -82.5 }, }, 14: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 88°30'W and 85°30'W.", "bbox": { "south_latitude": 47.17, "west_longitude": -88.5, "north_latitude": 56.7, "east_longitude": -85.5 }, }, 15: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 91°30'W and 88°30'W.", "bbox": { "south_latitude": 47.97, "west_longitude": -91.5, "north_latitude": 56.9, "east_longitude": -88.5 }, }, 16: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - between 94°30'W and 91°30'W.", "bbox": { "south_latitude": 48.06, "west_longitude": -94.5, "north_latitude": 55.2, "east_longitude": -91.5 }, }, 17: { "scope": "Engineering survey, topographic mapping.", "area": "Canada - Ontario - west of 94°30'W.", "bbox": { "south_latitude": 48.69, "west_longitude": -95.16, "north_latitude": 53.24, "east_longitude": -94.5 }, }, } usages_UTM = { 7: { "scope": "Engineering survey, topographic mapping.", "area": "Canada west of 138°W, onshore and offshore south of 84°N - British Columbia, Yukon.", "bbox": { "south_latitude": 52.05, "west_longitude": -141.01, "north_latitude": 72.53, "east_longitude": -138 }, }, 8: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 138°W and 132°W, onshore and offshore south of 84°N - British Columbia, Northwest Territories, Yukon.", "bbox": { "south_latitude": 48.06, "west_longitude": -138, "north_latitude": 79.42, "east_longitude": -132 }, }, 9: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 138°W and 132°W, onshore and offshore south of 84°N - British Columbia, Northwest Territories, Yukon.", "bbox": { "south_latitude": 48.06, "west_longitude": -138, "north_latitude": 79.42, "east_longitude": -132 }, }, 10: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 126°W and 120°W, onshore and offshore south of 84°N - British Columbia, Northwest Territories, Yukon.", "bbox": { "south_latitude": 48.13, "west_longitude": -126, "north_latitude": 81.8, "east_longitude": -120 }, }, 11: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 120°W and 114°W onshore and offshore - Alberta, British Columbia, Northwest Territories, Nunavut.", "bbox": { "south_latitude": 48.99, "west_longitude": -120, "north_latitude": 83.5, "east_longitude": -114 }, }, 12: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 114°W and 108°W onshore and offshore - Alberta, Northwest Territories, Nunavut, Saskatchewan.", "bbox": { "south_latitude": 48.99, "west_longitude": -114, "north_latitude": 84, "east_longitude": -108 }, }, 13: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 108°W and 102°W onshore and offshore - Northwest Territories, Nunavut, Saskatchewan.", "bbox": { "south_latitude": 48.99, "west_longitude": -108, "north_latitude": 84, "east_longitude": -102 }, }, 14: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 102°W and 96°W, onshore and offshore south of 84°N - Manitoba, Nunavut, Saskatchewan.", "bbox": { "south_latitude": 48.99, "west_longitude": -102, "north_latitude": 84, "east_longitude": -96 }, }, 15: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 96°W and 90°W, onshore and offshore south of 84°N - Manitoba, Nunavut, Ontario.", "bbox": { "south_latitude": 48.03, "west_longitude": -96, "north_latitude": 84, "east_longitude": -90 }, }, 16: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 90°W and 84°W, onshore and offshore south of 84°N - Manitoba, Nunavut, Ontario.", "bbox": { "south_latitude": 46.11, "west_longitude": -90, "north_latitude": 84, "east_longitude": -84 }, }, 17: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 84°W and 78°W, onshore and offshore south of 84°N - Nunavut, Ontario and Quebec.", "bbox": { "south_latitude": 41.67, "west_longitude": -84, "north_latitude": 84, "east_longitude": -78 }, }, 18: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 78°W and 72°W, onshore and offshore south of 84°N - Nunavut, Ontario and Quebec.", "bbox": { "south_latitude": 43.63, "west_longitude": -78, "north_latitude": 84, "east_longitude": -72 }, }, 19: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 72°W and 66°W onshore and offshore - New Brunswick, Labrador, Nova Scotia, Nunavut, Quebec.", "bbox": { "south_latitude": 40.8, "west_longitude": -72, "north_latitude": 84, "east_longitude": -66 }, }, 20: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 66°W and 60°W onshore and offshore - New Brunswick, Labrador, Nova Scotia, Nunavut, Prince Edward Island, Quebec.", "bbox": { "south_latitude": 40.04, "west_longitude": -66, "north_latitude": 84, "east_longitude": -60 }, }, 21: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 60°W and 54°W - Newfoundland and Labrador; Nunavut; Quebec.", "bbox": { "south_latitude": 38.56, "west_longitude": -60, "north_latitude": 84, "east_longitude": -54 }, }, 22: { "scope": "Engineering survey, topographic mapping.", "area": "Canada between 54°W and 48°W onshore and offshore - Newfoundland and Labrador.", "bbox": { "south_latitude": 39.5, "west_longitude": -54, "north_latitude": 57.65, "east_longitude": -47.99 }, } } def compound_crs_MTM_HT_1997(zone): j = { "type": "CompoundCRS", "components": [ MTM_NAD83CSRSv7(zone), vert_crs_CGVD28_HT2_1997() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_MTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_MTM_HT_2002(zone): j = { "type": "CompoundCRS", "components": [ MTM_NAD83CSRSv7(zone), vert_crs_CGVD28_HT2_2002() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_MTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_MTM_HT_2010(zone): j = { "type": "CompoundCRS", "components": [ MTM_NAD83CSRSv7(zone), vert_crs_CGVD28_HT2_2010() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_MTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_MTM_CGVD2013_1997(zone): j = { "type": "CompoundCRS", "components": [ MTM_NAD83CSRSv7(zone), vert_crs_CGVD2013a_1997() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_MTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_MTM_CGVD2013_2002(zone): j = { "type": "CompoundCRS", "components": [ MTM_NAD83CSRSv7(zone), vert_crs_CGVD2013a_2002() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_MTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_MTM_CGVD2013_2010(zone): j = { "type": "CompoundCRS", "components": [ MTM_NAD83CSRSv7(zone), vert_crs_CGVD2013a_2010() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_MTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_UTM_CGVD2013_1997(zone): j = { "type": "CompoundCRS", "components": [ UTM_NAD83CSRSv7(zone), vert_crs_CGVD2013a_1997() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_UTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_UTM_CGVD2013_2002(zone): j = { "type": "CompoundCRS", "components": [ UTM_NAD83CSRSv7(zone), vert_crs_CGVD2013a_2002() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_UTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_UTM_CGVD2013_2010(zone): j = { "type": "CompoundCRS", "components": [ UTM_NAD83CSRSv7(zone), vert_crs_CGVD2013a_2010() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_UTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_UTM_HT_1997(zone): j = { "type": "CompoundCRS", "components": [ UTM_NAD83CSRSv7(zone), vert_crs_CGVD28_HT2_1997() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_UTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_UTM_HT_2002(zone): j = { "type": "CompoundCRS", "components": [ UTM_NAD83CSRSv7(zone), vert_crs_CGVD28_HT2_2002() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_UTM[zone] for key in usage: j[key] = usage[key] return j def compound_crs_UTM_HT_2010(zone): j = { "type": "CompoundCRS", "components": [ UTM_NAD83CSRSv7(zone), vert_crs_CGVD28_HT2_2010() ] } j["name"] = j["components"][0]["name"] + " + " + j["components"][1]["name"] usage = usages_UTM[zone] for key in usage: j[key] = usage[key] return j script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') all_sql = [] all_sql.append("""INSERT INTO "grid_transformation" VALUES('NRCAN','HT2_1997_NAD83CSRSV7','NAD83(CSRS)v7 to CGVD28 height',NULL,'EPSG','1060','Geographic3D to GravityRelatedHeight (NRCan byn)','EPSG','8254','EPSG','5713',0.05,'EPSG','8666','Geoid (height correction) model file','HT2_1997.byn',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'NRC-Can CGG2000',0);""") all_sql.append("""INSERT INTO "usage" VALUES('NRCAN','USAGE_HT2_1997_NAD83CSRSV7','grid_transformation','NRCAN','HT2_1997_NAD83CSRSV7','EPSG','1289','EPSG','1133');""") all_sql.append("""INSERT INTO "grid_transformation" VALUES('NRCAN','HT2_2002_NAD83CSRSV7','NAD83(CSRS)v7 to CGVD28 height',NULL,'EPSG','1060','Geographic3D to GravityRelatedHeight (NRCan byn)','EPSG','8254','EPSG','5713',0.05,'EPSG','8666','Geoid (height correction) model file','HT2_2002v70.byn',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'NRC-Can CGG2000 2002',0);""") all_sql.append("""INSERT INTO "usage" VALUES('NRCAN','USAGE_HT2_2002_NAD83CSRSV7','grid_transformation','NRCAN','HT2_2002_NAD83CSRSV7','EPSG','1289','EPSG','1133');""") for zone in range(1, 17+1): projjson = json.dumps(compound_crs_MTM_HT_1997(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_1997_MTM{zone}_HT2_1997', NULL, NULL, NULL, '{projjson}', 1997.0, 0);""") for zone in range(1, 17+1): projjson = json.dumps(compound_crs_MTM_HT_2002(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2002_MTM{zone}_HT2_2002', NULL, NULL, NULL, '{projjson}', 2002.0, 0);""") for zone in range(1, 17+1): projjson = json.dumps(compound_crs_MTM_HT_2010(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2010_MTM{zone}_HT2_2010', NULL, NULL, NULL, '{projjson}', 2010.0, 0);""") for zone in range(1, 17+1): projjson = json.dumps(compound_crs_MTM_CGVD2013_1997(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_1997_MTM{zone}_CGVD2013_1997', NULL, NULL, NULL, '{projjson}', 1997.0, 0);""") for zone in range(1, 17+1): projjson = json.dumps(compound_crs_MTM_CGVD2013_2002(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2002_MTM{zone}_CGVD2013_2002', NULL, NULL, NULL, '{projjson}', 2002.0, 0);""") for zone in range(1, 17+1): projjson = json.dumps(compound_crs_MTM_CGVD2013_2010(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2010_MTM{zone}_CGVD2013_2010', NULL, NULL, NULL, '{projjson}', 2010.0, 0);""") for zone in range(7, 22+1): projjson = json.dumps( compound_crs_UTM_HT_1997(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_1997_UTM{zone}_HT2_1997', NULL, NULL, NULL, '{projjson}', 1997.0, 0);""") for zone in range(7, 22+1): projjson = json.dumps( compound_crs_UTM_HT_2002(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2002_UTM{zone}_HT2_2002', NULL, NULL, NULL, '{projjson}', 2002.0, 0);""") for zone in range(7, 22+1): projjson = json.dumps( compound_crs_UTM_HT_2010(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2010_UTM{zone}_HT2_2010', NULL, NULL, NULL, '{projjson}', 2010.0, 0);""") for zone in range(7, 22+1): projjson = json.dumps( compound_crs_UTM_CGVD2013_1997(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_1997_UTM{zone}_CGVD2013_1997', NULL, NULL, NULL, '{projjson}', 1997.0, 0);""") for zone in range(7, 22+1): projjson = json.dumps( compound_crs_UTM_CGVD2013_2002(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2002_UTM{zone}_CGVD2013_2002', NULL, NULL, NULL, '{projjson}', 2002.0, 0);""") for zone in range(7, 22+1): projjson = json.dumps( compound_crs_UTM_CGVD2013_2010(zone)).replace("'", "''") all_sql.append( f"""INSERT INTO coordinate_metadata VALUES('NRCAN', 'NAD83_CSRS_2010_UTM{zone}_CGVD2013_2010', NULL, NULL, NULL, '{projjson}', 2010.0, 0);""") f = open(os.path.join(sql_dir_name, 'nrcan') + '.sql', 'wb') f.write("--- This file has been generated by scripts/build_nrcan.py. DO NOT EDIT !\n\n".encode('UTF-8')) for sql in all_sql: f.write((sql + '\n').encode('UTF-8')) f.close() proj-9.8.1/scripts/ci/000775 001750 001750 00000000000 15166171735 014537 5ustar00eveneven000000 000000 proj-9.8.1/scripts/ci/emscripten/000775 001750 001750 00000000000 15166171735 016710 5ustar00eveneven000000 000000 proj-9.8.1/scripts/ci/emscripten/proj.ems.tests.js000664 001750 001750 00000021316 15166171715 022145 0ustar00eveneven000000 000000 const assert = require("node:assert/strict"); const ProjModuleFactory = require("./projModule.js"); // Small class to help to manage memory. class Keeper { constructor(proj, debug = false) { if (!proj) { throw new Error("proj cannot be empty in class Keeper"); } this.proj = proj; this.debug = debug; this.to_free = []; this.to_destroy = []; }; add(ptr, proj_destroy = true) { if (proj_destroy) { if (this.debug) console.debug("add destroy", ptr); this.to_destroy.push(ptr); } else { if (this.debug) console.debug("add free", ptr); this.to_free.push(ptr); } return ptr; }; call(name, ...args) { const ptr = this.proj[name](...args); return this.add(ptr, true); } malloc(ptrSize) { const ptr = this.proj._malloc(ptrSize); return this.add(ptr, false); }; string(str) { const ptr = this.proj.stringToNewUTF8(str); return this.add(ptr, false); }; destroy() { this.to_destroy.reverse(); for (const p of this.to_destroy) { if (this.debug) console.debug("call destroy", p); this.proj._proj_destroy(p); } this.to_destroy = []; this.to_free.reverse(); for (const p of this.to_free) { if (this.debug) console.debug("call free", p); this.proj._free(p); } this.to_free = []; }; }; const tests = { test_proj_info : (proj) => { const r = proj.proj_info_ems(); const compilation_date = proj.UTF8ToString(proj._get_compilation_date()); // Print it for visual inspection console.log(r); console.log(compilation_date); assert(compilation_date.length > 5); assert(r.major >= 9); if (r.major === 9) { assert(r.minor >= 8); } assert(typeof r.minor === "number") assert(typeof r.patch === "number") const rel = `${r.major}.${r.minor}.${r.patch}`; assert(r.release.includes(rel)); assert(r.version.includes(rel)); }, test_projinfo : (proj) => { function get_projinfo(args) { let msg = ""; const myCallback = (level, message) => { msg += message; }; const rc = proj.projinfo_ems(0, args, myCallback); return [ rc, msg ]; } { let [rc, msg] = get_projinfo([ "EPSG:32633", "-o", "WKT1:GDAL" ]); assert.equal(rc, 0); assert.ok(msg.includes('PROJCS["WGS 84 / UTM zone 33N"')); assert.ok(msg.includes('AUTHORITY["EPSG","32633"]')); } {let [rc, msg] = get_projinfo([ "EPSG:32633", "-o", "invalid" ]); assert.equal(rc, 1); assert.ok(msg.includes('Unrecognized value '));} { let [rc, msg] = get_projinfo( [ "EPSG:4326", "EPSG:32633", "-o", "proj", "--single-line" ]); assert.equal(rc, 0); assert.ok(msg.includes('Candidate operations')); assert.ok( msg.includes(('+proj=pipeline +step +proj=axisswap +order=2,1 ', '+step +proj=unitconvert +xy_in=deg +xy_out=rad ', '+step +proj=utm +zone=33 +ellps=WGS84'))); } }, test_convert : (proj) => { let ctx = proj._proj_context_create(); if (ctx === 0) { throw new Error("proj_context_create returned NULL"); } const sourceCRS = proj.stringToNewUTF8("EPSG:4326"); const targetCRS = proj.stringToNewUTF8("EPSG:32633"); const P = proj._proj_create_crs_to_crs(ctx, sourceCRS, targetCRS, 0); if (P === 0) { throw new Error("proj_create_crs_to_crs returned NULL."); } // PJ_COORD is 4 doubles: (x, y, z, t) or (lon, lat, z, t) // We need 4 * 8 = 32 bytes of memory let coordPtr = proj._malloc(32); const coordView = new Float64Array(proj.HEAPF64.buffer, coordPtr, 4); coordView[0] = 52; coordView[1] = 13.5; coordView[2] = 0; coordView[3] = Infinity; // HUGE_VAL let res = proj._proj_trans_array(P, 1, 1, coordPtr); if (res != 0) { let msgPtr = proj._proj_context_errno_string(ctx, res); let msg = proj.UTF8ToString(msgPtr); throw new Error(`_proj_trans_array error ${msg}`); } assert(Math.abs(coordView[0] - 397027.0183) < 1e-3); assert(Math.abs(coordView[1] - 5762100.4897) < 1e-3); proj._free(coordPtr); proj._proj_destroy(P); proj._free(sourceCRS); proj._free(targetCRS); proj._proj_destroy(ctx); }, test_axes : (proj) => { let ctx = proj._proj_context_create(); if (ctx === 0) { throw new Error("proj_context_create returned NULL"); } function get_str(ptr) { const strPtr = proj.getValue(ptr, 'i32'); // It's a pointer (i32) const str = proj.UTF8ToString(strPtr); return str; } function get_axes(crs) { let keep = new Keeper(proj); try { // TODO support compound CRS const ptrSize = proj._get_ptr_size(); assert.ok(ptrSize == 4 || ptrSize == 8); const doubleSize = 8; // Doubles are 8 bytes const sourceCRS = keep.string(crs); const P_crs = keep.call("_proj_create", ctx, sourceCRS); const P_cs = keep.add(proj._proj_crs_get_coordinate_system(ctx, P_crs)); const axis_count = proj._proj_cs_get_axis_count(ctx, P_cs); const outNamePtr = keep.malloc(ptrSize); const outAbbrevPtr = keep.malloc(ptrSize); const outDirectionPtr = keep.malloc(ptrSize); const outConvFactorPtr = keep.malloc(doubleSize); const outUnitPtr = keep.malloc(ptrSize); res = { name : [], abbr : [], direction : [], conv_factor : [], unit : [] }; for (let i = 0; i < axis_count; i++) { const r = proj._proj_cs_get_axis_info( ctx, P_cs, i, outNamePtr, outAbbrevPtr, outDirectionPtr, outConvFactorPtr, outUnitPtr, 0, 0); if (r != 1) { throw new Error("error calling proj_cs_get_axis_info"); } res.name.push(get_str(outNamePtr)); res.abbr.push(get_str(outAbbrevPtr)); res.direction.push(get_str(outDirectionPtr)); res.conv_factor.push( proj.getValue(outConvFactorPtr, 'double')); res.unit.push(get_str(outUnitPtr)); } } finally { keep.destroy(); } return res; } const ax4326 = get_axes("EPSG:4326"); assert.deepEqual(ax4326, { name : [ 'Geodetic latitude', 'Geodetic longitude' ], abbr : [ 'Lat', 'Lon' ], direction : [ 'north', 'east' ], conv_factor : [ 0.017453292519943295, 0.017453292519943295 ], unit : [ 'degree', 'degree' ] }); const ax25833 = get_axes("EPSG:25833"); assert.deepEqual(ax25833, { name : [ 'Easting', 'Northing' ], abbr : [ 'E', 'N' ], direction : [ 'east', 'north' ], conv_factor : [ 1, 1 ], unit : [ 'metre', 'metre' ] }); const ax3855 = get_axes("EPSG:3855"); assert.deepEqual(ax3855, { name : [ 'Gravity-related height' ], abbr : [ 'H' ], direction : [ 'up' ], conv_factor : [ 1 ], unit : [ 'metre' ] }); proj._proj_destroy(ctx); } }; if (typeof ProjModuleFactory === 'undefined') { console.error( "'ProjModuleFactory' is not defined. Have you loaded projModule.js."); process.exit(1); } else { ProjModuleFactory() .then(module => { proj = module; console.log("Starting tests"); let counter = 0; for (let test of Object.keys(tests)) { console.log(`+++++ Running test "${test}"`) tests[test](proj); counter++; }; console.log(`Number of tests run: ${counter}`) process.exit(0); }) .catch(err => { console.error(`Tests failed: ${err}`); process.exit(1); }); } proj-9.8.1/scripts/ci/emscripten/build_wasm.sh000664 001750 001750 00000027572 15166171715 021405 0ustar00eveneven000000 000000 #!/bin/bash ############################################################################### # $Id$ # # Project: PROJ # Purpose: Build with emscripten compiler # Author: Javier Jimenez Shaw # ############################################################################### # Copyright (c) 2025 Javier Jimenez Shaw ############################################################################### # Exit immediately if a command exits with a non-zero status. set -e # --- Configuration Variables --- # Use environment variables from Dockerfile or runtime. PROJ_REPO=${PROJ_REPO:-https://github.com/OSGeo/PROJ.git} INSTALL_DIR=${INSTALL_DIR:-/build/install} BUILD_DIR=${BUILD_DIR:-/build} PROJ_SRC_DIR="${BUILD_DIR}/proj_src" PROJ_BUILD_WASM_DIR="${BUILD_DIR}/proj_build_wasm" DEPS_SRC_DIR="${BUILD_DIR}/deps_src" TEMP_BUILD_DIR="${BUILD_DIR}/temp_build" # Clean build directories to ensure a fresh build rm -rf ${TEMP_BUILD_DIR} # We keep DEPS_SRC_DIR to cache downloads mkdir -p ${PROJ_SRC_DIR} mkdir -p ${DEPS_SRC_DIR} mkdir -p ${TEMP_BUILD_DIR} # Emscripten flags for Pthreads (multithreading support for synchronous emscripten_fetch) # and some other. EM_PTHREADS_FLAGS="-pthread -matomics -mbulk-memory -fexceptions" # --- Utility Functions --- function log_step { echo "" echo "--- $1 ---" echo "" } function configure_cmake { # Use emcmake wrapper to correctly configure the toolchain emcmake cmake "$@" \ -G Ninja \ -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ -D CMAKE_BUILD_TYPE=Release \ -D BUILD_SHARED_LIBS=OFF \ -D CMAKE_C_FLAGS="${EM_PTHREADS_FLAGS}" \ -D CMAKE_CXX_FLAGS="${EM_PTHREADS_FLAGS}" \ -D CMAKE_FIND_ROOT_PATH="${INSTALL_DIR}" \ -D CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY \ -D CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \ -D CMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY } function build_and_install { cmake --build . --parallel $(nproc) cmake --install . } # --- Preparation --- log_step "1. Setting up Environment and Directories" if [ "${FORCE_REBUILD}" = "1" ]; then echo "!!! FORCE_REBUILD is set. Existing libraries will be ignored and rebuilt. !!!" rm -rf "${INSTALL_DIR}"/* fi if [ -f "${PROJ_SRC_DIR}/CMakeLists.txt" ]; then echo "Using content from ${PROJ_SRC_DIR} to build PROJ" elif [ -n "${PROJ_TAG}" ]; then echo "Using PROJ from ${PROJ_REPO} : ${PROJ_TAG}" else echo "Please set PROJ_TAG env variable, or populate ${PROJ_SRC_DIR}" exit 1 fi echo "Installation target: ${INSTALL_DIR}" # --- 2. Build and Install Zlib (Dependency) --- log_step "2. Building and Installing Zlib" if [ "${FORCE_REBUILD}" != "1" ] && [ -f "${INSTALL_DIR}/lib/libz.a" ] && [ -f "${INSTALL_DIR}/include/zlib.h" ]; then echo "Zlib library and header already installed. Skipping." else ZLIB_DIR="${DEPS_SRC_DIR}/zlib-1.3.1" cd ${DEPS_SRC_DIR} if [ ! -f "v1.3.1.zip" ]; then wget -nc "https://github.com/madler/zlib/archive/refs/tags/v1.3.1.zip" fi unzip -qo v1.3.1.zip cd ${ZLIB_DIR} # Zlib defines both SHARED and STATIC targets with the same output name (libz.a) # on Emscripten, causing Ninja errors. # Fixed in https://github.com/madler/zlib/commit/b3907c2cd99908259c71fab6754f88e25d370fed # Instead of renaming, we simply remove the SHARED target definitions entirely. sed -i '/add_library(zlib SHARED/d' CMakeLists.txt sed -i '/target_include_directories(zlib /d' CMakeLists.txt sed -i '/set_target_properties(zlib /d' CMakeLists.txt sed -i 's/install(TARGETS zlib zlibstatic/install(TARGETS zlibstatic/g' CMakeLists.txt rm -rf build_wasm mkdir -p build_wasm cd build_wasm configure_cmake .. -DZLIB_BUILD_EXAMPLES=OFF build_and_install # Handle case where Zlib installs as libzlibstatic.a instead of libz.a if [ -f "${INSTALL_DIR}/lib/libzlibstatic.a" ]; then echo "Detected libzlibstatic.a, copying to libz.a..." cp "${INSTALL_DIR}/lib/libzlibstatic.a" "${INSTALL_DIR}/lib/libz.a" fi fi # --- 3. Build and Install LibTIFF (Dependency) --- log_step "3. Building and Installing LibTIFF" if [ "${FORCE_REBUILD}" != "1" ] && [ -f "${INSTALL_DIR}/lib/libtiff.a" ] && [ -f "${INSTALL_DIR}/include/tiff.h" ]; then echo "LibTIFF library and header already installed. Skipping." else TIFF_VERSION="4.7.0" TIFF_DIR="${DEPS_SRC_DIR}/tiff-${TIFF_VERSION}" cd ${DEPS_SRC_DIR} if [ ! -f "tiff-${TIFF_VERSION}.tar.gz" ]; then wget -nc "https://download.osgeo.org/libtiff/tiff-${TIFF_VERSION}.tar.gz" fi tar -xzf tiff-${TIFF_VERSION}.tar.gz cd ${TIFF_DIR} rm -rf build_wasm mkdir -p build_wasm cd build_wasm # Configure minimal LibTIFF: No JPEG, No LZMA, No WebP, No ZSTD. # Only Zlib support enabled. configure_cmake .. \ -D tiff-tools=OFF \ -D tiff-tests=OFF \ -D tiff-contrib=OFF \ -D tiff-docs=OFF \ -D jpeg=OFF \ -D zlib=ON \ -D lzma=OFF \ -D zstd=OFF \ -D webp=OFF \ -D jbig=OFF \ -D CMAKE_PREFIX_PATH="${INSTALL_DIR}" build_and_install fi # --- 4. Build and Install SQLite3 (Dependency) --- log_step "4. Building and Installing SQLite3" # Check if both the library AND the header exist before skipping if [ "${FORCE_REBUILD}" != "1" ] && [ -f "${INSTALL_DIR}/lib/libsqlite3.a" ] && [ -f "${INSTALL_DIR}/include/sqlite3.h" ]; then echo "SQLite3 static library and header already found. Skipping build." else SQLITE_VERSION="3440200" SQLITE_AMALGAMATION_DIR="${DEPS_SRC_DIR}/sqlite3_amalgamation" mkdir -p ${SQLITE_AMALGAMATION_DIR} cd ${SQLITE_AMALGAMATION_DIR} if [ ! -f "sqlite-amalgamation-${SQLITE_VERSION}.zip" ]; then wget "https://sqlite.org/2023/sqlite-amalgamation-${SQLITE_VERSION}.zip" fi unzip -qo sqlite-amalgamation-${SQLITE_VERSION}.zip # Move files to the parent directory for a flat structure mv sqlite-amalgamation-${SQLITE_VERSION}/* . rmdir sqlite-amalgamation-${SQLITE_VERSION} # Step 4a: Compile SQLite3 to an object file (.o) emcc sqlite3.c \ -o ${TEMP_BUILD_DIR}/sqlite3.o \ -c \ -O3 \ -DSQLITE_THREADSAFE=1 \ -D_REENTRANT \ ${EM_PTHREADS_FLAGS} # Step 4b: Create the static library archive (.a) from the object file emmake ar rcs ${INSTALL_DIR}/lib/libsqlite3.a ${TEMP_BUILD_DIR}/sqlite3.o # Copy the header to the install directory mkdir -p ${INSTALL_DIR}/include # Force copy to overwrite any broken symlinks from previous runs rm -f ${INSTALL_DIR}/include/sqlite3.h cp ${SQLITE_AMALGAMATION_DIR}/sqlite3.h ${INSTALL_DIR}/include/sqlite3.h fi # --- 5. Download PROJ Source if needed --- log_step "5. Downloading PROJ Source" if [ ! -f "${PROJ_SRC_DIR}/CMakeLists.txt" ]; then git clone --depth 1 --branch ${PROJ_TAG} ${PROJ_REPO} ${PROJ_SRC_DIR} fi cd ${PROJ_SRC_DIR} # --- 6. Build and Install PROJ --- # This step uses the NATIVE sqlite3 binary (from the Docker image) # to generate proj.db, which is then embedded into libproj.a. log_step "6. Building and Installing PROJ" if [ "${FORCE_REBUILD}" != "1" ] && [ -f "${INSTALL_DIR}/lib/libproj.a" ]; then echo "PROJ already built. Skipping." else # If forcing a rebuild, clean the PROJ build directory too to be safe if [ "${FORCE_REBUILD}" = "1" ]; then echo "Cleaning PROJ build directory due to FORCE_REBUILD..." rm -rf ${PROJ_BUILD_WASM_DIR} fi mkdir -p ${PROJ_BUILD_WASM_DIR} cd ${PROJ_BUILD_WASM_DIR} # Configure PROJ configure_cmake ${PROJ_SRC_DIR} \ -D BUILD_TESTING=OFF \ -D BUILD_APPS=OFF \ -D ENABLE_TIFF=ON \ -D ENABLE_CURL=OFF \ -D ENABLE_EMSCRIPTEN_FETCH=ON \ -D EXE_SQLITE3=/usr/bin/sqlite3 \ -D SQLite3_INCLUDE_DIR="${INSTALL_DIR}/include" \ -D SQLite3_LIBRARY="${INSTALL_DIR}/lib/libsqlite3.a" \ -D TIFF_INCLUDE_DIR="${INSTALL_DIR}/include" \ -D TIFF_LIBRARY_RELEASE="${INSTALL_DIR}/lib/libtiff.a" build_and_install fi # --- 6.5 Create C Wrappers --- # Helper functions to extract some info from PROJ in C and C++ log_step "6.5 Creating C Wrapper Functions" WRAPPER_FILE="${TEMP_BUILD_DIR}/proj_wrappers.cpp" WRAPPER_OBJ_FILE="${TEMP_BUILD_DIR}/proj_wrappers.o" DDD=`date +"%Y-%m-%dT%H:%M:%S%z" -u` cat << EOF > ${WRAPPER_FILE} #include "proj.h" #include "math.h" #include "projapps_lib.h" #include #include #include #include void trampoline(PJ_PROJINFO_LOG_LEVEL level, const char *msg, void *user_data) { if (!user_data) return; const emscripten::val* js_callback = reinterpret_cast(user_data); (*js_callback)(level, std::string(msg)); } int projinfo_wrapper(uintptr_t ctx_ptr, emscripten::val jsArgs, emscripten::val jsCallback) { std::vector args = emscripten::vecFromJSArray(jsArgs); std::vector argv; for (auto& s : args) { argv.push_back(&s[0]); } PJ_CONTEXT *ctx = reinterpret_cast(ctx_ptr); return projinfo(ctx, args.size(), argv.data(), &trampoline, &jsCallback); } EMSCRIPTEN_BINDINGS(proj_module) { emscripten::value_object("PJ_INFO") .field("major", &PJ_INFO::major) .field("minor", &PJ_INFO::minor) .field("patch", &PJ_INFO::patch) .field("release", std::function([](const PJ_INFO& i) { return std::string(i.release); }), std::function([](PJ_INFO& i, std::string v) {})) .field("version", std::function([](const PJ_INFO& i) { return std::string(i.version); }), std::function([](PJ_INFO& i, std::string v) {})); emscripten::function("proj_info_ems", &proj_info); emscripten::enum_("PJ_PROJINFO_LOG_LEVEL") .value("INFO", PJ_PROJINFO_LOG_LEVEL_INFO) .value("WARN", PJ_PROJINFO_LOG_LEVEL_WARN) .value("ERR", PJ_PROJINFO_LOG_LEVEL_ERR); emscripten::function("projinfo_ems", &projinfo_wrapper, emscripten::allow_raw_pointers()); } extern "C" { const char* get_compilation_date() { return "$DDD" ; } int get_ptr_size() { return sizeof(void*); } } EOF # Compile the wrapper into an object file emcc ${WRAPPER_FILE} \ -c \ -o ${WRAPPER_OBJ_FILE} \ -I ${INSTALL_DIR}/include \ ${EM_PTHREADS_FLAGS} # --- 7. Final WASM Module Generation --- # Link all the static libraries and the wrapper object file # into the final JS/WASM module and expose functions. log_step "7. Generating Final WASM Module (projModule.js + .wasm)" FINAL_LIBS="${INSTALL_DIR}/lib/libproj.a \ ${INSTALL_DIR}/lib/libsqlite3.a \ ${INSTALL_DIR}/lib/libtiff.a \ ${INSTALL_DIR}/lib/libz.a \ ${WRAPPER_OBJ_FILE}" # include all exported symbols echo -e "_malloc\n_free\n_get_compilation_date\n_get_ptr_size" > ${INSTALL_DIR}/exported_symbols.txt grep "^proj_\|^geod_" ${PROJ_SRC_DIR}/scripts/reference_exported_symbols.txt | grep -v "(" | sed 's/^/_/' >> ${INSTALL_DIR}/exported_symbols.txt head ${INSTALL_DIR}/exported_symbols.txt emcc -v ${FINAL_LIBS} \ -o ${INSTALL_DIR}/projModule.js \ -O3 \ -lembind \ -s STACK_OVERFLOW_CHECK=1 \ -s STACK_SIZE=5MB \ -s NO_DISABLE_EXCEPTION_CATCHING \ -s FETCH=1 \ -s USE_PTHREADS=1 \ -s FETCH_SUPPORT_INDEXEDDB=0 \ -s WASM=1 \ -s MODULARIZE=1 \ -s EXPORT_NAME="'ProjModuleFactory'" \ -s FORCE_FILESYSTEM=1 \ -s ALLOW_MEMORY_GROWTH=1 \ -s EXPORTED_RUNTIME_METHODS="[ccall, cwrap, FS, HEAP8, HEAP16, HEAP32, HEAPF64, lengthBytesUTF8, stringToUTF8, stringToNewUTF8, UTF8ToString, getValue, setValue]" \ -s EXPORTED_FUNCTIONS=@${INSTALL_DIR}/exported_symbols.txt \ ${EM_PTHREADS_FLAGS} log_step "BUILD SUCCESSFUL!" echo "ls -l ${INSTALL_DIR}" ls -l ${INSTALL_DIR} echo "Artifacts are installed in ${INSTALL_DIR}" proj-9.8.1/scripts/ci/conda/000775 001750 001750 00000000000 15166171735 015623 5ustar00eveneven000000 000000 proj-9.8.1/scripts/ci/conda/setup.sh000775 001750 001750 00000000451 15166171715 017320 0ustar00eveneven000000 000000 #!/bin/bash conda update -n base -c defaults conda -y conda install rattler-build ninja compilers yq -y pwd ls git clone https://github.com/conda-forge/proj.4-feedstock.git cd proj.4-feedstock yq -y -i '.source = {"path": "../../../PROJ"} | .build.number = 2112' recipe/recipe.yaml ls recipe proj-9.8.1/scripts/ci/conda/compile.sh000775 001750 001750 00000001176 15166171715 017615 0ustar00eveneven000000 000000 #!/bin/bash mkdir packages CONDA_PLAT="" if grep -q "windows" <<< "$GHA_CI_PLATFORM"; then CONDA_PLAT="win" ARCH="64" fi if grep -q "ubuntu" <<< "$GHA_CI_PLATFORM"; then CONDA_PLAT="linux" ARCH="64" fi if grep -q "macos-14" <<< "$GHA_CI_PLATFORM"; then CONDA_PLAT="osx" ARCH="arm64" elif grep -q "macos-15-intel" <<< "$GHA_CI_PLATFORM"; then CONDA_PLAT="osx" ARCH="64" fi rattler-build build \ --recipe-dir recipe \ --output-dir packages \ --variant-config ".ci_support/${CONDA_PLAT}_${ARCH}_.yaml" conda install -c ./packages proj #projinfo -s NAD27 -t EPSG:4269 --area "USA - Missouri" proj-9.8.1/scripts/create_proj_symbol_rename.sh000775 001750 001750 00000005123 15166171715 021713 0ustar00eveneven000000 000000 #!/bin/bash # Script to extract exported symbols and rename them # to avoid clashing with other older libproj # # This is done in provision for the integration of PROJ master with GDAL. GDAL is # a complex library that links with many libraries, a number of them linking with # PROJ. However in the continuous integration setups used by GDAL, it would be # impractical to recompile all those libraries that use the system libproj (4.X or 5.X) # that comes with the distribution. Linking GDAL directly against PROJ master # and this system libproj with potential different ABI is prone to clash/crash # (we experimented that painfully in GDAL with its internal libtiff 4.X copy whereas # systems shipped with libtiff 3.X) # Hence this solution to rename the symbols of PROJ master so that PROJ master # can be used by GDAL without conflicting with the system PROJ. # The renaming only happens if -DPROJ_RENAME_SYMBOLS is passed in CFLAGS and CXXFLAGS. # # Note: we potentially should do the same for C++ symbols, but that is not needed # for now, and we would just set the NS_PROJ existing macro to change the C++ base # namespace which defaults to osgeo::proj currently. # Run as "create_proj_symbol_rename.sh lib/libproj.so". SCRIPT_DIR=$(dirname "$(readlink -f "$0")") PROJ_ROOT=$(dirname $(readlink -f "${SCRIPT_DIR}")) error() { echo "Error: $*" exit 1 } LIBPROJ="$1" [ -f "$LIBPROJ" ] || error "\"$LIBPROJ\" does not look like path to libproj.so" OUT_FILE=proj_symbol_rename.h rm -f $OUT_FILE 2>/dev/null echo "/* This is a generated file by create_proj_symbol_rename.sh. *DO NOT EDIT MANUALLY !* */" >> $OUT_FILE echo "#ifndef PROJ_SYMBOL_RENAME_H" >> $OUT_FILE echo "#define PROJ_SYMBOL_RENAME_H" >> $OUT_FILE symbol_list=$(objdump -t "$LIBPROJ" | grep " g " | grep .text | awk '{print $6}' | grep -v -e _Z | sort) for symbol in $symbol_list do echo "#define $symbol internal_$symbol" >> $OUT_FILE done rodata_symbol_list=$(objdump -t "$LIBPROJ" | grep " g O \\.rodata" | awk '{print $6}') for symbol in $rodata_symbol_list do echo "#define $symbol internal_$symbol" >> $OUT_FILE done #data_symbol_list=$(objdump -t "$LIBPROJ" | grep "\\.data" | grep -v -e __dso_handle -e __TMC_END__ | awk '{print $6}' | grep -v "\\."| grep -v _Z) #for symbol in $data_symbol_list #do # echo "#define $symbol internal_$symbol" >> $OUT_FILE #done bss_symbol_list=$(objdump -t "$LIBPROJ" | grep "g O \\.bss" | awk '{print $6}' | grep -v "\\."| grep -v _Z) for symbol in $bss_symbol_list do echo "#define $symbol internal_$symbol" >> $OUT_FILE done echo "#endif /* PROJ_SYMBOL_RENAME_H */" >> $OUT_FILE proj-9.8.1/scripts/typos_whitelist.txt000664 001750 001750 00000017362 15166171715 020166 0ustar00eveneven000000 000000 $EXE +units=us-ft +init=${INIT_FILE}:404 -E -f '%.3f' >>${OUT} <`_) {"CPM", "a=6375738.7", "rf=334.29", "Comm. des Poids et Mesures 1799"}, double lamtp, cl, sd, sp, sav, tanphi; sav = lampp; lamt = lp.lam + Q->p22 * sav; if (fabs(fabs(sav) - fabs(lamdp)) < TOL) sav = lamdp; double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp; sav = lamdp; } while (fabs(lamdp - sav) >= TOL && --nn); double yt, ba; ba = 1. / (yt * Q->sw * Q->h + Q->cw); xy.x = (xy.x * Q->cg - xy.y * Q->sg) * Q->cw * ba; xy.y = yt * ba; * This implements Space Oblique Mercator (SOM) projection, used by the * The code is identical to that of Landsat SOM (PJ_lsat.c) with the following static double phi1_(double qs, double Te, double Tone_es) { if (Te < EPSILON) con = Te * sinpi; sinpi / com + .5 / Te * log ((1. - con) / else { /* point mean of intersepts */ operation proj=latlong geoidgrids=egm96_15.gtx axis=dne ellps=GRS80 Url = {https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/notice/NTG_88.pdf} it (neither in current documentation, nor in `historic one `_). `Deformation model master file `_ of a Deformation Model `__ return strcmp(abbrev, "lon") == 0 || strcmp(abbrev, "Lon") == 0; projections/som.cpp PROJ_HEAD(som, "Space Oblique Mercator") static const std::string lon; } else if (abbreviation == AxisAbbreviation::lon) { abbreviation = AxisAbbreviation::lon; : isGeographic ? AxisAbbreviation::lon : isGeographic ? AxisAbbreviation::lon : std::string(), const std::string AxisAbbreviation::lon("lon"); AxisAbbreviation::lon, AxisDirection::EAST, unit); static const ParamMapping paramLonCentreLonCenterLonc = { ¶mLonCentreLonCenterLonc, * This implements the Space Oblique Mercator (SOM) projection, used by the * This code was originally developed for the Landsat SOM projection with the * For the MISR path based SOM projection, the code is identical to that of *Landsat SOM with the following parameter changes: * For the generic SOM projection, the code is identical to the above for MISR PJ *PJ_PROJECTION(som) { operation +proj=som +ellps=GRS80 +inc_angle=1.7157253262878522r +ps_rev=0.06866666666666667 +asc_lon=2.2298420007209447r operation +proj=som +R=6400000 +inc_angle=1.7157253262878522r +ps_rev=0.06866666666666667 +asc_lon=2.2298420007209447r operation +proj=som +ellps=GRS80 +inc_angle=98.30382 +ps_rev=0.06866666666666667 +asc_lon=127.7605356226 operation +proj=som +R=6400000 +inc_angle=98.30382 +ps_rev=0.06866666666666667 +asc_lon=127.7605356226 " AXIS[\"Geodetic longitude (Lon)\",east," " AXIS[\"geodetic longitude (Lon)\",east,\n" " \"abbreviation\": \"lon\",\n" " \"abbreviation\": \"Lon\",\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " \"abbreviation\": \"Lon\",\n" " \"abbreviation\": \"Lon\",\n" " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," EXPECT_EQ(tolower(cs->axisList()[1]->abbreviation()), "lon"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lon"); "AXIS[\"Lat\",north],AXIS[\"Lon\",east]," " AXIS[\"longitude (lon)\",east],\n" " AXIS[\"(lon)\",east],\n" EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lon"); " VERSION[\"ENRD-Shn Hel\"],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" "AXIS[\"geodetic longitude (Lon)\",east," " AXIS[\"geodetic longitude (Lon)\",east],\n" " \"abbreviation\": \"Lon\",\n" " \"abbreviation\": \"Lon\",\n" " \"abbreviation\": \"lon\",\n" " \"abbreviation\": \"lon\",\n" " \"abbreviation\": \"Lon\",\n" EXPECT_EQ(std::string(abbrev), "lon"); " AXIS[\"geodetic longitude (Lon)\",east," EXPECT_EQ(axisList[1]->abbreviation(), "Lon"); " AXIS[\"geodetic longitude (Lon)\",east],\n" export NOT_A_TRANSFORMATION='GEOGCRS["WGS 84",DATUM["World Geodetic System 1984 (G2139)",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ANGLEUNIT["degree",0.0174532925199433]]]' AXIS["geodetic longitude (Lon)",east, "abbreviation": "Lon", "filename": "som.png", "name": "som", "projstring": "+proj=som +inc_angle=98.303820000243860022 +ps_rev=0.06866666666666667 +asc_lon=64.412896137498847793", - `Land Information New Zealand (LINZ) `_ Z_{dest} = Z_{src} + \left( dh + slope_{lat} * {\rho}_0 * (\phi - {\phi}_0) + slope_{lon} * {\nu}_0 * (\lambda - {\lambda}_0) * cos(\phi) \right) * :math:`dh`, :math:`slope_{lat}` and :math:`slope_{lon}` are the above mentioned parameters Z_{src} = Z_{dest} - \left( dh + slope_{lat} * {\rho}_0 * (\phi - {\phi}_0) + slope_{lon} * {\nu}_0 * (\lambda - {\lambda}_0) * cos(\phi) \r Z_{src} = Z_{dest} - \left( dh + slope_{lat} * {\rho}_0 * (\phi - {\phi}_0) + slope_{lon} * {\nu}_0 * (\lambda - {\lambda}_0) * cos(\phi) \right) Space oblique for LANDSAT is a specialization of :doc:`Space Oblique Mercator` #. :doc:`Space Oblique Mercator Projection` Space Oblique Mercator (SOM) The Space Oblique Mercator (SOM) projection is a generalization The Space Oblique Mercator (SOM) projection is a generalization of the Oblique representing satellite remote sensing data. In response, SOM was specifically | **Alias** | som | .. figure:: ./images/som.png :alt: Space Oblique Mercator (SOM) proj-string: ``+proj=som +inc_angle=98.303820000243860022 +ps_rev=0.06866666666666667 +asc_lon=64.412896137498847793`` proj-string: ``+proj=som +inc_angle=1.7157253262878522r +ps_rev=0.06866666666666667 +asc_lon=1.1242171183417042r`` Space oblique for MISR is a specialization of :doc:`Space Oblique Mercator` som "value": "nadcon5.nad27.nad83_1986.alaska.lon.trn.20160901.b", set HEL="60.171 24.938" export HEL="60.171 24.938" echo %HEL% %TAL% | geod -I +ellps=GRS80 echo %HEL% %TAL% | geod -I -f "%.12f" +ellps=GRS80 echo %HEL% -172.370214337896 41244.25 | geod -f "%.12f" +ellps=GRS80 echo %HEL% | %utm35% set HEL=60.171 24.938 NOTE: This is Windows syntax - Unix users use $HEL, etc. * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java " \"abbreviation\": \"lon\",\n" .5 / Te * log((1. - con) / (1. + con))); * C++ API function :cpp:func:`createTunisiaMapingGrid()`. Use :cpp:func:`createTunisiaMiningGrid()` instead (`#3559 `_) (gives same results as: ``+proj=som +inc_angle=1.7157253262878522r +ps_rev=0.06866666666666667 +asc_lon=1.1242171183417042r``) :ref:`som` :alt: som proj-9.8.1/scripts/build_wgs84_realizations_concatenated_operations.py000775 001750 001750 00000011326 15166171715 026414 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Populate data/sql/wgs84_realizations_concatenated_operations.sql # Author: Even Rouault # ############################################################################### # Copyright (c) 2025, Even Rouault # # SPDX-License-Identifier: MIT ############################################################################### import os script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') out_filename = os.path.join(sql_dir_name, 'wgs84_realizations_concatenated_operations') + '.sql' #print(out_filename) def sanitize_crs_name_for_code(name): return name.replace('(', '_').replace(')', '').replace(' ','_').replace('__','_').upper() def gen_transformations(sql, transformations, crs_dict): for i in range(len(transformations)): if transformations[i][0] == "WGS 84 (G1150)" and transformations[i][1] == "WGS 84 (G1762)": continue for j in range(i+1,len(transformations)): if transformations[j][0] == "WGS 84 (G1150)" and transformations[j][1] == "WGS 84 (G1762)": continue source_crs = transformations[i][0] target_crs = transformations[j][1] source_crs_week = 0 if source_crs == "WGS 84 (Transit)" else int(source_crs[len("WGS 84 (G"):-1]) target_crs_week = int(target_crs[len("WGS 84 (G"):-1]) transfm_code = sanitize_crs_name_for_code(source_crs) + "_TO_" + sanitize_crs_name_for_code(target_crs) transfm_name = f"{source_crs} to {target_crs}" source_crs_code = crs_dict[source_crs] target_crs_code = crs_dict[target_crs] step = 0 steps_sql = [] total_acc = 0 for k in range(i,j+1): source_crs_name = transformations[k][0] target_crs_name = transformations[k][1] if source_crs_week <= 1150 and target_crs_week >= 1762 and \ ((source_crs_name == "WGS 84 (G1150)" and target_crs_name == "WGS 84 (G1674)") or \ (source_crs_name == "WGS 84 (G1674)" and target_crs_name == "WGS 84 (G1762)")): continue step += 1 step_code = transformations[k][2] acc = transformations[k][3] total_acc += acc steps_sql.append(f"INSERT INTO concatenated_operation_step VALUES('PROJ','{transfm_code}',{step},'EPSG','{step_code}','forward'); -- {source_crs_name} to {target_crs_name} (EPSG:{step_code}), {acc} m\n") if len(steps_sql) <= 1: continue total_acc = round(total_acc * 100) / 100.0 sql += f"INSERT INTO concatenated_operation VALUES('PROJ','{transfm_code}','{transfm_name}','Transformation based on concatenation of transformations between WGS 84 realizations','EPSG','{source_crs_code}','EPSG','{target_crs_code}',{total_acc},NULL,0);\n" for step_sql in steps_sql: sql += step_sql sql += f"INSERT INTO usage VALUES('PROJ','{transfm_code}_USAGE','concatenated_operation','PROJ','{transfm_code}',\n" sql += " 'EPSG','1262', -- extent: World\n" sql += " 'EPSG','1027' -- scope: Geodesy\n" sql += ");\n" sql += "\n" return sql crs_dict = { "WGS 84 (Transit)": 7815, "WGS 84 (G730)": 7656, "WGS 84 (G873)": 7658, "WGS 84 (G1150)": 7660, "WGS 84 (G1674)": 7662, "WGS 84 (G1762)": 7664, "WGS 84 (G2139)": 9753, "WGS 84 (G2296)": 10604, } f = open(out_filename, 'wb') f.write("--- This file has been generated by scripts/build_wgs84_realizations_concatenated_operations.py. DO NOT EDIT !\n\n".encode('UTF-8')) f.write("-- Concatenated accuracy is sum of accuracies\n\n".encode('UTF-8')) sql = "" transformations = [ ("WGS 84 (Transit)", "WGS 84 (G730)", 9960, 0.7), # regular Helmert ("WGS 84 (G730)", "WGS 84 (G873)", 9961, 0.04), # regular Helmert ("WGS 84 (G873)", "WGS 84 (G1150)", 9962, 0.03), # time-dependent Helmert ("WGS 84 (G1150)", "WGS 84 (G1674)", 9963, 0.02), # time-dependent Helmert ("WGS 84 (G1150)", "WGS 84 (G1762)", 7668, 0.02), # regular Helmert. Note that it doesn't go through G1674 ("WGS 84 (G1674)", "WGS 84 (G1762)", 7667, 0.01), # regular Helmert ("WGS 84 (G1762)", "WGS 84 (G2139)", 9756, 0.01), # regular Helmert ("WGS 84 (G2139)", "WGS 84 (G2296)", 10607, 0.01), # regular Helmert ] sql = gen_transformations(sql, transformations, crs_dict) f.write(sql.encode('UTF-8')) f.close() proj-9.8.1/scripts/grid_checks.py000775 001750 001750 00000012626 15166171715 016773 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Tool to check consistency of database regarding grids and against # what is available in PROJ-data # Author: Even Rouault # ############################################################################### # Copyright (c) 2019, Even Rouault # # 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. ############################################################################### import argparse import fnmatch import os import sqlite3 parser = argparse.ArgumentParser( description='Check database and PROJ-data consistency.') parser.add_argument('path_to_proj_db', help='Full pathname to proj.db') parser.add_argument('path_to_proj_data', help='Full pathname to the root of the proj_data_geotiff git repository') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--not-in-grid-alternatives', dest='not_in_grid_alternatives', action='store_true', help='list grids mentioned in grid_transformation but missing in grid_alternatives') group.add_argument('--not-in-proj-data', dest='not_in_proj_data', action='store_true', help='list grids registered in grid_alternatives, but missing in PROJ-data repository') group.add_argument('--not-in-db', dest='not_in_db', action='store_true', help='list grids in PROJ-data repository, but not registered in grid_alternatives') parser.add_argument('--show-superseded', dest='show_superseded', action='store_true', help='Show superseded grid transformations in --not-in-grid-alternatives mode') args = parser.parse_args() dbname = args.path_to_proj_db proj_data = args.path_to_proj_data if args.not_in_grid_alternatives: conn = sqlite3.connect(dbname) header = '"Authority","code","name","extent_name","grid_name"' if args.show_superseded: header += ',"is_superseded"' print(header) sql = """ SELECT gt.auth_name, gt.code, gt.name, e.name, gt.grid_name, EXISTS (SELECT 1 FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = gt.auth_name AND superseded_code = gt.code) AS superseded FROM grid_transformation gt JOIN usage u ON u.object_table_name = 'grid_transformation' AND u.object_auth_name = gt.auth_name AND u.object_code = gt.code JOIN extent e ON e.auth_name = u.extent_auth_name AND e.code = u.extent_code WHERE gt.deprecated = 0 AND NOT EXISTS (SELECT 1 FROM grid_alternatives WHERE original_grid_name = gt.grid_name)""" if not args.show_superseded: sql += " AND superseded = 0" res = conn.execute(sql) for row in res: if not args.show_superseded: row = [x for x in row][0:-1] print(','.join(['"' + str(x) + '"' for x in row])) elif args.not_in_proj_data: set_grids = set() for root, dirnames, filenames in os.walk(proj_data): for filename in fnmatch.filter(filenames, '*'): set_grids.add(filename) conn = sqlite3.connect(dbname) res = conn.execute( "SELECT DISTINCT proj_grid_name FROM grid_alternatives WHERE open_license is NULL OR open_license != 0") for (grid_name,) in res: if grid_name not in set_grids: print('ERROR: grid ' + grid_name + ' in grid_alternatives but missing in PROJ-data') elif args.not_in_db: set_grids = set() for root, dirnames, filenames in os.walk(proj_data): if '.git' in root: continue for filename in fnmatch.filter(filenames, '*'): filename_lower = filename.lower() if '.aux.xml' in filename_lower: continue if '.gsb' in filename_lower or '.gtx' in filename_lower or '.tif' in filename_lower: set_grids.add(filename) conn = sqlite3.connect(dbname) for filename in sorted(set_grids): res = conn.execute( "SELECT 1 FROM grid_alternatives WHERE proj_grid_name = ?", (filename,)) if not res.fetchone(): print('WARNING: grid ' + filename + ' in PROJ-data but missing in grid_alternatives') else: raise Exception('unknown mode') proj-9.8.1/scripts/doxygen.sh000775 001750 001750 00000006625 15166171715 016167 0ustar00eveneven000000 000000 #!/bin/bash set -eu SCRIPT_DIR=$(dirname "$0") case $SCRIPT_DIR in "/"*) ;; ".") SCRIPT_DIR=$(pwd) ;; *) SCRIPT_DIR=$(pwd)/$(dirname "$0") ;; esac TOPDIR="$SCRIPT_DIR/.." pushd "${TOPDIR}" > /dev/null || exit doxygen_version_cmd=$(doxygen --version) # remove extra info after space doxygen_version=${doxygen_version_cmd%% *} echo "Doxygen version $doxygen_version" if test $doxygen_version = "1.8.17" ; then echo "Doxygen 1.8.17 is incompatible" && /bin/false; fi # HTML generation # Check that doxygen runs warning free rm -rf docs/build/html/ mkdir -p docs/build/html/ doxygen > docs/build/html/docs_log.txt 2>&1 if grep -i warning docs/build/html/docs_log.txt; then echo "Doxygen warnings found" && cat docs/build/html/docs_log.txt && /bin/false; else echo "No Doxygen warnings found"; fi rm -rf docs/build/xml/ (cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 src/iso19111/operation include/proj src/proj.h src/filemanager.cpp src/networkfilemanager.cpp src/coordinates.cpp src/trans_bounds.cpp src/general_doc.dox") | doxygen - > docs/build/docs_log.txt 2>&1 if grep -i warning docs/build/docs_log.txt; then echo "Doxygen warnings found" && cat docs/build/docs_log.txt && /bin/false; else echo "No Doxygen warnings found"; fi # There is a confusion for Breathe between PROJStringFormatter::Convention and WKTFormatter:Convention sed "s/Convention/Convention_/g" < ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml | sed "s/WKT2_2018/_WKT2_2018/g" | sed "s/WKT2_2019/_WKT2_2019/g" | sed "s/WKT2_2015/_WKT2_2015/g" | sed "s/WKT1_GDAL/_WKT1_GDAL/g" | sed "s/WKT1_ESRI/_WKT1_ESRI/g" > ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml.tmp mv ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml.tmp ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1io_1_1WKTFormatter.xml # Hack for Breathe 4.17.0 issue that is confused by osgeo::proj::common::UnitOfMeasure::Type::NONE (enumeration value of Type) and osgeo::proj::common::UnitOfMeasure::NONE (member value), whereas 4.16.0 works fine. # Filed as https://github.com/michaeljones/breathe/issues/518 sed "s/ NONE<\/name>/ Type::NONE<\/name>/" < ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1common_1_1UnitOfMeasure.xml > ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1common_1_1UnitOfMeasure.xml.tmp mv ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1common_1_1UnitOfMeasure.xml.tmp ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1common_1_1UnitOfMeasure.xml sed 's/refid="classosgeo_1_1proj_1_1common_1_1UnitOfMeasure_1a400c71b5a6d7927e3e5850cee2b07d10ab50339a10e1de285ac99d4c3990b8693" kind="enumvalue">NONE<\/name><\/member>/refid="classosgeo_1_1proj_1_1common_1_1UnitOfMeasure_1a400c71b5a6d7927e3e5850cee2b07d10ab50339a10e1de285ac99d4c3990b8693" kind="enumvalue">Type::NONE<\/name><\/member>/' < ${TOPDIR}/docs/build/xml/index.xml > ${TOPDIR}/docs/build/xml/index.xml.tmp mv ${TOPDIR}/docs/build/xml/index.xml.tmp ${TOPDIR}/docs/build/xml/index.xml sed "s/noexceptoverride/noexcept override/" < ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1util_1_1Exception.xml > ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1util_1_1Exception.xml.tmp mv ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1util_1_1Exception.xml.tmp ${TOPDIR}/docs/build/xml/classosgeo_1_1proj_1_1util_1_1Exception.xml popd > /dev/null || exit proj-9.8.1/scripts/detect_missing_include.sh000775 001750 001750 00000004152 15166171715 021207 0ustar00eveneven000000 000000 #!/bin/bash set -eu SCRIPT_DIR=$(dirname "$0") case $SCRIPT_DIR in "/"*) ;; ".") SCRIPT_DIR=$(pwd) ;; *) SCRIPT_DIR=$(pwd)/$(dirname "$0") ;; esac GDAL_ROOT=$SCRIPT_DIR/.. cd "$GDAL_ROOT" ret_code=0 find src test \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) > /tmp/proj_list_files.txt echo "Checking for missing #include statements..." rm -f /tmp/missing_include.txt while read -r i; do grep -e std::min -e std::max $i >/dev/null && (grep "#include " $i >/dev/null || echo $i) | tee -a /tmp/missing_include.txt; done < /tmp/proj_list_files.txt if test -s /tmp/missing_include.txt; then echo "FAIL: missing #include in above listed files" ret_code=1 else echo "OK." fi echo "Checking for missing #include statements..." rm -f /tmp/missing_include.txt while read -r i; do grep -e std::numeric_limits $i >/dev/null && (grep "#include " $i >/dev/null || echo $i) | tee -a /tmp/missing_include.txt; done < /tmp/proj_list_files.txt if test -s /tmp/missing_include.txt; then echo "FAIL: missing #include in above listed files" ret_code=1 else echo "OK." fi echo "Checking for missing #include statements..." rm -f /tmp/missing_include.txt while read -r i; do grep -e std::isalpha $i >/dev/null && (grep "#include " $i >/dev/null || echo $i) | tee -a /tmp/missing_include.txt; done < /tmp/proj_list_files.txt if test -s /tmp/missing_include.txt; then echo "FAIL: missing #include in above listed files" ret_code=1 else echo "OK." fi echo "Checking for missing #include statements..." rm -f /tmp/missing_include.txt while read -r i; do grep -e std::isnan -e std::isinf -e std::isfinite $i >/dev/null && (grep "#include " $i >/dev/null || echo $i) | tee -a /tmp/missing_include.txt; done < /tmp/proj_list_files.txt if test -s /tmp/missing_include.txt; then echo "FAIL: missing #include in above listed files" ret_code=1 else echo "OK." fi rm -f /tmp/missing_include.txt rm -f /tmp/proj_list_files.txt exit $ret_code proj-9.8.1/scripts/fix_typos.sh000775 001750 001750 00000006377 15166171715 016542 0ustar00eveneven000000 000000 #!/bin/sh # -*- coding: utf-8 -*- ############################################################################### # $Id$ # # Project: GDAL # Purpose: (Interactive) script to identify and fix typos # Author: Even Rouault # ############################################################################### # Copyright (c) 2016, Even Rouault # # 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. ############################################################################### if ! test -d fix_typos; then # Get our fork of codespell that adds --words-white-list and full filename support for -S option mkdir fix_typos (cd fix_typos git clone https://github.com/rouault/codespell (cd codespell && git checkout gdal_improvements) # Aggregate base dictionary + QGIS one + Debian Lintian one curl https://raw.githubusercontent.com/qgis/QGIS/master/scripts/spell_check/spelling.dat | sed "s/:/->/" | sed "s/:%//" | grep -v "colour->" | grep -v "colours->" > qgis.txt curl https://salsa.debian.org/lintian/lintian/-/raw/master/data/spelling/corrections | grep "||" | grep -v "#" | sed "s/||/->/" > debian.txt cat codespell/data/dictionary.txt qgis.txt debian.txt | awk 'NF' > gdal_dict.txt echo "difered->deferred" >> gdal_dict.txt echo "differed->deferred" >> gdal_dict.txt grep -v 404 < gdal_dict.txt > gdal_dict.txt.tmp mv gdal_dict.txt.tmp gdal_dict.txt ) fi EXCLUDED_FILES="*configure,config.status,config.sub,*/autom4te.cache/*,libtool,aclocal.m4,depcomp,ltmain.sh,*.pdf,./m4/*,./fix_typos/*,./docs/build/*,./src/*generated*,./test/googletest/*,./include/proj/internal/nlohmann/json.hpp,*.before_reformat,geodesic.h,geodesic.c,geodtest.c,./docs/source/spelling_wordlist.txt,./test/cli/test_proj_nad83.yaml,./test/cli/test_proj_nad27.yaml" WORDS_WHITE_LIST="metres,als,lsat,twon,ang,PJD_ERR_LSAT_NOT_IN_RANGE,COLOR_GRAT,interm,Interm,Cartesian,cartesian,CARTESIAN,kilometre,centimetre,millimetre,millimetres,Australia,LINZ,LaTeX,BibTeX,lon,Lon" python3 fix_typos/codespell/codespell.py -w -i 3 -q 2 -S $EXCLUDED_FILES \ -x scripts/typos_whitelist.txt --words-white-list=$WORDS_WHITE_LIST \ -D fix_typos/gdal_dict.txt ./include ./src ./test ./docs ./cmake ./examples proj-9.8.1/scripts/update_man.sh000775 001750 001750 00000000462 15166171715 016620 0ustar00eveneven000000 000000 # run from root of repo cd docs make man cd .. cp -r docs/build/man/*.1 man/man1 git add man/man1/proj.1 git add man/man1/cs2cs.1 git add man/man1/cct.1 git add man/man1/geod.1 git add man/man1/gie.1 git add man/man1/projinfo.1 git add man/man1/projsync.1 git commit -m "Update man-pages from Sphinx-docs" proj-9.8.1/scripts/projinfo-bash-completion.sh000664 001750 001750 00000003125 15166171715 021407 0ustar00eveneven000000 000000 # Hashbang deliberately missing because this file should be sourced, not executed function_exists() { declare -f -F "$1" > /dev/null return $? } # Checks that bash-completion is recent enough function_exists _get_comp_words_by_ref || return 0 _projinfo() { local cur prev COMPREPLY=() _get_comp_words_by_ref cur prev choices=$(projinfo completion ${COMP_LINE}) if [[ "$cur" == "=" ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" --) elif [[ "$cur" == ":" ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" --) elif [[ "${cur: -1}" == "/" ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" --) elif [[ "${cur: -2}" == "/ " ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" --) elif [[ "${cur: -1}" == "+" ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" --) elif [[ "${cur: -2}" == "+ " ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" --) elif [[ "$cur" == "!" ]]; then mapfile -t COMPREPLY < <(compgen -W "$choices" -P "! " --) else mapfile -t COMPREPLY < <(compgen -W "$choices" -- "$cur") fi for element in "${COMPREPLY[@]}"; do if [[ $element == */ ]]; then # Do not add a space if one of the suggestion ends with slash compopt -o nospace break elif [[ $element == *= ]]; then # Do not add a space if one of the suggestion ends with equal compopt -o nospace break elif [[ $element == *: ]]; then # Do not add a space if one of the suggestion ends with colon compopt -o nospace break fi done } complete -o default -F _projinfo projinfo proj-9.8.1/scripts/reformat.sh000775 001750 001750 00000000671 15166171715 016324 0ustar00eveneven000000 000000 #!/bin/sh set -eu # Refuse to reformat nn.hpp: this is third-party code if test $(basename $1) = "nn.hpp"; then exit 0 fi clang-format-15 -style="{BasedOnStyle: llvm, IndentWidth: 4}" $1 > $1.reformatted if diff -u $1.reformatted $1; then # No reformatting: remove temporary file rm $1.reformatted else # Differences. Backup original file, and use reformatted file cp $1 $1.before_reformat mv $1.reformatted $1 fi proj-9.8.1/scripts/build_esri_projection_mapping.py000664 001750 001750 00000117744 15166171715 022622 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Generate mappings between ESRI projection names and parameters and # their EPSG equivalents. # Author: Even Rouault # ############################################################################### # Copyright (c) 2018, Even Rouault # # 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. ############################################################################### import yaml # Map methods from pe_list_projection.csv to WKT2 naming config_str = """ - Equidistant_Cylindrical: WKT2_name: EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Plate_Carree: WKT2_name: - EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL - EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN Cond: - EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL = 0 - Miller_Cylindrical: WKT2_name: PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Mercator: # Mercator 2SP WKT2_name: EPSG_NAME_METHOD_MERCATOR_VARIANT_B Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Gauss_Kruger: WKT2_name: EPSG_NAME_METHOD_TRANSVERSE_MERCATOR Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Transverse_Mercator: WKT2_name: EPSG_NAME_METHOD_TRANSVERSE_MERCATOR Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Transverse_Mercator_Complex: # This is likely PROJ etmerc method WKT2_name: EPSG_NAME_METHOD_TRANSVERSE_MERCATOR Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Albers: WKT2_name: EPSG_NAME_METHOD_ALBERS_EQUAL_AREA Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN - False_Northing: EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Standard_Parallel_2: EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN - Sinusoidal: WKT2_name: PROJ_WKT2_NAME_METHOD_SINUSOIDAL Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Mollweide: WKT2_name: PROJ_WKT2_NAME_METHOD_MOLLWEIDE Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Eckert_I: WKT2_name: PROJ_WKT2_NAME_METHOD_ECKERT_I Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Eckert_II: WKT2_name: PROJ_WKT2_NAME_METHOD_ECKERT_II Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Eckert_III: WKT2_name: PROJ_WKT2_NAME_METHOD_ECKERT_III Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Eckert_IV: WKT2_name: PROJ_WKT2_NAME_METHOD_ECKERT_IV Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Eckert_V: WKT2_name: PROJ_WKT2_NAME_METHOD_ECKERT_V Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Eckert_VI: WKT2_name: PROJ_WKT2_NAME_METHOD_ECKERT_VI Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Gall_Stereographic: WKT2_name: PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Winkel_I: WKT2_name: "Winkel I" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Winkel_II: WKT2_name: "Winkel II" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Lambert_Conformal_Conic: - WKT2_name: EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN # should be the same as Standard_Parallel_1 - WKT2_name: EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN - False_Northing: EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Standard_Parallel_2: EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN # From GDAL autotest - WKT2_name: EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN - False_Northing: EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Standard_Parallel_2: EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL - Scale_Factor: 1.0 - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN # Tempative mapping. Did not find any example - WKT2_name: EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN - False_Northing: EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Standard_Parallel_2: EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL - Scale_Factor: EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN - Polyconic: WKT2_name: EPSG_NAME_METHOD_AMERICAN_POLYCONIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Quartic_Authalic: WKT2_name: "Quartic Authalic" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Loximuthal: WKT2_name: "Loximuthal" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Central_Parallel: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Bonne: WKT2_name: EPSG_NAME_METHOD_BONNE Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Hotine_Oblique_Mercator_Two_Point_Natural_Origin: WKT2_name: PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE - False_Northing: EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE - Latitude_Of_1st_Point: "Latitude of 1st point" - Latitude_Of_2nd_Point: "Latitude of 2nd point" - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Longitude_Of_1st_Point: "Longitude of 1st point" - Longitude_Of_2nd_Point: "Longitude of 2nd point" - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - Stereographic: WKT2_name: PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Polar_Stereographic_Variant_A: WKT2_name: EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Equidistant_Conic: WKT2_name: EPSG_NAME_METHOD_EQUIDISTANT_CONIC Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN - False_Northing: EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Standard_Parallel_2: EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN - Cassini: WKT2_name: EPSG_NAME_METHOD_CASSINI_SOLDNER Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: 1.0 # fixed - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Van_der_Grinten_I: WKT2_name: PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Robinson: WKT2_name: PROJ_WKT2_NAME_METHOD_ROBINSON Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Two_Point_Equidistant: WKT2_name: PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Latitude_Of_1st_Point: "Latitude of 1st point" - Latitude_Of_2nd_Point: "Latitude of 2nd point" - Longitude_Of_1st_Point: "Longitude of 1st point" - Longitude_Of_2nd_Point: "Longitude of 2nd point" - Azimuthal_Equidistant: WKT2_name: EPSG_NAME_METHOD_AZIMUTHAL_EQUIDISTANT Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Lambert_Azimuthal_Equal_Area: WKT2_name: EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Cylindrical_Equal_Area: WKT2_name: EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Behrmann: WKT2_name: EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: Name: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN Default: 0.0 - Standard_Parallel_1: Name: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL Default: 30.0 Cond: - EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL = 30 # No example in pe_list_projection.csv: temptative mapping ! - Hotine_Oblique_Mercator_Two_Point_Center: WKT2_name: PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE - False_Northing: EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE - Latitude_Of_1st_Point: "Latitude of 1st point" - Latitude_Of_2nd_Point: "Latitude of 2nd point" - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Longitude_Of_1st_Point: "Longitude of 1st point" - Longitude_Of_2nd_Point: "Longitude of 2nd point" - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Hotine_Oblique_Mercator_Azimuth_Natural_Origin: WKT2_name: EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Azimuth: EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE # No EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - Hotine_Oblique_Mercator_Azimuth_Center: WKT2_name: EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE - False_Northing: EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Azimuth: EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE # No EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - Double_Stereographic: WKT2_name: EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Krovak: - WKT2_name: EPSG_NAME_METHOD_KROVAK Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Pseudo_Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL - Azimuth: EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - X_Scale: 1.0 - Y_Scale: 1.0 - XY_Plane_Rotation: 0.0 - WKT2_name: EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Pseudo_Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL - Azimuth: EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - X_Scale: -1.0 - Y_Scale: 1.0 - XY_Plane_Rotation: 90.0 - New_Zealand_Map_Grid: WKT2_name: EPSG_NAME_METHOD_NZMG Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Origin: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN # ESRI's Orthographic is a spherical-only formulation. The ellipsoidal capable # name is Local. See below - Orthographic: WKT2_name: PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Local: WKT2_name: EPSG_NAME_METHOD_LOCAL_ORTHOGRAPHIC Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE - False_Northing: EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Azimuth: EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - Winkel_Tripel: WKT2_name: "Winkel Tripel" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Aitoff: WKT2_name: "Aitoff" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Flat_Polar_Quartic: WKT2_name: PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Craster_Parabolic: WKT2_name: "Craster Parabolic" Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Gnomonic: WKT2_name: PROJ_WKT2_NAME_METHOD_GNOMONIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Times: WKT2_name: PROJ_WKT2_NAME_METHOD_TIMES Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Vertical_Near_Side_Perspective: WKT2_name: EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN - Height: EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT - Stereographic_North_Pole: WKT2_name: EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL Cond: - EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL > 0 - Stereographic_South_Pole: WKT2_name: EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL Cond: - EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL < 0 - Rectified_Skew_Orthomorphic_Natural_Origin: WKT2_name: EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Azimuth: EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - XY_Plane_Rotation: EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID # temptative mapping: no example - Rectified_Skew_Orthomorphic_Center: WKT2_name: EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B Params: - False_Easting: EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE - False_Northing: EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Azimuth: EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - XY_Plane_Rotation: EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID - Goode_Homolosine: - WKT2_name: PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Option: 0.0 - WKT2_name: PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Option: 1.0 - WKT2_name: PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Option: 2.0 - Equidistant_Cylindrical_Ellipsoidal: WKT2_name: EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Laborde_Oblique_Mercator: WKT2_name: EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE - Azimuth: EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE - Gnomonic_Ellipsoidal: WKT2_name: PROJ_WKT2_NAME_METHOD_GNOMONIC Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Wagner_IV: WKT2_name: PROJ_WKT2_NAME_METHOD_WAGNER_IV Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Origin: 0 - Wagner_V: WKT2_name: PROJ_WKT2_NAME_METHOD_WAGNER_V Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Wagner_VII: WKT2_name: PROJ_WKT2_NAME_METHOD_WAGNER_VII Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Natural_Earth: WKT2_name: PROJ_WKT2_NAME_METHOD_NATURAL_EARTH Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Natural_Earth_II: WKT2_name: PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Patterson: WKT2_name: PROJ_WKT2_NAME_METHOD_PATTERSON Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Compact_Miller: WKT2_name: PROJ_WKT2_NAME_METHOD_COMPACT_MILLER Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Geostationary_Satellite: WKT2_name: PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Height: "Satellite Height" - Option: 0.0 - Mercator_Auxiliary_Sphere: WKT2_name: EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Auxiliary_Sphere_Type: 0.0 - Mercator_Variant_A: WKT2_name: EPSG_NAME_METHOD_MERCATOR_VARIANT_A Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Mercator_Variant_C: WKT2_name: EPSG_NAME_METHOD_MERCATOR_VARIANT_B Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Standard_Parallel_1: EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL - Latitude_Of_Origin: 0 - Transverse_Cylindrical_Equal_Area: WKT2_name: Transverse Cylindrical Equal Area Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - IGAC_Plano_Cartesiano: WKT2_name: EPSG_NAME_METHOD_COLOMBIA_URBAN Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Longitude_Of_Center: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Latitude_Of_Center: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Height: EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT - Equal_Earth: WKT2_name: EPSG_NAME_METHOD_EQUAL_EARTH Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Peirce_Quincuncial: - WKT2_name: PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Option: 0.0 - WKT2_name: PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND Params: - False_Easting: EPSG_NAME_PARAMETER_FALSE_EASTING - False_Northing: EPSG_NAME_PARAMETER_FALSE_NORTHING - Central_Meridian: EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN - Scale_Factor: EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN - Latitude_Of_Origin: EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN - Option: 1.0 # Missing/unclear mappings # Hammer_Aitoff: possibly hammer? # Hammer_Ellipsoidal: possibly hammer? # Eckert_Greifendorff: +proj=hammer +W=0.25 +M=1 # Tobler_Cylindrical_I: likely tobmerc, but parameters TBD # Tobler_Cylindrical_II: likely tobmerc, but parameters TBD # Missing mappings # Transverse_Mercator_NGA_2014: utm -- tricky mapping from Central_Meridian to zone # Transverse Mercator: alias for Transverse_Mercator, as seen in ESRI:102470 - ESRI:102489 # The following methods are not currently possible in PROJ: # Ney_Modified_Conic # Fuller # Berghaus_Star # Cube # Robinson_ARC_INFO # Equidistant_Cylindrical_Auxiliary_Sphere # Aspect_Adaptive_Cylindrical # Mollweide_Auxiliary_Sphere # Eckert_VI_Auxiliary_Sphere # Eckert_IV_Auxiliary_Sphere # Stereographic_Auxiliary_Sphere # Van_der_Grinten_I_Auxiliary_Sphere # Azimuthal_Equidistant_Auxiliary_Sphere # Lambert_Azimuthal_Equal_Area_Auxiliary_Sphere # Orthographic_Auxiliary_Sphere # Gnomonic_Auxiliary_Sphere # Polar_Stereographic_Variant_B # Polar_Stereographic_Variant_C # Quartic_Authalic_Ellipsoidal # Adams_Square_II """ config = yaml.load(config_str) all_projs = [] def generate_mapping(WKT2_name, esri_proj_name, Params, suffix=''): c_name = 'paramsESRI_%s%s' % (esri_proj_name, suffix) if isinstance(WKT2_name, list): for WKT2_name_s in WKT2_name: all_projs.append([esri_proj_name, WKT2_name_s, c_name]) else: all_projs.append([esri_proj_name, WKT2_name, c_name]) qualifier = 'static ' if c_name in ('paramsESRI_Plate_Carree', 'paramsESRI_Equidistant_Cylindrical', 'paramsESRI_Gauss_Kruger', 'paramsESRI_Transverse_Mercator', 'paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin', 'paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin', 'paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center', 'paramsESRI_Rectified_Skew_Orthomorphic_Center'): qualifier = '' print(qualifier + 'const ESRIParamMapping %s[] = { ' % c_name) for param in Params: for param_name in param: param_value = param[param_name] default_value = None if isinstance(param_value, dict): default_value = param_value.get('Default', None) param_value = param_value['Name'] if isinstance(param_value, str): if param_value.startswith('EPSG_'): print(' { "%s", %s, %s, "%.1f", %s },' % (param_name, param_value, param_value.replace('_NAME_', '_CODE_'), default_value or 0.0, "true" if default_value is not None else "false")) else: print(' { "%s", "%s", 0, "%.1f", %s },' % (param_name, param_value, default_value or 0.0, "true" if default_value is not None else "false")) else: print(' { "%s", nullptr, 0, "%.1f", false },' % (param_name, param_value)) print(' { nullptr, nullptr, 0, "0.0", false }') print('};') print('// This file was generated by scripts/build_esri_projection_mapping.py. DO NOT EDIT !') print('') print(""" /****************************************************************************** * * Project: PROJ * Purpose: Mappings between ESRI projection and parameters names and WKT2 * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "esriparammappings.hpp" #include "proj_constants.h" #include "proj/internal/internal.hpp" NS_PROJ_START using namespace internal; namespace operation { //! @cond Doxygen_Suppress """) for item in config: for esri_proj_name in item: proj_config = item[esri_proj_name] if isinstance(proj_config, dict): WKT2_name = proj_config['WKT2_name'] Params = proj_config['Params'] generate_mapping(WKT2_name, esri_proj_name, Params) else: count = 1 for subconfig in proj_config: WKT2_name = subconfig['WKT2_name'] Params = subconfig['Params'] generate_mapping(WKT2_name, esri_proj_name, Params, suffix='_alt%d' % count) count += 1 print('') print('static const ESRIMethodMapping esriMappings[] = {') for esri_proj_name, WKT2_name, c_name in all_projs: if WKT2_name.startswith('EPSG_'): print(' { "%s", %s, %s, %s },' % (esri_proj_name, WKT2_name, WKT2_name.replace('_NAME_', '_CODE_'), c_name)) elif WKT2_name.startswith('PROJ_'): print(' { "%s", %s, 0, %s },' % (esri_proj_name, WKT2_name, c_name)) else: print(' { "%s", "%s", 0, %s },' % (esri_proj_name, WKT2_name, c_name)) print('};') print(""" // --------------------------------------------------------------------------- const ESRIMethodMapping *getEsriMappings(size_t &nElts) { nElts = sizeof(esriMappings) / sizeof(esriMappings[0]); return esriMappings; } // --------------------------------------------------------------------------- std::vector getMappingsFromESRI(const std::string &esri_name) { std::vector res; for (const auto &mapping : esriMappings) { if (ci_equal(esri_name, mapping.esri_name)) { res.push_back(&mapping); } } return res; } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END """) proj-9.8.1/scripts/install_bash_completions.cmake.in000664 001750 001750 00000000720 15166171715 022627 0ustar00eveneven000000 000000 set(PROGRAMS projinfo ) set(INSTALL_DIR "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@BASH_COMPLETIONS_DIR@") file(MAKE_DIRECTORY "${INSTALL_DIR}") foreach (program IN LISTS PROGRAMS) message(STATUS "Installing ${INSTALL_DIR}/${program}") configure_file("@CMAKE_CURRENT_SOURCE_DIR@/${program}-bash-completion.sh" "${INSTALL_DIR}/${program}" COPYONLY) file(APPEND "@PROJECT_BINARY_DIR@/install_manifest_extra.txt" "${INSTALL_DIR}/${program}\n") endforeach () proj-9.8.1/scripts/reformat_cpp.sh000775 001750 001750 00000002633 15166171715 017166 0ustar00eveneven000000 000000 #!/bin/sh set -eu SCRIPT_DIR=$(dirname "$0") case $SCRIPT_DIR in "/"*) ;; ".") SCRIPT_DIR=$(pwd) ;; *) SCRIPT_DIR=$(pwd)/$(dirname "$0") ;; esac TOPDIR="$(readlink -f $SCRIPT_DIR/..)" if ! (clang-format-15 --version 2>/dev/null); then echo "clang-format 15 not available. Running it from a Docker image"; docker pull silkeh/clang:15-bullseye UID=$(id -u "${USER}") GID=$(id -g "${USER}") exec docker run --rm -u "$UID:$GID" -v "$TOPDIR":"$TOPDIR" silkeh/clang:15-bullseye "$TOPDIR"/scripts/reformat_cpp.sh fi for i in `find "$TOPDIR" -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.c"`; do if echo "$i" | grep -q "lru_cache.hpp"; then continue fi if echo "$i" | grep -q "json.hpp"; then continue fi if echo "$i" | grep -q "generated"; then continue fi if echo "$i" | grep -q "geodesic"; then continue fi if echo "$i" | grep -q "geodtest"; then continue fi if echo "$i" | grep -q "geodsigntest"; then continue fi if echo "$i" | grep -q "nn.hpp"; then continue fi if echo "$i" | grep -q "googletest-src"; then continue fi if echo "$i" | grep -q "/build"; then continue fi if echo "$i" | grep -q "fix_typos"; then continue fi echo "reformat.sh $i" "$SCRIPT_DIR"/reformat.sh "$i" done proj-9.8.1/scripts/filter_lcov_info.py000775 001750 001750 00000001270 15166171715 020042 0ustar00eveneven000000 000000 #!/usr/bin/env python # Remove coverage from system include files in lcov output import sys import fnmatch extension_filter = None if len(sys.argv) == 2: extension_filter = sys.argv[1].split(',') output_lines = True for line in sys.stdin.readlines(): if line.startswith('SF:/usr/include'): output_lines = False elif line.startswith('SF:'): if extension_filter: output_lines = False for filter in extension_filter: if fnmatch.fnmatch(line.strip('\n'), filter): output_lines = True break else: output_lines = True if output_lines: sys.stdout.write(line) proj-9.8.1/scripts/generate_itrf2020.py000775 001750 001750 00000006543 15166171715 017651 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Generate ITRF2020 to ITRFxxxx Helmert transformations (geocentric # coordinates) from EPSG database. # Author: Even Rouault # ############################################################################### # Copyright (c) 2024, Even Rouault # # 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. ############################################################################### import subprocess def change_sign(param): tokens = param.split("=") if tokens[1] == "0": return None if tokens[1][0] == '-': return tokens[0] + "=" + tokens[1][1:] else: return tokens[0] + "=-" + tokens[1] def generate(source, dest): process = subprocess.Popen(f"bin/projinfo -s {source} -t {dest} --single-line -o PROJ -q", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, _ = process.communicate() steps = out.decode('ASCII').split('+step') assert len(steps) == 8 txt = steps[4] txt = txt[:-1] assert txt.startswith(" +inv +proj=helmert") txt = txt.replace(" +inv ", "") new_tokens = [] for token in txt.split(" "): if token.startswith("+x=") or token.startswith("+y=") or token.startswith("+z=") or \ token.startswith("+dx=") or token.startswith("+dy=") or token.startswith("+dz=") or \ token.startswith("+rx=") or token.startswith("+ry=") or token.startswith("+rz=") or \ token.startswith("+drx=") or token.startswith("+dry=") or token.startswith("+drz=") or \ token.startswith("+s=") or token.startswith("+ds="): new_token = change_sign(token) if new_token: new_tokens.append(new_token) elif token.startswith("+proj=") or token.startswith("+t_epoch=") or token.startswith("+convention="): new_tokens.append(token) else: assert False, (txt, token) print("<" + dest + "> " + (" ".join(new_tokens))) for dest in ("ITRF2014", "ITRF2008", "ITRF2005", "ITRF2000", "ITRF97", "ITRF96", "ITRF94", "ITRF93", "ITRF92", "ITRF91", "ITRF90", "ITRF89", "ITRF88"): generate("ITRF2020", dest) print("") proj-9.8.1/scripts/data/000775 001750 001750 00000000000 15166171735 015055 5ustar00eveneven000000 000000 proj-9.8.1/scripts/data/naifcodes_radii_m_wAsteroids_IAU2015.csv000664 001750 001750 00000021123 15166171715 024420 0ustar00eveneven000000 000000 Naif_id,Body,IAU2015_Mean,IAU2015_Semimajor,IAU2015_Axisb,IAU2015_Semiminor,rotation,origin_long_name,origin_lon_pos 10,Sun,695700000.00,695700000.00,695700000.00,695700000.00,Direct,, 199,Mercury,2439400.00,2440530.00,2440530.00,2438260.00,Direct,Hun Kal,20 W 299,Venus,6051800.00,6051800.00,6051800.00,6051800.00,Retrograde,Ariadne,0 399,Earth,6371008.40,6378136.60,6378136.60,6356751.90,Direct,Greenwich,0 301,Moon,1737400.00,1737400.00,1737400.00,1737400.00,Direct,, 499,Mars,3389500.00,3396190.00,3396190.00,3376200.00,Direct,Viking 1 lander,47.95137 W 401,Phobos,11080.00,13000.00,11400.00,9100.00,Direct,, 402,Deimos,6200.00,7800.00,6000.00,5100.00,Direct,, 599,Jupiter,69911000.00,71492000.00,71492000.00,66854000.00,Direct,, 501,Io,1821490.00,1829400.00,1819400.00,1815700.00,Direct,The mean sub-Jovian direction,0 502,Europa,1560800.00,1562600.00,1560300.00,1559500.00,Direct,Cilix,182 W 503,Ganymede,2631200.00,2631200.00,2631200.00,2631200.00,Direct,Anat,128 W 504,Callisto,2410300.00,2410300.00,2410300.00,2410300.00,Direct,Saga,326 W 505,Amalthea,83500.00,125000.00,73000.00,64000.00,Direct,, 506,Himalia,85000.00,85000.00,85000.00,85000.00,,, 507,Elara,40000.00,40000.00,40000.00,40000.00,,, 508,Pasiphae,18000.00,18000.00,18000.00,18000.00,,, 509,Sinope,14000.00,14000.00,14000.00,14000.00,,, 510,Lysithea,12000.00,12000.00,12000.00,12000.00,,, 511,Carme,15000.00,15000.00,15000.00,15000.00,,, 512,Ananke,10000.00,10000.00,10000.00,10000.00,,, 513,Leda,5000.00,5000.00,5000.00,5000.00,,, 514,Thebe,49300.00,58000.00,49000.00,42000.00,Direct,, 515,Adrastea,8200.00,10000.00,8000.00,7000.00,Direct,, 516,Metis,21500.00,30000.00,20000.00,17000.00,Direct,, 517,Callirrhoe,-1,-1,-1,-1,,, 518,Themisto,-1,-1,-1,-1,,, 519,Magaclite,-1,-1,-1,-1,,, 520,Taygete,-1,-1,-1,-1,,, 521,Chaldene,-1,-1,-1,-1,,, 522,Harpalyke,-1,-1,-1,-1,,, 523,Kalyke,-1,-1,-1,-1,,, 524,Iocaste,-1,-1,-1,-1,,, 525,Erinome,-1,-1,-1,-1,,, 526,Isonoe,-1,-1,-1,-1,,, 527,Praxidike,-1,-1,-1,-1,,, 528,Autonoe,-1,-1,-1,-1,,, 529,Thyone,-1,-1,-1,-1,,, 530,Hermippe,-1,-1,-1,-1,,, 531,Aitne,-1,-1,-1,-1,,, 532,Eurydome,-1,-1,-1,-1,,, 533,Euanthe,-1,-1,-1,-1,,, 534,Euporie,-1,-1,-1,-1,,, 535,Orthosie,-1,-1,-1,-1,,, 536,Sponde,-1,-1,-1,-1,,, 537,Kale,-1,-1,-1,-1,,, 538,Pasithee,-1,-1,-1,-1,,, 539,Hegemone,-1,-1,-1,-1,,, 540,Mneme,-1,-1,-1,-1,,, 541,Aoede,-1,-1,-1,-1,,, 542,Thelxinoe,-1,-1,-1,-1,,, 543,Arche,-1,-1,-1,-1,,, 544,Kallichore,-1,-1,-1,-1,,, 545,Helike,-1,-1,-1,-1,,, 546,Carpo,-1,-1,-1,-1,,, 547,Eukelade,-1,-1,-1,-1,,, 548,Cyllene,-1,-1,-1,-1,,, 549,Kore,-1,-1,-1,-1,,, 550,Herse,-1,-1,-1,-1,,, 699,Saturn,58232000.00,60268000.00,60268000.00,54364000.00,Direct,, 601,Mimas,198200.00,207800.00,196700.00,190600.00,Direct,Palomides,162 W 602,Enceladus,252100.00,256600.00,251400.00,248300.00,Direct,Salih,5 W 603,Tethys,531000.00,538400.00,528300.00,526300.00,Direct,Arete,299 W 604,Dione,561400.00,563400.00,561300.00,559600.00,Direct,Palinurus,63 W 605,Rhea,763500.00,765000.00,763100.00,762400.00,Direct,Tore,340 W 606,Titan,2575000.00,2575150.00,2574780.00,2574470.00,Direct,, 607,Hyperion,135000.00,180100.00,133000.00,102700.00,,, 608,Iapetus,734300.00,745700.00,745700.00,712100.00,Direct,Almeric,276 W 609,Phoebe,106500.00,109400.00,108500.00,101800.00,Direct,, 610,Janus,89200.00,101700.00,93000.00,76300.00,Direct,, 611,Epimetheus,58200.00,64900.00,57300.00,53000.00,Direct,, 612,Helene,18000.00,22500.00,19600.00,13300.00,Direct,, 613,Telesto,12400.00,16300.00,11800.00,9800.00,Direct,, 614,Calypso,9600.00,15300.00,9300.00,6300.00,Direct,, 615,Atlas,15100.00,20500.00,17800.00,9400.00,Direct,, 616,Prometheus,43100.00,68200.00,41600.00,28200.00,Direct,, 617,Pandora,40600.00,52200.00,40800.00,31500.00,Direct,, 618,Pan,14000.00,17200.00,15400.00,10400.00,Direct,, 619,Ymir,-1,-1,-1,-1,,, 620,Paaliaq,-1,-1,-1,-1,,, 621,Tarvos,-1,-1,-1,-1,,, 622,Ijiraq,-1,-1,-1,-1,,, 623,Suttungr,-1,-1,-1,-1,,, 624,Kiviuq,-1,-1,-1,-1,,, 625,Mundilfari,-1,-1,-1,-1,,, 626,Albiorix,-1,-1,-1,-1,,, 627,Skathi,-1,-1,-1,-1,,, 628,Erriapo,-1,-1,-1,-1,,, 629,Siarnaq,-1,-1,-1,-1,,, 630,Thrymr,-1,-1,-1,-1,,, 631,Narvi,-1,-1,-1,-1,,, 632,Methone,1450.00,1940.00,1290.00,1210.00,,, 633,Pallene,2230.00,2880.00,2080.00,1800.00,,, 634,Polydeuces,1300.00,1500.00,1200.00,1000.00,,, 635,Daphnis,3800.00,4600.00,4500.00,2800.00,,, 636,Aegir,-1,-1,-1,-1,,, 637,Bebhionn,-1,-1,-1,-1,,, 638,Bergelmir,-1,-1,-1,-1,,, 639,Bestla,-1,-1,-1,-1,,, 640,Farbauti,-1,-1,-1,-1,,, 641,Fenrir,-1,-1,-1,-1,,, 642,Fornjot,-1,-1,-1,-1,,, 643,Hati,-1,-1,-1,-1,,, 644,Hyrrokkin,-1,-1,-1,-1,,, 645,Kari,-1,-1,-1,-1,,, 646,Loge,-1,-1,-1,-1,,, 647,Skoll,-1,-1,-1,-1,,, 648,Sutur,-1,-1,-1,-1,,, 649,Anthe,500.00,500.00,500.00,500.00,,, 650,Jarnsaxa,-1,-1,-1,-1,,, 651,Greip,-1,-1,-1,-1,,, 652,Tarqeq,-1,-1,-1,-1,,, 653,Aegaeon,330.00,700.00,250.00,200.00,,, 799,Uranus,25362000.00,25559000.00,25559000.00,24973000.00,Retrograde,, 701,Ariel,578900.00,581100.00,577900.00,577700.00,Retrograde,, 702,Umbriel,584700.00,584700.00,584700.00,584700.00,Retrograde,, 703,Titania,788900.00,788900.00,788900.00,788900.00,Retrograde,, 704,Oberon,761400.00,761400.00,761400.00,761400.00,Retrograde,, 705,Miranda,235800.00,240400.00,234200.00,232900.00,Retrograde,, 706,Cordelia,13000.00,13000.00,13000.00,13000.00,Retrograde,, 707,Ophelia,15000.00,15000.00,15000.00,15000.00,Retrograde,, 708,Bianca,21000.00,21000.00,21000.00,21000.00,Retrograde,, 709,Cressida,31000.00,31000.00,31000.00,31000.00,Retrograde,, 710,Desdemona,27000.00,27000.00,27000.00,27000.00,Retrograde,, 711,Juliet,42000.00,42000.00,42000.00,42000.00,Retrograde,, 712,Portia,54000.00,54000.00,54000.00,54000.00,Retrograde,, 713,Rosalind,27000.00,27000.00,27000.00,27000.00,Retrograde,, 714,Belinda,33000.00,33000.00,33000.00,33000.00,Retrograde,, 715,Puck,77000.00,77000.00,77000.00,77000.00,Retrograde,, 716,Caliban,-1,-1,-1,-1,,, 717,Sycorax,-1,-1,-1,-1,,, 718,Prospero,-1,-1,-1,-1,,, 719,Setebos,-1,-1,-1,-1,,, 720,Stephano,-1,-1,-1,-1,,, 721,Trinculo,-1,-1,-1,-1,,, 722,Francisco,-1,-1,-1,-1,,, 723,Margaret,-1,-1,-1,-1,,, 724,Ferdinand,-1,-1,-1,-1,,, 725,Perdita,-1,-1,-1,-1,,, 726,Mab,-1,-1,-1,-1,,, 727,Cupid,-1,-1,-1,-1,,, 899,Neptune,24622000.00,24764000.00,24764000.00,24341000.00,Direct,, 801,Triton,1352600.00,1352600.00,1352600.00,1352600.00,Retrograde,, 802,Nereid,170000.00,170000.00,170000.00,170000.00,,, 803,Naiad,29000.00,29000.00,29000.00,29000.00,Direct,, 804,Thalassa,40000.00,40000.00,40000.00,40000.00,Direct,, 805,Despina,74000.00,74000.00,74000.00,74000.00,Direct,, 806,Galatea,79000.00,79000.00,79000.00,79000.00,Direct,, 807,Larissa,96000.00,96000.00,96000.00,89000.00,Direct,, 808,Proteus,208000.00,218000.00,208000.00,201000.00,Direct,, 809,Halimede,-1,-1,-1,-1,,, 810,Psamathe,-1,-1,-1,-1,,, 811,Sao,-1,-1,-1,-1,,, 812,Laomedeia,-1,-1,-1,-1,,, 813,Neso,-1,-1,-1,-1,,, 999,Pluto,1188300.00,1188300.00,1188300.00,1188300.00,Direct,Mean sub-Charon meridian,0 901,Charon,606000.00,606000.00,606000.00,606000.00,Direct,Mean sub-Pluto meridian,0 1000005,Borrelly,4220.00,3500.00,-1,-1,Direct,, 1000012,Churyumov-Gerasimenko,1650.00,2400.00,1550.00,1200.00,Direct,a large boulder called Cheops,0 1000036,Halley,-1.00,8000.00,4000.00,4000.00,,, 1000041,Hartley 2,580.00,340.00,1160.00,1160.00,,An isolated large mount on the waist near the large lobe,0 1000093,Tempel 1,3000.00,3700.00,2500.00,-1,Direct,A 350 m diameter unnamed circular feature near the Deep Impactor impact site,0 1000107,Wild 2,1975.00,2700.00,1900.00,1500.00,,, 9511010,Gaspra,6100.00,9100.00,5200.00,4400.00,Direct,Charax,0 2431010,Ida,15650.00,26800.00,12000.00,7600.00,Direct,Afon,0 2431011,Dactyl,-1,-1,-1,-1,,, 2000001,Ceres,470000.00,487300.00,487300.00,446000.00,Direct,Kait,0 2000002,Pallas,-1,-1,-1,-1,Direct,The direction (positive x) of the long axis of the Carry et al. (2010) shape model,0 2000004,Vesta,255000.00,289000.00,280000.00,229000.00,Direct,Claudia,146 2000016,Psyche,113000.00,139500.00,116000.0,94500.00,,, 2000021,Lutetia,52500.00,62000.00,50500.00,46500.00,Direct,Arbitrarily defined based on light curve information,0 2000052,52 Europa,157500.00,189500.00,165000.00,124500.00,Direct,long axis that pointed toward the Earth on 2007 May28 8.3125 UT (light-time corrected),0 2000216,Kleopatra,-1,108500.00,47000.00,40500.00,,, 2000433,Eros,8450.00,17000.00,5500.00,5500.00,Direct,unnamed crater,0 2000511,Davida,150000.00,180000.00,147000.00,127000.00,Direct,the long axis that points toward the Earth on 2002 December27 7.83 UT,0 2000253,Mathilde,26500.00,33000.00,24000.00,23000.00,,, 2002867,Steins,2700.00,3240.00,2730.00,2040.00,Direct,Topaz,0 2009969,1992KD,-1,-1,-1,-1,,, 2009969,Braille,-1,-1,-1,-1,,, 2004015,Wilson-Harrington,-1,-1,-1,-1,,, 2004179,Toutatis,-1,2130.00,1015.00,850.00,,, 2025143,Itokawa,-1,268.00,147.00,104.00,Direct,defined with W0=0,0 proj-9.8.1/scripts/releasenotes.py000664 001750 001750 00000006755 15166171715 017222 0ustar00eveneven000000 000000 """ Create a first draft of release notes for a new version of PROJ The script outputs a section for the NEWS.md file. It's based on the title of entries added to the specified GitHub milestone. The script tries to group the entries into "updates" and "bug fixes" although it might not always be succesful. The output of the scripts should be edited before adding to NEWS.md. Less editing is needed if PR's are given good titles when originally created. The script requires a token from GitHub. These can be generated under the "Settings" section in your GitHub profile. Go to "Developer Settings" and then "Personal access tokens". The token does not need any special permissions. Usage: python releasenotes.py """ import argparse import requests # Base API URL to fetch issues associated with a milestone REPO = "OSGeo/PROJ" STATE = "closed" BASE_URL = "https://api.github.com/repos/OSGeo/PROJ/issues" def get_milestone_number(repo, milestone_title, headers): """Fetch the milestone number by title.""" url = f"https://api.github.com/repos/{repo}/milestones" response = requests.get(url, headers=headers) milestones = response.json() for milestone in milestones: if milestone["title"] == milestone_title: return milestone["number"] return None def fetch_issues(url, params, headers): """Fetch all issues for a milestone with pagination.""" issues = [] while url: response = requests.get(url, headers=headers, params=params) response_data = response.json() issues.extend(response_data) # Check if there is a next page if "next" in response.links: url = response.links["next"]["url"] else: url = None return issues def is_merged(pr_url, headers): """Check if a pull request is merged.""" pr_response = requests.get(pr_url, headers=headers) pr_data = pr_response.json() return pr_data.get("merged_at") is not None def main(milestone_title, token): headers = { "Authorization": f"token {token}" } milestone_number = get_milestone_number(REPO, milestone_title, headers) if milestone_number is None: print(f"Milestone '{milestone_title}' not found.") return params = { "milestone": milestone_number, "state": STATE, "per_page": 100 # Fetch 100 results per page } issues = fetch_issues(BASE_URL, params, headers) pull_requests = [issue for issue in issues if "pull_request" in issue] new_features = [] bug_fixes = [] for pr in pull_requests: pr_url = pr["pull_request"]["url"] if is_merged(pr_url, headers): title = pr["title"] if "fix" in title.lower() or "bug" in title.lower(): bug_fixes.append((pr["number"], title)) else: new_features.append((pr["number"], title)) print(f"## {milestone_title}\n") print("### Updates\n") for pr in reversed(new_features): print(f"* {pr[1]} (#{pr[0]})\n") print("\n### Bug Fixes\n") for pr in reversed(bug_fixes): print(f"* {pr[1]} (#{pr[0]})\n") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate release notes for a specific milestone.") parser.add_argument("milestone", type=str, help="The title of the milestone") parser.add_argument("token", type=str, help="Your personal access token") args = parser.parse_args() main(args.milestone, args.token) proj-9.8.1/scripts/create_c_api_projections.py000775 001750 001750 00000020213 15166171715 021532 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Parse XML output of Doxygen on coordinateoperation.hpp to creat # C API for projections. # Author: Even Rouault # ############################################################################### # Copyright (c) 2018, Even Rouault # # 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. ############################################################################### from lxml import etree import os script_dir_name = os.path.dirname(os.path.realpath(__file__)) # Make sure to run doxygen if not 'SKIP_DOXYGEN' in os.environ: os.system("bash " + os.path.join(script_dir_name, "doxygen.sh")) xmlfilename = os.path.join(os.path.dirname(script_dir_name), 'docs/build/xml/classosgeo_1_1proj_1_1operation_1_1Conversion.xml') tree = etree.parse(open(xmlfilename, 'rt')) root = tree.getroot() compounddef = root.find('compounddef') header = open('projections.h', 'wt') cppfile = open('projections.cpp', 'wt') test_cppfile = open('test_projections.cpp', 'wt') header.write("/* BEGIN: Generated by scripts/create_c_api_projections.py*/\n") cppfile.write("/* BEGIN: Generated by scripts/create_c_api_projections.py*/\n") cppfile.write("\n"); test_cppfile.write("/* BEGIN: Generated by scripts/create_c_api_projections.py*/\n") def snake_casify(s): out = '' lastWasLowerAlpha = False for c in s: if c.isupper(): if lastWasLowerAlpha: out += '_' out += c.lower() lastWasLowerAlpha = False else: out += c lastWasLowerAlpha = c.isalpha() return out for sectiondef in compounddef.iter('sectiondef'): if sectiondef.attrib['kind'] == 'public-static-func': for func in sectiondef.iter('memberdef'): name = func.find('name').text assert name.startswith('create') if name in ('create', 'createChangeVerticalUnit', 'createAxisOrderReversal', 'createGeographicGeocentric'): continue params = [] has_angle = False has_linear = False for param in func.iter('param'): type = param.find('type').xpath("normalize-space()") if type.find('Angle') >= 0: has_angle = True if type.find('Length') >= 0: has_linear = True paramname = param.find('declname').text if paramname == 'properties': continue params.append((type, snake_casify(paramname))) shortName = name[len('create'):] c_shortName = snake_casify(shortName) decl = "proj_create_conversion_" decl += c_shortName decl += "(\n" decl += " PJ_CONTEXT *ctx,\n" has_output_params = False for param in params: if has_output_params: decl += ",\n" if param[0] in ('int', 'bool'): decl += " int " + param[1] else: decl += " double " + param[1] has_output_params = True if has_angle: if has_output_params: decl += ",\n" decl += " const char* ang_unit_name, double ang_unit_conv_factor" has_output_params = True if has_linear: if has_output_params: decl += ",\n" decl += " const char* linear_unit_name, double linear_unit_conv_factor" decl += ")" header.write("PJ PROJ_DLL *" + decl + ";\n\n") briefdescription = func.find('briefdescription/para').xpath("normalize-space()") briefdescription = briefdescription.replace("Instantiate ", "Instantiate a ProjectedCRS with ") cppfile.write("// ---------------------------------------------------------------------------\n\n") cppfile.write("/** \\brief " + briefdescription + "\n") cppfile.write(" *\n") cppfile.write(" * See osgeo::proj::operation::Conversion::create" + shortName + "().\n") cppfile.write(" *\n") cppfile.write(" * Linear parameters are expressed in (linear_unit_name, linear_unit_conv_factor).\n") if has_angle: cppfile.write(" * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).\n") cppfile.write(" */\n") cppfile.write("PJ* " + decl + "{\n"); cppfile.write(" SANITIZE_CTX(ctx);\n"); cppfile.write(" try {\n"); if has_linear: cppfile.write(" UnitOfMeasure linearUnit(createLinearUnit(linear_unit_name, linear_unit_conv_factor));\n") if has_angle: cppfile.write(" UnitOfMeasure angUnit(createAngularUnit(ang_unit_name, ang_unit_conv_factor));\n") cppfile.write(" auto conv = Conversion::create" + shortName + "(PropertyMap()") for param in params: if param[0] in 'int': cppfile.write(", " + param[1]) elif param[0] in 'bool': cppfile.write(", " + param[1] + " != 0") elif param[0].find('Angle') >= 0: cppfile.write(", Angle(" + param[1] + ", angUnit)") elif param[0].find('Length') >= 0: cppfile.write(", Length(" + param[1] + ", linearUnit)") elif param[0].find('Scale') >= 0: cppfile.write(", Scale(" + param[1] + ")") cppfile.write(");\n") cppfile.write(" return proj_create_conversion(ctx, conv);\n") cppfile.write(" } catch (const std::exception &e) {\n"); cppfile.write(" proj_log_error(ctx, __FUNCTION__, e.what());\n") cppfile.write(" }\n") cppfile.write(" return nullptr;\n") cppfile.write("}\n") test_cppfile.write("{\n") test_cppfile.write(" auto projCRS = proj_create_conversion_" + c_shortName + "(\n") test_cppfile.write(" m_ctxt") if c_shortName == 'utm': test_cppfile.write(", 1") else: for param in params: test_cppfile.write(", 0") if has_angle: test_cppfile.write(", \"Degree\", 0.0174532925199433") if has_linear: test_cppfile.write(", \"Metre\", 1.0") test_cppfile.write(");\n") test_cppfile.write(" ObjectKeeper keeper_projCRS(projCRS);\n") test_cppfile.write(" ASSERT_NE(projCRS, nullptr);\n") test_cppfile.write("}\n") header.write("/* END: Generated by scripts/create_c_api_projections.py*/\n") cppfile.write("/* END: Generated by scripts/create_c_api_projections.py*/\n") test_cppfile.write("/* END: Generated by scripts/create_c_api_projections.py*/\n") print('projections.h and .cpp, and test_projections.cpp have been generated. Manually merge them now') proj-9.8.1/scripts/build_grid_alternatives_generated_noaa.py000775 001750 001750 00000020235 15166171715 024422 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Populate grid_alternatives with NAD83 -> NAD83(HPGN/HARN) grids # Author: Even Rouault # ############################################################################### # Copyright (c) 2018, Even Rouault # # 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. ############################################################################### import os nadcon_grids = ['conus', 'alaska', 'hawaii', 'prvi', 'stgeorge', 'stlrnc', 'stpaul'] hpgn_grids = [ 'alhpgn.gsb', 'arhpgn.gsb', 'azhpgn.gsb', #'c1hpgn.gsb', -- Not used in EPSG #'c2hpgn.gsb', -- Not used in EPSG 'cnhpgn.gsb', 'cohpgn.gsb', 'cshpgn.gsb', 'emhpgn.gsb', 'eshpgn.gsb', 'ethpgn.gsb', ('flhpgn.gsb', 'FL'), 'gahpgn.gsb', 'guhpgn.gsb', 'hihpgn.gsb', 'iahpgn.gsb', 'ilhpgn.gsb', 'inhpgn.gsb', 'kshpgn.gsb', 'kyhpgn.gsb', 'lahpgn.gsb', ('mdhpgn.gsb', 'MD'), 'mehpgn.gsb', 'mihpgn.gsb', 'mnhpgn.gsb', 'mohpgn.gsb', 'mshpgn.gsb', 'nbhpgn.gsb', 'nchpgn.gsb', 'ndhpgn.gsb', 'nehpgn.gsb', 'njhpgn.gsb', 'nmhpgn.gsb', 'nvhpgn.gsb', 'nyhpgn.gsb', 'ohhpgn.gsb', 'okhpgn.gsb', 'pahpgn.gsb', 'pvhpgn.gsb', 'schpgn.gsb', 'sdhpgn.gsb', ('tnhpgn.gsb', 'TN'), 'uthpgn.gsb', 'vahpgn.gsb', ('wihpgn.gsb', 'WI'), 'wmhpgn.gsb', ('wohpgn.gsb', 'WO'), 'wshpgn.gsb', 'wthpgn.gsb', 'wvhpgn.gsb', 'wyhpgn.gsb', ] script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') out_filename = os.path.join(sql_dir_name, 'grid_alternatives_generated_noaa') + '.sql' #print(out_filename) f = open(out_filename, 'wb') f.write("--- This file has been generated by scripts/build_grid_alternatives_generated_noaa.py. DO NOT EDIT !\n\n".encode('UTF-8')) f.write("-- NADCON (NAD27 -> NAD83) entries\n\n".encode('UTF-8')) for grid in nadcon_grids: tiff_name = 'us_noaa_' + grid + '.tif' sql = """INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('%s', '%s', '%s', 'GTiff', 'hgridshift', 0, NULL, '%s', 1, 1, NULL);""" % (grid + '.las', tiff_name, grid, 'https://cdn.proj.org/' + tiff_name) f.write((sql + '\n').encode('UTF-8')) f.write("-- NAD83 -> NAD83(HPGN) entries\n\n".encode('UTF-8')) for row in hpgn_grids: try: ntv2_name, ctable2_name = row except: ntv2_name = row ctable2_name = None las_filename = ntv2_name[0:-4] + ".las" if ctable2_name: tiff_name = 'us_noaa_' + ctable2_name+'.tif' sql = """INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('%s', '%s', '%s', 'GTiff', 'hgridshift', 0, NULL, '%s', 1, 1, NULL);""" % (las_filename, tiff_name, ctable2_name, 'https://cdn.proj.org/' + tiff_name) else: tiff_name = 'us_noaa_' + ntv2_name[0:-4]+'.tif' sql = """INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('%s', '%s', '%s', 'GTiff', 'hgridshift', 0, NULL, '%s', 1, 1, NULL);""" % (las_filename, tiff_name, ntv2_name, 'https://cdn.proj.org/' + tiff_name) f.write((sql + '\n').encode('UTF-8')) f.write("-- NADCON5 entries\n\n".encode('UTF-8')) nadcon5 = ["as62.nad83_1993.as", "gu63.nad83_1993.guamcnmi", "nad27.nad83_1986.alaska", "nad27.nad83_1986.conus", "nad83_1986.nad83_1992.alaska", "nad83_1986.nad83_1993.hawaii", "nad83_1986.nad83_1993.prvi", "nad83_1986.nad83_harn.conus", "nad83_1992.nad83_2007.alaska", "nad83_1993.nad83_1997.prvi", "nad83_1993.nad83_2002.as", "nad83_1993.nad83_2002.guamcnmi", "nad83_1993.nad83_pa11.hawaii", "nad83_1997.nad83_2002.prvi", "nad83_2002.nad83_2007.prvi", "nad83_2002.nad83_ma11.guamcnmi", "nad83_2002.nad83_pa11.as", "nad83_2007.nad83_2011.alaska", "nad83_2007.nad83_2011.conus", "nad83_2007.nad83_2011.prvi", "nad83_fbn.nad83_2007.conus", "nad83_harn.nad83_fbn.conus", "ohd.nad83_1986.hawaii", "pr40.nad83_1986.prvi", #"sg1897.sg1952.stgeorge", "sg1952.nad83_1986.stgeorge", "sl1952.nad83_1986.stlawrence", #"sp1897.sp1952.stpaul", "sp1952.nad83_1986.stpaul", #"ussd.nad27.conus", ] for subdir in nadcon5: tiff_name = 'us_noaa_nadcon5_' + subdir.replace('.', '_') + '.tif' lat_filename = 'nadcon5.' + subdir + '.lat.trn.20160901.b' sql = """INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('%s', '%s', NULL, 'GTiff', 'gridshift', 0, NULL, '%s', 1, 1, NULL);""" % (lat_filename, tiff_name, 'https://cdn.proj.org/' + tiff_name) f.write((sql + '\n').encode('UTF-8')) f.close() proj-9.8.1/scripts/cppcheck.sh000775 001750 001750 00000023470 15166171715 016267 0ustar00eveneven000000 000000 #!/bin/bash echo `cppcheck --version` LOG_FILE=/tmp/cppcheck_proj.txt SCRIPT_DIR=$(dirname "$0") case $SCRIPT_DIR in "/"*) ;; ".") SCRIPT_DIR=$(pwd) ;; *) SCRIPT_DIR=$(pwd)/$(dirname "$0") ;; esac TOPDIR="$SCRIPT_DIR/.." CPPCHECK_VERSION="$(cppcheck --version | awk '{print $2}')" CPPCHECK_MAJOR_VERSION=$(echo "$CPPCHECK_VERSION" | cut -d. -f1) CPPCHECK_MINOR_VERSION=$(echo "$CPPCHECK_VERSION" | cut -d. -f2) CPPCHECK_VERSION=$(("$CPPCHECK_MAJOR_VERSION" * 100 + "$CPPCHECK_MINOR_VERSION")) CPPCHECK_VERSION_GT_2_7=$(expr "$CPPCHECK_VERSION" \>= 207 || /bin/true) if test "$CPPCHECK_VERSION_GT_2_7" = 1; then POSIX="--library=posix" else POSIX="--std=posix" fi echo "" > ${LOG_FILE} for dirname in ${TOPDIR}/src; do echo "Running cppcheck on $dirname... (can be long)" if ! cppcheck --inline-suppr --template='{file}:{line},{severity},{id},{message}' \ --enable=all --inconclusive "$POSIX" \ -DCPPCHECK -D__cplusplus=201103L -DNAN \ -I${TOPDIR}/src -I${TOPDIR}/include \ "$dirname" \ -j 8 >>${LOG_FILE} 2>&1 ; then echo "cppcheck failed" exit 1 fi done ret_code=0 grep -v "unmatchedSuppression" ${LOG_FILE} \ | grep -v "nn.hpp" \ | grep -v "nlohmann/json.hpp" \ | grep -v "wkt1_generated_parser" \ | grep -v "wkt2_generated_parser" \ | grep -v "passedByValue,Function parameter 'coo' should be passed by const reference" \ | grep -v "passedByValue,Function parameter 'lpz' should be passed by const reference" \ | grep -v "passedByValue,Function parameter 'xyz' should be passed by const reference" \ | grep -v "passedByValue,Function parameter 'in' should be passed by const reference" \ | grep -v "knownConditionTrueFalse,Condition '!allowEmptyIntersection' is always false" \ | grep -v -e "unitconvert.*unreadVariable,Variable 'point.*' is assigned a value that is never used" \ | grep -v -e "helmert.*unreadVariable,Variable 'point.*' is assigned a value that is never used" \ | grep -v -e "molodensky.*unreadVariable,Variable 'point.*' is assigned a value that is never used" \ | grep -v -e "vgridshift.*unreadVariable,Variable 'point.*' is assigned a value that is never used" \ | grep -v -e "defines member function with name.*also defined in its parent" \ | grep -v "passedByValueCallback,Function parameter 'lpz' should be passed by const reference" \ | grep -v "passedByValueCallback,Function parameter 'xyz' should be passed by const reference" \ | grep -v "passedByValueCallback,Function parameter 'geod' should be passed by const reference" \ | grep -v "passedByValueCallback,Function parameter 'cart' should be passed by const reference" \ | grep -v "passedByValueCallback,Function parameter 'in' should be passed by const reference" \ > ${LOG_FILE}.tmp mv ${LOG_FILE}.tmp ${LOG_FILE} if grep "null pointer" ${LOG_FILE} ; then echo "Null pointer check failed" ret_code=1 fi if grep "duplicateBreak" ${LOG_FILE} ; then echo "duplicateBreak check failed" ret_code=1 fi if grep "duplicateBranch" ${LOG_FILE} ; then echo "duplicateBranch check failed" ret_code=1 fi if grep "uninitMemberVar" ${LOG_FILE} ; then echo "uninitMemberVar check failed" ret_code=1 fi if grep "useInitializationList" ${LOG_FILE} ; then echo "uninitMemberVar check failed" ret_code=1 fi if grep "clarifyCalculation" ${LOG_FILE} ; then echo "clarifyCalculation check failed" ret_code=1 fi if grep "invalidPrintfArgType_uint" ${LOG_FILE} ; then echo "invalidPrintfArgType_uint check failed" ret_code=1 fi if grep "catchExceptionByValue" ${LOG_FILE} ; then echo "catchExceptionByValue check failed" ret_code=1 fi if grep "memleakOnRealloc" ${LOG_FILE} ; then echo "memleakOnRealloc check failed" ret_code=1 fi if grep "arrayIndexOutOfBoundsCond" ${LOG_FILE} ; then echo "arrayIndexOutOfBoundsCond check failed" ret_code=1 fi if grep "arrayIndexOutOfBounds," ${LOG_FILE} ; then echo "arrayIndexOutOfBounds check failed" ret_code=1 fi if grep "syntaxError" ${LOG_FILE} | grep -v "is invalid C code" ; then echo "syntaxError check failed" ret_code=1 fi if grep "memleak," ${LOG_FILE} ; then echo "memleak check failed" ret_code=1 fi if grep "eraseDereference" ${LOG_FILE} ; then echo "eraseDereference check failed" ret_code=1 fi if grep "memsetClass," ${LOG_FILE} ; then echo "memsetClass check failed" ret_code=1 fi if grep "uninitvar," ${LOG_FILE} ; then echo "uninitvar check failed" ret_code=1 fi if grep "uninitdata," ${LOG_FILE} ; then echo "uninitdata check failed" ret_code=1 fi if grep "va_list_usedBeforeStarted" ${LOG_FILE} ; then echo "va_list_usedBeforeStarted check failed" ret_code=1 fi if grep "duplInheritedMember" ${LOG_FILE} ; then echo "duplInheritedMember check failed" ret_code=1 fi if grep "terminateStrncpy" ${LOG_FILE} ; then echo "terminateStrncpy check failed" ret_code=1 fi if grep "operatorEqVarError" ${LOG_FILE} ; then echo "operatorEqVarError check failed" ret_code=1 fi if grep "uselessAssignmentPtrArg" ${LOG_FILE} ; then echo "uselessAssignmentPtrArg check failed" ret_code=1 fi if grep "bufferNotZeroTerminated" ${LOG_FILE} ; then echo "bufferNotZeroTerminated check failed" ret_code=1 fi if grep "sizeofDivisionMemfunc" ${LOG_FILE} ; then echo "sizeofDivisionMemfunc check failed" ret_code=1 fi if grep "selfAssignment" ${LOG_FILE} ; then echo "selfAssignment check failed" ret_code=1 fi if grep "invalidPrintfArgType_sint" ${LOG_FILE} ; then echo "invalidPrintfArgType_sint check failed" ret_code=1 fi if grep "redundantAssignInSwitch" ${LOG_FILE} ; then echo "redundantAssignInSwitch check failed" ret_code=1 fi if grep "publicAllocationError" ${LOG_FILE} ; then echo "publicAllocationError check failed" ret_code=1 fi if grep "invalidScanfArgType_int" ${LOG_FILE} ; then echo "invalidScanfArgType_int check failed" ret_code=1 fi if grep "invalidscanf," ${LOG_FILE} ; then echo "invalidscanf check failed" ret_code=1 fi if grep "moduloAlwaysTrueFalse" ${LOG_FILE} ; then echo "moduloAlwaysTrueFalse check failed" ret_code=1 fi if grep "charLiteralWithCharPtrCompare" ${LOG_FILE} ; then echo "charLiteralWithCharPtrCompare check failed" ret_code=1 fi if grep "noConstructor" ${LOG_FILE} ; then echo "noConstructor check failed" ret_code=1 fi if grep "noExplicitConstructor" ${LOG_FILE} ; then echo "noExplicitConstructor check failed" ret_code=1 fi if grep "noCopyConstructor" ${LOG_FILE} ; then echo "noCopyConstructor check failed" ret_code=1 fi if grep "passedByValue" ${LOG_FILE} ; then echo "passedByValue check failed" ret_code=1 fi if grep "postfixOperator" ${LOG_FILE} ; then echo "postfixOperator check failed" ret_code=1 fi if grep "redundantCopy" ${LOG_FILE} ; then echo "redundantCopy check failed" ret_code=1 fi if grep "stlIfStrFind" ${LOG_FILE} ; then echo "stlIfStrFind check failed" ret_code=1 fi if grep "functionStatic" ${LOG_FILE} ; then echo "functionStatic check failed" ret_code=1 fi if grep "knownConditionTrueFalse" ${LOG_FILE} ; then echo "knownConditionTrueFalse check failed" ret_code=1 fi if grep "arrayIndexThenCheck" ${LOG_FILE} ; then echo "arrayIndexThenCheck check failed" ret_code=1 fi if grep "unusedPrivateFunction" ${LOG_FILE} ; then echo "unusedPrivateFunction check failed" ret_code=1 fi if grep "redundantCondition" ${LOG_FILE} ; then echo "redundantCondition check failed" ret_code=1 fi if grep "unusedStructMember" ${LOG_FILE} ; then echo "unusedStructMember check failed" ret_code=1 fi if grep "multiCondition" ${LOG_FILE} ; then echo "multiCondition check failed" ret_code=1 fi if grep "duplicateExpression" ${LOG_FILE} ; then echo "duplicateExpression check failed" ret_code=1 fi if grep "operatorEq" ${LOG_FILE} ; then echo "operatorEq check failed" ret_code=1 fi if grep "truncLongCastAssignment" ${LOG_FILE} ; then echo "truncLongCastAssignment check failed" ret_code=1 fi if grep "exceptRethrowCopy" ${LOG_FILE} ; then echo "exceptRethrowCopy check failed" ret_code=1 fi if grep "unusedVariable" ${LOG_FILE} ; then echo "unusedVariable check failed" ret_code=1 fi if grep "unsafeClassCanLeak" ${LOG_FILE} ; then echo "unsafeClassCanLeak check failed" ret_code=1 fi if grep "unsignedLessThanZero" ${LOG_FILE} ; then echo "unsignedLessThanZero check failed" ret_code=1 fi if grep "unpreciseMathCall" ${LOG_FILE} ; then echo "unpreciseMathCall check failed" ret_code=1 fi if grep "unreachableCode" ${LOG_FILE} ; then echo "unreachableCode check failed" ret_code=1 fi if grep "clarifyCondition" ${LOG_FILE} ; then echo "clarifyCondition check failed" ret_code=1 fi if grep "redundantIfRemove" ${LOG_FILE} ; then echo "redundantIfRemove check failed" ret_code=1 fi if grep "unassignedVariable" ${LOG_FILE} ; then echo "unassignedVariable check failed" ret_code=1 fi if grep "redundantAssignment" ${LOG_FILE} ; then echo "redundantAssignment check failed" ret_code=1 fi if grep "unreadVariable" ${LOG_FILE} ; then echo "unreadVariable check failed" ret_code=1 fi if grep "AssignmentAddressToInteger" ${LOG_FILE} ; then echo "AssignmentAddressToInteger check failed" ret_code=1 fi # Check any remaining errors if grep "error," ${LOG_FILE} | grep -v "uninitvar" | \ grep -v "memleak," | grep -v "memleakOnRealloc" | \ grep -v "is invalid C code" ; then echo "Errors check failed" ret_code=1 fi # Check any remaining warnings if grep "warning," ${LOG_FILE}; then echo "Warnings check failed" ret_code=1 fi if [ ${ret_code} = 0 ]; then echo "cppcheck succeeded" fi exit ${ret_code} proj-9.8.1/scripts/dump_exported_symbols.sh000775 001750 001750 00000001005 15166171715 021124 0ustar00eveneven000000 000000 #!/bin/sh if objdump -TC "$1" | grep "elf64-x86-64">/dev/null; then COLUMN=62 #elif objdump -TC "$1" | grep "elf32-i386">/dev/null; then # COLUMN=46 else echo "Unsupported architecture" exit 1 fi objdump -TC "$1" | grep " g DF .text" | cut -b "${COLUMN}-" | grep -v "thunk to" | sed "s/internal_//" | grep -v "Java_" | sed "s/std::__cxx11::basic_string, std::allocator >/std::string/g" | sed "s/std::string >/std::string>/g" | sed "s/\[abi:cxx11\]//g" | sort -u proj-9.8.1/scripts/build_db_from_esri.py000775 001750 001750 00000375724 15166171715 020352 0ustar00eveneven000000 000000 #!/usr/bin/env python ############################################################################### # $Id$ # # Project: PROJ # Purpose: Build SRS and coordinate transform database from ESRI database # provided at https://github.com/Esri/projection-engine-db-doc # (from the csv/ directory content) # Author: Even Rouault # ############################################################################### # Copyright (c) 2018, Even Rouault # # 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. ############################################################################### import argparse import csv import os import re import sqlite3 from dataclasses import dataclass from enum import Enum from pathlib import Path from typing import Optional, List, Dict parser = argparse.ArgumentParser() parser.add_argument('esri_dir', help='Path to ESRI projection-engine-db-doc dir,' 'typically the path to a "git clone ' 'https://github.com/Esri/projection-engine-db-doc"', type=Path) parser.add_argument('proj_db', help='Path to current proj.db file', type=Path) parser.add_argument('version', help='ArcMap version string, e.g. "ArcMap 10.8.1"') parser.add_argument('date', help='ArcMap version date as a yyyy-MM-dd string, e.g. "2020-05-24"') args = parser.parse_args() path_to_csv = args.esri_dir / "csv" path_to_objedit = args.esri_dir / "objedit" proj_db = args.proj_db version = args.version date = args.date ######################## def import_syn(filename): lst = [] with open(filename, "rt", encoding="UTF-8") as f: lines = f.readlines() for idx in range(len(lines)): line = lines[idx][0:-1] if line.startswith('"') and line.endswith('", \\'): old_name = line[1:-4] idx += 1 line = lines[idx][0:-1] assert line.startswith(' "') assert line.endswith('", \\') new_name = line[4:-4] idx += 1 line = lines[idx][0:-1] assert line in (' TRUE, \\', ' FALSE, \\') if line == ' TRUE, \\': lst.append((new_name, old_name)) return lst conn = sqlite3.connect(proj_db) cursor = conn.cursor() all_sql = ["""INSERT INTO "metadata" VALUES('ESRI.VERSION', '{}');""".format(version), """INSERT INTO "metadata" VALUES('ESRI.DATE', '{}');""".format(date)] manual_grids = """------------------ -- ESRI grid names ------------------ INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, old_proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory) VALUES ('DLX_ETRS89_geo','pt_dgt_DLx_ETRS89_geo.tif','DLX_ETRS89_geo.gsb','GTiff','hgridshift',0,NULL,'https://cdn.proj.org/pt_dgt_DLx_ETRS89_geo.tif',1,1,NULL), ('D73_ETRS89_geo','pt_dgt_D73_ETRS89_geo.tif','D73_ETRS89_geo.gsb','GTiff','hgridshift',0,NULL,'https://cdn.proj.org/pt_dgt_D73_ETRS89_geo.tif',1,1,NULL), ('rdtrans2008','','rdtrans2008.gsb','NTv2','hgridshift',0,NULL,'https://salsa.debian.org/debian-gis-team/proj-rdnap/raw/upstream/2008/rdtrans2008.gsb',1,0,NULL), ('GS7783','ca_nrc_GS7783.tif','GS7783.GSB','GTiff','hgridshift',0,NULL,'https://cdn.proj.org/ca_nrc_GS7783.tif',1,1,NULL), ('100800401','es_cat_icgc_100800401.tif','100800401.gsb','GTiff','hgridshift',0,NULL,'https://cdn.proj.org/es_cat_icgc_100800401.tif',1,1,NULL), ('QLD_0900','au_icsm_National_84_02_07_01.tif','National_84_02_07_01.gsb','GTiff','hgridshift',0,NULL,'https://cdn.proj.org/au_icsm_National_84_02_07_01.tif',1,1,NULL), -- From https://www.dnrme.qld.gov.au/__data/assets/pdf_file/0006/105765/gday-21-user-guide.pdf: "Note that the Queensland grid QLD_0900.gsb produces identical results to the National AGD84 grid for the equivalent coverage." ('PENR2009','es_ign_SPED2ETV2.tif',NULL,'GTiff','hgridshift',0,NULL,'https://cdn.proj.org/es_ign_SPED2ETV2.tif',1,1,NULL), ('BALR2009','es_ign_SPED2ETV2.tif',NULL,'GTiff','hgridshift',0,NULL,'https://cdn.proj.org/es_ign_SPED2ETV2.tif',1,1,NULL), ('peninsula','es_ign_SPED2ETV2.tif',NULL,'GTiff','hgridshift',0,NULL,'https://cdn.proj.org/es_ign_SPED2ETV2.tif',1,1,NULL), ('baleares','es_ign_SPED2ETV2.tif',NULL,'GTiff','hgridshift',0,NULL,'https://cdn.proj.org/es_ign_SPED2ETV2.tif',1,1,NULL); -- 'RGNC1991_IGN72GrandeTerre' : we have a 3D geocentric corresponding one: no need for mapping -- 'RGNC1991_NEA74Noumea' : we have a 3D geocentric corresponding one: no need for mapping """ def escape_literal(x): return x.replace("'", "''") ######################## map_extentname_to_auth_code = {} esri_area_counter = 1 def find_extent(extentname, slat, nlat, llon, rlon): global esri_area_counter if extentname in map_extentname_to_auth_code: return map_extentname_to_auth_code[extentname] deg = b'\xC2\xB0'.decode('utf-8') cursor.execute("SELECT auth_name, code FROM extent WHERE name = ? AND auth_name != 'ESRI'", (extentname.replace('~', deg),)) row = cursor.fetchone() if row is None: cursor.execute( "SELECT auth_name, code FROM extent WHERE auth_name != 'ESRI' AND south_lat = ? AND north_lat = ? AND west_lon = ? AND east_lon = ?", (slat, nlat, llon, rlon)) row = cursor.fetchone() if row is None: # print('unknown extent inserted: ' + extentname) if float(rlon) > 180: new_rlon = '%s' % (float(rlon) - 360) print('Correcting rlon from %s to %s for %s' % (rlon, new_rlon, extentname)) rlon = new_rlon assert -90 <= float(slat) <= 90, (extentname, slat, nlat, llon, rlon) assert -90 <= float(nlat) <= 90, (extentname, slat, nlat, llon, rlon) assert float(nlat) > float(slat), (extentname, slat, nlat, llon, rlon) assert -180 <= float(llon) <= 180, (extentname, slat, nlat, llon, rlon) assert -180 <= float(rlon) <= 180, (extentname, slat, nlat, llon, rlon) sql = """INSERT INTO "extent" VALUES('ESRI','%d','%s','%s',%s,%s,%s,%s,0);""" % ( esri_area_counter, escape_literal(extentname), escape_literal(extentname), slat, nlat, llon, rlon) all_sql.append(sql) map_extentname_to_auth_code[extentname] = [ 'ESRI', '%d' % esri_area_counter] esri_area_counter += 1 else: auth_name = row[0] code = row[1] map_extentname_to_auth_code[extentname] = [auth_name, code] return map_extentname_to_auth_code[extentname] ################# esri_linear_units = {} def import_linunit(): with open(path_to_csv / 'pe_list_linunit.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_description = header.index('description') assert idx_description >= 0 while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] wkt = row[idx_wkt2] assert wkt.startswith('LENGTHUNIT[') and wkt.endswith(']') tokens = wkt[len('LENGTHUNIT['):len(wkt) - 1].split(',') assert len(tokens) == 2 esri_conv_factor = float(tokens[1]) if authority == 'EPSG': cursor.execute( "SELECT name, conv_factor FROM unit_of_measure WHERE code = ? AND auth_name = 'EPSG'", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] epsg_conv_factor = src_row[1] assert abs(esri_conv_factor - epsg_conv_factor) <= 1e-15 * epsg_conv_factor, (esri_name, esri_conv_factor, epsg_conv_factor) if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('unit_of_measure','EPSG','%s','%s','ESRI');""" % ( latestWkid, escape_literal(esri_name)) all_sql.append(sql) elif authority == 'Esri': code = row[idx_wkid] if latestWkid == code: assert "LENGTHUNIT" in wkt sql = """INSERT INTO unit_of_measure VALUES('ESRI','%s','%s','length',%s,'%s',0);""" % (code, escape_literal(esri_name), esri_conv_factor, escape_literal(row[idx_description])) all_sql.append(sql) esri_linear_units[esri_name] = (code, esri_conv_factor) ################# map_spheroid_esri_name_to_auth_code = {} def import_spheroid(): with open(path_to_csv / 'pe_list_spheroid.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 body_set = set() while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if authority == 'EPSG': cursor.execute( "SELECT name FROM ellipsoid WHERE code = ? AND auth_name = 'EPSG'", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] map_spheroid_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('ellipsoid','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) else: assert authority.upper() == 'ESRI', row wkt2 = row[idx_wkt2] wkt2_tokens_re = re.compile(r'ELLIPSOID\[""?(.*?)""?,(-?[\d]+(?:\.[\d]*)?),(-?[\d]+(?:\.[\d]*)?)]') match = wkt2_tokens_re.match(wkt2) assert match, wkt2 a = match.group(2) rf = match.group(3) description = row[idx_description] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 if esri_name not in map_spheroid_esri_name_to_auth_code: map_spheroid_esri_name_to_auth_code[esri_name] = [ 'ESRI', code] if abs(float(a) - 6375000) > 0.01 * 6375000: pos = esri_name.find('_19') if pos < 0: pos = esri_name.find('_20') assert pos > 0 body_code = esri_name[0:pos] if body_code not in body_set: body_set.add(body_code) sql = """INSERT INTO celestial_body VALUES('ESRI', '%s', '%s', %s);""" % ( body_code, body_code, a) all_sql.append(sql) body_auth = 'ESRI' else: body_auth = 'PROJ' body_code = 'EARTH' sql = """INSERT INTO ellipsoid VALUES('ESRI','%s','%s','%s','%s','%s',%s,'EPSG','9001',%s,NULL,%d);""" % ( code, esri_name, description, body_auth, body_code, a, rf, deprecated) all_sql.append(sql) ######################## map_pm_esri_name_to_auth_code = {} def import_prime_meridian(): with open(path_to_csv / 'pe_list_primem.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if authority == 'EPSG': cursor.execute( "SELECT name FROM prime_meridian WHERE code = ? AND auth_name = 'EPSG'", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] map_pm_esri_name_to_auth_code[esri_name] = ['EPSG', latestWkid] if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('prime_meridian','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) else: assert authority.upper() == 'ESRI', row wkt2 = row[idx_wkt2] wkt2_tokens_re = re.compile(r'PRIMEM\[""?(.*?)""?,(-?[\d]+(?:\.[\d]*)?)]') match = wkt2_tokens_re.match(wkt2) assert match, wkt2 value = match.group(2) assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 if esri_name not in map_pm_esri_name_to_auth_code: map_pm_esri_name_to_auth_code[esri_name] = ['ESRI', code] sql = """INSERT INTO "prime_meridian" VALUES('ESRI','%s','%s',%s,'EPSG','9110',%d);""" % ( code, esri_name, value, deprecated) all_sql.append(sql) ######################## map_datum_esri_name_to_auth_code = {} map_datum_esri_to_parameters = {} def get_old_esri_name(s): """Massage datum/CRS name like old ESRI software did""" # Needed for EPSG:8353 S_JTSK_JTSK03_Krovak_East_North # Cf https://github.com/OSGeo/gdal/issues/7505 if '[' in s: s = s.replace('-', '_') s = s.replace('[', '') s = s.replace(']', '') # Not totally sure of below, so disable if False: s = s.replace('_(', '_').replace(')_', '_') s = s.replace('.', '_') s = s.replace('(', '_').replace(')', '_') if s.endswith('_'): s = s[0:-1] return s def import_datum(): with open(path_to_csv / 'pe_list_datum.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if authority == 'EPSG': map_datum_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] cursor.execute( "SELECT name FROM geodetic_datum WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] # We create the alias even if it the esri_name is the same as the # EPSG name, otherwise we'll massage by adding a D_ prefix # when exporting to WKT1:ESRI sql = """INSERT INTO alias_name VALUES('geodetic_datum','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) old_esri_name = get_old_esri_name(esri_name) if old_esri_name != esri_name: sql = """INSERT INTO alias_name VALUES('geodetic_datum','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(old_esri_name)) all_sql.append(sql) description = row[idx_description] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 map_datum_esri_to_parameters[code] = { 'esri_name': esri_name, 'description': description, 'deprecated': deprecated } else: assert authority.upper() == 'ESRI', row map_datum_esri_name_to_auth_code[esri_name] = [ 'ESRI', code] wkt2 = row[idx_wkt2] wkt2_tokens_re = re.compile( r'DATUM\[""?(.*?)""?,\s*ELLIPSOID\[""?(.*?)""?,(-?[\d]+(?:\.[\d]*)?),(-?[\d]+(?:\.[\d]*)?)](,ANCHOREPOCH\[[\d\.]+])?]') match = wkt2_tokens_re.match(wkt2) ellps_name = match.group(2) assert ellps_name in map_spheroid_esri_name_to_auth_code, ( ellps_name, row) ellps_auth_name, ellps_code = map_spheroid_esri_name_to_auth_code[ellps_name] description = row[idx_description] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 map_datum_esri_to_parameters[code] = { 'esri_name': esri_name, 'description': description, 'ellps_auth_name': ellps_auth_name, 'ellps_code': ellps_code, 'deprecated': deprecated } # We cannot write it since we lack the prime meridian !!! (and # the area of use) ######################## map_geogcs_esri_name_to_auth_code = {} def import_geogcs(): # Those 2 maps are used to fill the deprecation table map_code_to_authority = {} mapDeprecatedToNonDeprecated = {} with open(path_to_csv / 'pe_list_geogcs.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 idx_areaname = header.index('areaname') assert idx_areaname >= 0 idx_slat = header.index('slat') assert idx_slat >= 0 idx_nlat = header.index('nlat') assert idx_nlat >= 0 idx_llon = header.index('llon') assert idx_llon >= 0 idx_rlon = header.index('rlon') assert idx_rlon >= 0 datum_written = set() # last 2 ones epoch,model are actually missing in most records... assert nfields == 17 while True: try: row = next(reader) except StopIteration: break assert len(row) in (nfields, nfields -2), (row, header, len(row), nfields) code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if code == latestWkid and authority.upper() == 'ESRI': cursor.execute( "SELECT name FROM geodetic_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() if src_row: src_name = src_row[0] modified_epsg_name = src_name.replace(' ', '_') if modified_epsg_name.upper() == esri_name.upper() or modified_epsg_name.upper() + "_3D" == esri_name.upper() or modified_epsg_name.upper() + "_(3D)" == esri_name.upper(): print("GeogCRS ESRI:%s (%s) has the same name as EPSG:%s. Fixing authority to be EPSG" % (latestWkid, esri_name, latestWkid)) authority = "EPSG" map_code_to_authority[code] = authority.upper() if authority == 'EPSG': map_geogcs_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] cursor.execute( "SELECT name FROM geodetic_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] # We create the alias even if it the esri_name is the same as the # EPSG name, otherwise we'll massage by adding a GCS_ prefix # when exporting to WKT1:ESRI sql = """INSERT INTO alias_name VALUES('geodetic_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) old_esri_name = get_old_esri_name(esri_name) if old_esri_name != esri_name: sql = """INSERT INTO alias_name VALUES('geodetic_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(old_esri_name)) all_sql.append(sql) else: assert authority.upper() == 'ESRI', row wkt2 = row[idx_wkt2] wkt2_datum_re = re.compile(r'.*DATUM\[""?(.*?)""?.*') match = wkt2_datum_re.match(wkt2) assert match, wkt2 datum_name = match.group(1) # strip datum out of wkt wkt2 = re.sub(r'DATUM\[""?(.*?)""?,\s*ELLIPSOID\[""?(.*?)""?,\s*(-?[\d]+(?:\.[\d]*)?),\s*(-?[\d]+(?:\.[\d]*)?),\s*LENGTHUNIT\[""?(.*?)""?,\s*(-?[\d]+(?:\.[\d]*)?)]]],?', '', wkt2) assert datum_name in map_datum_esri_name_to_auth_code, ( datum_name, row) datum_auth_name, datum_code = map_datum_esri_name_to_auth_code[datum_name] wkt2_datum_re = re.compile(r'.*PRIMEM\[""?(.*?)""?.*') match = wkt2_datum_re.match(wkt2) assert match, wkt2 pm_name = match.group(1) # strip prime meridian out of wkt wkt2 = re.sub(r'PRIMEM\[""?(.*?)""?,(-?[\d]+(?:\.[\d]*)?),ANGLEUNIT\[""?(.*?)""?,(-?[\d]+(?:\.[\d]*)?)]],?', '', wkt2) assert pm_name in map_pm_esri_name_to_auth_code, (pm_name, row) pm_auth_name, pm_code = map_pm_esri_name_to_auth_code[pm_name] wkt2_angle_unit_re = re.compile( r'.*ANGLEUNIT\[""?(.*?)""?,(-?[\d]+(?:\.[\d]*)?)].*') match = wkt2_angle_unit_re.match(wkt2) assert match, wkt2 angle_unit = match.group(1) assert angle_unit in ('Degree', 'Grad'), 'Unhandled angle unit {}'.format(angle_unit) is_degree = angle_unit == 'Degree' is_grad = angle_unit == 'Grad' assert is_degree or is_grad, row cs_code = '6422' if is_degree else '6403' if "CS[ellipsoidal,3]" in wkt2: assert 'AXIS["Ellipsoidal height (h)",up,ORDER[3],LENGTHUNIT["Meter",1.0]' in wkt2 cs_code = '6423' geodetic_crs_type = "geographic 3D" else: geodetic_crs_type = "geographic 2D" assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 extent_auth_name, extent_code = find_extent( row[idx_areaname], row[idx_slat], row[idx_nlat], row[idx_llon], row[idx_rlon]) if datum_auth_name == 'ESRI': if datum_code not in datum_written: datum_written.add(datum_code) p = map_datum_esri_to_parameters[datum_code] sql = """INSERT INTO "geodetic_datum" VALUES('ESRI','%s','%s','%s','%s','%s','%s','%s',NULL,NULL,NULL,NULL,NULL,%d);""" % ( datum_code, p['esri_name'], p['description'], p['ellps_auth_name'], p['ellps_code'], pm_auth_name, pm_code, p['deprecated']) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','geodetic_datum','ESRI','%s','%s','%s','%s','%s');""" % (datum_code, datum_code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) p['pm_auth_name'] = pm_auth_name p['pm_code'] = pm_code map_datum_esri_to_parameters[datum_code] = p else: assert map_datum_esri_to_parameters[datum_code]['pm_auth_name'] == pm_auth_name, ( row, map_datum_esri_to_parameters[datum_code]['pm_auth_name'], pm_auth_name) if map_datum_esri_to_parameters[datum_code]['pm_code'] != pm_code: p = map_datum_esri_to_parameters[datum_code] # Case of GCS_Voirol_Unifie_1960 and GCS_Voirol_Unifie_1960_Paris which use the same # datum D_Voirol_Unifie_1960 but with different prime meridian # We create an artificial datum to avoid that issue datum_name += '_' + pm_name datum_code += '_' + pm_name datum_written.add(datum_code) map_datum_esri_name_to_auth_code[datum_name] = [ 'ESRI', latestWkid] map_datum_esri_to_parameters[datum_code] = { 'esri_name': datum_name, 'description': p['description'] + ' with ' + pm_name + ' prime meridian', 'ellps_auth_name': p['ellps_auth_name'], 'ellps_code': p['ellps_code'], 'deprecated': p['deprecated'] } sql = """INSERT INTO "geodetic_datum" VALUES('ESRI','%s','%s','%s','%s','%s','%s','%s',NULL,NULL,NULL,NULL,NULL,%d);""" % ( datum_code, p['esri_name'], p['description'], p['ellps_auth_name'], p['ellps_code'], pm_auth_name, pm_code, p['deprecated']) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','geodetic_datum','ESRI','%s','%s','%s','%s','%s');""" % (datum_code, datum_code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) p['pm_auth_name'] = pm_auth_name p['pm_code'] = pm_code map_datum_esri_to_parameters[datum_code] = p # We may have already the EPSG entry, so use it preferably if esri_name not in map_geogcs_esri_name_to_auth_code: map_geogcs_esri_name_to_auth_code[esri_name] = ['ESRI', code] sql = f"""INSERT INTO "geodetic_crs" VALUES('ESRI','%s','%s',NULL,'{geodetic_crs_type}','EPSG','%s','%s','%s',NULL,%d);""" % ( code, esri_name, cs_code, datum_auth_name, datum_code, deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','geodetic_crs','ESRI','%s','%s','%s','%s','%s');""" % (code, code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) if deprecated and code != latestWkid and code not in ('4305', '4812'): # Voirol 1960 no longer in EPSG cursor.execute( "SELECT name, deprecated FROM geodetic_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, (code, latestWkid) _, deprecated = src_row if not deprecated: sql = """INSERT INTO "deprecation" VALUES('geodetic_crs','ESRI','%s','EPSG','%s','ESRI');""" % ( code, latestWkid) all_sql.append(sql) elif deprecated and code != latestWkid: mapDeprecatedToNonDeprecated[code] = latestWkid for code in mapDeprecatedToNonDeprecated: replacement_code = mapDeprecatedToNonDeprecated[code] if replacement_code in map_code_to_authority: sql = """INSERT INTO "deprecation" VALUES('geodetic_crs','ESRI','%s','%s','%s','ESRI');""" % ( code, map_code_to_authority[replacement_code], replacement_code) all_sql.append(sql) aliases = import_syn(path_to_objedit / "datum_syn.txt") for (new_name, old_name) in aliases: (auth, code) = map_datum_esri_name_to_auth_code[new_name] sql = """INSERT INTO alias_name VALUES('geodetic_datum','%s','%s','%s','ESRI_OLD');""" % ( auth, code, escape_literal(old_name)) all_sql.append(sql) aliases = import_syn(path_to_objedit / "geogcs_syn.txt") for (new_name, old_name) in aliases: (auth, code) = map_geogcs_esri_name_to_auth_code[new_name] sql = """INSERT INTO alias_name VALUES('geodetic_crs','%s','%s','%s','ESRI_OLD');""" % ( auth, code, escape_literal(old_name)) all_sql.append(sql) ######################## def parse_wkt(s, level): if s[0] == '"': return s pos = s.find('[') if pos < 0: return s return {s[0:pos]: parse_wkt_array(s[pos+1:-1], level + 1)} def parse_wkt_array(s, level=0): ar = [] in_string = False cur_token = '' indent_level = 0 for c in s: if in_string: if c == '"': in_string = False else: cur_token += c elif c == '"': in_string = True elif c == '[': cur_token += c indent_level += 1 elif c == ']': cur_token += c indent_level -= 1 assert indent_level >= 0 elif indent_level == 0 and c == ',': ar.append(parse_wkt(cur_token, level + 1)) cur_token = '' else: cur_token += c assert indent_level == 0 if cur_token: ar.append(parse_wkt(cur_token, level + 1)) if level == 0: return wkt_array_to_dict(ar) else: return ar def wkt_array_to_dict(ar): d = {} for elt in ar: assert isinstance(elt, dict), elt assert len(elt) == 1 if 'PROJECTION' in elt: assert len(elt['PROJECTION']) == 1, elt['PROJECTION'] assert 'PROJECTION' not in d name = elt['PROJECTION'][0] d['PROJECTION'] = name elif 'CONVERSION' in elt: d['CONVERSION'] = [elt['CONVERSION'][0], wkt_array_to_dict(elt['CONVERSION'][1:])] elif 'COORDINATEOPERATION' in elt: d['COORDINATEOPERATION'] = [elt['COORDINATEOPERATION'][0], wkt_array_to_dict(elt['COORDINATEOPERATION'][1:])] elif 'SOURCECRS' in elt: assert len(elt['SOURCECRS']) == 1, elt['SOURCECRS'] d['SOURCECRS'] = elt['SOURCECRS'][0] elif 'TARGETCRS' in elt: assert len(elt['TARGETCRS']) == 1, elt['TARGETCRS'] d['TARGETCRS'] = elt['TARGETCRS'][0] elif 'VERTCRS' in elt: d['VERTCRS'] = [elt['VERTCRS'][0], wkt_array_to_dict(elt['VERTCRS'][1:])] elif 'VDATUM' in elt: assert len(elt['VDATUM']) == 1, elt['VDATUM'] d['VDATUM'] = elt['VDATUM'][0] elif 'CS' in elt: assert len(elt['CS']) == 2, elt['CS'] d['CS'] = elt['CS'] elif 'AXIS' in elt: assert len(elt['AXIS']) == 3 d['AXIS'] = [elt['AXIS'][0], elt['AXIS'][1], wkt_array_to_dict(elt['AXIS'][2:])] elif 'DATUM' in elt: d['DATUM'] = [elt['DATUM'][0], wkt_array_to_dict(elt['DATUM'][1:])] elif 'METHOD' in elt: assert len(elt['METHOD']) == 1, elt['METHOD'] d['METHOD'] = elt['METHOD'][0] elif 'PARAMETERFILE' in elt: assert len(elt['PARAMETERFILE']) == 1, elt['PARAMETERFILE'] d['PARAMETERFILE'] = elt['PARAMETERFILE'][0] elif 'OPERATIONACCURACY' in elt: assert len(elt['OPERATIONACCURACY']) == 1, elt['OPERATIONACCURACY'] d['OPERATIONACCURACY'] = elt['OPERATIONACCURACY'][0] #elif 'ELLIPSOID' in elt: # d['ELLIPSOID'] = [elt['ELLIPSOID'][0], wkt_array_to_dict(elt['ELLIPSOID'][1:])] elif 'PARAMETER' in elt: assert len(elt['PARAMETER']) >= 2, elt['PARAMETER'] name = elt['PARAMETER'][0] assert name not in d d[name] = elt['PARAMETER'][1] if len(elt['PARAMETER']) == 2 else elt['PARAMETER'][1:] elif 'UNIT' in elt: assert len(elt['UNIT']) == 2, elt['UNIT'] name = elt['UNIT'][0] assert 'UNIT_NAME' not in d d['UNIT_NAME'] = name d['UNIT_VALUE'] = elt['UNIT'][1] elif 'LENGTHUNIT' in elt: assert len(elt['LENGTHUNIT']) == 2, elt['LENGTHUNIT'] name = elt['LENGTHUNIT'][0] assert 'UNIT_NAME' not in d d['UNIT_NAME'] = name d['UNIT_VALUE'] = elt['LENGTHUNIT'][1] else: assert True return d ######################## @dataclass class CoordinateSystem: """ Encapsulates a coordinate system """ auth_name: str code: str @dataclass class Unit: """ Encapsulates a WKT unit """ uom_auth_name: str uom_code: str cs: Optional[CoordinateSystem] = None def get_wkt_unit(UNIT_NAME, UNIT_VALUE, is_rate=False) -> Unit: cs = None if UNIT_NAME == 'Meter': uom_auth_name = 'EPSG' uom_code = '1042' if is_rate else '9001' cs = CoordinateSystem('EPSG', '4400') assert UNIT_VALUE == '1.0', UNIT_VALUE elif UNIT_NAME == 'Millimeter': uom_auth_name = 'EPSG' uom_code = '1027' if is_rate else '1025' assert UNIT_VALUE == '0.001', UNIT_VALUE cs = CoordinateSystem('ESRI', 'Millimeter') elif UNIT_NAME == 'Chain': uom_auth_name = 'EPSG' assert not is_rate uom_code = '9097' cs = CoordinateSystem('ESRI', UNIT_NAME) assert UNIT_VALUE == '20.1168', UNIT_VALUE elif UNIT_NAME == 'Degree': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9102' assert UNIT_VALUE == '0.0174532925199433', UNIT_VALUE elif UNIT_NAME == 'Arcsecond': uom_auth_name = 'EPSG' uom_code = '1043' if is_rate else '9104' assert UNIT_VALUE == '0.00000484813681109536', UNIT_VALUE elif UNIT_NAME == 'Milliarcsecond': uom_auth_name = 'EPSG' uom_code = '1032' if is_rate else '1031' assert UNIT_VALUE == '4.84813681109536e-09', UNIT_VALUE elif UNIT_NAME == 'Microradian': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9109' assert float(UNIT_VALUE) == 1e-6, UNIT_VALUE elif UNIT_NAME == 'Grad': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9105' assert UNIT_VALUE == '0.01570796326794897', UNIT_VALUE elif UNIT_NAME == 'Foot': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9002' cs = CoordinateSystem('EPSG', '4495') assert UNIT_VALUE == '0.3048', UNIT_VALUE elif UNIT_NAME == 'Foot_US': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9003' cs = CoordinateSystem('EPSG', '4497') assert UNIT_VALUE == '0.3048006096012192', UNIT_VALUE elif UNIT_NAME == 'Yard_Indian_1937': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9085' cs = CoordinateSystem('ESRI', UNIT_NAME) assert UNIT_VALUE == '0.91439523', UNIT_VALUE elif UNIT_NAME == 'Parts_Per_Million': uom_auth_name = 'EPSG' uom_code = '1041' if is_rate else '9202' assert UNIT_VALUE == '0.000001', UNIT_VALUE elif UNIT_NAME == 'Parts_Per_Billion': uom_auth_name = 'EPSG' uom_code = '1030' if is_rate else '1028' assert UNIT_VALUE == '0.000000001', UNIT_VALUE elif UNIT_NAME == 'Unity': assert not is_rate uom_auth_name = 'EPSG' uom_code = '9201' assert UNIT_VALUE == '1.0', UNIT_VALUE elif UNIT_NAME in esri_linear_units: uom_auth_name = 'ESRI' uom_code = esri_linear_units[UNIT_NAME][0] cs = CoordinateSystem('ESRI', UNIT_NAME) else: assert False, UNIT_NAME if cs is not None and cs.auth_name == 'ESRI' and cs.code not in set_esri_cs_code: sql = f"""INSERT INTO "coordinate_system" VALUES('ESRI','{cs.code}','Cartesian',2);""" all_sql.append(sql) sql = f"""INSERT INTO "axis" VALUES('ESRI','{2 * len(set_esri_cs_code) + 1}','Easting','E','east','ESRI','{cs.code}',1,'{uom_auth_name}','{uom_code}');""" all_sql.append(sql) sql = f"""INSERT INTO "axis" VALUES('ESRI','{2 * len(set_esri_cs_code) + 2}','Northing','N','north','ESRI','{cs.code}',2,'{uom_auth_name}','{uom_code}');""" all_sql.append(sql) set_esri_cs_code.add(cs.code) return Unit(uom_auth_name=uom_auth_name, uom_code=uom_code, cs=cs) class UnitType(Enum): """ WKT unit types """ Angle = 0 Length = 1 Scale = 2 STRING_TO_UNIT_TYPE = { 'ANGLEUNIT': UnitType.Angle, 'LENGTHUNIT': UnitType.Length, 'SCALEUNIT': UnitType.Scale } @dataclass class ParameterValue: """ Encapsulates a WKT parameter value """ value: float unit_type: UnitType unit: Unit def get_parameter_value(wkt_definition: List) -> ParameterValue: """ Retrieves a WKT parameter value from its definition """ value = wkt_definition[0] assert len(wkt_definition[1]) == 1 unit_type_str = list(wkt_definition[1].keys())[0] unit_type = STRING_TO_UNIT_TYPE[unit_type_str] return ParameterValue(value=value, unit_type=unit_type, unit=get_wkt_unit(*wkt_definition[1][unit_type_str])) def get_parameter_values(wkt_definition: dict, global_linear_unit: Unit|None) -> Dict[str, ParameterValue]: """ Retrieves all WKT parameter values from a WKT string """ res = {} for param, value in wkt_definition.items(): if isinstance(value, str): if global_linear_unit and param != 'METHOD': if param == "Scale_Factor": res[param] = ParameterValue(value=float(value), unit_type=UnitType.Scale, unit=get_wkt_unit('Unity', '1.0')) elif param.startswith("Longitude") or param.startswith("Latitude") or "Parallel" in param or param in ('Central_Meridian', 'Azimuth'): res[param] = ParameterValue(value=float(value), unit_type=UnitType.Angle, unit=get_wkt_unit('Degree', '0.0174532925199433')) elif param in ('False_Easting', 'False_Northing', 'Height'): res[param] = ParameterValue(value=float(value), unit_type=UnitType.Length, unit=global_linear_unit) else: assert False, param else: res[param] = value else: try: res[param] = get_parameter_value(value) except AssertionError: print(wkt_definition) raise return res ######################## def get_cs_from_false_easting_and_northing(params: Dict[str, ParameterValue]) -> CoordinateSystem: """ Extracts the CoordinateSystem from False_Easting and False_Northing parameters """ assert params['False_Easting'].unit.cs.auth_name == params[ 'False_Northing'].unit.cs.auth_name, 'Cannot handle False_Easting CS auth {} != False_Northing CS auth {}'.format( params['False_Easting'].unit.cs.auth_name, params['False_Northing'].unit.cs.auth_name) cs_auth_name = params['False_Easting'].unit.cs.auth_name assert params['False_Easting'].unit.cs.code == params[ 'False_Northing'].unit.cs.code, 'Cannot handle False_Easting CS code {} != False_Northing CS code {}'.format( params['False_Easting'].unit.cs.code, params['False_Northing'].unit.cs.code) cs_code = params['False_Easting'].unit.cs.code return CoordinateSystem(cs_auth_name, cs_code) map_projcs_esri_name_to_auth_code = {} set_esri_cs_code = set() MAP_CONVERSION_SQL_TO_CODE = {} EPSG_CONVERSION_PARAM_NAMES = { 1039: 'Projection plane origin height', 8801: 'Latitude of natural origin', 8802: 'Longitude of natural origin', 8805: 'Scale factor at natural origin', 8806: 'False easting', 8807: 'False northing', 8811: 'Latitude of projection centre', 8812: 'Longitude of projection centre', 8813: 'Azimuth at projection centre', 8814: 'Angle from Rectified to Skew Grid', 8815: 'Scale factor at projection centre', 8816: 'Easting at projection centre', 8817: 'Northing at projection centre', 8821: 'Latitude of false origin', 8822: 'Longitude of false origin', 8823: 'Latitude of 1st standard parallel', 8824: 'Latitude of 2nd standard parallel', 8826: 'Easting at false origin', 8827: 'Northing at false origin' } @dataclass class ConversionMapping: """ Encapsulates a mapping from an ESRI projcs """ epsg_code: str epsg_name: str param_mapping: Dict[int, str] # special cases REQUIRES_ORIGINAL_WKT_DEF = { '102113' # see https://github.com/OSGeo/PROJ/pull/2954#issuecomment-977771912 for rationale } MAPPED_PROJCS: Dict[str, ConversionMapping] = { 'Hotine_Oblique_Mercator_Azimuth_Natural_Origin': ConversionMapping( epsg_code='9812', epsg_name='Hotine Oblique Mercator (variant A)', param_mapping={ 8811: 'Latitude_Of_Center', 8812: 'Longitude_Of_Center', 8813: 'Azimuth', 8814: 'Azimuth', 8815: 'Scale_Factor', 8806: 'False_Easting', 8807: 'False_Northing' } ), 'Equal_Earth': ConversionMapping( epsg_code='1078', epsg_name='Equal Earth', param_mapping={ 8802: 'Central_Meridian', 8806: 'False_Easting', 8807: 'False_Northing' } ), 'Albers': ConversionMapping( epsg_code='9822', epsg_name='Albers Equal Area', param_mapping={ 8821: 'Latitude_Of_Origin', 8822: 'Central_Meridian', 8823: 'Standard_Parallel_1', 8824: 'Standard_Parallel_2', 8826: 'False_Easting', 8827: 'False_Northing', } ), 'Cassini': ConversionMapping( epsg_code='9806', epsg_name='Cassini-Soldner', param_mapping={ 8801: 'Latitude_Of_Origin', 8802: 'Central_Meridian', 8806: 'False_Easting', 8807: 'False_Northing', } ), 'IGAC_Plano_Cartesiano': ConversionMapping( epsg_code='1052', epsg_name='Colombia Urban', param_mapping={ 8801: 'Latitude_Of_Center', 8802: 'Longitude_Of_Center', 8806: 'False_Easting', 8807: 'False_Northing', 1039: 'Height' } ), 'Equidistant_Cylindrical': ConversionMapping( epsg_code='1029', epsg_name='Equidistant Cylindrical (Spherical)', param_mapping={ 8823: 'Standard_Parallel_1', 8802: 'Central_Meridian', 8806: 'False_Easting', 8807: 'False_Northing', } ), 'Mercator': ConversionMapping( epsg_code='9805', epsg_name='Mercator (variant B)', param_mapping={ 8823: 'Standard_Parallel_1', 8802: 'Central_Meridian', 8806: 'False_Easting', 8807: 'False_Northing', } ), 'Polyconic': ConversionMapping( epsg_code='9818', epsg_name='American Polyconic', param_mapping={ 8801: 'Latitude_Of_Origin', 8802: 'Central_Meridian', 8806: 'False_Easting', 8807: 'False_Northing', } ), 'Local': ConversionMapping( epsg_code='1130', epsg_name='Local Orthographic', param_mapping={ 8811: 'Latitude_Of_Center', 8812: 'Longitude_Of_Center', 8813: 'Azimuth', 8815: 'Scale_Factor', 8816: 'False_Easting', 8817: 'False_Northing', } ), } MAPPED_PROJCS_WITH_EXTRA_LOGIC: Dict[str, Dict[str, ConversionMapping]] = { 'Lambert_Conformal_Conic': { 'Lambert Conic Conformal (1SP)': ConversionMapping( epsg_code='9801', epsg_name='Lambert Conic Conformal (1SP)', param_mapping={ 8801: 'Latitude_Of_Origin', 8802: 'Central_Meridian', 8805: 'Scale_Factor', 8806: 'False_Easting', 8807: 'False_Northing' } ), 'Lambert Conic Conformal (2SP)': ConversionMapping( epsg_code='9802', epsg_name='Lambert Conic Conformal (2SP)', param_mapping={ 8821: 'Latitude_Of_Origin', 8822: 'Central_Meridian', 8823: 'Standard_Parallel_1', 8824: 'Standard_Parallel_2', 8826: 'False_Easting', 8827: 'False_Northing' } ) } } def insert_conversion_sql(esri_code: str, esri_name: str, epsg_code: str, epsg_name: str, params: Dict[str, ParameterValue], param_mapping: Dict[int, str], extent_auth_name: str, extent_code: str, geogcs_auth_name: str, geogcs_code: str, cs: CoordinateSystem, deprecated: bool = False) -> List[str]: """ Generates INSERT sql command for conversion """ param_strings = [] for param_epsg_code, param_name in param_mapping.items(): param_strings.append("'EPSG','{}','{}',{},'{}','{}'".format( param_epsg_code, EPSG_CONVERSION_PARAM_NAMES[param_epsg_code], params[param_name].value, params[param_name].unit.uom_auth_name, params[param_name].unit.uom_code )) if len(param_strings) < 7: for _ in range(len(param_strings), 7): param_strings.append('NULL,NULL,NULL,NULL,NULL,NULL') assert len(param_strings) == 7, 'Too many parameters' sql = "INSERT INTO \"conversion\" VALUES('ESRI','{code}','{name}',NULL,'{method_auth_name}','{method_code}','{method_name}'," \ "{param1},{param2},{param3},{param4},{param5},{param6},{param7},{deprecated});".format( code=esri_code, name='unnamed', method_auth_name='EPSG', method_code=epsg_code, method_name=epsg_name, param1=param_strings[0], param2=param_strings[1], param3=param_strings[2], param4=param_strings[3], param5=param_strings[4], param6=param_strings[5], param7=param_strings[6], deprecated=1 if deprecated else 0) conv_name = 'unnamed' conv_auth_name = 'ESRI' conv_code = esri_code results = [] sql_extract = sql[sql.find('NULL'):] if conv_name != 'unnamed' or sql_extract not in MAP_CONVERSION_SQL_TO_CODE: results.append(sql) sql = f"""INSERT INTO "usage" VALUES('ESRI', 'CONV_{esri_code}_USAGE','conversion','ESRI','{esri_code}','{extent_auth_name}','{extent_code}','EPSG','1024');""" results.append(sql) MAP_CONVERSION_SQL_TO_CODE[sql_extract] = conv_code else: conv_code = MAP_CONVERSION_SQL_TO_CODE[sql_extract] sql = f"""INSERT INTO "projected_crs" VALUES('ESRI','{esri_code}','{esri_name}',NULL,'{cs.auth_name}','{cs.code}','{geogcs_auth_name}',""" \ f"""'{geogcs_code}','{conv_auth_name}','{conv_code}',NULL,{1 if deprecated else 0});""" results.append(sql) sql = f"""INSERT INTO "usage" VALUES('ESRI', 'PCRS_{esri_code}_USAGE','projected_crs','ESRI','{esri_code}','{extent_auth_name}','{extent_code}','EPSG','1024');""" results.append(sql) return results def import_projcs(): with open(path_to_csv / 'pe_list_projcs.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt = header.index('wkt') assert idx_wkt >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 idx_areaname = header.index('areaname') assert idx_areaname >= 0 idx_slat = header.index('slat') assert idx_slat >= 0 idx_nlat = header.index('nlat') assert idx_nlat >= 0 idx_llon = header.index('llon') assert idx_llon >= 0 idx_rlon = header.index('rlon') assert idx_rlon >= 0 wkid_set = set() mapDeprecatedToNonDeprecated = {} while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if code == latestWkid and authority.upper() == 'ESRI': cursor.execute( "SELECT name FROM projected_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() if src_row: src_name = src_row[0] modified_epsg_name = src_name.replace(' / ', '_').replace(' ', '_') if modified_epsg_name.upper() == esri_name.upper() or modified_epsg_name.upper() + "_3D" == esri_name.upper() or modified_epsg_name.upper() + "_(3D)" == esri_name.upper(): print("ProjCRS ESRI:%s (%s) has the same name as EPSG:%s. Fixing authority to be EPSG" % (latestWkid, esri_name, latestWkid)) authority = "EPSG" if authority == 'EPSG': # Patch weirdness in source file if code == '3991' and latestWkid == '103987': latestWkid = '3991' map_projcs_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] cursor.execute( "SELECT name FROM projected_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, (row, latestWkid) src_name = src_row[0] esri_name = row[idx_name] if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('projected_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) old_esri_name = get_old_esri_name(esri_name) if old_esri_name != esri_name: sql = """INSERT INTO alias_name VALUES('projected_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(old_esri_name)) all_sql.append(sql) else: assert authority.upper() == 'ESRI', row wkid_set.add(code) wkt = row[idx_wkt] wkt2 = row[idx_wkt2] wkt2_basegeogcrs_re = re.compile(r'.*BASEGEOGCRS\[""?(.*?)""?.*') match = wkt2_basegeogcrs_re.match(wkt2) assert match, wkt2 geogcs_name = match.group(1) pos = wkt2.find('CONVERSION[') assert pos >= 0 parsed_conv_wkt2 = parse_wkt_array(wkt2[pos:-1]) assert geogcs_name in map_geogcs_esri_name_to_auth_code, ( geogcs_name, row) geogcs_auth_name, geogcs_code = map_geogcs_esri_name_to_auth_code[geogcs_name] extent_auth_name, extent_code = find_extent( row[idx_areaname], row[idx_slat], row[idx_nlat], row[idx_llon], row[idx_rlon]) map_projcs_esri_name_to_auth_code[esri_name] = ['ESRI', code] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 method = parsed_conv_wkt2['CONVERSION'][0] match = re.compile(r'.*,ORDER\[2\]],LENGTHUNIT\["?([^"]+)"?,([\d\.]+)\].*').match(wkt2) global_linear_unit = None if match: global_linear_unit = get_wkt_unit(match.group(1), match.group(2)) assert global_linear_unit, wkt2 if method in ('Transverse_Mercator', 'Gauss_Kruger'): params = get_parameter_values(parsed_conv_wkt2['CONVERSION'][1], global_linear_unit) cs = get_cs_from_false_easting_and_northing(params) assert params['Central_Meridian'].unit.uom_auth_name == 'EPSG', 'Unhandled Central_Meridian authority {}'.format(params['Central_Meridian'].unit.uom_auth_name) assert params['Scale_Factor'].unit.uom_code == '9201', 'Unhandled scale factor unit {}'.format(params['Scale_Factor'].unit.uom_code) assert params['Latitude_Of_Origin'].unit.uom_auth_name == 'EPSG', 'Unhandled Latitude_Of_Origin authority {}'.format(params['Latitude_Of_Origin'].unit.uom_auth_name) conv_name = 'unnamed' if method == 'Gauss_Kruger' and 'GK_' not in esri_name and 'Gauss' not in esri_name: conv_name = esri_name + " (Gauss Kruger)" cursor.execute( """SELECT code, deprecated FROM conversion WHERE auth_name = 'EPSG' AND method_code = '9807' AND param1_code = '8801' AND param1_value = ? AND param1_uom_code = ? AND param2_code = '8802' AND param2_value = ? AND param2_uom_code = ? AND param3_code = '8805' AND param3_value = ? AND param3_uom_code = '9201' AND param4_code = '8806' AND param4_value = ? AND param4_uom_code = ? AND param5_code = '8807' AND param5_value = ? AND param5_uom_code = ?""", (params['Latitude_Of_Origin'].value, params['Latitude_Of_Origin'].unit.uom_code, params['Central_Meridian'].value, params['Central_Meridian'].unit.uom_code, params['Scale_Factor'].value, params['False_Easting'].value, params['False_Easting'].unit.uom_code, params['False_Northing'].value, params['False_Northing'].unit.uom_code)) src_row = cursor.fetchone() if conv_name == 'unnamed' and src_row: conv_auth_name = 'EPSG' conv_code = src_row[0] conv_is_deprecated = bool(src_row[1]) while not deprecated and conv_is_deprecated: # if we found a deprecated conversion but the CRS isn't deprecated, keep looking... src_row = cursor.fetchone() if not src_row: break conv_code = src_row[0] conv_is_deprecated = bool(src_row[1]) if conv_is_deprecated and not deprecated: # if conversion is marked as deprecated, we have to deprecate the CRS also assert False, 'ESRI:{} ({}) is NOT marked as deprecated but conversion EPSG:{} is deprecated'.format(code, esri_name, conv_code) else: conv_auth_name = 'ESRI' conv_code = code sql = """INSERT INTO "conversion" VALUES('ESRI','%s','%s',NULL,'EPSG','9807','Transverse Mercator','EPSG','8801','Latitude of natural origin',%s,'EPSG','%s','EPSG','8802','Longitude of natural origin',%s,'EPSG','%s','EPSG','8805','Scale factor at natural origin',%s,'EPSG','9201','EPSG','8806','False easting',%s,'EPSG','%s','EPSG','8807','False northing',%s,'EPSG','%s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,%d);""" % ( code, conv_name, params['Latitude_Of_Origin'].value, params['Latitude_Of_Origin'].unit.uom_code, params['Central_Meridian'].value, params['Central_Meridian'].unit.uom_code, params['Scale_Factor'].value, params['False_Easting'].value, params['False_Easting'].unit.uom_code, params['False_Northing'].value, params['False_Northing'].unit.uom_code, deprecated) sql_extract = sql[sql.find('NULL'):] if conv_name != 'unnamed' or sql_extract not in MAP_CONVERSION_SQL_TO_CODE: all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', 'CONV_%s_USAGE','conversion','ESRI','%s','%s','%s','%s','%s');""" % (code, code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) MAP_CONVERSION_SQL_TO_CODE[sql_extract] = conv_code else: conv_code = MAP_CONVERSION_SQL_TO_CODE[sql_extract] sql = """INSERT INTO "projected_crs" VALUES('ESRI','%s','%s',NULL,'%s','%s','%s','%s','%s','%s',NULL,%d);""" % ( code, esri_name, cs.auth_name, cs.code, geogcs_auth_name, geogcs_code, conv_auth_name, conv_code, deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', 'PCRS_%s_USAGE','projected_crs','ESRI','%s','%s','%s','%s','%s');""" % (code, code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif (method in MAPPED_PROJCS or method in MAPPED_PROJCS_WITH_EXTRA_LOGIC) and code not in REQUIRES_ORIGINAL_WKT_DEF: params = get_parameter_values(parsed_conv_wkt2['CONVERSION'][1], global_linear_unit) cs = get_cs_from_false_easting_and_northing(params) if method in MAPPED_PROJCS: conversion_mapping = MAPPED_PROJCS[method] elif method == 'Lambert_Conformal_Conic' and 'Scale_Factor' in params: conversion_mapping = MAPPED_PROJCS_WITH_EXTRA_LOGIC['Lambert_Conformal_Conic'][ 'Lambert Conic Conformal (1SP)'] assert params['Scale_Factor'].unit.uom_code == '9201', 'Unhandled scale unit {}'.format( params['Scale_Factor'].unit.uom_code) elif method == 'Lambert_Conformal_Conic' and 'Standard_Parallel_2' in params: conversion_mapping = MAPPED_PROJCS_WITH_EXTRA_LOGIC['Lambert_Conformal_Conic']['Lambert Conic Conformal (2SP)'] else: assert False # additional validation required for these methods: if method == 'Hotine_Oblique_Mercator_Azimuth_Natural_Origin': assert params['Scale_Factor'].unit.uom_code == '9201', 'Unhandled scale unit {}'.format(params['Scale_Factor'].unit.uom_code) elif method == 'Cassini': assert params['Scale_Factor'].unit.uom_code == '9201', 'Unhandled scale unit {}'.format( params['Scale_Factor'].unit.uom_code) assert params['Scale_Factor'].value == 1.0 sql = insert_conversion_sql(esri_code=code, esri_name=esri_name, epsg_code=conversion_mapping.epsg_code, epsg_name=conversion_mapping.epsg_name, params=params, param_mapping=conversion_mapping.param_mapping, extent_auth_name=extent_auth_name, extent_code=extent_code, geogcs_auth_name=geogcs_auth_name, geogcs_code=geogcs_code, cs=cs, deprecated=bool(deprecated) ) all_sql.extend(sql) else: # TODO -- add more method mapping! print('Direct mapping for {} not yet implemented, falling back to default handling'.format(method)) sql = """INSERT INTO "projected_crs" VALUES('ESRI','%s','%s',NULL,NULL,NULL,'%s','%s',NULL,NULL,'%s',%d);""" % ( code, esri_name, geogcs_auth_name, geogcs_code, escape_literal(wkt), deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', 'PCRS_%s_USAGE','projected_crs','ESRI','%s','%s','%s','%s','%s');""" % (code, code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) if deprecated and code != latestWkid: mapDeprecatedToNonDeprecated[code] = latestWkid for deprecated in mapDeprecatedToNonDeprecated: code = deprecated latestWkid = mapDeprecatedToNonDeprecated[deprecated] if latestWkid in wkid_set: sql = """INSERT INTO "deprecation" VALUES('projected_crs','ESRI','%s','ESRI','%s','ESRI');""" % ( code, latestWkid) all_sql.append(sql) else: cursor.execute( "SELECT name, deprecated FROM projected_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row _, deprecated = src_row if not deprecated: sql = """INSERT INTO "deprecation" VALUES('projected_crs','ESRI','%s','EPSG','%s','ESRI');""" % ( code, latestWkid) all_sql.append(sql) aliases = import_syn(path_to_objedit / "projcs_syn.txt") for (new_name, old_name) in aliases: (auth, code) = map_projcs_esri_name_to_auth_code[new_name] sql = """INSERT INTO alias_name VALUES('projected_crs','%s','%s','%s','ESRI_OLD');""" % ( auth, code, escape_literal(old_name)) all_sql.append(sql) ######################## map_vdatum_esri_name_to_auth_code = {} map_vdatum_esri_to_parameters = {} def import_vdatum(): with open(path_to_csv / 'pe_list_vdatum.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row wkid = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if authority == 'EPSG': map_vdatum_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] cursor.execute( "SELECT name FROM vertical_datum WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] esri_name = row[idx_name] if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('vertical_datum','EPSG','%s','%s','ESRI');""" % ( wkid, escape_literal(esri_name)) all_sql.append(sql) old_esri_name = get_old_esri_name(esri_name) if old_esri_name != esri_name: sql = """INSERT INTO alias_name VALUES('vertical_datum','EPSG','%s','%s','ESRI');""" % ( wkid, escape_literal(old_esri_name)) all_sql.append(sql) else: assert authority.upper() == 'ESRI', row map_vdatum_esri_name_to_auth_code[esri_name] = ['ESRI', wkid] description = row[idx_description] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 map_vdatum_esri_to_parameters[wkid] = { 'esri_name': esri_name, 'description': description, 'deprecated': deprecated } # We cannot write it since we lack the area of use ######################## map_vertcs_esri_name_to_auth_code = {} def import_vertcs(): # Those 2 maps are used to fill the deprecation table map_code_to_authority = {} mapDeprecatedToNonDeprecated = {} with open(path_to_csv / 'pe_list_vertcs.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt = header.index('wkt') assert idx_wkt >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 idx_areaname = header.index('areaname') assert idx_areaname >= 0 idx_slat = header.index('slat') assert idx_slat >= 0 idx_nlat = header.index('nlat') assert idx_nlat >= 0 idx_llon = header.index('llon') assert idx_llon >= 0 idx_rlon = header.index('rlon') assert idx_rlon >= 0 assert nfields == 17 vdatum_written = set() sql = """-- vertical coordinate system for ellipsoidal height. Not really ISO 19111 valid...""" all_sql.append(sql) sql = """INSERT INTO "coordinate_system" VALUES('ESRI','ELLPS_HEIGHT_METRE','vertical',1);""" all_sql.append(sql) sql = """INSERT INTO "axis" VALUES('ESRI','ELLPS_HEIGHT_METRE','Ellipsoidal height','h','up','ESRI','ELLPS_HEIGHT_METRE',1,'EPSG','9001');""" all_sql.append(sql) while True: try: row = next(reader) except StopIteration: break assert len(row) in (nfields, nfields - 2), (row, len(row), nfields) code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if code == latestWkid and authority.upper() == 'ESRI': cursor.execute( "SELECT name FROM vertical_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() if src_row: src_name = src_row[0] modified_epsg_name = src_name.replace(' ', '_') if modified_epsg_name.upper() == esri_name.upper(): print("VertCRS ESRI:%s (%s) has the same name as EPSG:%s. Fixing authority to be EPSG" % (latestWkid, esri_name, latestWkid)) authority = "EPSG" map_code_to_authority[code] = authority.upper() if authority == 'EPSG': map_vertcs_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] cursor.execute( "SELECT name FROM vertical_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] esri_name = row[idx_name] if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('vertical_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) old_esri_name = get_old_esri_name(esri_name) if old_esri_name != esri_name: sql = """INSERT INTO alias_name VALUES('vertical_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(old_esri_name)) all_sql.append(sql) else: assert authority.upper() == 'ESRI', row wkt2 = row[idx_wkt2] vdatum_re = re.compile(r'.*VDATUM\[""?(.*?)""?.*') match = vdatum_re.match(wkt2) is_vdatum = True if match: datum_name = match.group(1) if datum_name not in map_vdatum_esri_name_to_auth_code: print('Skipping vertcs %s. Cannot find vertical datum %s' % ( str(row), datum_name)) sql = """-- Skipping vertcs %s. Cannot find vertical datum %s""" % ( esri_name, datum_name) all_sql.append(sql) continue datum_auth_name, datum_code = map_vdatum_esri_name_to_auth_code[datum_name] else: datum_re = re.compile(r'.*DATUM\[""?(.*?)""?.*') match = datum_re.match(wkt2) assert match is_vdatum = False datum_name = match.group(1) if datum_name not in map_datum_esri_name_to_auth_code: print('Skipping vertcs %s. Cannot find geodetic datum %s' % ( str(row), datum_name)) sql = """-- Skipping vertcs %s. Cannot find geodetic datum %s""" % ( esri_name, datum_name) all_sql.append(sql) continue datum_auth_name, datum_code = map_datum_esri_name_to_auth_code[datum_name] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 extent_auth_name, extent_code = find_extent( row[idx_areaname], row[idx_slat], row[idx_nlat], row[idx_llon], row[idx_rlon]) if not is_vdatum: new_datum_code = 'from_geogdatum_' + datum_auth_name + '_' + datum_code if new_datum_code not in vdatum_written: vdatum_written.add(new_datum_code) p = map_datum_esri_to_parameters[datum_code] datum_code = new_datum_code sql = """INSERT INTO "vertical_datum" VALUES('ESRI','%s','%s',NULL,NULL,NULL,NULL,NULL,NULL,%d);""" % ( datum_code, p['esri_name'], p['deprecated']) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','vertical_datum','ESRI','%s','%s','%s','%s','%s');""" % (datum_code, datum_code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) else: datum_code = new_datum_code datum_auth_name = 'ESRI' elif datum_auth_name == 'ESRI': # e.g Mean_Sea_Level_Hawaii datum is used both by ESRI:105795 'MSL_Hawaii_height_(m)' and SL_Hawaii_height_(ftUS)' if datum_code not in vdatum_written: vdatum_written.add(datum_code) p = map_vdatum_esri_to_parameters[datum_code] sql = """INSERT INTO "vertical_datum" VALUES('ESRI','%s','%s',NULL,NULL,NULL,NULL,NULL,NULL,%d);""" % ( datum_code, p['esri_name'], p['deprecated']) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','vertical_datum','ESRI','%s','%s','%s','%s','%s');""" % (datum_code, datum_code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) map_vertcs_esri_name_to_auth_code[esri_name] = ['ESRI', code] parsed_wkt2 = parse_wkt_array(wkt2) assert not set(k for k in parsed_wkt2['VERTCRS'][1].keys() if k not in ('CS','AXIS','VDATUM','DATUM')), 'Unhandled parameter in VERTCRS: {}'.format(list(parsed_wkt2['VERTCRS'][1].keys())) assert parsed_wkt2['VERTCRS'][1]['CS'] == ['vertical', '1'], 'Unhandled vertcrs CS: {}'.format(parsed_wkt2['VERTCRS'][1]['CS']) axis = parsed_wkt2['VERTCRS'][1]['AXIS'] is_ellipsoidal_height = (axis[:2] == ['Ellipsoidal height (h)', 'up']) assert axis[:2] == ['Gravity-related height (H)', 'up'] or is_ellipsoidal_height, 'Unhandled vertcrs AXIS: {}'.format(axis) vertical_unit = axis[2]['UNIT_NAME'] cs_auth = 'EPSG' if vertical_unit == 'Meter': cs_code = 6499 elif vertical_unit == 'Foot': cs_code = 1030 elif vertical_unit == 'Foot_US': cs_code = 6497 else: assert False, ('unknown coordinate system for %s' % str(row)) if is_ellipsoidal_height or not is_vdatum: assert cs_code == 6499 cs_auth = 'ESRI' cs_code = 'ELLPS_HEIGHT_METRE' sql = """INSERT INTO "vertical_crs" VALUES('ESRI','%s','%s',NULL,'%s','%s','%s','%s',%d);""" % ( code, esri_name, cs_auth, cs_code, datum_auth_name, datum_code, deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','vertical_crs','ESRI','%s','%s','%s','%s','%s');""" % (code, code, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) if deprecated and code != latestWkid: cursor.execute( "SELECT name, deprecated FROM vertical_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row _, deprecated = src_row if not deprecated: sql = """INSERT INTO "deprecation" VALUES('vertical_crs','ESRI','%s','EPSG','%s','ESRI');""" % ( code, latestWkid) all_sql.append(sql) elif deprecated and code != latestWkid: mapDeprecatedToNonDeprecated[code] = latestWkid for code in mapDeprecatedToNonDeprecated: replacement_code = mapDeprecatedToNonDeprecated[code] if replacement_code in map_code_to_authority: sql = """INSERT INTO "deprecation" VALUES('vertical_crs','ESRI','%s','%s','%s','ESRI');""" % ( code, map_code_to_authority[replacement_code], replacement_code) all_sql.append(sql) aliases = import_syn(path_to_objedit / "vdatum_syn.txt") for (new_name, old_name) in aliases: (auth, code) = map_vdatum_esri_name_to_auth_code[new_name] sql = """INSERT INTO alias_name VALUES('vertical_datum','%s','%s','%s','ESRI_OLD');""" % ( auth, code, escape_literal(old_name)) all_sql.append(sql) aliases = import_syn(path_to_objedit / "vertcs_syn.txt") for (new_name, old_name) in aliases: (auth, code) = map_vertcs_esri_name_to_auth_code[new_name] sql = """INSERT INTO alias_name VALUES('vertical_crs','%s','%s','%s','ESRI_OLD');""" % ( auth, code, escape_literal(old_name)) all_sql.append(sql) ######################## map_compoundcrs_esri_name_to_auth_code = {} def import_hvcoordsys(): with open(path_to_csv / 'pe_list_hvcoordsys.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 idx_areaname = header.index('areaname') assert idx_areaname >= 0 idx_slat = header.index('slat') assert idx_slat >= 0 idx_nlat = header.index('nlat') assert idx_nlat >= 0 idx_llon = header.index('llon') assert idx_llon >= 0 idx_rlon = header.index('rlon') assert idx_rlon >= 0 while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row code = row[idx_wkid] latestWkid = row[idx_latestWkid] authority = row[idx_authority] esri_name = row[idx_name] if authority == 'Esri' and code in ('9897',): # .csv file pretents this is a Esri code, but it is a EPSG one authority = 'EPSG' if authority == 'EPSG': map_compoundcrs_esri_name_to_auth_code[esri_name] = [ 'EPSG', latestWkid] cursor.execute( "SELECT name FROM compound_crs WHERE auth_name = 'EPSG' AND code = ?", (latestWkid,)) src_row = cursor.fetchone() assert src_row, row src_name = src_row[0] esri_name = row[idx_name] if src_name != esri_name: sql = """INSERT INTO alias_name VALUES('compound_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(esri_name)) all_sql.append(sql) old_esri_name = get_old_esri_name(esri_name) if old_esri_name != esri_name: sql = """INSERT INTO alias_name VALUES('compound_crs','EPSG','%s','%s','ESRI');""" % ( code, escape_literal(old_esri_name)) all_sql.append(sql) else: assert False, row # no ESRI specific entries at that time ! aliases = import_syn(path_to_objedit / "hvcoordsys_syn.txt") for (new_name, old_name) in aliases: (auth, code) = map_compoundcrs_esri_name_to_auth_code[new_name] sql = """INSERT INTO alias_name VALUES('compound_crs','%s','%s','%s','ESRI_OLD');""" % ( auth, code, escape_literal(old_name)) all_sql.append(sql) ######################## def get_parameter(wkt, param_name): needle = ',PARAMETER["' + param_name + '",' pos = wkt.find(needle) assert pos >= 0, wkt pos += len(needle) end_pos = wkt[pos:].find(']') assert end_pos >= 0, (wkt, wkt[pos:]) end_pos += pos return wkt[pos:end_pos] def import_geogtran(): with open(path_to_csv / 'pe_list_geogtran.csv', 'rt') as csvfile: reader = csv.reader(csvfile) header = next(reader) nfields = len(header) idx_wkid = header.index('wkid') assert idx_wkid >= 0 idx_latestWkid = header.index('latestWkid') assert idx_latestWkid >= 0 idx_name = header.index('name') assert idx_name >= 0 idx_description = header.index('description') assert idx_description >= 0 idx_wkt = header.index('wkt') assert idx_wkt >= 0 idx_wkt2 = header.index('wkt2') assert idx_wkt2 >= 0 idx_authority = header.index('authority') assert idx_authority >= 0 idx_deprecated = header.index('deprecated') assert idx_deprecated >= 0 idx_areaname = header.index('areaname') assert idx_areaname >= 0 idx_slat = header.index('slat') assert idx_slat >= 0 idx_nlat = header.index('nlat') assert idx_nlat >= 0 idx_llon = header.index('llon') assert idx_llon >= 0 idx_rlon = header.index('rlon') assert idx_rlon >= 0 idx_accuracy = header.index('accuracy') assert idx_accuracy >= 0 set_names = set() while True: try: row = next(reader) except StopIteration: break assert len(row) == nfields, row wkid = row[idx_wkid] authority = row[idx_authority] esri_name = row[idx_name] wkt2 = row[idx_wkt2] assert row[idx_deprecated] in ('yes', 'codechange', 'no') deprecated = 1 if row[idx_deprecated] in ('yes', 'codechange') else 0 if authority == 'EPSG': map_compoundcrs_esri_name_to_auth_code[esri_name] = [ 'EPSG', wkid] cursor.execute( "SELECT name, table_name FROM coordinate_operation_view WHERE auth_name = 'EPSG' AND code = ?", (wkid,)) src_row = cursor.fetchone() if not src_row: if 'Molodensky_Badekas' in wkt2: # print('Skipping GEOGTRAN %s (EPSG source) since it uses a non-supported yet supported method'% esri_name) assert False # no longer present in db if 'NADCON5' in wkt2: print('Skipping NADCON5 %s (EPSG source) since it uses a non-supported yet supported method' % esri_name) continue assert src_row, row _, table_name = src_row # Insert alias sql = f"INSERT INTO \"alias_name\" VALUES('{table_name}','EPSG',{wkid},'{esri_name}','ESRI');" all_sql.append(sql) else: # We don't want to import ESRI deprecated transformations # (there are a lot), do we ? if deprecated: # print('Skipping deprecated GEOGTRAN %s' % esri_name) continue if esri_name in set_names: print(f'Skipping ESRI:{wkid} since transformation with same name {esri_name} already found!') continue set_names.add(esri_name) parsed_wkt2 = parse_wkt_array(wkt2) assert 'COORDINATEOPERATION' in parsed_wkt2 src_crs_name = parsed_wkt2['COORDINATEOPERATION'][1]['SOURCECRS']['GEOGCRS'][0] dst_crs_name = parsed_wkt2['COORDINATEOPERATION'][1]['TARGETCRS']['GEOGCRS'][0] assert src_crs_name in map_geogcs_esri_name_to_auth_code, ( src_crs_name, row) src_crs_auth_name, src_crs_code = map_geogcs_esri_name_to_auth_code[src_crs_name] assert dst_crs_name in map_geogcs_esri_name_to_auth_code, ( dst_crs_name, row) dst_crs_auth_name, dst_crs_code = map_geogcs_esri_name_to_auth_code[dst_crs_name] method = parsed_wkt2['COORDINATEOPERATION'][1]['METHOD'] is_longitude_rotation = method == "Longitude_Rotation" if is_longitude_rotation: # skip it as it is automatically handled by PROJ continue is_cf = method == "Coordinate_Frame" is_pv = method == "Position_Vector" is_geocentric_translation = method == "Geocentric_Translation" is_geog2d_offset = method == "Geographic_2D_Offset" is_null = method == "Null" is_unitchange = method == "Unit_Change" is_nadcon = method == "NADCON" is_ntv2 = method == "NTv2" is_geocon = method == "GEOCON" is_harn = method == "HARN" is_molodensky_badekas_cf = method == "Molodensky_Badekas" is_molodensky_badekas_pv = method == "Molodensky_Badekas_Position_Vector" is_Time_Based_Helmert_Position_Vector = method == "Time_Based_Helmert_Position_Vector" is_Time_Based_Helmert_Coordinate_Frame = method == "Time_Based_Helmert_Coordinate_Frame" assert is_cf or is_pv or is_geocentric_translation or is_molodensky_badekas_cf or is_molodensky_badekas_pv or is_nadcon or is_geog2d_offset or is_ntv2 or is_geocon or is_null or is_harn or is_unitchange or is_Time_Based_Helmert_Position_Vector or is_Time_Based_Helmert_Coordinate_Frame, row extent_auth_name, extent_code = find_extent( row[idx_areaname], row[idx_slat], row[idx_nlat], row[idx_llon], row[idx_rlon]) accuracy = row[idx_accuracy] if float(accuracy) == 999: accuracy = 'NULL' if is_cf or is_pv: x = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][0] x_axis_translation_unit = get_wkt_unit(*parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][1]['LENGTHUNIT']) y = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][0] y_axis_translation_unit = get_wkt_unit(*parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][1]['LENGTHUNIT']) z = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][0] z_axis_translation_unit = get_wkt_unit(*parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][1]['LENGTHUNIT']) assert x_axis_translation_unit.uom_auth_name == y_axis_translation_unit.uom_auth_name == z_axis_translation_unit.uom_auth_name, 'Cannot handle different translation axis authorities' assert x_axis_translation_unit.uom_code == y_axis_translation_unit.uom_code == z_axis_translation_unit.uom_code, 'Cannot handle different translation axis unit codes' rx = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation'][0] x_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation'][1]['ANGLEUNIT']) ry = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation'][0] y_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation'][1]['ANGLEUNIT']) rz = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation'][0] z_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation'][1]['ANGLEUNIT']) assert x_axis_rotation_unit.uom_auth_name == y_axis_rotation_unit.uom_auth_name == z_axis_rotation_unit.uom_auth_name, 'Cannot handle different rotation axis authorities' assert x_axis_rotation_unit.uom_code == y_axis_rotation_unit.uom_code == z_axis_rotation_unit.uom_code, 'Cannot handle different rotation axis unit codes' s = parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference'][0] scale_difference_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference'][1]['SCALEUNIT']) if is_cf: method_code = '9607' method_name = 'Coordinate Frame rotation (geog2D domain)' else: method_code = '9606' method_name = 'Position Vector transformation (geog2D domain)' sql = "INSERT INTO \"helmert_transformation\" VALUES('ESRI','{code}','{name}',NULL,'EPSG','{method_code}','{method_name}','{source_crs_auth_name}'," \ "'{source_crs_code}','{target_crs_auth_name}','{target_crs_code}',{accuracy},{tx},{ty},{tz},'{translation_uom_auth_name}','{translation_uom_code}'," \ "{rx},{ry},{rz},'{rotation_uom_auth_name}','{rotation_uom_code}',{scale_difference},'{scale_difference_uom_auth_name}','{scale_difference_uom_code}'," \ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,{deprecated});".format( code=wkid, name=esri_name, method_code=method_code, method_name=method_name, source_crs_auth_name=src_crs_auth_name, source_crs_code=src_crs_code, target_crs_auth_name=dst_crs_auth_name, target_crs_code=dst_crs_code, accuracy=accuracy, tx=x, ty=y, tz=z, translation_uom_auth_name=x_axis_translation_unit.uom_auth_name, translation_uom_code=x_axis_translation_unit.uom_code, rx=rx, ry=ry, rz=rz, rotation_uom_auth_name=x_axis_rotation_unit.uom_auth_name, rotation_uom_code=x_axis_rotation_unit.uom_code, scale_difference=s, scale_difference_uom_auth_name=scale_difference_unit.uom_auth_name, scale_difference_uom_code=scale_difference_unit.uom_code, deprecated=deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','helmert_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif is_molodensky_badekas_cf or is_molodensky_badekas_pv: x = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][0] x_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][1]['LENGTHUNIT']) y = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][0] y_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][1]['LENGTHUNIT']) z = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][0] z_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][1]['LENGTHUNIT']) assert x_axis_translation_unit.uom_auth_name == y_axis_translation_unit.uom_auth_name == z_axis_translation_unit.uom_auth_name, 'Cannot handle different translation axis authorities' assert x_axis_translation_unit.uom_code == y_axis_translation_unit.uom_code == z_axis_translation_unit.uom_code, 'Cannot handle different translation axis unit codes' rx = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation'][0] x_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation'][1]['ANGLEUNIT']) ry = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation'][0] y_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation'][1]['ANGLEUNIT']) rz = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation'][0] z_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation'][1]['ANGLEUNIT']) assert x_axis_rotation_unit.uom_auth_name == y_axis_rotation_unit.uom_auth_name == z_axis_rotation_unit.uom_auth_name, 'Cannot handle different rotation axis authorities' assert x_axis_rotation_unit.uom_code == y_axis_rotation_unit.uom_code == z_axis_rotation_unit.uom_code, 'Cannot handle different rotation axis unit codes' s = parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference'][0] scale_difference_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference'][1]['SCALEUNIT']) px = parsed_wkt2['COORDINATEOPERATION'][1]['X_Coordinate_of_Rotation_Origin'][0] x_coordinate_of_rotation_origin_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Coordinate_of_Rotation_Origin'][1]['LENGTHUNIT']) py = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Coordinate_of_Rotation_Origin'][0] y_coordinate_of_rotation_origin_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Coordinate_of_Rotation_Origin'][1]['LENGTHUNIT']) pz = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Coordinate_of_Rotation_Origin'][0] z_coordinate_of_rotation_origin_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Coordinate_of_Rotation_Origin'][1]['LENGTHUNIT']) assert x_coordinate_of_rotation_origin_unit.uom_auth_name == y_coordinate_of_rotation_origin_unit.uom_auth_name == z_coordinate_of_rotation_origin_unit.uom_auth_name, 'Cannot handle different coordinate of rotation axis authorities' assert x_coordinate_of_rotation_origin_unit.uom_code == y_coordinate_of_rotation_origin_unit.uom_code == z_coordinate_of_rotation_origin_unit.uom_code, 'Cannot handle different coordinate of rotation axis unit codes' if is_molodensky_badekas_cf: method_code = '9636' method_name = 'Molodensky-Badekas (CF geog2D domain)' elif is_molodensky_badekas_pv: method_code = '1063' method_name = 'Molodensky-Badekas (PV geog2D domain)' else: assert False sql = "INSERT INTO \"helmert_transformation\" VALUES('ESRI','{code}','{name}',NULL,'EPSG','{method_code}','{method_name}'," \ "'{source_crs_auth_name}','{source_crs_code}','{target_crs_auth_name}','{target_crs_code}',{accuracy},{tx},{ty},{tz}," \ "'{translation_uom_auth_name}','{translation_uom_code}',{rx},{ry},{rz},'{rotation_uom_auth_name}','{rotation_uom_code}'," \ "{scale_difference},'{scale_difference_uom_auth_name}','{scale_difference_uom_code}',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," \ "NULL,NULL,NULL,NULL,{px},{py},{pz},'{pivot_uom_auth_name}','{pivot_uom_code}',NULL,{deprecated});".format( code=wkid, name=esri_name, method_code=method_code, method_name=method_name, source_crs_auth_name=src_crs_auth_name, source_crs_code=src_crs_code, target_crs_auth_name=dst_crs_auth_name, target_crs_code=dst_crs_code, accuracy=accuracy, tx=x, ty=y, tz=z, translation_uom_auth_name=x_axis_translation_unit.uom_auth_name, translation_uom_code=x_axis_translation_unit.uom_code, rx=rx, ry=ry, rz=rz, rotation_uom_auth_name=x_axis_rotation_unit.uom_auth_name, rotation_uom_code=x_axis_rotation_unit.uom_code, scale_difference=s, scale_difference_uom_auth_name=scale_difference_unit.uom_auth_name, scale_difference_uom_code=scale_difference_unit.uom_code, px=px, py=py, pz=pz, pivot_uom_auth_name=x_coordinate_of_rotation_origin_unit.uom_auth_name, pivot_uom_code=x_coordinate_of_rotation_origin_unit.uom_code, deprecated=deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','helmert_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif is_geocentric_translation: x = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][0] x_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][1]['LENGTHUNIT']) y = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][0] y_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][1]['LENGTHUNIT']) z = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][0] z_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][1]['LENGTHUNIT']) assert x_axis_translation_unit.cs.auth_name == y_axis_translation_unit.cs.auth_name == z_axis_translation_unit.cs.auth_name, 'Cannot handle different translation axis authorities' assert x_axis_translation_unit.uom_code == y_axis_translation_unit.uom_code == z_axis_translation_unit.uom_code, 'Cannot handle different translation axis unit codes' method_code = '9603' method_name = 'Geocentric translations (geog2D domain)' sql = "INSERT INTO \"helmert_transformation\" VALUES('ESRI','{code}','{name}',NULL,'EPSG','{method_code}','{method_name}','{source_crs_auth_name}'," \ "'{source_crs_code}','{target_crs_auth_name}','{target_crs_code}',{accuracy},{tx},{ty},{tz},'{translation_uom_auth_name}','{translation_uom_code}',"\ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," \ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,{deprecated});".format( code=wkid, name=esri_name, method_code=method_code, method_name=method_name, source_crs_auth_name=src_crs_auth_name, source_crs_code=src_crs_code, target_crs_auth_name=dst_crs_auth_name, target_crs_code=dst_crs_code, accuracy=accuracy, tx=x, ty=y, tz=z, translation_uom_auth_name=x_axis_translation_unit.uom_auth_name, translation_uom_code=x_axis_translation_unit.uom_code, deprecated=deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','helmert_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif is_Time_Based_Helmert_Position_Vector or is_Time_Based_Helmert_Coordinate_Frame: x = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][0] x_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation'][1]['LENGTHUNIT']) y = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][0] y_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation'][1]['LENGTHUNIT']) z = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][0] z_axis_translation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation'][1]['LENGTHUNIT']) assert x_axis_translation_unit.uom_auth_name == y_axis_translation_unit.uom_auth_name == z_axis_translation_unit.uom_auth_name, 'Cannot handle different translation axis authorities' assert x_axis_translation_unit.uom_code == y_axis_translation_unit.uom_code == z_axis_translation_unit.uom_code, 'Cannot handle different translation axis unit codes' rx = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation'][0] x_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation'][1]['ANGLEUNIT']) ry = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation'][0] y_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation'][1]['ANGLEUNIT']) rz = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation'][0] z_axis_rotation_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation'][1]['ANGLEUNIT']) assert x_axis_rotation_unit.uom_auth_name == y_axis_rotation_unit.uom_auth_name == z_axis_rotation_unit.uom_auth_name, 'Cannot handle different rotation axis authorities' assert x_axis_rotation_unit.uom_code == y_axis_rotation_unit.uom_code == z_axis_rotation_unit.uom_code, 'Cannot handle different rotation axis unit codes' s = parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference'][0] scale_difference_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference'][1]['SCALEUNIT']) rate_x = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation_Rate'][0] x_axis_translation_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Translation_Rate'][1]['LENGTHUNIT'], is_rate=True) rate_y = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation_Rate'][0] y_axis_translation_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Translation_Rate'][1]['LENGTHUNIT'], is_rate=True) rate_z = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation_Rate'][0] z_axis_translation_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Translation_Rate'][1]['LENGTHUNIT'], is_rate=True) assert x_axis_translation_rate_unit.uom_auth_name == y_axis_translation_rate_unit.uom_auth_name == z_axis_translation_rate_unit.uom_auth_name, 'Cannot handle different translation rate axis authorities' assert x_axis_translation_rate_unit.uom_code == y_axis_translation_rate_unit.uom_code == z_axis_translation_rate_unit.uom_code, 'Cannot handle different translation rate axis unit codes' rate_rx = parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation_Rate'][0] x_axis_rotation_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['X_Axis_Rotation_Rate'][1]['ANGLEUNIT'], is_rate=True) rate_ry = parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation_Rate'][0] y_axis_rotation_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Y_Axis_Rotation_Rate'][1]['ANGLEUNIT'], is_rate=True) rate_rz = parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation_Rate'][0] z_axis_rotation_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Z_Axis_Rotation_Rate'][1]['ANGLEUNIT'], is_rate=True) assert x_axis_rotation_rate_unit.uom_auth_name == y_axis_rotation_rate_unit.uom_auth_name == z_axis_rotation_rate_unit.uom_auth_name, 'Cannot handle different rotation rate axis authorities' assert x_axis_rotation_rate_unit.uom_code == y_axis_rotation_rate_unit.uom_code == z_axis_rotation_rate_unit.uom_code, 'Cannot handle different rotation rate axis unit codes' rate_s = parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference_Rate'][0] scale_difference_rate_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Scale_Difference_Rate'][1]['SCALEUNIT'], is_rate=True) reference_time = parsed_wkt2['COORDINATEOPERATION'][1]['Reference_Time'][0] reference_time_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Reference_Time'][1]['SCALEUNIT']) if reference_time_unit.uom_auth_name == 'EPSG' and reference_time_unit.uom_code == '9201': # convert "Unity" values for Reference_Time to "years". The helmert_transformation table requires # a time type uom for epochs reference_time_unit.uom_code = '1029' if is_Time_Based_Helmert_Coordinate_Frame: method_code = '1057' method_name = 'Time-dependent Coordinate Frame rotation (geog2D)' else: method_code = '1054' method_name = 'Time-dependent Position Vector tfm (geog2D)' sql = "INSERT INTO \"helmert_transformation\" VALUES('ESRI','{code}','{name}',NULL,'EPSG','{method_code}','{method_name}'," \ "'{source_crs_auth_name}','{source_crs_code}','{target_crs_auth_name}','{target_crs_code}'," \ "{accuracy},{tx},{ty},{tz},'{translation_uom_auth_name}','{translation_uom_code}'," \ "{rx},{ry},{rz},'{rotation_uom_auth_name}','{rotation_uom_code}',{scale_difference}," \ "'{scale_difference_uom_auth_name}','{scale_difference_uom_code}',{rate_tx},{rate_ty},{rate_tz}," \ "'{rate_translation_uom_auth_name}','{rate_translation_uom_code}',{rate_rx},{rate_ry},{rate_rz}," \ "'{rate_rotation_uom_auth_name}','{rate_rotation_uom_code}',{rate_scale_difference},"\ "'{rate_scale_difference_uom_auth_name}','{rate_scale_difference_uom_code}',{epoch},"\ "'{epoch_uom_auth_name}','{epoch_uom_code}',NULL,NULL,NULL,NULL,NULL,NULL,{deprecated});".format( code=wkid, name=esri_name, method_code=method_code, method_name=method_name, source_crs_auth_name=src_crs_auth_name, source_crs_code=src_crs_code, target_crs_auth_name=dst_crs_auth_name, target_crs_code=dst_crs_code, accuracy=accuracy, tx=x, ty=y, tz=z, translation_uom_auth_name=x_axis_translation_unit.uom_auth_name, translation_uom_code=x_axis_translation_unit.uom_code, rx=rx, ry=ry, rz=rz, rotation_uom_auth_name=x_axis_rotation_unit.uom_auth_name, rotation_uom_code=x_axis_rotation_unit.uom_code, scale_difference=s, scale_difference_uom_auth_name=scale_difference_unit.uom_auth_name, scale_difference_uom_code=scale_difference_unit.uom_code, rate_tx=rate_x, rate_ty=rate_y, rate_tz=rate_z, rate_translation_uom_auth_name=x_axis_translation_rate_unit.uom_auth_name, rate_translation_uom_code=x_axis_translation_rate_unit.uom_code, rate_rx=rate_rx, rate_ry=rate_ry, rate_rz=rate_rz, rate_rotation_uom_auth_name=x_axis_rotation_rate_unit.uom_auth_name, rate_rotation_uom_code=x_axis_rotation_rate_unit.uom_code, rate_scale_difference=rate_s, rate_scale_difference_uom_auth_name=scale_difference_rate_unit.uom_auth_name, rate_scale_difference_uom_code=scale_difference_rate_unit.uom_code, epoch=reference_time, epoch_uom_auth_name=reference_time_unit.uom_auth_name, epoch_uom_code=reference_time_unit.uom_code, deprecated=deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','helmert_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif is_geog2d_offset: # The only occurrence is quite boring: from NTF(Paris) to NTF. # But interestingly the longitude offset value is not # completely exactly the value of the Paris prime meridian long_offset = parsed_wkt2['COORDINATEOPERATION'][1]['Longitude_Offset'][0] longitude_offset_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Longitude_Offset'][1]['ANGLEUNIT']) lat_offset = parsed_wkt2['COORDINATEOPERATION'][1]['Latitude_Offset'][0] latitude_offset_unit = get_wkt_unit( *parsed_wkt2['COORDINATEOPERATION'][1]['Latitude_Offset'][1]['ANGLEUNIT']) sql = "INSERT INTO \"other_transformation\" VALUES('ESRI','{code}','{name}',NULL,'EPSG','9619','Geographic2D offsets',"\ "'{source_crs_auth_name}','{source_crs_code}','{target_crs_auth_name}','{target_crs_code}',{accuracy},"\ "'EPSG','8601','Latitude offset',{param1_value},'{param1_uom_auth_name}','{param1_uom_code}'," \ "'EPSG','8602','Longitude offset',{param2_value},'{param2_uom_auth_name}','{param2_uom_code}',NULL,NULL,NULL,NULL,NULL,NULL,"\ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"\ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"\ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"\ "NULL,NULL,NULL,NULL,"\ "{deprecated});".format( code=wkid, name=esri_name, source_crs_auth_name=src_crs_auth_name, source_crs_code=src_crs_code, target_crs_auth_name=dst_crs_auth_name, target_crs_code=dst_crs_code, accuracy=accuracy, param1_value=lat_offset, param1_uom_auth_name=latitude_offset_unit.uom_auth_name, param1_uom_code=latitude_offset_unit.uom_code, param2_value=long_offset, param2_uom_auth_name=longitude_offset_unit.uom_auth_name, param2_uom_code=longitude_offset_unit.uom_code, deprecated=deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','other_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif is_null: long_offset = '0' lat_offset = '0' assert set(parsed_wkt2['COORDINATEOPERATION'][1].keys()) == {'SOURCECRS', 'METHOD', 'TARGETCRS', 'OPERATIONACCURACY'}, set(parsed_wkt2['COORDINATEOPERATION'][1].keys()) sql = """INSERT INTO "other_transformation" VALUES('ESRI','%s','%s',NULL,'EPSG','9619','Geographic2D offsets','%s','%s','%s','%s',%s,'EPSG','8601','Latitude offset',%s,'EPSG','9104','EPSG','8602','Longitude offset',%s,'EPSG','9104',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,%d);""" % ( wkid, esri_name, src_crs_auth_name, src_crs_code, dst_crs_auth_name, dst_crs_code, accuracy, lat_offset, long_offset, deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','other_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) elif is_unitchange: # Automatically handled by PROJ. Not worth importing continue else: assert set(k for k in parsed_wkt2['COORDINATEOPERATION'][1].keys() if k != 'OPERATIONACCURACY') == {'SOURCECRS', 'METHOD', 'TARGETCRS', 'PARAMETERFILE'}, set(parsed_wkt2['COORDINATEOPERATION'][1].keys()) filename = parsed_wkt2['COORDINATEOPERATION'][1]['PARAMETERFILE'] cursor.execute( "SELECT g.name, g.grid_name FROM grid_transformation g JOIN usage u ON u.object_table_name = 'grid_transformation' AND u.object_auth_name = g.auth_name AND u.object_code = g.code JOIN extent e ON u.extent_auth_name = e.auth_name AND u.extent_code = e.code WHERE g.auth_name != 'ESRI' AND g.source_crs_auth_name = ? AND g.source_crs_code = ? AND g.target_crs_auth_name = ? AND g.target_crs_code = ? AND e.auth_name = ? AND e.code = ?", (src_crs_auth_name, src_crs_code, dst_crs_auth_name, dst_crs_code, extent_auth_name, extent_code)) src_row = cursor.fetchone() if src_row: print('A grid_transformation (%s, using grid %s) is already known for the equivalent of %s (%s:%s --> %s:%s) for area %s, which uses grid %s. Skipping it' % (src_row[0], src_row[1], esri_name, src_crs_auth_name, src_crs_code, dst_crs_auth_name, dst_crs_code, row[idx_areaname], filename)) continue sql = """INSERT INTO "grid_transformation" VALUES('ESRI','%s','%s',NULL,'EPSG','9615','NTv2','%s','%s','%s','%s',%s,'EPSG','8656','Latitude and longitude difference file','%s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,%d);""" % ( wkid, esri_name, src_crs_auth_name, src_crs_code, dst_crs_auth_name, dst_crs_code, accuracy, filename, deprecated) all_sql.append(sql) sql = """INSERT INTO "usage" VALUES('ESRI', '%s_USAGE','grid_transformation','ESRI','%s','%s','%s','%s','%s');""" % (wkid, wkid, extent_auth_name, extent_code, 'EPSG', '1024') all_sql.append(sql) global manual_grids if ("'" + filename + "'") not in manual_grids: print('not handled grid: ' + filename) manual_grids += "-- '" + filename + "': no mapping\n" import_linunit() import_spheroid() import_prime_meridian() import_datum() import_geogcs() import_projcs() import_vdatum() import_vertcs() import_hvcoordsys() # compoundcrs import_geogtran() # transformations between GeogCRS script_dir_name = os.path.dirname(os.path.realpath(__file__)) sql_dir_name = os.path.join(os.path.dirname(script_dir_name), 'data', 'sql') old_aliases = """------------------- -- ESRI old aliases ------------------- -- Changed in ArcGIS Pro 3.0 INSERT INTO alias_name VALUES('geodetic_datum','EPSG','6181','D_Luxembourg_1930','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','4181','GCS_Luxembourg_1930','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','2169','Luxembourg_1930_Gauss','ESRI_OLD'); INSERT INTO alias_name VALUES('vertical_crs','EPSG','5774','NG_L','ESRI_OLD'); -- Changed in ArcGIS Pro 3.2 INSERT INTO alias_name VALUES('geodetic_datum','EPSG','1064','D_SIRGAS-Chile','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_datum','EPSG','1254','D_SIRGAS-Chile','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_datum','EPSG','6737','D_Korea_2000','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','4737','GCS_Korea_2000','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','4927','Korea_2000_3D','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','5342','POSGAR_3D','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','5360','GCS_SIRGAS-Chile','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9183','SIRGAS-Chile_3D','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9184','GCS_SIRGAS-Chile','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9308','ATRF2014_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9332','KSA-GRF17_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9379','IGb14_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9469','SRGI2013_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9546','LTF2004(G)_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9695','REDGEOMIN_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9701','ETRF2000-PL_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9754','WGS_84_(G2139)_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','9893','LUREF_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('geodetic_crs','EPSG','20040','SIRGAS-Chile_2021_(3D)','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','2176','ETRS_1989_Poland_CS2000_Zone_5','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','2177','ETRS_1989_Poland_CS2000_Zone_6','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','2178','ETRS_1989_Poland_CS2000_Zone_7','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','2179','ETRS_1989_Poland_CS2000_Zone_8','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','2180','ETRS_1989_Poland_CS92','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5179','Korea_2000_Korea_Unified_Coordinate_System','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5180','Korea_2000_Korea_West_Belt','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5181','Korea_2000_Korea_Central_Belt','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5182','Korea_2000_Korea_Central_Belt_Jeju','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5183','Korea_2000_Korea_East_Belt','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5184','Korea_2000_Korea_East_Sea_Belt','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5185','Korea_2000_Korea_West_Belt_2010','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5186','Korea_2000_Korea_Central_Belt_2010','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5187','Korea_2000_Korea_East_Belt_2010','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5188','Korea_2000_Korea_East_Sea_Belt_2010','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5361','SIRGAS-Chile_UTM_Zone_19S','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','5362','SIRGAS-Chile_UTM_Zone_18S','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','6622','NAD_1983_CSRS_Quebec_Lambert','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','6624','NAD_1983_CSRS_Quebec_Albers','ESRI_OLD'); INSERT INTO alias_name VALUES('projected_crs','EPSG','9377','MAGNA-SIRGAS_Origen-Nacional','ESRI_OLD'); INSERT INTO alias_name VALUES('vertical_crs','EPSG','5193','Incheon_height','ESRI_OLD'); INSERT INTO alias_name VALUES('compound_crs','EPSG','9462','GDA2020_+_AVWS_height','ESRI_OLD'); INSERT INTO alias_name VALUES('compound_crs','EPSG','9463','GDA2020_+_AHD_height','ESRI_OLD'); INSERT INTO alias_name VALUES('compound_crs','EPSG','9464','GDA94_+_AHD_height','ESRI_OLD'); -- Changed in ArcGIS Pro 3.3 INSERT INTO alias_name VALUES('vertical_datum','EPSG','5181','Deutches_Haupthoehennetz_1992','ESRI_OLD'); INSERT INTO alias_name VALUES('vertical_datum','EPSG','5182','Deutches_Haupthoehennetz_1985','ESRI_OLD'); -- Changed in ArcGIS Pro 3.4 INSERT INTO alias_name VALUES('projected_crs','EPSG','9895','LUREF_Luxembourg_TM_(3D)','ESRI_OLD'); -- Changed in ArcGIS Pro 3.5 INSERT INTO alias_name VALUES('vertical_crs','EPSG','3855','EGM2008_Geoid','ESRI_OLD'); INSERT INTO alias_name VALUES('vertical_crs','EPSG','5773','EGM96_Geoid','ESRI_OLD'); INSERT INTO alias_name VALUES('vertical_crs','EPSG','5798','EGM84_Geoid','ESRI_OLD'); """ f = open(os.path.join(sql_dir_name, 'esri') + '.sql', 'wb') f.write("--- This file has been generated by scripts/build_db_from_esri.py. DO NOT EDIT !\n\n".encode('UTF-8')) for sql in all_sql: f.write((sql + '\n').encode('UTF-8')) f.write(manual_grids.encode('UTF-8')) f.write(old_aliases.encode('UTF-8')) f.close() print('') print('Finished !') print('NOTE: adding into metadata: ESRI.VERSION = %s. Update if needed !' % version) proj-9.8.1/scripts/gen_html_coverage.sh000775 001750 001750 00000001706 15166171715 020155 0ustar00eveneven000000 000000 #!/bin/sh set -eu # To filter only on c++ stuff: # scripts/gen_html_coverage.sh -ext "*.cpp,*.hh" SCRIPT_DIR=$(dirname "$0") case $SCRIPT_DIR in "/"*) ;; ".") SCRIPT_DIR=$(pwd) ;; *) SCRIPT_DIR=$(pwd)/$(dirname "$0") ;; esac FILTER="" if test $# -ge 1; then if test "$1" = "--help"; then echo "Usage: gen_html_coverage.sh [--help] [-ext \"ext1,...\"]" exit fi if test "$1" = "-ext"; then FILTER="$2" shift shift fi if test $# -ge 1; then echo "Invalid option: $1" echo "Usage: gen_html_coverage.sh [--help] [-ext \"ext1,...\"]" exit fi fi rm -rf coverage_html lcov --directory src --directory include --capture --output-file proj.info "$SCRIPT_DIR"/filter_lcov_info.py "$FILTER" < proj.info > proj.info.filtered mv proj.info.filtered proj.info genhtml -o ./coverage_html --ignore-errors source --num-spaces 2 proj.info proj-9.8.1/scripts/reference_exported_symbols.txt000664 001750 001750 00000323523 15166171715 022333 0ustar00eveneven000000 000000 adjlon(double) dmstor(char const*, char**) geod_direct geod_directline geod_gendirect geod_gendirectline geod_geninverse geod_genposition geod_gensetdistance geod_init geod_inverse geod_inverseline geod_lineinit geod_polygon_addedge geod_polygon_addpoint geod_polygonarea geod_polygon_clear geod_polygon_compute geod_polygon_init geod_polygon_testedge geod_polygon_testpoint geod_position geod_setdistance osgeo::proj::common::Angle::~Angle() osgeo::proj::common::Angle::Angle(double) osgeo::proj::common::Angle::Angle(double, osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::common::Angle::Angle(osgeo::proj::common::Angle const&) osgeo::proj::common::DataEpoch::coordinateEpoch() const osgeo::proj::common::DataEpoch::~DataEpoch() osgeo::proj::common::DataEpoch::DataEpoch(osgeo::proj::common::DataEpoch const&) osgeo::proj::common::DataEpoch::DataEpoch(osgeo::proj::common::Measure const&) osgeo::proj::common::DateTime::create(std::string const&) osgeo::proj::common::DateTime::~DateTime() osgeo::proj::common::DateTime::DateTime(osgeo::proj::common::DateTime const&) osgeo::proj::common::DateTime::isISO_8601() const osgeo::proj::common::DateTime::toString() const osgeo::proj::common::IdentifiedObject::alias() const osgeo::proj::common::IdentifiedObject::aliases() const osgeo::proj::common::IdentifiedObject::formatID(osgeo::proj::io::WKTFormatter*) const osgeo::proj::common::IdentifiedObject::getEPSGCode() const osgeo::proj::common::IdentifiedObject::hasEquivalentNameToUsingAlias(osgeo::proj::common::IdentifiedObject const*, std::shared_ptr const&) const osgeo::proj::common::IdentifiedObject::~IdentifiedObject() osgeo::proj::common::IdentifiedObject::IdentifiedObject() osgeo::proj::common::IdentifiedObject::IdentifiedObject(osgeo::proj::common::IdentifiedObject const&) osgeo::proj::common::IdentifiedObject::identifiers() const osgeo::proj::common::IdentifiedObject::isDeprecated() const osgeo::proj::common::IdentifiedObject::name() const osgeo::proj::common::IdentifiedObject::nameStr() const osgeo::proj::common::IdentifiedObject::remarks() const osgeo::proj::common::IdentifiedObject::setProperties(osgeo::proj::util::PropertyMap const&) osgeo::proj::common::Length::~Length() osgeo::proj::common::Length::Length(double) osgeo::proj::common::Length::Length(double, osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::common::Length::Length(osgeo::proj::common::Length const&) osgeo::proj::common::Measure::convertToUnit(osgeo::proj::common::UnitOfMeasure const&) const osgeo::proj::common::Measure::getSIValue() const osgeo::proj::common::Measure::~Measure() osgeo::proj::common::Measure::Measure(double, osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::common::Measure::Measure(osgeo::proj::common::Measure const&) osgeo::proj::common::Measure::operator==(osgeo::proj::common::Measure const&) const osgeo::proj::common::Measure::unit() const osgeo::proj::common::Measure::value() const osgeo::proj::common::ObjectDomain::create(osgeo::proj::util::optional const&, std::shared_ptr const&) osgeo::proj::common::ObjectDomain::domainOfValidity() const osgeo::proj::common::ObjectDomain::_exportToWKT(osgeo::proj::io::WKTFormatter*) const osgeo::proj::common::ObjectDomain::_isEquivalentTo(osgeo::proj::util::IComparable const*, osgeo::proj::util::IComparable::Criterion, std::shared_ptr const&) const osgeo::proj::common::ObjectDomain::~ObjectDomain() osgeo::proj::common::ObjectDomain::ObjectDomain(osgeo::proj::common::ObjectDomain const&) osgeo::proj::common::ObjectDomain::ObjectDomain(osgeo::proj::util::optional const&, std::shared_ptr const&) osgeo::proj::common::ObjectDomain::scope() const osgeo::proj::common::ObjectUsage::baseExportToJSON(osgeo::proj::io::JSONFormatter*) const osgeo::proj::common::ObjectUsage::baseExportToWKT(osgeo::proj::io::WKTFormatter*) const osgeo::proj::common::ObjectUsage::domains() const osgeo::proj::common::ObjectUsage::_isEquivalentTo(osgeo::proj::util::IComparable const*, osgeo::proj::util::IComparable::Criterion, std::shared_ptr const&) const osgeo::proj::common::ObjectUsage::~ObjectUsage() osgeo::proj::common::ObjectUsage::ObjectUsage() osgeo::proj::common::ObjectUsage::ObjectUsage(osgeo::proj::common::ObjectUsage const&) osgeo::proj::common::ObjectUsage::setProperties(osgeo::proj::util::PropertyMap const&) osgeo::proj::common::Scale::~Scale() osgeo::proj::common::Scale::Scale(double) osgeo::proj::common::Scale::Scale(double, osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::common::Scale::Scale(osgeo::proj::common::Scale const&) osgeo::proj::common::UnitOfMeasure::code() const osgeo::proj::common::UnitOfMeasure::codeSpace() const osgeo::proj::common::UnitOfMeasure::conversionToSI() const osgeo::proj::common::UnitOfMeasure::name() const osgeo::proj::common::UnitOfMeasure::operator=(osgeo::proj::common::UnitOfMeasure&&) osgeo::proj::common::UnitOfMeasure::operator=(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::common::UnitOfMeasure::operator!=(osgeo::proj::common::UnitOfMeasure const&) const osgeo::proj::common::UnitOfMeasure::operator==(osgeo::proj::common::UnitOfMeasure const&) const osgeo::proj::common::UnitOfMeasure::type() const osgeo::proj::common::UnitOfMeasure::~UnitOfMeasure() osgeo::proj::common::UnitOfMeasure::UnitOfMeasure(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::common::UnitOfMeasure::UnitOfMeasure(std::string const&, double, osgeo::proj::common::UnitOfMeasure::Type, std::string const&, std::string const&) osgeo::proj::coordinates::CoordinateMetadata::coordinateEpochAsDecimalYear() const osgeo::proj::coordinates::CoordinateMetadata::coordinateEpoch() const osgeo::proj::coordinates::CoordinateMetadata::~CoordinateMetadata() osgeo::proj::coordinates::CoordinateMetadata::create(dropbox::oxygen::nn > const&) osgeo::proj::coordinates::CoordinateMetadata::create(dropbox::oxygen::nn > const&, double) osgeo::proj::coordinates::CoordinateMetadata::create(dropbox::oxygen::nn > const&, double, std::shared_ptr const&) osgeo::proj::coordinates::CoordinateMetadata::crs() const osgeo::proj::coordinates::CoordinateMetadata::promoteTo3D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::BoundCRS::baseCRS() const osgeo::proj::crs::BoundCRS::baseCRSWithCanonicalBoundCRS() const osgeo::proj::crs::BoundCRS::~BoundCRS() osgeo::proj::crs::BoundCRS::create(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::BoundCRS::createFromNadgrids(dropbox::oxygen::nn > const&, std::string const&) osgeo::proj::crs::BoundCRS::createFromTOWGS84(dropbox::oxygen::nn > const&, std::vector > const&) osgeo::proj::crs::BoundCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::BoundCRS::hubCRS() const osgeo::proj::crs::BoundCRS::transformation() const osgeo::proj::crs::CompoundCRS::componentReferenceSystems() const osgeo::proj::crs::CompoundCRS::~CompoundCRS() osgeo::proj::crs::CompoundCRS::create(osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&) osgeo::proj::crs::CompoundCRS::identify(std::shared_ptr const&) const osgeo::proj::crs::CRS::alterCSLinearUnit(osgeo::proj::common::UnitOfMeasure const&) const osgeo::proj::crs::CRS::alterGeodeticCRS(dropbox::oxygen::nn > const&) const osgeo::proj::crs::CRS::alterId(std::string const&, std::string const&) const osgeo::proj::crs::CRS::alterName(std::string const&) const osgeo::proj::crs::CRS::canonicalBoundCRS() const osgeo::proj::crs::CRS::createBoundCRSToWGS84IfPossible(std::shared_ptr const&, osgeo::proj::operation::CoordinateOperationContext::IntermediateCRSUse) const osgeo::proj::crs::CRS::~CRS() osgeo::proj::crs::CRS::demoteTo2D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::CRS::extractGeodeticCRS() const osgeo::proj::crs::CRS::extractGeographicCRS() const osgeo::proj::crs::CRS::extractVerticalCRS() const osgeo::proj::crs::CRS::getNonDeprecated(dropbox::oxygen::nn > const&) const osgeo::proj::crs::CRS::identify(std::shared_ptr const&) const osgeo::proj::crs::CRS::isDynamic(bool) const osgeo::proj::crs::CRS::normalizeForVisualization() const osgeo::proj::crs::CRS::promoteTo3D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::CRS::shallowClone() const osgeo::proj::crs::CRS::stripVerticalComponent() const osgeo::proj::crs::DerivedCRS::baseCRS() const osgeo::proj::crs::DerivedCRS::~DerivedCRS() osgeo::proj::crs::DerivedCRS::derivingConversion() const osgeo::proj::crs::DerivedGeodeticCRS::baseCRS() const osgeo::proj::crs::DerivedGeodeticCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::DerivedGeodeticCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::DerivedGeodeticCRS::~DerivedGeodeticCRS() osgeo::proj::crs::DerivedGeodeticCRS::_exportToWKT(osgeo::proj::io::WKTFormatter*) const osgeo::proj::crs::DerivedGeographicCRS::baseCRS() const osgeo::proj::crs::DerivedGeographicCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::DerivedGeographicCRS::demoteTo2D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::DerivedGeographicCRS::~DerivedGeographicCRS() osgeo::proj::crs::DerivedProjectedCRS::baseCRS() const osgeo::proj::crs::DerivedProjectedCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::DerivedProjectedCRS::demoteTo2D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::DerivedProjectedCRS::~DerivedProjectedCRS() osgeo::proj::crs::DerivedVerticalCRS::baseCRS() const osgeo::proj::crs::DerivedVerticalCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::DerivedVerticalCRS::~DerivedVerticalCRS() osgeo::proj::crs::EngineeringCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::EngineeringCRS::datum() const osgeo::proj::crs::EngineeringCRS::~EngineeringCRS() osgeo::proj::crs::GeodeticCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::GeodeticCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::GeodeticCRS::create(osgeo::proj::util::PropertyMap const&, std::shared_ptr const&, std::shared_ptr const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::GeodeticCRS::create(osgeo::proj::util::PropertyMap const&, std::shared_ptr const&, std::shared_ptr const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::GeodeticCRS::datum() const osgeo::proj::crs::GeodeticCRS::ellipsoid() const osgeo::proj::crs::GeodeticCRS::~GeodeticCRS() osgeo::proj::crs::GeodeticCRS::identify(std::shared_ptr const&) const osgeo::proj::crs::GeodeticCRS::isGeocentric() const osgeo::proj::crs::GeodeticCRS::isSphericalPlanetocentric() const osgeo::proj::crs::GeodeticCRS::primeMeridian() const osgeo::proj::crs::GeodeticCRS::velocityModel() const osgeo::proj::crs::GeographicCRS::coordinateSystem() const osgeo::proj::crs::GeographicCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::GeographicCRS::create(osgeo::proj::util::PropertyMap const&, std::shared_ptr const&, std::shared_ptr const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::GeographicCRS::demoteTo2D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::GeographicCRS::~GeographicCRS() osgeo::proj::crs::GeographicCRS::is2DPartOf3D(dropbox::oxygen::nn, std::shared_ptr const&) const osgeo::proj::crs::InvalidCompoundCRSException::~InvalidCompoundCRSException() osgeo::proj::crs::InvalidCompoundCRSException::InvalidCompoundCRSException(osgeo::proj::crs::InvalidCompoundCRSException const&) osgeo::proj::crs::ParametricCRS::coordinateSystem() const osgeo::proj::crs::ParametricCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::ParametricCRS::datum() const osgeo::proj::crs::ParametricCRS::~ParametricCRS() osgeo::proj::crs::ProjectedCRS::alterParametersLinearUnit(osgeo::proj::common::UnitOfMeasure const&, bool) const osgeo::proj::crs::ProjectedCRS::baseCRS() const osgeo::proj::crs::ProjectedCRS::coordinateSystem() const osgeo::proj::crs::ProjectedCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::ProjectedCRS::demoteTo2D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::ProjectedCRS::identify(std::shared_ptr const&) const osgeo::proj::crs::ProjectedCRS::~ProjectedCRS() osgeo::proj::crs::SingleCRS::coordinateSystem() const osgeo::proj::crs::SingleCRS::datum() const osgeo::proj::crs::SingleCRS::datumEnsemble() const osgeo::proj::crs::SingleCRS::~SingleCRS() osgeo::proj::crs::TemporalCRS::coordinateSystem() const osgeo::proj::crs::TemporalCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::TemporalCRS::datum() const osgeo::proj::crs::TemporalCRS::~TemporalCRS() osgeo::proj::crs::VerticalCRS::coordinateSystem() const osgeo::proj::crs::VerticalCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::VerticalCRS::create(osgeo::proj::util::PropertyMap const&, std::shared_ptr const&, std::shared_ptr const&, dropbox::oxygen::nn > const&) osgeo::proj::crs::VerticalCRS::datum() const osgeo::proj::crs::VerticalCRS::geoidModel() const osgeo::proj::crs::VerticalCRS::identify(std::shared_ptr const&) const osgeo::proj::crs::VerticalCRS::velocityModel() const osgeo::proj::crs::VerticalCRS::~VerticalCRS() osgeo::proj::cs::AffineCS::~AffineCS() osgeo::proj::cs::AffineCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::AffineCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::AxisDirection::valueOf(std::string const&) osgeo::proj::cs::CartesianCS::~CartesianCS() osgeo::proj::cs::CartesianCS::createEastingNorthing(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::CartesianCS::createGeocentric(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::CartesianCS::createNorthingEasting(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::CartesianCS::createNorthPoleEastingSouthNorthingSouth(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::CartesianCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::CartesianCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::CartesianCS::createSouthPoleEastingNorthNorthingNorth(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::CartesianCS::createWestingSouthing(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::CoordinateSystemAxis::abbreviation() const osgeo::proj::cs::CoordinateSystemAxis::~CoordinateSystemAxis() osgeo::proj::cs::CoordinateSystemAxis::create(osgeo::proj::util::PropertyMap const&, std::string const&, osgeo::proj::cs::AxisDirection const&, osgeo::proj::common::UnitOfMeasure const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&, std::shared_ptr const&) osgeo::proj::cs::CoordinateSystemAxis::create(osgeo::proj::util::PropertyMap const&, std::string const&, osgeo::proj::cs::AxisDirection const&, osgeo::proj::common::UnitOfMeasure const&, std::shared_ptr const&) osgeo::proj::cs::CoordinateSystemAxis::direction() const osgeo::proj::cs::CoordinateSystem::axisList() const osgeo::proj::cs::CoordinateSystemAxis::maximumValue() const osgeo::proj::cs::CoordinateSystemAxis::meridian() const osgeo::proj::cs::CoordinateSystemAxis::minimumValue() const osgeo::proj::cs::CoordinateSystemAxis::rangeMeaning() const osgeo::proj::cs::CoordinateSystemAxis::unit() const osgeo::proj::cs::CoordinateSystem::~CoordinateSystem() osgeo::proj::cs::DateTimeTemporalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::DateTimeTemporalCS::~DateTimeTemporalCS() osgeo::proj::cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight(osgeo::proj::common::UnitOfMeasure const&, osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::EllipsoidalCS::createLatitudeLongitude(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight(osgeo::proj::common::UnitOfMeasure const&, osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::EllipsoidalCS::createLongitudeLatitude(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::EllipsoidalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::EllipsoidalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::EllipsoidalCS::~EllipsoidalCS() osgeo::proj::cs::Meridian::create(osgeo::proj::common::Angle const&) osgeo::proj::cs::Meridian::longitude() const osgeo::proj::cs::Meridian::~Meridian() osgeo::proj::cs::OrdinalCS::create(osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&) osgeo::proj::cs::OrdinalCS::~OrdinalCS() osgeo::proj::cs::ParametricCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::ParametricCS::~ParametricCS() osgeo::proj::cs::RangeMeaning::valueOf(std::string const&) osgeo::proj::cs::SphericalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::SphericalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::SphericalCS::~SphericalCS() osgeo::proj::cs::TemporalCountCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::TemporalCountCS::~TemporalCountCS() osgeo::proj::cs::TemporalCS::~TemporalCS() osgeo::proj::cs::TemporalMeasureCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::TemporalMeasureCS::~TemporalMeasureCS() osgeo::proj::cs::VerticalCS::createGravityRelatedHeight(osgeo::proj::common::UnitOfMeasure const&) osgeo::proj::cs::VerticalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::VerticalCS::~VerticalCS() osgeo::proj::datum::Datum::anchorDefinition() const osgeo::proj::datum::Datum::anchorEpoch() const osgeo::proj::datum::Datum::conventionalRS() const osgeo::proj::datum::Datum::~Datum() osgeo::proj::datum::DatumEnsemble::asDatum(std::shared_ptr const&) const osgeo::proj::datum::DatumEnsemble::create(osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&, dropbox::oxygen::nn > const&) osgeo::proj::datum::DatumEnsemble::~DatumEnsemble() osgeo::proj::datum::DatumEnsemble::datums() const osgeo::proj::datum::DatumEnsemble::positionalAccuracy() const osgeo::proj::datum::Datum::publicationDate() const osgeo::proj::datum::DynamicGeodeticReferenceFrame::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, osgeo::proj::util::optional const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Measure const&, osgeo::proj::util::optional const&) osgeo::proj::datum::DynamicGeodeticReferenceFrame::deformationModelName() const osgeo::proj::datum::DynamicGeodeticReferenceFrame::~DynamicGeodeticReferenceFrame() osgeo::proj::datum::DynamicGeodeticReferenceFrame::frameReferenceEpoch() const osgeo::proj::datum::DynamicVerticalReferenceFrame::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&, osgeo::proj::common::Measure const&, osgeo::proj::util::optional const&) osgeo::proj::datum::DynamicVerticalReferenceFrame::deformationModelName() const osgeo::proj::datum::DynamicVerticalReferenceFrame::~DynamicVerticalReferenceFrame() osgeo::proj::datum::DynamicVerticalReferenceFrame::frameReferenceEpoch() const osgeo::proj::datum::Ellipsoid::celestialBody() const osgeo::proj::datum::Ellipsoid::computedInverseFlattening() const osgeo::proj::datum::Ellipsoid::computeSemiMinorAxis() const osgeo::proj::datum::Ellipsoid::createFlattenedSphere(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Length const&, osgeo::proj::common::Scale const&, std::string const&) osgeo::proj::datum::Ellipsoid::createSphere(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Length const&, std::string const&) osgeo::proj::datum::Ellipsoid::createTwoAxis(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, std::string const&) osgeo::proj::datum::Ellipsoid::~Ellipsoid() osgeo::proj::datum::Ellipsoid::identify() const osgeo::proj::datum::Ellipsoid::inverseFlattening() const osgeo::proj::datum::Ellipsoid::isSphere() const osgeo::proj::datum::Ellipsoid::semiMajorAxis() const osgeo::proj::datum::Ellipsoid::semiMedianAxis() const osgeo::proj::datum::Ellipsoid::semiMinorAxis() const osgeo::proj::datum::Ellipsoid::squaredEccentricity() const osgeo::proj::datum::EngineeringDatum::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional const&) osgeo::proj::datum::EngineeringDatum::~EngineeringDatum() osgeo::proj::datum::GeodeticReferenceFrame::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, osgeo::proj::util::optional const&, dropbox::oxygen::nn > const&) osgeo::proj::datum::GeodeticReferenceFrame::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&, dropbox::oxygen::nn > const&) osgeo::proj::datum::GeodeticReferenceFrame::ellipsoid() const osgeo::proj::datum::GeodeticReferenceFrame::~GeodeticReferenceFrame() osgeo::proj::datum::GeodeticReferenceFrame::hasEquivalentNameToUsingAlias(osgeo::proj::common::IdentifiedObject const*, std::shared_ptr const&) const osgeo::proj::datum::GeodeticReferenceFrame::primeMeridian() const osgeo::proj::datum::ParametricDatum::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional const&) osgeo::proj::datum::ParametricDatum::~ParametricDatum() osgeo::proj::datum::PrimeMeridian::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&) osgeo::proj::datum::PrimeMeridian::longitude() const osgeo::proj::datum::PrimeMeridian::~PrimeMeridian() osgeo::proj::datum::RealizationMethod::operator=(osgeo::proj::datum::RealizationMethod const&) osgeo::proj::datum::RealizationMethod::RealizationMethod(std::string const&) osgeo::proj::datum::TemporalDatum::calendar() const osgeo::proj::datum::TemporalDatum::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::DateTime const&, std::string const&) osgeo::proj::datum::TemporalDatum::~TemporalDatum() osgeo::proj::datum::TemporalDatum::temporalOrigin() const osgeo::proj::datum::VerticalReferenceFrame::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&) osgeo::proj::datum::VerticalReferenceFrame::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::optional const&, osgeo::proj::util::optional const&) osgeo::proj::datum::VerticalReferenceFrame::realizationMethod() const osgeo::proj::datum::VerticalReferenceFrame::~VerticalReferenceFrame() osgeo::proj::File::~File() osgeo::proj::FileManager::exists(pj_ctx*, char const*) osgeo::proj::FileManager::open(pj_ctx*, char const*, osgeo::proj::FileAccess) osgeo::proj::File::read_line(unsigned long, bool&, bool&) osgeo::proj::GenericShiftGrid::~GenericShiftGrid() osgeo::proj::GenericShiftGrid::GenericShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&) osgeo::proj::GenericShiftGrid::gridAt(double, double) const osgeo::proj::GenericShiftGridSet::~GenericShiftGridSet() osgeo::proj::GenericShiftGridSet::GenericShiftGridSet() osgeo::proj::GenericShiftGridSet::gridAt(double, double) const osgeo::proj::GenericShiftGridSet::gridAt(std::string const&, double, double) const osgeo::proj::GenericShiftGridSet::open(pj_ctx*, std::string const&) osgeo::proj::GenericShiftGridSet::reassign_context(pj_ctx*) osgeo::proj::GenericShiftGridSet::reopen(pj_ctx*) osgeo::proj::GenericShiftGrid::valuesAt(int, int, int, int, int, int const*, float*, bool&) const osgeo::proj::Grid::~Grid() osgeo::proj::Grid::Grid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&) osgeo::proj::HorizontalShiftGrid::gridAt(double, double) const osgeo::proj::HorizontalShiftGrid::~HorizontalShiftGrid() osgeo::proj::HorizontalShiftGrid::HorizontalShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&) osgeo::proj::HorizontalShiftGridSet::gridAt(double, double) const osgeo::proj::HorizontalShiftGridSet::~HorizontalShiftGridSet() osgeo::proj::HorizontalShiftGridSet::HorizontalShiftGridSet() osgeo::proj::HorizontalShiftGridSet::open(pj_ctx*, std::string const&) osgeo::proj::HorizontalShiftGridSet::reassign_context(pj_ctx*) osgeo::proj::HorizontalShiftGridSet::reopen(pj_ctx*) osgeo::proj::internal::ci_equal(std::string const&, char const*) osgeo::proj::internal::ci_equal(std::string const&, std::string const&) osgeo::proj::internal::ci_find(std::string const&, char const*) osgeo::proj::internal::ci_starts_with(char const*, char const*) osgeo::proj::internal::c_locale_stod(std::string const&) osgeo::proj::internal::replaceAll(std::string const&, std::string const&, std::string const&) osgeo::proj::internal::split(std::string const&, char) osgeo::proj::internal::split(std::string const&, std::string const&) osgeo::proj::internal::tolower(std::string const&) osgeo::proj::internal::toString(double, int) osgeo::proj::io::AuthorityFactory::~AuthorityFactory() osgeo::proj::io::AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() osgeo::proj::io::AuthorityFactory::createCompoundCRS(std::string const&) const osgeo::proj::io::AuthorityFactory::createConversion(std::string const&) const osgeo::proj::io::AuthorityFactory::createCoordinateMetadata(std::string const&) const osgeo::proj::io::AuthorityFactory::createCoordinateOperation(std::string const&, bool) const osgeo::proj::io::AuthorityFactory::createCoordinateReferenceSystem(std::string const&) const osgeo::proj::io::AuthorityFactory::createCoordinateSystem(std::string const&) const osgeo::proj::io::AuthorityFactory::createDatumEnsemble(std::string const&, std::string const&) const osgeo::proj::io::AuthorityFactory::createDatum(std::string const&) const osgeo::proj::io::AuthorityFactory::create(dropbox::oxygen::nn > const&, std::string const&) osgeo::proj::io::AuthorityFactory::createEllipsoid(std::string const&) const osgeo::proj::io::AuthorityFactory::createEngineeringCRS(std::string const&) const osgeo::proj::io::AuthorityFactory::createEngineeringDatum(std::string const&) const osgeo::proj::io::AuthorityFactory::createExtent(std::string const&) const osgeo::proj::io::AuthorityFactory::createFromCoordinateReferenceSystemCodes(std::string const&, std::string const&) const osgeo::proj::io::AuthorityFactory::createFromCoordinateReferenceSystemCodes(std::string const&, std::string const&, std::string const&, std::string const&, bool, bool, bool, bool, bool, bool, std::shared_ptr const&, std::shared_ptr const&) const osgeo::proj::io::AuthorityFactory::createFromCRSCodesWithIntermediates(std::string const&, std::string const&, std::string const&, std::string const&, bool, bool, bool, bool, std::vector, std::allocator > > const&, osgeo::proj::io::AuthorityFactory::ObjectType, std::vector > const&, std::shared_ptr const&, std::shared_ptr const&) const osgeo::proj::io::AuthorityFactory::createGeodeticCRS(std::string const&) const osgeo::proj::io::AuthorityFactory::createGeodeticDatum(std::string const&) const osgeo::proj::io::AuthorityFactory::createGeographicCRS(std::string const&) const osgeo::proj::io::AuthorityFactory::createObjectsFromName(std::string const&, std::vector > const&, bool, unsigned long) const osgeo::proj::io::AuthorityFactory::createObject(std::string const&) const osgeo::proj::io::AuthorityFactory::createPrimeMeridian(std::string const&) const osgeo::proj::io::AuthorityFactory::createProjectedCRS(std::string const&) const osgeo::proj::io::AuthorityFactory::createUnitOfMeasure(std::string const&) const osgeo::proj::io::AuthorityFactory::createVerticalCRS(std::string const&) const osgeo::proj::io::AuthorityFactory::createVerticalDatum(std::string const&) const osgeo::proj::io::AuthorityFactory::CRSInfo::CRSInfo() osgeo::proj::io::AuthorityFactory::databaseContext() const osgeo::proj::io::AuthorityFactory::getAuthorityCodes(osgeo::proj::io::AuthorityFactory::ObjectType const&, bool) const osgeo::proj::io::AuthorityFactory::getAuthority() const osgeo::proj::io::AuthorityFactory::getCelestialBodyList() const osgeo::proj::io::AuthorityFactory::getCRSInfoList() const osgeo::proj::io::AuthorityFactory::getDescriptionText(std::string const&) const osgeo::proj::io::AuthorityFactory::getGeoidModels(std::string const&) const osgeo::proj::io::AuthorityFactory::getOfficialNameFromAlias(std::string const&, std::string const&, std::string const&, bool, std::string&, std::string&, std::string&) const osgeo::proj::io::AuthorityFactory::getPointMotionOperationsFor(dropbox::oxygen::nn > const&, bool) const osgeo::proj::io::AuthorityFactory::getUnitList() const osgeo::proj::io::AuthorityFactory::identifyBodyFromSemiMajorAxis(double, double) const osgeo::proj::io::AuthorityFactory::listAreaOfUseFromName(std::string const&, bool) const osgeo::proj::io::AuthorityFactory::UnitInfo::UnitInfo() osgeo::proj::io::createFromUserInput(std::string const&, pj_ctx*) osgeo::proj::io::createFromUserInput(std::string const&, std::shared_ptr const&, bool) osgeo::proj::io::DatabaseContext::create(std::string const&, std::vector > const&, pj_ctx*) osgeo::proj::io::DatabaseContext::create(void*) osgeo::proj::io::DatabaseContext::~DatabaseContext() osgeo::proj::io::DatabaseContext::getAuthorities() const osgeo::proj::io::DatabaseContext::getDatabaseStructure() const osgeo::proj::io::DatabaseContext::getInsertStatementsFor(dropbox::oxygen::nn > const&, std::string const&, std::string const&, bool, std::vector > const&) osgeo::proj::io::DatabaseContext::getMetadata(char const*) const osgeo::proj::io::DatabaseContext::getPath() const osgeo::proj::io::DatabaseContext::getQueryCounter() const osgeo::proj::io::DatabaseContext::getSqliteHandle() const osgeo::proj::io::DatabaseContext::getVersionedAuthoritiesFromName(std::string const&) osgeo::proj::io::DatabaseContext::lookForGridInfo(std::string const&, bool, std::string&, std::string&, std::string&, bool&, bool&, bool&) const osgeo::proj::io::DatabaseContext::startInsertStatementsSession() osgeo::proj::io::DatabaseContext::stopInsertStatementsSession() osgeo::proj::io::DatabaseContext::suggestsCodeFor(dropbox::oxygen::nn > const&, std::string const&, bool) osgeo::proj::io::DatabaseContext::toWGS84AutocorrectWrongValues(double&, double&, double&, double&, double&, double&, double&) const osgeo::proj::io::FactoryException::~FactoryException() osgeo::proj::io::FactoryException::FactoryException(char const*) osgeo::proj::io::FactoryException::FactoryException(osgeo::proj::io::FactoryException const&) osgeo::proj::io::FactoryException::FactoryException(std::string const&) osgeo::proj::io::FormattingException::~FormattingException() osgeo::proj::io::FormattingException::FormattingException(osgeo::proj::io::FormattingException const&) osgeo::proj::io::IJSONExportable::exportToJSON(osgeo::proj::io::JSONFormatter*) const osgeo::proj::io::IJSONExportable::~IJSONExportable() osgeo::proj::io::IPROJStringExportable::exportToPROJString(osgeo::proj::io::PROJStringFormatter*) const osgeo::proj::io::IPROJStringExportable::~IPROJStringExportable() osgeo::proj::io::IWKTExportable::exportToWKT(osgeo::proj::io::WKTFormatter*) const osgeo::proj::io::IWKTExportable::~IWKTExportable() osgeo::proj::io::JSONFormatter::create(std::shared_ptr) osgeo::proj::io::JSONFormatter::~JSONFormatter() osgeo::proj::io::JSONFormatter::ObjectContext::~ObjectContext() osgeo::proj::io::JSONFormatter::ObjectContext::ObjectContext(osgeo::proj::io::JSONFormatter&, char const*, bool) osgeo::proj::io::JSONFormatter::setIndentationWidth(int) osgeo::proj::io::JSONFormatter::setMultiLine(bool) osgeo::proj::io::JSONFormatter::setSchema(std::string const&) osgeo::proj::io::JSONFormatter::toString() const osgeo::proj::io::NoSuchAuthorityCodeException::getAuthorityCode() const osgeo::proj::io::NoSuchAuthorityCodeException::getAuthority() const osgeo::proj::io::NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() osgeo::proj::io::NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(osgeo::proj::io::NoSuchAuthorityCodeException const&) osgeo::proj::io::NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(std::string const&, std::string const&, std::string const&) osgeo::proj::io::ParsingException::~ParsingException() osgeo::proj::io::ParsingException::ParsingException(osgeo::proj::io::ParsingException const&) osgeo::proj::io::PROJStringFormatter::addParam(char const*, char const*) osgeo::proj::io::PROJStringFormatter::addParam(char const*, double) osgeo::proj::io::PROJStringFormatter::addParam(char const*, int) osgeo::proj::io::PROJStringFormatter::addParam(char const*, std::string const&) osgeo::proj::io::PROJStringFormatter::addParam(char const*, std::vector > const&) osgeo::proj::io::PROJStringFormatter::addParam(std::string const&) osgeo::proj::io::PROJStringFormatter::addParam(std::string const&, char const*) osgeo::proj::io::PROJStringFormatter::addParam(std::string const&, double) osgeo::proj::io::PROJStringFormatter::addParam(std::string const&, int) osgeo::proj::io::PROJStringFormatter::addParam(std::string const&, std::string const&) osgeo::proj::io::PROJStringFormatter::addStep(char const*) osgeo::proj::io::PROJStringFormatter::addStep(std::string const&) osgeo::proj::io::PROJStringFormatter::create(osgeo::proj::io::PROJStringFormatter::Convention, std::shared_ptr) osgeo::proj::io::PROJStringFormatter::ingestPROJString(std::string const&) osgeo::proj::io::PROJStringFormatter::~PROJStringFormatter() osgeo::proj::io::PROJStringFormatter::setCRSExport(bool) osgeo::proj::io::PROJStringFormatter::setCurrentStepInverted(bool) osgeo::proj::io::PROJStringFormatter::setIndentationWidth(int) osgeo::proj::io::PROJStringFormatter::setMaxLineLength(int) osgeo::proj::io::PROJStringFormatter::setMultiLine(bool) osgeo::proj::io::PROJStringFormatter::setUseApproxTMerc(bool) osgeo::proj::io::PROJStringFormatter::startInversion() osgeo::proj::io::PROJStringFormatter::stopInversion() osgeo::proj::io::PROJStringFormatter::toString() const osgeo::proj::io::PROJStringParser::attachContext(pj_ctx*) osgeo::proj::io::PROJStringParser::attachDatabaseContext(std::shared_ptr const&) osgeo::proj::io::PROJStringParser::createFromPROJString(std::string const&) osgeo::proj::io::PROJStringParser::~PROJStringParser() osgeo::proj::io::PROJStringParser::PROJStringParser() osgeo::proj::io::PROJStringParser::setUsePROJ4InitRules(bool) osgeo::proj::io::PROJStringParser::warningList() const osgeo::proj::io::WKTFormatter::create(dropbox::oxygen::nn > > const&) osgeo::proj::io::WKTFormatter::create(osgeo::proj::io::WKTFormatter::Convention, std::shared_ptr) osgeo::proj::io::WKTFormatter::isAllowedEllipsoidalHeightAsVerticalCRS() const osgeo::proj::io::WKTFormatter::isAllowedLINUNITNode() const osgeo::proj::io::WKTFormatter::isStrict() const osgeo::proj::io::WKTFormatter::setAllowEllipsoidalHeightAsVerticalCRS(bool) osgeo::proj::io::WKTFormatter::setAllowLINUNITNode(bool) osgeo::proj::io::WKTFormatter::setIndentationWidth(int) osgeo::proj::io::WKTFormatter::setMultiLine(bool) osgeo::proj::io::WKTFormatter::setOutputAxis(osgeo::proj::io::WKTFormatter::OutputAxisRule) osgeo::proj::io::WKTFormatter::setOutputId(bool) osgeo::proj::io::WKTFormatter::setStrict(bool) osgeo::proj::io::WKTFormatter::simulCurNodeHasId() osgeo::proj::io::WKTFormatter::toString() const osgeo::proj::io::WKTFormatter::~WKTFormatter() osgeo::proj::io::WKTNode::addChild(dropbox::oxygen::nn > >&&) osgeo::proj::io::WKTNode::children() const osgeo::proj::io::WKTNode::countChildrenOfName(std::string const&) const osgeo::proj::io::WKTNode::createFrom(std::string const&, unsigned long) osgeo::proj::io::WKTNode::lookForChild(std::string const&, int) const osgeo::proj::io::WKTNode::toString() const osgeo::proj::io::WKTNode::value() const osgeo::proj::io::WKTNode::~WKTNode() osgeo::proj::io::WKTNode::WKTNode(std::string const&) osgeo::proj::io::WKTParser::attachDatabaseContext(std::shared_ptr const&) osgeo::proj::io::WKTParser::createFromWKT(std::string const&) osgeo::proj::io::WKTParser::grammarErrorList() const osgeo::proj::io::WKTParser::guessDialect(std::string const&) osgeo::proj::io::WKTParser::setStrict(bool) osgeo::proj::io::WKTParser::setUnsetIdentifiersIfIncompatibleDef(bool) osgeo::proj::io::WKTParser::warningList() const osgeo::proj::io::WKTParser::~WKTParser() osgeo::proj::io::WKTParser::WKTParser() osgeo::proj::metadata::Citation::~Citation() osgeo::proj::metadata::Citation::Citation() osgeo::proj::metadata::Citation::Citation(osgeo::proj::metadata::Citation const&) osgeo::proj::metadata::Citation::Citation(std::string const&) osgeo::proj::metadata::Citation::title() const osgeo::proj::metadata::Extent::contains(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::Extent::createFromBBOX(double, double, double, double, osgeo::proj::util::optional const&) osgeo::proj::metadata::Extent::create(osgeo::proj::util::optional const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::metadata::Extent::description() const osgeo::proj::metadata::Extent::~Extent() osgeo::proj::metadata::Extent::Extent(osgeo::proj::metadata::Extent const&) osgeo::proj::metadata::Extent::geographicElements() const osgeo::proj::metadata::Extent::intersection(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::Extent::intersects(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::Extent::temporalElements() const osgeo::proj::metadata::Extent::verticalElements() const osgeo::proj::metadata::GeographicBoundingBox::create(double, double, double, double) osgeo::proj::metadata::GeographicBoundingBox::eastBoundLongitude() const osgeo::proj::metadata::GeographicBoundingBox::~GeographicBoundingBox() osgeo::proj::metadata::GeographicBoundingBox::northBoundLatitude() const osgeo::proj::metadata::GeographicBoundingBox::southBoundLatitude() const osgeo::proj::metadata::GeographicBoundingBox::westBoundLongitude() const osgeo::proj::metadata::GeographicExtent::~GeographicExtent() osgeo::proj::metadata::Identifier::authority() const osgeo::proj::metadata::Identifier::code() const osgeo::proj::metadata::Identifier::codeSpace() const osgeo::proj::metadata::Identifier::create(std::string const&, osgeo::proj::util::PropertyMap const&) osgeo::proj::metadata::Identifier::description() const osgeo::proj::metadata::Identifier::~Identifier() osgeo::proj::metadata::Identifier::Identifier(osgeo::proj::metadata::Identifier const&) osgeo::proj::metadata::Identifier::isEquivalentName(char const*, char const*) osgeo::proj::metadata::Identifier::isEquivalentName(char const*, char const*, bool) osgeo::proj::metadata::Identifier::uri() const osgeo::proj::metadata::Identifier::version() const osgeo::proj::metadata::PositionalAccuracy::create(std::string const&) osgeo::proj::metadata::PositionalAccuracy::~PositionalAccuracy() osgeo::proj::metadata::PositionalAccuracy::value() const osgeo::proj::metadata::TemporalExtent::contains(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::TemporalExtent::create(std::string const&, std::string const&) osgeo::proj::metadata::TemporalExtent::intersects(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::TemporalExtent::start() const osgeo::proj::metadata::TemporalExtent::stop() const osgeo::proj::metadata::TemporalExtent::~TemporalExtent() osgeo::proj::metadata::VerticalExtent::contains(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::VerticalExtent::create(double, double, dropbox::oxygen::nn > const&) osgeo::proj::metadata::VerticalExtent::intersects(dropbox::oxygen::nn > const&) const osgeo::proj::metadata::VerticalExtent::maximumValue() const osgeo::proj::metadata::VerticalExtent::minimumValue() const osgeo::proj::metadata::VerticalExtent::unit() const osgeo::proj::metadata::VerticalExtent::~VerticalExtent() osgeo::proj::operation::ConcatenatedOperation::~ConcatenatedOperation() osgeo::proj::operation::ConcatenatedOperation::createComputeMetadata(std::vector >, std::allocator > > > const&, bool) osgeo::proj::operation::ConcatenatedOperation::create(osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::ConcatenatedOperation::gridsNeeded(std::shared_ptr const&, bool) const osgeo::proj::operation::ConcatenatedOperation::inverse() const osgeo::proj::operation::ConcatenatedOperation::operations() const osgeo::proj::operation::Conversion::~Conversion() osgeo::proj::operation::Conversion::convertToOtherMethod(int) const osgeo::proj::operation::Conversion::createAffineParametric(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Measure const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Measure const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Scale const&) osgeo::proj::operation::Conversion::createAlbersEqualArea(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createAmericanPolyconic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createAxisOrderReversal(bool) osgeo::proj::operation::Conversion::createAzimuthalEquidistant(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createBonne(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createCassiniSoldner(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createChangeVerticalUnit(osgeo::proj::util::PropertyMap const&) osgeo::proj::operation::Conversion::createChangeVerticalUnit(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Scale const&) osgeo::proj::operation::Conversion::createEckertIII(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEckertII(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEckertI(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEckertIV(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEckertVI(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEckertV(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEqualEarth(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEquidistantConic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEquidistantCylindrical(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createEquidistantCylindricalSpherical(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGall(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGaussSchreiberTransverseMercator(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGeographic2DOffsets(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&) osgeo::proj::operation::Conversion::createGeographic2DWithHeightOffsets(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGeographic3DOffsets(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGeographicGeocentric(osgeo::proj::util::PropertyMap const&) osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepX(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepY(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGnomonic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGoodeHomolosine(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createGuamProjection(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createHeightDepthReversal(osgeo::proj::util::PropertyMap const&) osgeo::proj::operation::Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantA(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantB(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createInternationalMapWorldPolyconic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createInterruptedGoodeHomolosine(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createKrovakNorthOriented(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createKrovak(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLabordeObliqueMercator(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertAzimuthalEqualArea(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertConicConformal_1SP(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertConicConformal_1SP_VariantB(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Belgium(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Michigan(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, osgeo::proj::common::Scale const&) osgeo::proj::operation::Conversion::createLambertConicConformal_2SP(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertCylindricalEqualArea(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLambertCylindricalEqualAreaSpherical(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createLocalOrthographic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createMercatorSpherical(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createMercatorVariantA(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createMercatorVariantB(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createMillerCylindrical(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createMollweide(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createNewZealandMappingGrid(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createObliqueStereographic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createOrthographic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Conversion::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Conversion::createPolarStereographicVariantA(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createPolarStereographicVariantB(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createPoleRotationGRIBConvention(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&) osgeo::proj::operation::Conversion::createPoleRotationNetCDFCFConvention(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&) osgeo::proj::operation::Conversion::createPopularVisualisationPseudoMercator(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createQuadrilateralizedSphericalCube(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createRobinson(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createSinusoidal(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createSphericalCrossTrackHeight(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createStereographic(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createTransverseMercator(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createTransverseMercatorSouthOriented(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createTunisiaMappingGrid(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createTunisiaMiningGrid(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createTwoPointEquidistant(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createUTM(osgeo::proj::util::PropertyMap const&, int, bool) osgeo::proj::operation::Conversion::createVanDerGrinten(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createVerticalOffset(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createVerticalPerspective(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerIII(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerII(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerI(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerIV(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerVII(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerVI(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createWagnerV(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::identify() const osgeo::proj::operation::Conversion::inverse() const osgeo::proj::operation::Conversion::isUTM(int&, bool&) const osgeo::proj::operation::CoordinateOperationContext::clone() const osgeo::proj::operation::CoordinateOperationContext::~CoordinateOperationContext() osgeo::proj::operation::CoordinateOperationContext::create(std::shared_ptr const&, std::shared_ptr const&, double) osgeo::proj::operation::CoordinateOperationContext::getAllowBallparkTransformations() const osgeo::proj::operation::CoordinateOperationContext::getAllowUseIntermediateCRS() const osgeo::proj::operation::CoordinateOperationContext::getAreaOfInterest() const osgeo::proj::operation::CoordinateOperationContext::getAuthorityFactory() const osgeo::proj::operation::CoordinateOperationContext::getDesiredAccuracy() const osgeo::proj::operation::CoordinateOperationContext::getDiscardSuperseded() const osgeo::proj::operation::CoordinateOperationContext::getGridAvailabilityUse() const osgeo::proj::operation::CoordinateOperationContext::getIntermediateCRS() const osgeo::proj::operation::CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const osgeo::proj::operation::CoordinateOperationContext::getSourceCoordinateEpoch() const osgeo::proj::operation::CoordinateOperationContext::getSpatialCriterion() const osgeo::proj::operation::CoordinateOperationContext::getTargetCoordinateEpoch() const osgeo::proj::operation::CoordinateOperationContext::getUsePROJAlternativeGridNames() const osgeo::proj::operation::CoordinateOperationContext::setAllowBallparkTransformations(bool) osgeo::proj::operation::CoordinateOperationContext::setAllowUseIntermediateCRS(osgeo::proj::operation::CoordinateOperationContext::IntermediateCRSUse) osgeo::proj::operation::CoordinateOperationContext::setAreaOfInterest(std::shared_ptr const&) osgeo::proj::operation::CoordinateOperationContext::setDesiredAccuracy(double) osgeo::proj::operation::CoordinateOperationContext::setDiscardSuperseded(bool) osgeo::proj::operation::CoordinateOperationContext::setGridAvailabilityUse(osgeo::proj::operation::CoordinateOperationContext::GridAvailabilityUse) osgeo::proj::operation::CoordinateOperationContext::setIntermediateCRS(std::vector, std::allocator > > const&) osgeo::proj::operation::CoordinateOperationContext::setSourceAndTargetCRSExtentUse(osgeo::proj::operation::CoordinateOperationContext::SourceTargetCRSExtentUse) osgeo::proj::operation::CoordinateOperationContext::setSourceCoordinateEpoch(osgeo::proj::util::optional const&) osgeo::proj::operation::CoordinateOperationContext::setSpatialCriterion(osgeo::proj::operation::CoordinateOperationContext::SpatialCriterion) osgeo::proj::operation::CoordinateOperationContext::setTargetCoordinateEpoch(osgeo::proj::util::optional const&) osgeo::proj::operation::CoordinateOperationContext::setUsePROJAlternativeGridNames(bool) osgeo::proj::operation::CoordinateOperation::~CoordinateOperation() osgeo::proj::operation::CoordinateOperation::coordinateOperationAccuracies() const osgeo::proj::operation::CoordinateOperation::coordinateTransformer(pj_ctx*) const osgeo::proj::operation::CoordinateOperationFactory::~CoordinateOperationFactory() osgeo::proj::operation::CoordinateOperationFactory::create() osgeo::proj::operation::CoordinateOperationFactory::createOperation(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) const osgeo::proj::operation::CoordinateOperationFactory::createOperations(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > > const&) const osgeo::proj::operation::CoordinateOperationFactory::createOperations(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > > const&) const osgeo::proj::operation::CoordinateOperationFactory::createOperations(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > > const&) const osgeo::proj::operation::CoordinateOperationFactory::createOperations(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > > const&) const osgeo::proj::operation::CoordinateOperation::hasBallparkTransformation() const osgeo::proj::operation::CoordinateOperation::interpolationCRS() const osgeo::proj::operation::CoordinateOperation::isPROJInstantiable(std::shared_ptr const&, bool) const osgeo::proj::operation::CoordinateOperation::normalizeForVisualization() const osgeo::proj::operation::CoordinateOperation::operationVersion() const osgeo::proj::operation::CoordinateOperation::requiresPerCoordinateInputTime() const osgeo::proj::operation::CoordinateOperation::shallowClone() const osgeo::proj::operation::CoordinateOperation::sourceCoordinateEpoch() const osgeo::proj::operation::CoordinateOperation::sourceCRS() const osgeo::proj::operation::CoordinateOperation::targetCoordinateEpoch() const osgeo::proj::operation::CoordinateOperation::targetCRS() const osgeo::proj::operation::CoordinateTransformer::~CoordinateTransformer() osgeo::proj::operation::CoordinateTransformer::transform(PJ_COORD) osgeo::proj::operation::GeneralOperationParameter::~GeneralOperationParameter() osgeo::proj::operation::GeneralParameterValue::~GeneralParameterValue() osgeo::proj::operation::GridDescription::~GridDescription() osgeo::proj::operation::GridDescription::GridDescription() osgeo::proj::operation::GridDescription::GridDescription(osgeo::proj::operation::GridDescription&&) osgeo::proj::operation::GridDescription::GridDescription(osgeo::proj::operation::GridDescription const&) osgeo::proj::operation::InvalidOperation::~InvalidOperation() osgeo::proj::operation::InvalidOperation::InvalidOperation(osgeo::proj::operation::InvalidOperation const&) osgeo::proj::operation::OperationMethod::create(osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::OperationMethod::create(osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::OperationMethod::formulaCitation() const osgeo::proj::operation::OperationMethod::formula() const osgeo::proj::operation::OperationMethod::getEPSGCode() const osgeo::proj::operation::OperationMethod::~OperationMethod() osgeo::proj::operation::OperationMethod::parameters() const osgeo::proj::operation::OperationParameter::create(osgeo::proj::util::PropertyMap const&) osgeo::proj::operation::OperationParameter::getEPSGCode() const osgeo::proj::operation::OperationParameter::getNameForEPSGCode(int) osgeo::proj::operation::OperationParameter::~OperationParameter() osgeo::proj::operation::OperationParameterValue::create(dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::operation::OperationParameterValue::~OperationParameterValue() osgeo::proj::operation::OperationParameterValue::parameter() const osgeo::proj::operation::OperationParameterValue::parameterValue() const osgeo::proj::operation::ParameterValue::booleanValue() const osgeo::proj::operation::ParameterValue::create(bool) osgeo::proj::operation::ParameterValue::create(char const*) osgeo::proj::operation::ParameterValue::createFilename(std::string const&) osgeo::proj::operation::ParameterValue::create(int) osgeo::proj::operation::ParameterValue::create(osgeo::proj::common::Measure const&) osgeo::proj::operation::ParameterValue::create(std::string const&) osgeo::proj::operation::ParameterValue::integerValue() const osgeo::proj::operation::ParameterValue::~ParameterValue() osgeo::proj::operation::ParameterValue::stringValue() const osgeo::proj::operation::ParameterValue::type() const osgeo::proj::operation::ParameterValue::value() const osgeo::proj::operation::ParameterValue::valueFile() const osgeo::proj::operation::PointMotionOperation::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::PointMotionOperation::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::PointMotionOperation::inverse() const osgeo::proj::operation::PointMotionOperation::~PointMotionOperation() osgeo::proj::operation::PointMotionOperation::sourceCRS() const osgeo::proj::operation::PointMotionOperation::substitutePROJAlternativeGridNames(dropbox::oxygen::nn >) const osgeo::proj::operation::SingleOperation::createPROJBased(osgeo::proj::util::PropertyMap const&, std::string const&, std::shared_ptr const&, std::shared_ptr const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::SingleOperation::gridsNeeded(std::shared_ptr const&, bool) const osgeo::proj::operation::SingleOperation::method() const osgeo::proj::operation::SingleOperation::parameterValue(int) const osgeo::proj::operation::SingleOperation::parameterValueMeasure(int) const osgeo::proj::operation::SingleOperation::parameterValueMeasure(std::string const&, int) const osgeo::proj::operation::SingleOperation::parameterValueNumeric(int, osgeo::proj::common::UnitOfMeasure const&) const osgeo::proj::operation::SingleOperation::parameterValues() const osgeo::proj::operation::SingleOperation::parameterValue(std::string const&, int) const osgeo::proj::operation::SingleOperation::~SingleOperation() osgeo::proj::operation::SingleOperation::substitutePROJAlternativeGridNames(dropbox::oxygen::nn >) const osgeo::proj::operation::SingleOperation::validateParameters() const osgeo::proj::operation::Transformation::createAbridgedMolodensky(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createCartesianGridOffsets(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createChangeVerticalUnit(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Scale const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createCoordinateFrameRotation(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, double, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createGeocentricTranslations(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createGeographic2DOffsets(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createGeographic2DWithHeightOffsets(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createGeographic3DOffsets(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createGravityRelatedHeightToGeographic3D(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, std::shared_ptr const&, std::string const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createLongitudeRotation(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Angle const&) osgeo::proj::operation::Transformation::createMolodensky(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createNTv2(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, std::string const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, std::shared_ptr const&, dropbox::oxygen::nn > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, std::shared_ptr const&, osgeo::proj::util::PropertyMap const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createPositionVector(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, double, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createTimeDependentCoordinateFrameRotation(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createTimeDependentPositionVector(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createTOWGS84(dropbox::oxygen::nn > const&, std::vector > const&) osgeo::proj::operation::Transformation::createVERTCON(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, std::string const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::createVerticalOffset(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, osgeo::proj::common::Length const&, std::vector >, std::allocator > > > const&) osgeo::proj::operation::Transformation::getTOWGS84Parameters(bool) const osgeo::proj::operation::Transformation::inverse() const osgeo::proj::operation::Transformation::sourceCRS() const osgeo::proj::operation::Transformation::targetCRS() const osgeo::proj::operation::Transformation::~Transformation() osgeo::proj::util::ArrayOfBaseObject::add(osgeo::proj::util::BaseObjectNNPtr const&) osgeo::proj::util::ArrayOfBaseObject::~ArrayOfBaseObject() osgeo::proj::util::ArrayOfBaseObject::create() osgeo::proj::util::BaseObject::~BaseObject() osgeo::proj::util::BaseObjectNNPtr::~BaseObjectNNPtr() osgeo::proj::util::BoxedValue::~BoxedValue() osgeo::proj::util::BoxedValue::BoxedValue(bool) osgeo::proj::util::BoxedValue::BoxedValue(char const*) osgeo::proj::util::BoxedValue::BoxedValue(int) osgeo::proj::util::BoxedValue::BoxedValue(std::string const&) osgeo::proj::util::CodeList::~CodeList() osgeo::proj::util::Exception::~Exception() osgeo::proj::util::Exception::Exception(osgeo::proj::util::Exception const&) osgeo::proj::util::Exception::what() const osgeo::proj::util::GenericName::~GenericName() osgeo::proj::util::IComparable::~IComparable() osgeo::proj::util::IComparable::isEquivalentTo(osgeo::proj::util::IComparable const*, osgeo::proj::util::IComparable::Criterion, std::shared_ptr const&) const osgeo::proj::util::InvalidValueTypeException::~InvalidValueTypeException() osgeo::proj::util::InvalidValueTypeException::InvalidValueTypeException(osgeo::proj::util::InvalidValueTypeException const&) osgeo::proj::util::LocalName::~LocalName() osgeo::proj::util::LocalName::scope() const osgeo::proj::util::LocalName::toFullyQualifiedName() const osgeo::proj::util::LocalName::toString() const osgeo::proj::util::NameFactory::createGenericName(std::shared_ptr const&, std::vector > const&) osgeo::proj::util::NameFactory::createLocalName(std::shared_ptr const&, std::string const&) osgeo::proj::util::NameFactory::createNameSpace(dropbox::oxygen::nn > const&, osgeo::proj::util::PropertyMap const&) osgeo::proj::util::NameSpace::isGlobal() const osgeo::proj::util::NameSpace::name() const osgeo::proj::util::NameSpace::~NameSpace() osgeo::proj::util::PropertyMap::~PropertyMap() osgeo::proj::util::PropertyMap::PropertyMap() osgeo::proj::util::PropertyMap::PropertyMap(osgeo::proj::util::PropertyMap const&) osgeo::proj::util::PropertyMap::set(std::string const&, bool) osgeo::proj::util::PropertyMap::set(std::string const&, char const*) osgeo::proj::util::PropertyMap::set(std::string const&, int) osgeo::proj::util::PropertyMap::set(std::string const&, osgeo::proj::util::BaseObjectNNPtr const&) osgeo::proj::util::PropertyMap::set(std::string const&, std::string const&) osgeo::proj::util::PropertyMap::set(std::string const&, std::vector > const&) osgeo::proj::util::UnsupportedOperationException::~UnsupportedOperationException() osgeo::proj::util::UnsupportedOperationException::UnsupportedOperationException(osgeo::proj::util::UnsupportedOperationException const&) osgeo::proj::VerticalShiftGrid::gridAt(double, double) const osgeo::proj::VerticalShiftGridSet::gridAt(double, double) const osgeo::proj::VerticalShiftGridSet::open(pj_ctx*, std::string const&) osgeo::proj::VerticalShiftGridSet::reassign_context(pj_ctx*) osgeo::proj::VerticalShiftGridSet::reopen(pj_ctx*) osgeo::proj::VerticalShiftGridSet::~VerticalShiftGridSet() osgeo::proj::VerticalShiftGridSet::VerticalShiftGridSet() osgeo::proj::VerticalShiftGrid::~VerticalShiftGrid() osgeo::proj::VerticalShiftGrid::VerticalShiftGrid(std::string const&, int, int, osgeo::proj::ExtentAndRes const&) pj_add_type_crs_if_needed(std::string const&) pj_approx_2D_trans(PJconsts*, PJ_DIRECTION, PJ_COORD) pj_approx_3D_trans(PJconsts*, PJ_DIRECTION, PJ_COORD) pj_atof(char const*) pj_chomp(char*) pj_context_get_grid_cache_filename(pj_ctx*) pj_ctx::createDefault() pj_ctx::get_cpp_context() pj_ctx::~pj_ctx() pj_ctx::pj_ctx(pj_ctx const&) pj_ctx::set_ca_bundle_path(std::string const&) pj_ctx::set_search_paths(std::vector > const&) pj_ell_set(pj_ctx*, ARG_list*, double*, double*) pj_find_file(pj_ctx*, char const*, char*, unsigned long) pj_fwd(PJ_LP, PJconsts*) pj_get_datums_ref() pj_get_default_ctx() pj_get_default_searchpaths(pj_ctx*) pj_get_relative_share_proj(pj_ctx*) pj_get_release() pj_inv(PJ_XY, PJconsts*) pj_mkparam(char const*) pj_param_exists(ARG_list*, char const*) pj_param(pj_ctx*, ARG_list*, char const*) pj_phi2(pj_ctx*, double, double) pj_pr_list(PJconsts*) pj_shrink(char*) pj_stderr_proj_lib_deprecation_warning() proj_alter_id proj_alter_name proj_angular_input proj_angular_output proj_area_create proj_area_destroy proj_area_set_bbox proj_area_set_name proj_as_projjson proj_as_proj_string proj_assign_context proj_as_wkt proj_celestial_body_list_destroy proj_cleanup proj_clone proj_concatoperation_get_step proj_concatoperation_get_step_count proj_context_clone proj_context_create proj_context_delete_cpp_context(projCppContext*) proj_context_destroy proj_context_errno proj_context_errno_string proj_context_get_database_metadata proj_context_get_database_path proj_context_get_database_structure proj_context_get_url_endpoint proj_context_get_use_proj4_init_rules proj_context_get_user_writable_directory proj_context_guess_wkt_dialect proj_context_is_network_enabled proj_context_set_autoclose_database proj_context_set_ca_bundle_path proj_context_set_database_path proj_context_set_enable_network proj_context_set_fileapi proj_context_set_file_finder proj_context_set_network_callbacks proj_context_set(PJconsts*, pj_ctx*) proj_context_set_search_paths proj_context_set_sqlite3_vfs_name proj_context_set_url_endpoint proj_context_set_user_writable_directory proj_context_use_proj4_init_rules proj_convert_conversion_to_other_method proj_coord proj_coord_error() proj_coordinate_metadata_create proj_coordinate_metadata_get_epoch proj_coordoperation_create_inverse proj_coordoperation_get_accuracy proj_coordoperation_get_grid_used proj_coordoperation_get_grid_used_count proj_coordoperation_get_method_info proj_coordoperation_get_param proj_coordoperation_get_param_count proj_coordoperation_get_param_index proj_coordoperation_get_towgs84_values proj_coordoperation_has_ballpark_transformation proj_coordoperation_is_instantiable proj_coordoperation_requires_per_coordinate_input_time projCppContext::clone(pj_ctx*) const projCppContext::getDatabaseContext() projCppContext::projCppContext(pj_ctx*, char const*, std::vector > const&) projCppContext::toVector(char const* const*) proj_create proj_create_argv proj_create_cartesian_2D_cs proj_create_compound_crs proj_create_conversion proj_create_conversion_albers_equal_area proj_create_conversion_american_polyconic proj_create_conversion_azimuthal_equidistant proj_create_conversion_bonne proj_create_conversion_cassini_soldner proj_create_conversion_eckert_i proj_create_conversion_eckert_ii proj_create_conversion_eckert_iii proj_create_conversion_eckert_iv proj_create_conversion_eckert_v proj_create_conversion_eckert_vi proj_create_conversion_equal_earth proj_create_conversion_equidistant_conic proj_create_conversion_equidistant_cylindrical proj_create_conversion_equidistant_cylindrical_spherical proj_create_conversion_gall proj_create_conversion_gauss_schreiber_transverse_mercator proj_create_conversion_geostationary_satellite_sweep_x proj_create_conversion_geostationary_satellite_sweep_y proj_create_conversion_gnomonic proj_create_conversion_goode_homolosine proj_create_conversion_guam_projection proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin proj_create_conversion_hotine_oblique_mercator_variant_a proj_create_conversion_hotine_oblique_mercator_variant_b proj_create_conversion_international_map_world_polyconic proj_create_conversion_interrupted_goode_homolosine proj_create_conversion_krovak proj_create_conversion_krovak_north_oriented proj_create_conversion_laborde_oblique_mercator proj_create_conversion_lambert_azimuthal_equal_area proj_create_conversion_lambert_conic_conformal_1sp proj_create_conversion_lambert_conic_conformal_1sp_variant_b proj_create_conversion_lambert_conic_conformal_2sp proj_create_conversion_lambert_conic_conformal_2sp_belgium proj_create_conversion_lambert_conic_conformal_2sp_michigan proj_create_conversion_lambert_cylindrical_equal_area proj_create_conversion_lambert_cylindrical_equal_area_spherical proj_create_conversion_local_orthographic proj_create_conversion_mercator_variant_a proj_create_conversion_mercator_variant_b proj_create_conversion_miller_cylindrical proj_create_conversion_mollweide proj_create_conversion_new_zealand_mapping_grid proj_create_conversion_oblique_stereographic proj_create_conversion_orthographic proj_create_conversion_polar_stereographic_variant_a proj_create_conversion_polar_stereographic_variant_b proj_create_conversion_pole_rotation_grib_convention proj_create_conversion_pole_rotation_netcdf_cf_convention proj_create_conversion_popular_visualisation_pseudo_mercator proj_create_conversion_quadrilateralized_spherical_cube proj_create_conversion_robinson proj_create_conversion_sinusoidal proj_create_conversion_spherical_cross_track_height proj_create_conversion_stereographic proj_create_conversion_transverse_mercator proj_create_conversion_transverse_mercator_south_oriented proj_create_conversion_tunisia_mapping_grid proj_create_conversion_tunisia_mining_grid proj_create_conversion_two_point_equidistant proj_create_conversion_utm proj_create_conversion_van_der_grinten proj_create_conversion_vertical_perspective proj_create_conversion_wagner_i proj_create_conversion_wagner_ii proj_create_conversion_wagner_iii proj_create_conversion_wagner_iv proj_create_conversion_wagner_v proj_create_conversion_wagner_vi proj_create_conversion_wagner_vii proj_create_crs_to_crs proj_create_crs_to_crs_from_pj proj_create_cs proj_create_derived_geographic_crs proj_create_derived_projected_crs proj_create_ellipsoidal_2D_cs proj_create_ellipsoidal_3D_cs proj_create_engineering_crs proj_create_from_database proj_create_from_name proj_create_from_wkt proj_create_geocentric_crs proj_create_geocentric_crs_from_datum proj_create_geographic_crs proj_create_geographic_crs_from_datum proj_create_linear_affine_parametric_conversion proj_create_operation_factory_context proj_create_operations proj_create_projected_crs proj_create_transformation proj_create_vertical_crs proj_create_vertical_crs_ex proj_crs_add_horizontal_derived_conversion proj_crs_alter_cs_angular_unit proj_crs_alter_cs_linear_unit proj_crs_alter_geodetic_crs proj_crs_alter_parameters_linear_unit proj_crs_create_bound_crs proj_crs_create_bound_crs_to_WGS84 proj_crs_create_bound_vertical_crs proj_crs_create_projected_3D_crs_from_2D proj_crs_demote_to_2D proj_crs_get_coordinate_system proj_crs_get_coordoperation proj_crs_get_datum proj_crs_get_datum_ensemble proj_crs_get_datum_forced proj_crs_get_geodetic_crs proj_crs_get_horizontal_datum proj_crs_get_sub_crs proj_crs_has_point_motion_operation proj_crs_info_list_destroy proj_crs_is_derived proj_crs_promote_to_3D proj_cs_get_axis_count proj_cs_get_axis_info proj_cs_get_type proj_datum_ensemble_get_accuracy proj_datum_ensemble_get_member proj_datum_ensemble_get_member_count proj_degree_input proj_degree_output proj_destroy proj_dmstor proj_download_file proj_dynamic_datum_get_frame_reference_epoch proj_ellipsoid_get_parameters proj_errno proj_errno_reset proj_errno_restore proj_errno_set proj_errno_string proj_factors proj_geod proj_geod_direct proj_get_area_of_use proj_get_area_of_use_ex proj_get_authorities_from_database proj_get_celestial_body_list_from_database proj_get_celestial_body_name proj_get_codes_from_database proj_get_crs_info_list_from_database proj_get_crs_list_parameters_create proj_get_crs_list_parameters_destroy proj_get_domain_count proj_get_ellipsoid proj_get_geoid_models_from_database proj_get_id_auth_name proj_get_id_code proj_get_insert_statements proj_get_name proj_get_non_deprecated proj_get_prime_meridian proj_get_remarks proj_get_scope proj_get_scope_ex proj_get_source_crs proj_get_suggested_operation proj_get_target_crs proj_get_type proj_get_units_from_database proj_grid_cache_clear proj_grid_cache_set_enable proj_grid_cache_set_filename proj_grid_cache_set_max_size proj_grid_cache_set_ttl proj_grid_get_info_from_database proj_grid_info proj_identify projinfo proj_info proj_init_info proj_insert_object_session_create proj_insert_object_session_destroy proj_int_list_destroy proj_is_crs proj_is_deprecated proj_is_derived_crs proj_is_download_needed proj_is_equivalent_to proj_is_equivalent_to_with_ctx proj_list_angular_units proj_list_destroy proj_list_ellps proj_list_get proj_list_get_count proj_list_operations proj_list_prime_meridians proj_list_units proj_log_error(PJconsts const*, char const*, ...) proj_log_func proj_log_level proj_lp_dist proj_lpz_dist proj_normalize_for_visualization proj_operation_factory_context_destroy proj_operation_factory_context_set_allow_ballpark_transformations proj_operation_factory_context_set_allowed_intermediate_crs proj_operation_factory_context_set_allow_use_intermediate_crs proj_operation_factory_context_set_area_of_interest proj_operation_factory_context_set_area_of_interest_name proj_operation_factory_context_set_crs_extent_use proj_operation_factory_context_set_desired_accuracy proj_operation_factory_context_set_discard_superseded proj_operation_factory_context_set_grid_availability_use proj_operation_factory_context_set_spatial_criterion proj_operation_factory_context_set_use_proj_alternative_grid_names proj_pj_info proj_prime_meridian_get_parameters proj_query_geodetic_crs_from_datum proj_roundtrip proj_rtodms proj_rtodms2 proj_string_destroy proj_string_list_destroy proj_suggests_code_for proj_todeg proj_torad proj_trans proj_trans_array proj_trans_bounds proj_trans_bounds_3D proj_trans_generic proj_trans_get_last_used_operation proj_unit_list_destroy proj_uom_get_info_from_database proj_xy_dist proj_xyz_dist rtodms(char*, unsigned long, double, int, int) set_rtodms(int, int) proj-9.8.1/test/000775 001750 001750 00000000000 15166171735 013434 5ustar00eveneven000000 000000 proj-9.8.1/test/CMakeLists.txt000664 001750 001750 00000012313 15166171715 016172 0ustar00eveneven000000 000000 option(TESTING_USE_NETWORK "Permit use of network to fetch test requirements (if needed) \ and run network-dependent tests. Default ON." ON ) set(HAS_NETWORK OFF) # evaluate if ON below if(TESTING_USE_NETWORK) if(NOT CMAKE_REQUIRED_QUIET) # CMake 3.17+ use CHECK_START/CHECK_PASS/CHECK_FAIL message(STATUS "Checking if network is available") endif() set(NO_CONNECTION 1) find_program(HAS_CURL curl) if(HAS_CURL) # First try with curl as we can get an 'instant' answer if it is there execute_process( COMMAND ${HAS_CURL} -I https://www.google.com OUTPUT_QUIET ERROR_QUIET RESULT_VARIABLE NO_CONNECTION ) else() find_program(HAS_PING ping) if(HAS_PING) # Then fallback to ping -- https://stackoverflow.com/a/68376537/ if(WIN32) set(PING_COUNT "-n") else() set(PING_COUNT "-c") endif() execute_process( COMMAND ${HAS_PING} www.google.com ${PING_COUNT} 2 OUTPUT_QUIET ERROR_QUIET RESULT_VARIABLE NO_CONNECTION ) else() message(WARNING "Cannot establish if network is available - " "'curl' or 'ping' not found" ) endif() endif() if(NO_CONNECTION EQUAL 0) set(HAS_NETWORK ON) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Checking if network is available - yes") endif() else() if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Checking if network is available - no; skipping network-dependent tests." ) endif() endif() else() message(STATUS "Network access not premitted (TESTING_USE_NETWORK=OFF)") endif() option(RUN_NETWORK_DEPENDENT_TESTS "Whether to run tests dependent on network availability" ${HAS_NETWORK} ) # Regression tests proj_add_gie_test("Builtins" "gie/builtins.gie") proj_add_gie_test("Builtins2" "gie/more_builtins.gie") proj_add_gie_test("Axisswap" "gie/axisswap.gie") proj_add_gie_test("Ellipsoid" "gie/ellipsoid.gie") proj_add_gie_test("GDA" "gie/GDA.gie") proj_add_gie_test("4D-API-cs2cs-style" "gie/4D-API_cs2cs-style.gie") proj_add_gie_test("DHDN_ETRS89" "gie/DHDN_ETRS89.gie") proj_add_gie_test("Unitconvert" "gie/unitconvert.gie") proj_add_gie_test("adams_hemi" "gie/adams_hemi.gie") proj_add_gie_test("adams_ws1" "gie/adams_ws1.gie") proj_add_gie_test("adams_ws2" "gie/adams_ws2.gie") proj_add_gie_test("guyou" "gie/guyou.gie") proj_add_gie_test("peirce_q" "gie/peirce_q.gie") proj_add_gie_test("tinshift" "gie/tinshift.gie") proj_add_gie_test("spilhaus" "gie/spilhaus.gie") proj_add_gie_test("epsg_no_grid" "gie/epsg_no_grid.gie") if(TIFF_ENABLED) proj_add_gie_test("Deformation" "gie/deformation.gie") proj_add_gie_test("geotiff_grids" "gie/geotiff_grids.gie") proj_add_gie_test("defmodel" "gie/defmodel.gie") proj_add_gie_test("gridshift" "gie/gridshift.gie") # Does not really require the network, but test running with PROJ_NETWORK=ON proj_add_gie_network_dependent_test("gridshift_network_enabled" "gie/gridshift.gie") endif() if(TIFF_ENABLED AND CURL_ENABLED AND RUN_NETWORK_DEPENDENT_TESTS) proj_add_gie_network_dependent_test("nkg" "gie/nkg.gie") proj_add_gie_network_dependent_test("epsg_grid" "gie/epsg_grid.gie") endif() # GIGS tests. Uncommented tests are expected to fail due to issues with # various projections. Should be investigated further and fixed. proj_add_gie_test("GIGS-5101.1-jhs" "gigs/5101.1-jhs.gie") proj_add_gie_test("GIGS-5101.2-jhs" "gigs/5101.2-jhs.gie") proj_add_gie_test("GIGS-5101.3-jhs" "gigs/5101.3-jhs.gie") proj_add_gie_test("GIGS-5101.4-jhs-etmerc" "gigs/5101.4-jhs-etmerc.gie") # Same as above, but using etmerc instead of tmerc #proj_add_gie_test("GIGS-5101.4-jhs" "gigs/5101.4-jhs.gie") proj_add_gie_test("GIGS-5102.1" "gigs/5102.1.gie") proj_add_gie_test("GIGS-5102.2" "gigs/5102.2.gie") proj_add_gie_test("GIGS-5103.1" "gigs/5103.1.gie") proj_add_gie_test("GIGS-5103.2" "gigs/5103.2.gie") proj_add_gie_test("GIGS-5103.3" "gigs/5103.3.gie") proj_add_gie_test("GIGS-5104" "gigs/5104.gie") #proj_add_gie_test("GIGS-5105.1" "gigs/5105.1.gie") proj_add_gie_test("GIGS-5105.2" "gigs/5105.2.gie") proj_add_gie_test("GIGS-5106" "gigs/5106.gie") proj_add_gie_test("GIGS-5107" "gigs/5107.gie") proj_add_gie_test("GIGS-5108" "gigs/5108.gie") proj_add_gie_test("GIGS-5109" "gigs/5109.gie") #proj_add_gie_test("GIGS-5110" "gigs/5110.gie") proj_add_gie_test("GIGS-5111.1" "gigs/5111.1.gie") #proj_add_gie_test("GIGS-5111.2" "gigs/5111.2.gie") proj_add_gie_test("GIGS-5112" "gigs/5112.gie") proj_add_gie_test("GIGS-5113" "gigs/5113.gie") proj_add_gie_test("GIGS-5201" "gigs/5201.gie") #proj_add_gie_test("GIGS-5203" "gigs/5203.1.gie") #proj_add_gie_test("GIGS-5204.1" "gigs/5204.1.gie") #proj_add_gie_test("GIGS-5205.1" "gigs/5205.1.gie") #proj_add_gie_test("GIGS-5206" "gigs/5206.gie") #proj_add_gie_test("GIGS-5207.1" "gigs/5207.1.gie") #proj_add_gie_test("GIGS-5207.2" "gigs/5207.2.gie") proj_add_gie_test("GIGS-5208" "gigs/5208.gie") #SET(CATCH2_INCLUDE catch.hpp) #SET(TEST_MAIN_SRC test_main.cpp) #set(TEST_MAIN_LIBRARIES test_main) #add_library( ${TEST_MAIN_LIBRARIES} # ${TEST_MAIN_SRC} # ${CATCH2_INCLUDE} ) add_subdirectory(cli) add_subdirectory(unit) add_subdirectory(benchmark) if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/fuzzers") add_subdirectory(fuzzers) endif() proj-9.8.1/test/gigs/000775 001750 001750 00000000000 15166171735 014365 5ustar00eveneven000000 000000 proj-9.8.1/test/gigs/5113.gie000664 001750 001750 00000003775 15166171715 015456 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5113, Transverse Mercator (South Oriented), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4148 +inv \ +step +init=epsg:2049 -------------------------------------------------------------------------------- tolerance 0.03 m accept 22.5 0.0 expect -166998.44 0.0 tolerance 0.03 m accept 21.5 -25.0 expect -50475.46 2766147.25 tolerance 0.03 m accept 20.5 -30.0 expect 48243.45 3320218.65 tolerance 0.03 m accept 19.5 -35.0 expect 136937.65 3875621.18 tolerance 0.03 m accept 19.5 -35.0 expect 136937.65 3875621.18 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:2049 +inv \ +step +init=epsg:4148 -------------------------------------------------------------------------------- tolerance 0.03 m accept -166998.44 0.0 expect 22.5 0.0 tolerance 0.03 m accept -50475.46 2766147.25 expect 21.5 -25.0 tolerance 0.03 m accept 48243.45 3320218.65 expect 20.5 -30.0 tolerance 0.03 m accept 136937.65 3875621.18 expect 19.5 -35.0 tolerance 0.03 m accept 136937.65 3875621.18 expect 19.5 -35.0 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4148 +inv \ +step +init=epsg:2049 -------------------------------------------------------------------------------- tolerance 0.006 m accept 22.5 0.0 roundtrip 1000 tolerance 0.006 m accept 21.5 -25.0 roundtrip 1000 tolerance 0.006 m accept 20.5 -30.0 roundtrip 1000 tolerance 0.006 m accept 19.5 -35.0 roundtrip 1000 tolerance 0.006 m accept 19.5 -35.0 roundtrip 1000 proj-9.8.1/test/gigs/5107.gie000664 001750 001750 00000007554 15166171715 015460 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5107, American Polyconic, v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4674 +inv \ +step +proj=poly +lat_0=0 +lon_0=-54 +x_0=5000000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m -------------------------------------------------------------------------------- tolerance 0.05 m accept -54 0 expect 5000000.0 10000000.0 tolerance 0.05 m accept -45 6 expect 5996378.70982 10671650.0559 tolerance 0.05 m accept -45 0 expect 6001875.41714 10000000.0 tolerance 0.05 m accept -45 -6 expect 5996378.70982 9328349.94408 tolerance 0.05 m accept -41 -13 expect 6409689.58688 8526306.26193 tolerance 0.05 m accept -38 -20 expect 6671808.91963 7707735.72988 tolerance 0.05 m accept -37 -24 expect 6725584.49173 7240461.99578 tolerance 0.05 m accept -36 -30 expect 6729619.73995 6543762.57644 tolerance 0.05 m accept -57 -30 expect 4710574.22344 6676097.81117 tolerance 0.05 m accept -54 -29.3674766667 expect 5000000.0 6750000.0 tolerance 0.05 m accept -47 -27.5 expect 5691318.14689 6937461.05067 tolerance 0.05 m accept -37 -24 expect 6725584.49173 7240461.99578 tolerance 0.05 m accept -30 -22.5 expect 7458947.70133 7313327.31691 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=poly +lat_0=0 +lon_0=-54 +x_0=5000000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +inv \ +step +init=epsg:4674 -------------------------------------------------------------------------------- tolerance 0.05 m accept 5000000.0 10000000.0 expect -54 0 tolerance 0.05 m accept 5996378.70982 10671650.0559 expect -45 6 tolerance 0.05 m accept 6001875.41714 10000000.0 expect -45 0 tolerance 0.05 m accept 5996378.70982 9328349.94408 expect -45 -6 tolerance 0.05 m accept 6409689.58688 8526306.26193 expect -41 -13 tolerance 0.05 m accept 6671808.91963 7707735.72988 expect -38 -20 tolerance 0.05 m accept 6725584.49173 7240461.99578 expect -37 -24 tolerance 0.05 m accept 6729619.73995 6543762.57644 expect -36 -30 tolerance 0.05 m accept 4710574.22344 6676097.81117 expect -57 -30 tolerance 0.05 m accept 5000000.0 6750000.0 expect -54 -29.3674766667 tolerance 0.05 m accept 5691318.14689 6937461.05067 expect -47 -27.5 tolerance 0.05 m accept 6725584.49173 7240461.99578 expect -37 -24 tolerance 0.05 m accept 7458947.70133 7313327.31691 expect -30 -22.5 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4674 +inv \ +step +proj=poly +lat_0=0 +lon_0=-54 +x_0=5000000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m -------------------------------------------------------------------------------- tolerance 0.006 m accept -54 0 roundtrip 1000 tolerance 0.006 m accept -45 6 roundtrip 1000 tolerance 0.006 m accept -45 0 roundtrip 1000 tolerance 0.006 m accept -45 -6 roundtrip 1000 tolerance 0.006 m accept -41 -13 roundtrip 1000 tolerance 0.006 m accept -38 -20 roundtrip 1000 tolerance 0.006 m accept -37 -24 roundtrip 1000 tolerance 0.006 m accept -36 -30 roundtrip 1000 tolerance 0.006 m accept -57 -30 roundtrip 1000 tolerance 0.006 m accept -54 -29.3674766667 roundtrip 1000 tolerance 0.006 m accept -47 -27.5 roundtrip 1000 tolerance 0.006 m accept -37 -24 roundtrip 1000 tolerance 0.006 m accept -30 -22.5 roundtrip 1000 proj-9.8.1/test/gigs/5208.gie000664 001750 001750 00000007342 15166171715 015455 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5208, Longitude Rotation, v2.0_2011-06-28. The test tolerance is 0.01". Since gie can only use linear tolerances we convert that to an approximate liniar distance instead, by multiplying with 111km: 0.01" * 111 km = 2.777777778-7 * 111000 m = 0.03 m To be on the safe side we, use 0.01 m as the tolerance. -------------------------------------------------------------------------------- # NTF <4275> +proj=longlat +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 <> # NTF (Paris) <4807> +proj=longlat +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris <> use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline\ +step +init=epsg:4275 +inv\ +step +init=epsg:4807 -------------------------------------------------------------------------------- tolerance 0.01 m accept 5 58 expect 2.66277083 58 tolerance 0.01 m accept 5 56 expect 2.66277083 56 tolerance 0.01 m accept 5 55 expect 2.66277083 55 tolerance 0.01 m accept 5 53 expect 2.66277083 53 tolerance 0.01 m accept 4 51 expect 1.66277083 51 tolerance 0.01 m accept 4 49 expect 1.66277083 49 tolerance 0.01 m accept 2.33722917 46.8 expect 0 46.8 tolerance 0.01 m accept 3 53 expect 0.66277083 53 tolerance 0.01 m accept 4 53 expect 1.66277083 53 tolerance 0.01 m accept 6 53 expect 3.66277083 53 tolerance 0.01 m accept 7 53 expect 4.66277083 53 tolerance 0.01 m accept 9 53 expect 6.66277083 53 tolerance 0.01 m accept 10 53 expect 7.66277083 53 tolerance 0.01 m accept 11 53 expect 8.66277083 53 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4807 +inv \ +step +init=epsg:4275 -------------------------------------------------------------------------------- tolerance 0.01 m accept 2.66277083 58 expect 5 58 tolerance 0.01 m accept 2.66277083 56 expect 5 56 tolerance 0.01 m accept 2.66277083 55 expect 5 55 tolerance 0.01 m accept 2.66277083 53 expect 5 53 tolerance 0.01 m accept 1.66277083 51 expect 4 51 tolerance 0.01 m accept 1.66277083 49 expect 4 49 tolerance 0.01 m accept 0 46.8 expect 2.33722917 46.8 tolerance 0.01 m accept 0.66277083 53 expect 3 53 tolerance 0.01 m accept 1.66277083 53 expect 4 53 tolerance 0.01 m accept 3.66277083 53 expect 6 53 tolerance 0.01 m accept 4.66277083 53 expect 7 53 tolerance 0.01 m accept 6.66277083 53 expect 9 53 tolerance 0.01 m accept 7.66277083 53 expect 10 53 tolerance 0.01 m accept 8.66277083 53 expect 11 53 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4275 +inv \ +step +init=epsg:4807 -------------------------------------------------------------------------------- tolerance 0.01 m accept 5 58 roundtrip 1000 tolerance 0.01 m accept 5 56 roundtrip 1000 tolerance 0.01 m accept 5 55 roundtrip 1000 tolerance 0.01 m accept 5 53 roundtrip 1000 tolerance 0.01 m accept 4 51 roundtrip 1000 tolerance 0.01 m accept 4 49 roundtrip 1000 tolerance 0.01 m accept 2.33722917 46.8 roundtrip 1000 tolerance 0.01 m accept 3 53 roundtrip 1000 tolerance 0.01 m accept 4 53 roundtrip 1000 tolerance 0.01 m accept 6 53 roundtrip 1000 tolerance 0.01 m accept 7 53 roundtrip 1000 tolerance 0.01 m accept 9 53 roundtrip 1000 tolerance 0.01 m accept 10 53 roundtrip 1000 tolerance 0.01 m accept 11 53 roundtrip 1000 proj-9.8.1/test/gigs/5201.gie000664 001750 001750 00000021116 15166171715 015441 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5201, Geographic Geocentric conversions, v2.0_2011-09-28. (EPSG 4979 - WGS84 3d has been replaced with EPSG code 4326 WGS84 2d). -------------------------------------------------------------------------------- # WGS 84 <4978> +proj=geocent +datum=WGS84 +units=m <> # WGS 84 <4326> +proj=longlat +datum=WGS84 <> use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4978 +inv \ +step +init=epsg:4326 -------------------------------------------------------------------------------- tolerance 0.01 m accept -962479.5924 555687.8517 6260738.6526 expect 150 80 1214.137 tolerance 0.01 m accept -962297.0059 555582.4354 6259542.961 expect 150 80 0 tolerance 0.01 m accept -1598248.169 2768777.623 5501278.468 expect 119.99524538 60.00475191 619.6317 tolerance 0.01 m accept -1598023.169 2768387.912 5500499.045 expect 119.9952447 60.00475258 -280.3683 tolerance 0.01 m accept 2764210.4054 4787752.865 3170468.5199 expect 60 30 189.569 tolerance 0.01 m accept 2764128.3196 4787610.6883 3170373.7354 expect 60 30 0 tolerance 0.01 m accept 6377934.396 -112 434 expect -0.00100615 0.00392509 -202.5882 tolerance 0.01 m accept 6374934.396 -112 434 expect -0.00100662 0.00392695 -3202.5881 tolerance 0.01 m accept 6367934.396 -112 434 expect -0.00100773 0.00393129 -10202.5881 tolerance 0.01 m accept 2764128.3196 -4787610.6883 -3170373.7354 expect -60 -30 0 tolerance 0.01 m accept 2763900.3489 -4787215.8313 -3170110.4974 expect -60 -30 -526.476 tolerance 0.01 m accept 2763880.8633 -4787182.0813 -3170087.9974 expect -60 -30 -571.476 tolerance 0.01 m accept -1598023.169 -2768611.912 -5499631.045 expect -119.99323757 -59.99934884 -935.0995 tolerance 0.01 m accept -1597798.169 -2768222.201 -5498851.622 expect -119.99323663 -59.99934874 -1835.0995 tolerance 0.01 m accept -962297.0059 -555582.4354 -6259542.961 expect -150 -80 0 tolerance 0.01 m accept -962150.945 -555498.1071 -6258586.4616 expect -150 -80 -971.255 tolerance 0.01 m accept -961798.2951 -555294.5046 -6256277.0874 expect -150 -80 -3316.255 tolerance 0.01 m accept -2187336.719 -112 5971017.093 expect -179.99706624 70.00490733 -223.6178 tolerance 0.01 m accept -2904698.5551 -2904698.5551 4862789.0377 expect -135 50 0 tolerance 0.01 m accept 371 -5783593.614 2679326.11 expect -89.99632465 25.00366329 -274.7286 tolerance 0.01 m accept 6378137 0 0 expect 0 0 0 tolerance 0.01 m accept -4087095.478 2977467.559 -3875457.429 expect 143.92649252 -37.65282217 737.7182 tolerance 0.01 m accept -4085919.959 2976611.233 -3874335.274 expect 143.92649211 -37.65282206 -1099.2288 tolerance 0.01 m accept -4084000.165 2975212.729 -3872502.631 expect 143.92649143 -37.65282187 -4099.2288 tolerance 0.01 m accept -4079520.647 2971949.553 -3868226.465 expect 143.92648984 -37.65282143 -11099.2288 tolerance 0.01 m accept -2904698.5551 2904698.5551 -4862789.0377 expect 135 -50 0 tolerance 0.01 m accept -2187336.719 -112 -5970149.093 expect -179.99706624 -70.00224647 -1039.2896 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4326 +inv \ +step +init=epsg:4978 -------------------------------------------------------------------------------- tolerance 0.01 m accept 150 80 1214.137 expect -962479.5924 555687.8517 6260738.6526 tolerance 0.01 m accept 150 80 0 expect -962297.0059 555582.4354 6259542.961 tolerance 0.01 m accept 119.99524538 60.00475191 619.6317 expect -1598248.169 2768777.623 5501278.468 tolerance 0.01 m accept 119.9952447 60.00475258 -280.3683 expect -1598023.169 2768387.912 5500499.045 tolerance 0.01 m accept 60 30 189.569 expect 2764210.4054 4787752.865 3170468.5199 tolerance 0.01 m accept 60 30 0 expect 2764128.3196 4787610.6883 3170373.7354 tolerance 0.01 m accept -0.00100615 0.00392509 -202.5882 expect 6377934.396 -112 434 tolerance 0.01 m accept -0.00100662 0.00392695 -3202.5881 expect 6374934.396 -112 434 tolerance 0.01 m accept -0.00100773 0.00393129 -10202.5881 expect 6367934.396 -112 434 tolerance 0.01 m accept -60 -30 0 expect 2764128.3196 -4787610.6883 -3170373.7354 tolerance 0.01 m accept -60 -30 -526.476 expect 2763900.3489 -4787215.8313 -3170110.4974 tolerance 0.01 m accept -60 -30 -571.476 expect 2763880.8633 -4787182.0813 -3170087.9974 tolerance 0.01 m accept -119.99323757 -59.99934884 -935.0995 expect -1598023.169 -2768611.912 -5499631.045 tolerance 0.01 m accept -119.99323663 -59.99934874 -1835.0995 expect -1597798.169 -2768222.201 -5498851.622 tolerance 0.01 m accept -150 -80 0 expect -962297.0059 -555582.4354 -6259542.961 tolerance 0.01 m accept -150 -80 -971.255 expect -962150.945 -555498.1071 -6258586.4616 tolerance 0.01 m accept -150 -80 -3316.255 expect -961798.2951 -555294.5046 -6256277.0874 tolerance 0.01 m accept -179.99706624 70.00490733 -223.6178 expect -2187336.719 -112 5971017.093 tolerance 0.01 m accept -135 50 0 expect -2904698.5551 -2904698.5551 4862789.0377 tolerance 0.01 m accept -89.99632465 25.00366329 -274.7286 expect 371 -5783593.614 2679326.11 tolerance 0.01 m accept 0 0 0 expect 6378137 0 0 tolerance 0.01 m accept 143.92649252 -37.65282217 737.7182 expect -4087095.478 2977467.559 -3875457.429 tolerance 0.01 m accept 143.92649211 -37.65282206 -1099.2288 expect -4085919.959 2976611.233 -3874335.274 tolerance 0.01 m accept 143.92649143 -37.65282187 -4099.2288 expect -4084000.165 2975212.729 -3872502.631 tolerance 0.01 m accept 143.92648984 -37.65282143 -11099.2288 expect -4079520.647 2971949.553 -3868226.465 tolerance 0.01 m accept 135 -50 0 expect -2904698.5551 2904698.5551 -4862789.0377 tolerance 0.01 m accept -179.99706624 -70.00224647 -1039.2896 expect -2187336.719 -112 -5970149.093 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4978 +inv \ +step +init=epsg:4326 -------------------------------------------------------------------------------- tolerance 0.01 m accept -962479.5924 555687.8517 6260738.6526 roundtrip 1000 tolerance 0.01 m accept -962297.0059 555582.4354 6259542.961 roundtrip 1000 tolerance 0.01 m accept -1598248.169 2768777.623 5501278.468 roundtrip 1000 tolerance 0.01 m accept -1598023.169 2768387.912 5500499.045 roundtrip 1000 tolerance 0.01 m accept 2764210.4054 4787752.865 3170468.5199 roundtrip 1000 tolerance 0.01 m accept 2764128.3196 4787610.6883 3170373.7354 roundtrip 1000 tolerance 0.01 m accept 6377934.396 -112 434 roundtrip 1000 tolerance 0.01 m accept 6374934.396 -112 434 roundtrip 1000 tolerance 0.01 m accept 6367934.396 -112 434 roundtrip 1000 tolerance 0.01 m accept 2764128.3196 -4787610.6883 -3170373.7354 roundtrip 1000 tolerance 0.01 m accept 2763900.3489 -4787215.8313 -3170110.4974 roundtrip 1000 tolerance 0.01 m accept 2763880.8633 -4787182.0813 -3170087.9974 roundtrip 1000 tolerance 0.01 m accept -1598023.169 -2768611.912 -5499631.045 roundtrip 1000 tolerance 0.01 m accept -1597798.169 -2768222.201 -5498851.622 roundtrip 1000 tolerance 0.01 m accept -962297.0059 -555582.4354 -6259542.961 roundtrip 1000 tolerance 0.01 m accept -962150.945 -555498.1071 -6258586.4616 roundtrip 1000 tolerance 0.01 m accept -961798.2951 -555294.5046 -6256277.0874 roundtrip 1000 tolerance 0.01 m accept -2187336.719 -112 5971017.093 roundtrip 1000 tolerance 0.01 m accept -2904698.5551 -2904698.5551 4862789.0377 roundtrip 1000 tolerance 0.01 m accept 371 -5783593.614 2679326.11 roundtrip 1000 tolerance 0.01 m accept 6378137 0 0 roundtrip 1000 tolerance 0.01 m accept -4087095.478 2977467.559 -3875457.429 roundtrip 1000 tolerance 0.01 m accept -4085919.959 2976611.233 -3874335.274 roundtrip 1000 tolerance 0.01 m accept -4084000.165 2975212.729 -3872502.631 roundtrip 1000 tolerance 0.01 m accept -4079520.647 2971949.553 -3868226.465 roundtrip 1000 tolerance 0.01 m accept -2904698.5551 2904698.5551 -4862789.0377 roundtrip 1000 tolerance 0.01 m accept -2187336.719 -112 -5970149.093 roundtrip 1000 proj-9.8.1/test/gigs/5104.gie000664 001750 001750 00000011211 15166171715 015436 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5104, Oblique stereographic, v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4289 +inv \ +step +init=epsg:28992 -------------------------------------------------------------------------------- tolerance 0.05 m accept 5 58 expect 132023.27 1114054.87 tolerance 0.05 m accept 5 57 expect 131405.466 1002468.081 tolerance 0.05 m accept 5 56 expect 130792.264 890981.281 tolerance 0.05 m accept 5 55 expect 130183.56 779577.7 tolerance 0.05 m accept 5 54 expect 129579.26 668240.58 tolerance 0.05 m accept 5 53 expect 128979.26 556953.19 tolerance 0.05 m accept 5.38763888889 52.1561605556 expect 155000 463000 tolerance 0.05 m accept 4 51 expect 57605.946 335312.662 tolerance 0.05 m accept 4 50 expect 55502.306 224086.514 tolerance 0.05 m accept 4.0 49.0 expect 53412.76 112842.73 tolerance 0.05 m accept 3.31372805556 47.9752611111 expect 0 0 tolerance 0.05 m accept 3 53 expect -5253.06 559535.55 tolerance 0.05 m accept 4 53 expect 61856.78 557779.12 tolerance 0.05 m accept 5 53 expect 128979.26 556953.19 tolerance 0.05 m accept 6 53 expect 196105.28 557057.74 tolerance 0.05 m accept 7 53 expect 263225.72 558092.77 tolerance 0.05 m accept 8 53 expect 330331.46 560058.31 tolerance 0.05 m accept 9 53 expect 397413.385 562954.436 tolerance 0.05 m accept 10 53 expect 464462.35 566781.24 tolerance 0.05 m accept 11 53 expect 531469.2 571538.84 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:28992 +inv \ +step +init=epsg:4289 -------------------------------------------------------------------------------- tolerance 0.05 m accept 132023.27 1114054.87 expect 5 58 tolerance 0.05 m accept 131405.466 1002468.081 expect 5 57 tolerance 0.05 m accept 130792.264 890981.281 expect 5 56 tolerance 0.05 m accept 130183.56 779577.7 expect 5 55 tolerance 0.05 m accept 129579.26 668240.58 expect 5 54 tolerance 0.05 m accept 128979.26 556953.19 expect 5 53 tolerance 0.05 m accept 155000 463000 expect 5.38763888889 52.1561605556 tolerance 0.05 m accept 57605.946 335312.662 expect 4 51 tolerance 0.05 m accept 55502.306 224086.514 expect 4 50 tolerance 0.05 m accept 53412.76 112842.73 expect 4.0 49.0 tolerance 0.05 m accept 0 0 expect 3.31372805556 47.9752611111 tolerance 0.05 m accept -5253.06 559535.55 expect 3 53 tolerance 0.05 m accept 61856.78 557779.12 expect 4 53 tolerance 0.05 m accept 128979.26 556953.19 expect 5 53 tolerance 0.05 m accept 196105.28 557057.74 expect 6 53 tolerance 0.05 m accept 263225.72 558092.77 expect 7 53 tolerance 0.05 m accept 330331.46 560058.31 expect 8 53 tolerance 0.05 m accept 397413.385 562954.436 expect 9 53 tolerance 0.05 m accept 464462.35 566781.24 expect 10 53 tolerance 0.05 m accept 531469.2 571538.84 expect 11 53 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4289 +inv \ +step +init=epsg:28992 -------------------------------------------------------------------------------- tolerance 0.006 m accept 5 58 roundtrip 1000 tolerance 0.006 m accept 5 57 roundtrip 1000 tolerance 0.006 m accept 5 56 roundtrip 1000 tolerance 0.006 m accept 5 55 roundtrip 1000 tolerance 0.006 m accept 5 54 roundtrip 1000 tolerance 0.006 m accept 5 53 roundtrip 1000 tolerance 0.006 m accept 5.38763888889 52.1561605556 roundtrip 1000 tolerance 0.006 m accept 4 51 roundtrip 1000 tolerance 0.006 m accept 4 50 roundtrip 1000 tolerance 0.006 m accept 4.0 49.0 roundtrip 1000 tolerance 0.006 m accept 3.31372805556 47.9752611111 roundtrip 1000 tolerance 0.006 m accept 3 53 roundtrip 1000 tolerance 0.006 m accept 4 53 roundtrip 1000 tolerance 0.006 m accept 5 53 roundtrip 1000 tolerance 0.006 m accept 6 53 roundtrip 1000 tolerance 0.006 m accept 7 53 roundtrip 1000 tolerance 0.006 m accept 8 53 roundtrip 1000 tolerance 0.006 m accept 9 53 roundtrip 1000 tolerance 0.006 m accept 10 53 roundtrip 1000 tolerance 0.006 m accept 11 53 roundtrip 1000 proj-9.8.1/test/gigs/5101.3-jhs.gie000664 001750 001750 00000012571 15166171715 016370 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5101 (part 3), Transverse Mercator, v2-0_2011-06-28, recommended JHS formula -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4283 +inv \ +step +init=epsg:28354 -------------------------------------------------------------------------------- tolerance 0.03 m accept 146 80 expect 596813.055 18885748.71 tolerance 0.03 m accept 146 60 expect 778711.23 16661953.04 tolerance 0.03 m accept 146 40 expect 926893.302 14439746.92 tolerance 0.03 m accept 146 20 expect 1023538.687 12219308.24 tolerance 0.03 m accept 146 0 expect 1057087.12 10000000.0 tolerance 0.03 m accept 146 -20 expect 1023538.687 7780691.762 tolerance 0.03 m accept 146 -40 expect 926893.302 5560253.083 tolerance 0.03 m accept 146 -60 expect 778711.23 3338046.96 tolerance 0.03 m accept 146 -80 expect 596813.055 1114251.292 tolerance 0.03 m accept 136 -60 expect 221288.77 3338046.96 tolerance 0.03 m accept 137 -60 expect 276979.926 3341842.798 tolerance 0.03 m accept 138 -60 expect 332705.179 3344794.516 tolerance 0.03 m accept 139 -60 expect 388455.958 3346902.565 tolerance 0.03 m accept 140 -60 expect 444223.733 3348167.265 tolerance 0.03 m accept 141 -60 expect 500000.0 3348588.81 tolerance 0.03 m accept 142 -60 expect 555776.267 3348167.265 tolerance 0.03 m accept 143 -60 expect 611544.042 3346902.565 tolerance 0.03 m accept 144 -60 expect 667294.821 3344794.516 tolerance 0.03 m accept 145 -60 expect 723020.074 3341842.798 tolerance 0.03 m accept 146 -60 expect 778711.23 3338046.96 tolerance 0.03 m accept 147 -60 expect 834359.668 3333406.428 tolerance 0.03 m accept 148 -60 expect 889956.701 3327920.506 tolerance 0.03 m accept 149 -60 expect 945493.565 3321588.377 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:28354 +inv \ +step +init=epsg:4283 -------------------------------------------------------------------------------- tolerance 0.03 m accept 596813.055 18885748.71 expect 146 80 tolerance 0.03 m accept 778711.23 16661953.04 expect 146 60 tolerance 0.03 m accept 926893.302 14439746.92 expect 146 40 tolerance 0.03 m accept 1023538.687 12219308.24 expect 146 20 tolerance 0.03 m accept 1057087.12 10000000.0 expect 146 0 tolerance 0.03 m accept 1023538.687 7780691.762 expect 146 -20 tolerance 0.03 m accept 926893.302 5560253.083 expect 146 -40 tolerance 0.03 m accept 778711.23 3338046.96 expect 146 -60 tolerance 0.03 m accept 596813.055 1114251.292 expect 146 -80 tolerance 0.03 m accept 221288.77 3338046.96 expect 136 -60 tolerance 0.03 m accept 276979.926 3341842.798 expect 137 -60 tolerance 0.03 m accept 332705.179 3344794.516 expect 138 -60 tolerance 0.03 m accept 388455.958 3346902.565 expect 139 -60 tolerance 0.03 m accept 444223.733 3348167.265 expect 140 -60 tolerance 0.03 m accept 500000.0 3348588.81 expect 141 -60 tolerance 0.03 m accept 555776.267 3348167.265 expect 142 -60 tolerance 0.03 m accept 611544.042 3346902.565 expect 143 -60 tolerance 0.03 m accept 667294.821 3344794.516 expect 144 -60 tolerance 0.03 m accept 723020.074 3341842.798 expect 145 -60 tolerance 0.03 m accept 778711.23 3338046.96 expect 146 -60 tolerance 0.03 m accept 834359.668 3333406.428 expect 147 -60 tolerance 0.03 m accept 889956.701 3327920.506 expect 148 -60 tolerance 0.03 m accept 945493.565 3321588.377 expect 149 -60 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4283 +inv \ +step +init=epsg:28354 -------------------------------------------------------------------------------- tolerance 0.006 m accept 146 80 roundtrip 1000 tolerance 0.006 m accept 146 60 roundtrip 1000 tolerance 0.006 m accept 146 40 roundtrip 1000 tolerance 0.006 m accept 146 20 roundtrip 1000 tolerance 0.006 m accept 146 0 roundtrip 1000 tolerance 0.006 m accept 146 -20 roundtrip 1000 tolerance 0.006 m accept 146 -40 roundtrip 1000 tolerance 0.006 m accept 146 -60 roundtrip 1000 tolerance 0.006 m accept 146 -80 roundtrip 1000 tolerance 0.006 m accept 136 -60 roundtrip 1000 tolerance 0.006 m accept 137 -60 roundtrip 1000 tolerance 0.006 m accept 138 -60 roundtrip 1000 tolerance 0.006 m accept 139 -60 roundtrip 1000 tolerance 0.006 m accept 140 -60 roundtrip 1000 tolerance 0.006 m accept 141 -60 roundtrip 1000 tolerance 0.006 m accept 142 -60 roundtrip 1000 tolerance 0.006 m accept 143 -60 roundtrip 1000 tolerance 0.006 m accept 144 -60 roundtrip 1000 tolerance 0.006 m accept 145 -60 roundtrip 1000 tolerance 0.006 m accept 146 -60 roundtrip 1000 tolerance 0.006 m accept 147 -60 roundtrip 1000 tolerance 0.006 m accept 148 -60 roundtrip 1000 tolerance 0.006 m accept 149 -60 roundtrip 1000 proj-9.8.1/test/gigs/5101.1-jhs.gie000664 001750 001750 00000030133 15166171715 016360 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5101 (part 1), Transverse Mercator, v2-0_2011-06-28, recommended JHS formula -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4326 +inv \ +step +proj=etmerc +lat_0=49 +lon_0=-2 +k_0=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=WGS84 +units=m +no_def -------------------------------------------------------------------------------- tolerance 0.03 m accept 3 80 expect 496813.178 3358297.326 tolerance 0.03 m accept 2.9999999 60 expect 678711.584 1134498.83 tolerance 0.03 m accept 3 49 expect 765648.501 -87944.74 tolerance 0.03 m accept 3.0000001 40 expect 826893.845 -1087710.121 tolerance 0.03 m accept 3 20 expect 923539.353 -3308151.625 tolerance 0.03 m accept 3 0 expect 957087.829 -5527462.686 tolerance 0.03 m accept 3 -20 expect 923539.353 -7746773.748 tolerance 0.03 m accept 3 -40 expect 826893.845 -9967215.251 tolerance 0.03 m accept 3 -60 expect 678711.584 -12189424.202 tolerance 0.03 m accept 3 -80 expect 496813.178 -14413222.698 tolerance 0.03 m accept -2 80 expect 400000 3354134.429 tolerance 0.03 m accept -2 60 expect 400000 1123956.966 tolerance 0.03 m accept -2 49 expect 400000 -100000 tolerance 0.03 m accept -2 40 expect 400000 -1099699.834 tolerance 0.03 m accept -2 20 expect 400000 -3315978.565 tolerance 0.03 m accept -2 0 expect 400000 -5527462.686 tolerance 0.03 m accept -2 -20 expect 400000 -7738946.807 tolerance 0.03 m accept -2 -40 expect 400000 -9955225.538 tolerance 0.03 m accept -2 -60 expect 400000 -12178882.338 tolerance 0.03 m accept -2 -80 expect 400000 -14409059.801 tolerance 0.03 m accept -5 80 expect 341867.711 3355633.571 tolerance 0.03 m accept -5 60 expect 232704.966 1127751.264 tolerance 0.03 m accept -5 49 expect 180586.02 -95662.911 tolerance 0.03 m accept -5 40 expect 143900.026 -1095387.991 tolerance 0.03 m accept -5 20 expect 86073.28 -3313165.843 tolerance 0.03 m accept -5 0 expect 66021.018 -5527462.686 tolerance 0.03 m accept -5 -20 expect 86073.28 -7741759.529 tolerance 0.03 m accept -5 -40 expect 143900.026 -9959537.381 tolerance 0.03 m accept -5 -60 expect 232704.966 -12182676.637 tolerance 0.03 m accept -5 -80 expect 341867.711 -14410558.943 tolerance 0.03 m accept -7.5559037 49.7661327 expect 0 0 tolerance 0.03 m accept -5 0 expect 66021.018 -5527462.686 tolerance 0.03 m accept -4 0 expect 177404.277 -5527462.686 tolerance 0.03 m accept -3 0 expect 288719.208 -5527462.686 tolerance 0.03 m accept -2 0 expect 400000.0 -5527462.686 tolerance 0.03 m accept -1 0 expect 511280.792 -5527462.686 tolerance 0.03 m accept 0 0 expect 622595.723 -5527462.686 tolerance 0.03 m accept 1 0 expect 733978.982 -5527462.686 tolerance 0.03 m accept 2 0 expect 845464.865 -5527462.686 tolerance 0.03 m accept 3 0 expect 957087.829 -5527462.686 tolerance 0.03 m accept 4 0 expect 1068882.539 -5527462.686 tolerance 0.03 m accept 5 0 expect 1180883.933 -5527462.686 tolerance 0.03 m accept 6 0 expect 1293127.266 -5527462.686 tolerance 0.03 m accept 7 0 expect 1405648.179 -5527462.686 tolerance 0.03 m accept 8 0 expect 1518482.747 -5527462.686 tolerance 0.03 m accept -5 60 expect 232704.966 1127751.264 tolerance 0.03 m accept -4 60 expect 288455.816 1125643.213 tolerance 0.03 m accept -3 60 expect 344223.662 1124378.512 tolerance 0.03 m accept -2 60 expect 400000 1123956.966 tolerance 0.03 m accept -1 60 expect 455776.338 1124378.512 tolerance 0.03 m accept 0 60 expect 511544.184 1125643.213 tolerance 0.03 m accept 1 60 expect 567295.034 1127751.264 tolerance 0.03 m accept 2 60 expect 623020.357 1130702.987 tolerance 0.03 m accept 3 60 expect 678711.584 1134498.83 tolerance 0.03 m accept 4.0 60.0 expect 734360.093 1139139.367 tolerance 0.03 m accept 5.0 60.0 expect 789957.197 1144625.296 tolerance 0.03 m accept 6.0 60.0 expect 845494.132 1150957.434 tolerance 0.03 m accept 7.0 60.0 expect 900962.042 1158136.713 tolerance 0.03 m accept 8.0 60.0 expect 956351.967 1166164.18 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=etmerc +lat_0=49 +lon_0=-2 +k_0=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=WGS84 +units=m +no_def +inv \ +step +init=epsg:4326 -------------------------------------------------------------------------------- tolerance 0.03 m accept 496813.178 3358297.326 expect 3 80 tolerance 0.03 m accept 678711.584 1134498.83 expect 2.9999999 60 tolerance 0.03 m accept 765648.501 -87944.74 expect 3 49 tolerance 0.03 m accept 826893.845 -1087710.121 expect 3.0000001 40 tolerance 0.03 m accept 923539.353 -3308151.625 expect 3 20 tolerance 0.03 m accept 957087.829 -5527462.686 expect 3 0 tolerance 0.03 m accept 923539.353 -7746773.748 expect 3 -20 tolerance 0.03 m accept 826893.845 -9967215.251 expect 3 -40 tolerance 0.03 m accept 678711.584 -12189424.202 expect 3 -60 tolerance 0.03 m accept 496813.178 -14413222.698 expect 3 -80 tolerance 0.03 m accept 400000 3354134.429 expect -2 80 tolerance 0.03 m accept 400000 1123956.966 expect -2 60 tolerance 0.03 m accept 400000 -100000 expect -2 49 tolerance 0.03 m accept 400000 -1099699.834 expect -2 40 tolerance 0.03 m accept 400000 -3315978.565 expect -2 20 tolerance 0.03 m accept 400000 -5527462.686 expect -2 0 tolerance 0.03 m accept 400000 -7738946.807 expect -2 -20 tolerance 0.03 m accept 400000 -9955225.538 expect -2 -40 tolerance 0.03 m accept 400000 -12178882.338 expect -2 -60 tolerance 0.03 m accept 400000 -14409059.801 expect -2 -80 tolerance 0.03 m accept 341867.711 3355633.571 expect -5 80 tolerance 0.03 m accept 232704.966 1127751.264 expect -5 60 tolerance 0.03 m accept 180586.02 -95662.911 expect -5 49 tolerance 0.03 m accept 143900.026 -1095387.991 expect -5 40 tolerance 0.03 m accept 86073.28 -3313165.843 expect -5 20 tolerance 0.03 m accept 66021.018 -5527462.686 expect -5 0 tolerance 0.03 m accept 86073.28 -7741759.529 expect -5 -20 tolerance 0.03 m accept 143900.026 -9959537.381 expect -5 -40 tolerance 0.03 m accept 232704.966 -12182676.637 expect -5 -60 tolerance 0.03 m accept 341867.711 -14410558.943 expect -5 -80 tolerance 0.03 m accept 0 0 expect -7.5559037 49.7661327 tolerance 0.03 m accept 66021.018 -5527462.686 expect -5 0 tolerance 0.03 m accept 177404.277 -5527462.686 expect -4 0 tolerance 0.03 m accept 288719.208 -5527462.686 expect -3 0 tolerance 0.03 m accept 400000.0 -5527462.686 expect -2 0 tolerance 0.03 m accept 511280.792 -5527462.686 expect -1 0 tolerance 0.03 m accept 622595.723 -5527462.686 expect 0 0 tolerance 0.03 m accept 733978.982 -5527462.686 expect 1 0 tolerance 0.03 m accept 845464.865 -5527462.686 expect 2 0 tolerance 0.03 m accept 957087.829 -5527462.686 expect 3 0 tolerance 0.03 m accept 1068882.539 -5527462.686 expect 4 0 tolerance 0.03 m accept 1180883.933 -5527462.686 expect 5 0 tolerance 0.03 m accept 1293127.266 -5527462.686 expect 6 0 tolerance 0.03 m accept 1405648.179 -5527462.686 expect 7 0 tolerance 0.03 m accept 1518482.747 -5527462.686 expect 8 0 tolerance 0.03 m accept 232704.966 1127751.264 expect -5 60 tolerance 0.03 m accept 288455.816 1125643.213 expect -4 60 tolerance 0.03 m accept 344223.662 1124378.512 expect -3 60 tolerance 0.03 m accept 400000 1123956.966 expect -2 60 tolerance 0.03 m accept 455776.338 1124378.512 expect -1 60 tolerance 0.03 m accept 511544.184 1125643.213 expect 0 60 tolerance 0.03 m accept 567295.034 1127751.264 expect 1 60 tolerance 0.03 m accept 623020.357 1130702.987 expect 2 60 tolerance 0.03 m accept 678711.584 1134498.83 expect 3 60 tolerance 0.03 m accept 734360.093 1139139.367 expect 4.0 60.0 tolerance 0.03 m accept 789957.197 1144625.296 expect 5.0 60.0 tolerance 0.03 m accept 845494.132 1150957.434 expect 6.0 60.0 tolerance 0.03 m accept 900962.042 1158136.713 expect 7.0 60.0 tolerance 0.03 m accept 956351.967 1166164.18 expect 8.0 60.0 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4326 +inv \ +step +proj=etmerc +lat_0=49 +lon_0=-2 +k_0=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=WGS84 +units=m +no_def -------------------------------------------------------------------------------- tolerance 0.006 m accept 3 80 roundtrip 1000 tolerance 0.006 m accept 2.9999999 60 roundtrip 1000 tolerance 0.006 m accept 3 49 roundtrip 1000 tolerance 0.006 m accept 3.0000001 40 roundtrip 1000 tolerance 0.006 m accept 3 20 roundtrip 1000 tolerance 0.006 m accept 3 0 roundtrip 1000 tolerance 0.006 m accept 3 -20 roundtrip 1000 tolerance 0.006 m accept 3 -40 roundtrip 1000 tolerance 0.006 m accept 3 -60 roundtrip 1000 tolerance 0.006 m accept 3 -80 roundtrip 1000 tolerance 0.006 m accept -2 80 roundtrip 1000 tolerance 0.006 m accept -2 60 roundtrip 1000 tolerance 0.006 m accept -2 49 roundtrip 1000 tolerance 0.006 m accept -2 40 roundtrip 1000 tolerance 0.006 m accept -2 20 roundtrip 1000 tolerance 0.006 m accept -2 0 roundtrip 1000 tolerance 0.006 m accept -2 -20 roundtrip 1000 tolerance 0.006 m accept -2 -40 roundtrip 1000 tolerance 0.006 m accept -2 -60 roundtrip 1000 tolerance 0.006 m accept -2 -80 roundtrip 1000 tolerance 0.006 m accept -5 80 roundtrip 1000 tolerance 0.006 m accept -5 60 roundtrip 1000 tolerance 0.006 m accept -5 49 roundtrip 1000 tolerance 0.006 m accept -5 40 roundtrip 1000 tolerance 0.006 m accept -5 20 roundtrip 1000 tolerance 0.006 m accept -5 0 roundtrip 1000 tolerance 0.006 m accept -5 -20 roundtrip 1000 tolerance 0.006 m accept -5 -40 roundtrip 1000 tolerance 0.006 m accept -5 -60 roundtrip 1000 tolerance 0.006 m accept -5 -80 roundtrip 1000 tolerance 0.006 m accept -7.5559037 49.7661327 roundtrip 1000 tolerance 0.006 m accept -5 0 roundtrip 1000 tolerance 0.006 m accept -4 0 roundtrip 1000 tolerance 0.006 m accept -3 0 roundtrip 1000 tolerance 0.006 m accept -2 0 roundtrip 1000 tolerance 0.006 m accept -1 0 roundtrip 1000 tolerance 0.006 m accept 0 0 roundtrip 1000 tolerance 0.006 m accept 1 0 roundtrip 1000 tolerance 0.006 m accept 2 0 roundtrip 1000 tolerance 0.006 m accept 3 0 roundtrip 1000 tolerance 0.006 m accept 4 0 roundtrip 1000 tolerance 0.006 m accept 5 0 roundtrip 1000 tolerance 0.006 m accept 6 0 roundtrip 1000 tolerance 0.006 m accept 7 0 roundtrip 1000 tolerance 0.006 m accept 8 0 roundtrip 1000 tolerance 0.006 m accept -5 60 roundtrip 1000 tolerance 0.006 m accept -4 60 roundtrip 1000 tolerance 0.006 m accept -3 60 roundtrip 1000 tolerance 0.006 m accept -2 60 roundtrip 1000 tolerance 0.006 m accept -1 60 roundtrip 1000 tolerance 0.006 m accept 0 60 roundtrip 1000 tolerance 0.006 m accept 1 60 roundtrip 1000 tolerance 0.006 m accept 2 60 roundtrip 1000 tolerance 0.006 m accept 3 60 roundtrip 1000 tolerance 0.006 m accept 4.0 60.0 roundtrip 1000 tolerance 0.006 m accept 5.0 60.0 roundtrip 1000 tolerance 0.006 m accept 6.0 60.0 roundtrip 1000 tolerance 0.006 m accept 7.0 60.0 roundtrip 1000 tolerance 0.006 m accept 8.0 60.0 roundtrip 1000 proj-9.8.1/test/gigs/5111.1.gie000664 001750 001750 00000021164 15166171715 015603 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5111 (part 1), Mercator (variant A), v2-0_2011-06-28. -------------------------------------------------------------------------------- # Batavia <4211> +proj=longlat +ellps=bessel +towgs84=-377,681,-50,0,0,0,0 <> # Batavia / NEIEZ <3001> +proj=merc +lon_0=110 +k=0.997 +x_0=3900000 +y_0=900000 +ellps=bessel +towgs84=-377,681,-50,0,0,0,0 +units=m <> use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline towgs84=0,0,0 \ +step +init=epsg:4211 +inv \ +step +init=epsg:3001 -------------------------------------------------------------------------------- tolerance 0.05 m accept 100.0876483 77.6534822 expect 2800000.0 15000000.0 tolerance 0.055 m accept 100.0876483 73.1442856 expect 2800000.0 13000000.0 tolerance 0.05 m accept 100.0876483 67.0518325 expect 2800000.0 11000000.0 tolerance 0.05 m accept 100.0876483 58.9140458 expect 2800000.0 9000000.0 tolerance 0.05 m accept 100.0876483 48.2638981 expect 2800000.0 7000000.0 tolerance 0.05 m accept 100.0876483 34.8029044 expect 2800000.0 5000000.0 tolerance 0.05 m accept 100.0876483 18.7048581 expect 2800000.0 3000000.0 tolerance 0.05 m accept 100.0876483 0.9071392 expect 2800000.0 1000000.0 tolerance 0.05 m accept 110.0 0.0 expect 3900000.0 900000.0 tolerance 0.05 m accept 100.0876483 -0.9071392 expect 2800000.0 800000.0 tolerance 0.05 m accept 100.0876483 -1.8140483 expect 2800000.0 700000.0 tolerance 0.05 m accept 100.0876483 -2.0 expect 2800000.0 679490.65 tolerance 0.05 m accept 100.0876483 -3.6262553 expect 2800000.0 500000.0 tolerance 0.05 m accept 100.0876483 -4.531095 expect 2800000.0 400000.0 tolerance 0.05 m accept 100.0876483 -5.4347892 expect 2800000.0 300000.0 tolerance 0.05 m accept 100.0876483 -6.3371111 expect 2800000.0 200000.0 tolerance 0.05 m accept 100.0876483 -7.2378372 expect 2800000.0 100000.0 tolerance 0.05 m accept 74.8562083 -8.136745 expect 0.0 0.0 tolerance 0.05 m accept -71.0 -2.0 expect 23764105.84 679490.65 tolerance 0.05 m accept -90.0 -2.0 expect 21655625.33 679490.65 tolerance 0.05 m accept -120.0 -2.0 expect 18326445.58 679490.65 tolerance 0.05 m accept -150.0 -2.0 expect 14997265.83 679490.65 tolerance 0.05 m accept 180.0 -2.0 expect 11668086.08 679490.65 tolerance 0.05 m accept 150.0 -2.0 expect 8338906.33 679490.65 tolerance 0.05 m accept 120.0 -2.0 expect 5009726.58 679490.65 tolerance 0.05 m accept 110.0 -2.0 expect 3900000.0 679490.65 tolerance 0.05 m accept 106.8077194 -2.0 expect 3545744.14 679490.65 tolerance 0.05 m accept 100.0876483 -2.0 expect 2800000.0 679490.65 tolerance 0.05 m accept 90.0 -2.0 expect 1680546.83 679490.65 tolerance 0.05 m accept 60.0 -2.0 expect -1648632.92 679490.65 tolerance 0.05 m accept 30.0 -2.0 expect -4977812.67 679490.65 tolerance 0.05 m accept 0.0 -2.0 expect -8306992.42 679490.65 tolerance 0.05 m accept -30.0 -2.0 expect -11636172.17 679490.65 tolerance 0.05 m accept -60.0 -2.0 expect -14965351.92 679490.65 tolerance 0.05 m accept -69.0 -2.0 expect -15964105.84 679490.65 -------------------------------------------------------------------------------- operation +proj=pipeline towgs84=0,0,0 \ +step +init=epsg:3001 +inv \ +step +init=epsg:4211 -------------------------------------------------------------------------------- tolerance 0.05 m accept 2800000.0 15000000.0 expect 100.0876483 77.6534822 tolerance 0.05 m accept 2800000.0 13000000.0 expect 100.0876483 73.1442856 tolerance 0.05 m accept 2800000.0 11000000.0 expect 100.0876483 67.0518325 tolerance 0.05 m accept 2800000.0 9000000.0 expect 100.0876483 58.9140458 tolerance 0.05 m accept 2800000.0 7000000.0 expect 100.0876483 48.2638981 tolerance 0.05 m accept 2800000.0 5000000.0 expect 100.0876483 34.8029044 tolerance 0.05 m accept 2800000.0 3000000.0 expect 100.0876483 18.7048581 tolerance 0.05 m accept 2800000.0 1000000.0 expect 100.0876483 0.9071392 tolerance 0.05 m accept 3900000.0 900000.0 expect 110.0 0.0 tolerance 0.05 m accept 2800000.0 800000.0 expect 100.0876483 -0.9071392 tolerance 0.05 m accept 2800000.0 700000.0 expect 100.0876483 -1.8140483 tolerance 0.05 m accept 2800000.0 679490.65 expect 100.0876483 -2.0 tolerance 0.05 m accept 2800000.0 500000.0 expect 100.0876483 -3.6262553 tolerance 0.05 m accept 2800000.0 400000.0 expect 100.0876483 -4.531095 tolerance 0.05 m accept 2800000.0 300000.0 expect 100.0876483 -5.4347892 tolerance 0.05 m accept 2800000.0 200000.0 expect 100.0876483 -6.3371111 tolerance 0.05 m accept 2800000.0 100000.0 expect 100.0876483 -7.2378372 tolerance 0.05 m accept 0.0 0.0 expect 74.8562083 -8.136745 tolerance 0.05 m accept 23764105.84 679490.65 expect -71.0 -2.0 tolerance 0.05 m accept 21655625.33 679490.65 expect -90.0 -2.0 tolerance 0.05 m accept 18326445.58 679490.65 expect -120.0 -2.0 tolerance 0.05 m accept 14997265.83 679490.65 expect -150.0 -2.0 tolerance 0.05 m accept 11668086.08 679490.65 expect 180.0 -2.0 tolerance 0.05 m accept 8338906.33 679490.65 expect 150.0 -2.0 tolerance 0.05 m accept 5009726.58 679490.65 expect 120.0 -2.0 tolerance 0.05 m accept 3900000.0 679490.65 expect 110.0 -2.0 tolerance 0.05 m accept 3545744.14 679490.65 expect 106.8077194 -2.0 tolerance 0.05 m accept 2800000.0 679490.65 expect 100.0876483 -2.0 tolerance 0.05 m accept 1680546.83 679490.65 expect 90.0 -2.0 tolerance 0.05 m accept -1648632.92 679490.65 expect 60.0 -2.0 tolerance 0.05 m accept -4977812.67 679490.65 expect 30.0 -2.0 tolerance 0.05 m accept -8306992.42 679490.65 expect 0.0 -2.0 tolerance 0.05 m accept -11636172.17 679490.65 expect -30.0 -2.0 tolerance 0.05 m accept -14965351.92 679490.65 expect -60.0 -2.0 tolerance 0.05 m accept -15964105.84 679490.65 expect -69.0 -2.0 -------------------------------------------------------------------------------- operation +proj=pipeline towgs84=0,0,0 \ +step +init=epsg:4211 +inv \ +step +init=epsg:3001 -------------------------------------------------------------------------------- tolerance 0.006 m accept 100.0876483 77.6534822 roundtrip 1000 tolerance 0.006 m accept 100.0876483 73.1442856 roundtrip 1000 tolerance 0.006 m accept 100.0876483 67.0518325 roundtrip 1000 tolerance 0.006 m accept 100.0876483 58.9140458 roundtrip 1000 tolerance 0.006 m accept 100.0876483 48.2638981 roundtrip 1000 tolerance 0.006 m accept 100.0876483 34.8029044 roundtrip 1000 tolerance 0.006 m accept 100.0876483 18.7048581 roundtrip 1000 tolerance 0.006 m accept 100.0876483 0.9071392 roundtrip 1000 tolerance 0.006 m accept 110.0 0.0 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -0.9071392 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -1.8140483 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -2.0 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -3.6262553 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -4.531095 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -5.4347892 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -6.3371111 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -7.2378372 roundtrip 1000 tolerance 0.006 m accept 74.8562083 -8.136745 roundtrip 1000 tolerance 0.006 m accept -71.0 -2.0 roundtrip 1000 tolerance 0.006 m accept -90.0 -2.0 roundtrip 1000 tolerance 0.006 m accept -120.0 -2.0 roundtrip 1000 tolerance 0.006 m accept -150.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 180.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 150.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 120.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 110.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 106.8077194 -2.0 roundtrip 1000 tolerance 0.006 m accept 100.0876483 -2.0 roundtrip 1000 tolerance 0.006 m accept 90.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 60.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 30.0 -2.0 roundtrip 1000 tolerance 0.006 m accept 0.0 -2.0 roundtrip 1000 tolerance 0.006 m accept -30.0 -2.0 roundtrip 1000 tolerance 0.006 m accept -60.0 -2.0 roundtrip 1000 tolerance 0.006 m accept -69.0 -2.0 roundtrip 1000 proj-9.8.1/test/gigs/5108.gie000664 001750 001750 00000010604 15166171715 015447 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5108, Cassini-Soldner, v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true # GDM2000 # <4742> +proj=longlat +ellps=GRS80 <> # GDM2000 / Johor Grid # <3377> +proj=cass +lat_0=2.121679744444445 +lon_0=103.4279362361111 +x_0=-14810.562 +y_0=8758.32 +ellps=GRS80 +units=m <> -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4742 +inv \ +step +init=epsg:3377 -------------------------------------------------------------------------------- tolerance 0.05 m accept 106 10 expect 267186.017 881108.902 tolerance 0.05 m accept 106 9 expect 268006.024 770398.186 tolerance 0.05 m accept 106 8 expect 268740.351 659692.254 tolerance 0.05 m accept 106 7 expect 269388.786 548990.588 tolerance 0.05 m accept 106 6 expect 269951.141 438292.666 tolerance 0.05 m accept 106 5 expect 270427.255 327597.962 tolerance 0.05 m accept 106 4 expect 270816.99 216905.945 tolerance 0.05 m accept 106 3 expect 271120.234 106216.081 tolerance 0.05 m accept 103.561065778 2.0424676812 expect 0 0 tolerance 0.05 m accept 103.64025984 1.82776484381 expect 8813.252 -23740.095 tolerance 0.05 m accept 106 1 expect 271466.923 -115159.332 tolerance 0.05 m accept 109 5 expect 603116.703 329668.599 tolerance 0.05 m accept 108 5 expect 492221.308 328807.336 tolerance 0.05 m accept 107 5 expect 381324.74 328117.472 tolerance 0.05 m accept 106 5 expect 270427.255 327597.962 tolerance 0.05 m accept 105 5 expect 159529.111 327248.012 tolerance 0.05 m accept 104 5 expect 48630.563 327067.097 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:3377 +inv \ +step +init=epsg:4742 -------------------------------------------------------------------------------- tolerance 0.05 m accept 267186.017 881108.902 expect 106 10 tolerance 0.05 m accept 268006.024 770398.186 expect 106 9 tolerance 0.05 m accept 268740.351 659692.254 expect 106 8 tolerance 0.05 m accept 269388.786 548990.588 expect 106 7 tolerance 0.05 m accept 269951.141 438292.666 expect 106 6 tolerance 0.05 m accept 270427.255 327597.962 expect 106 5 tolerance 0.05 m accept 270816.99 216905.945 expect 106 4 tolerance 0.05 m accept 271120.234 106216.081 expect 106 3 tolerance 0.05 m accept 0 0 expect 103.561065778 2.0424676812 tolerance 0.05 m accept 8813.252 -23740.095 expect 103.64025984 1.82776484381 tolerance 0.05 m accept 271466.923 -115159.332 expect 106 1 tolerance 0.05 m accept 603116.703 329668.599 expect 109 5 tolerance 0.05 m accept 492221.308 328807.336 expect 108 5 tolerance 0.05 m accept 381324.74 328117.472 expect 107 5 tolerance 0.05 m accept 270427.255 327597.962 expect 106 5 tolerance 0.05 m accept 159529.111 327248.012 expect 105 5 tolerance 0.05 m accept 48630.563 327067.097 expect 104 5 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4742 +inv \ +step +init=epsg:3377 -------------------------------------------------------------------------------- tolerance 0.006 m accept 106 10 roundtrip 1000 tolerance 0.006 m accept 106 9 roundtrip 1000 tolerance 0.006 m accept 106 8 roundtrip 1000 tolerance 0.006 m accept 106 7 roundtrip 1000 tolerance 0.006 m accept 106 6 roundtrip 1000 tolerance 0.006 m accept 106 5 roundtrip 1000 tolerance 0.006 m accept 106 4 roundtrip 1000 tolerance 0.006 m accept 106 3 roundtrip 1000 tolerance 0.006 m accept 103.561065778 2.0424676812 roundtrip 1000 tolerance 0.006 m accept 103.64025984 1.82776484381 roundtrip 1000 tolerance 0.006 m accept 106 1 roundtrip 1000 tolerance 0.006 m accept 109 5 roundtrip 1000 tolerance 0.006 m accept 108 5 roundtrip 1000 tolerance 0.006 m accept 107 5 roundtrip 1000 tolerance 0.006 m accept 106 5 roundtrip 1000 tolerance 0.006 m accept 105 5 roundtrip 1000 tolerance 0.006 m accept 104 5 roundtrip 1000 proj-9.8.1/test/gigs/5102.2.gie000664 001750 001750 00000014522 15166171715 015604 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5102 (part 2), Lambert Conic Conformal (1SP), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- # We need to add this grad->rad step as +init=epsg:4807 assumes # degrees (if front operation), or radians (if non-front), as this was the case # in PROJ < 6 era # Note: "cs2cs EPSG:4807 EPSG:27572" does the right job. operation +proj=pipeline \ +step +proj=unitconvert +xy_in=grad +xy_out=rad \ +step +init=epsg:4807 +inv \ +step +init=epsg:27572 -------------------------------------------------------------------------------- tolerance 0.03 m accept 2.9586342556 64.4444444444 expect 760724.023 3457334.864 tolerance 0.03 m accept 2.9586342556 63.3333333333 expect 764567.882 3343917.044 tolerance 0.03 m accept 2.9586342556 62.2222222222 expect 768397.648 3230915.06 tolerance 0.03 m accept 2.9586342556 61.1111111111 expect 772214.859 3118283.535 tolerance 0.03 m accept 2.9586342556 60 expect 776020.989 3005978.979 tolerance 0.03 m accept 2.9586342556 58.8888888889 expect 779817.454 2893959.584 tolerance 0.03 m accept 1.8475231444 56.6666666667 expect 717027.602 2668679.866 tolerance 0.03 m accept 1.8475231444 55.5555555556 expect 719385.487 2557240.347 tolerance 0.03 m accept 1.8475231444 54.4444444444 expect 721740.59 2445932.319 tolerance 0.03 m accept 1.8475231444 52 expect 726915.726 2201342.51839 tolerance 0.03 m accept 0.7364120333 58.8888888889 expect 644765.081 2891102.088 tolerance 0.03 m accept 1.8475231444 58.8888888889 expect 712300.356 2892101.266 tolerance 0.03 m accept 2.9586342556 58.8888888889 expect 779817.454 2893959.584 tolerance 0.03 m accept 4.0697453667 58.8888888889 expect 847305.444 2896676.742 tolerance 0.03 m accept 5.1808564778 58.8888888889 expect 914753.403 2900252.301 tolerance 0.03 m accept 6.2919675889 58.8888888889 expect 982150.413 2904685.68 tolerance 0.03 m accept 7.4030787 58.8888888889 expect 1049485.565 2909976.163 tolerance 0.03 m accept 8.5141898111 58.8888888889 expect 1116747.958 2916122.894 tolerance 0.03 m accept 9.6253009222 58.8888888889 expect 1183926.705 2923124.876 -------------------------------------------------------------------------------- # We need to add this rad->grad step as +init=epsg:4807 assumes # degrees (if last operation), or radians (if non-last), as this was the case # in PROJ < 6 era operation +proj=pipeline \ +step +init=epsg:27572 +inv \ +step +init=epsg:4807 \ +step +proj=unitconvert +xy_in=rad +xy_out=grad -------------------------------------------------------------------------------- tolerance 0.03 m accept 760724.023 3457334.864 expect 2.9586342556 64.4444444444 tolerance 0.03 m accept 764567.882 3343917.044 expect 2.9586342556 63.3333333333 tolerance 0.03 m accept 768397.648 3230915.06 expect 2.9586342556 62.2222222222 tolerance 0.03 m accept 772214.859 3118283.535 expect 2.9586342556 61.1111111111 tolerance 0.03 m accept 776020.989 3005978.979 expect 2.9586342556 60 tolerance 0.03 m accept 779817.454 2893959.584 expect 2.9586342556 58.8888888889 tolerance 0.03 m accept 717027.602 2668679.866 expect 1.8475231444 56.6666666667 tolerance 0.03 m accept 719385.487 2557240.347 expect 1.8475231444 55.5555555556 tolerance 0.03 m accept 721740.59 2445932.319 expect 1.8475231444 54.4444444444 tolerance 0.03 m accept 726915.726 2201342.51839 expect 1.8475231444 52 tolerance 0.03 m accept 644765.081 2891102.088 expect 0.7364120333 58.8888888889 tolerance 0.03 m accept 712300.356 2892101.266 expect 1.8475231444 58.8888888889 tolerance 0.03 m accept 779817.454 2893959.584 expect 2.9586342556 58.8888888889 tolerance 0.03 m accept 847305.444 2896676.742 expect 4.0697453667 58.8888888889 tolerance 0.03 m accept 914753.403 2900252.301 expect 5.1808564778 58.8888888889 tolerance 0.03 m accept 982150.413 2904685.68 expect 6.2919675889 58.8888888889 tolerance 0.03 m accept 1049485.565 2909976.163 expect 7.4030787 58.8888888889 tolerance 0.03 m accept 1116747.958 2916122.894 expect 8.5141898111 58.8888888889 tolerance 0.03 m accept 1183926.705 2923124.876 expect 9.6253009222 58.8888888889 -------------------------------------------------------------------------------- # We need to add this grad->rad step as +init=epsg:4807 assumes # degrees (if front operation), or radians (if non-front), as this was the case # in PROJ < 6 era operation +proj=pipeline \ +step +proj=unitconvert +xy_in=grad +xy_out=rad \ +step +init=epsg:4807 +inv \ +step +init=epsg:27572 -------------------------------------------------------------------------------- tolerance 0.006 m accept 2.9586342556 64.4444444444 roundtrip 1000 tolerance 0.006 m accept 2.9586342556 63.3333333333 roundtrip 1000 tolerance 0.006 m accept 2.9586342556 62.2222222222 roundtrip 1000 tolerance 0.006 m accept 2.9586342556 61.1111111111 roundtrip 1000 tolerance 0.006 m accept 2.9586342556 60 roundtrip 1000 tolerance 0.006 m accept 2.9586342556 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 1.8475231444 56.6666666667 roundtrip 1000 tolerance 0.006 m accept 1.8475231444 55.5555555556 roundtrip 1000 tolerance 0.006 m accept 1.8475231444 54.4444444444 roundtrip 1000 tolerance 0.006 m accept 1.8475231444 52 roundtrip 1000 tolerance 0.006 m accept 0.7364120333 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 1.8475231444 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 2.9586342556 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 4.0697453667 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 5.1808564778 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 6.2919675889 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 7.4030787 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 8.5141898111 58.8888888889 roundtrip 1000 tolerance 0.006 m accept 9.6253009222 58.8888888889 roundtrip 1000 proj-9.8.1/test/gigs/5112.gie000664 001750 001750 00000003326 15166171715 015445 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5112, Mercator (variant B), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation proj=pipeline \ step init=epsg:4284 inv \ step init=epsg:3388 tolerance 50 mm -------------------------------------------------------------------------------- accept 51.0 42.0 expect 0.0 3819897.85 accept 51.0 0.0 expect 0.0 0.0 accept 57.0 0.0 expect 497112.88 0.0 accept 54.0 20.5 expect 248556.44 1724781.5 accept 67.0 -41.0 expect 1325634.35 -3709687.25 -------------------------------------------------------------------------------- operation proj=pipeline \ step init=epsg:3388 inv \ step init=epsg:4284 tolerance 50 mm -------------------------------------------------------------------------------- accept 0.0 3819897.85 expect 51.0 42.0 accept 0.0 0.0 expect 51.0 0.0 accept 497112.88 0.0 expect 57.0 0.0 accept 248556.44 1724781.5 expect 54.0 20.5 accept 1325634.35 -3709687.25 expect 67.0 -41.0 -------------------------------------------------------------------------------- operation proj=pipeline towgs84=0,0,0 \ step init=epsg:4284 inv \ step init=epsg:3388 tolerance 6 mm -------------------------------------------------------------------------------- accept 51.0 42.0 roundtrip 1000 accept 51.0 0.0 roundtrip 1000 accept 57.0 0.0 roundtrip 1000 accept 54.0 20.5 roundtrip 1000 accept 67.0 -41.0 roundtrip 1000 proj-9.8.1/test/gigs/5106.gie000664 001750 001750 00000012520 15166171715 015444 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5106, Hotine Oblique Mercator (variant A), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4742 +inv \ +step +init=epsg:3376 -------------------------------------------------------------------------------- tolerance 0.05 m accept 117 12 expect 807919.144 1329535.334 tolerance 0.05 m accept 117 10 expect 808784.981 1107678.473 tolerance 0.05 m accept 117 9 expect 809334.177 996918.212 tolerance 0.05 m accept 117 8 expect 809939.302 886240.183 tolerance 0.05 m accept 116.846552222 6.87845833333 expect 793704.631 762081.047 tolerance 0.05 m accept 117 6 expect 811253.303 665041.265 tolerance 0.05 m accept 117 5 expect 811930.345 554475.627 tolerance 0.05 m accept 117 4 expect 812599.582 443902.706 tolerance 0.05 m accept 115 4 expect 590521.147 442890.861 tolerance 0.05 m accept 117 3 expect 813245.133 333300.13 tolerance 0.05 m accept 117 2 expect 813851.067 222645.511 tolerance 0.05 m accept 117 1 expect 814401.375 111916.452 tolerance 0.05 m accept 109.685820833 -0.000173333333333 expect 0 0 tolerance 0.05 m accept 123 6 expect 1475669.281 673118.573 tolerance 0.05 m accept 122 6 expect 1364854.862 671146.254 tolerance 0.05 m accept 121 6 expect 1254086.173 669446.249 tolerance 0.05 m accept 120 6 expect 1143352.598 668002.074 tolerance 0.05 m accept 119 6 expect 1032643.312 666797.354 tolerance 0.05 m accept 118 6 expect 921947.286 665815.815 tolerance 0.05 m accept 117 6 expect 811253.303 665041.265 tolerance 0.05 m accept 116 6 expect 700549.965 664457.586 tolerance 0.05 m accept 115 6 expect 589825.706 664048.715 tolerance 0.05 m accept 114 6 expect 479068.802 663798.63 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:3376 +inv \ +step +init=epsg:4742 -------------------------------------------------------------------------------- tolerance 0.05 m accept 807919.144 1329535.334 expect 117 12 tolerance 0.05 m accept 808784.981 1107678.473 expect 117 10 tolerance 0.05 m accept 809334.177 996918.212 expect 117 9 tolerance 0.05 m accept 809939.302 886240.183 expect 117 8 tolerance 0.05 m accept 793704.631 762081.047 expect 116.846552222 6.87845833333 tolerance 0.05 m accept 811253.303 665041.265 expect 117 6 tolerance 0.05 m accept 811930.345 554475.627 expect 117 5 tolerance 0.05 m accept 812599.582 443902.706 expect 117 4 tolerance 0.05 m accept 590521.147 442890.861 expect 115 4 tolerance 0.05 m accept 813245.133 333300.13 expect 117 3 tolerance 0.05 m accept 813851.067 222645.511 expect 117 2 tolerance 0.05 m accept 814401.375 111916.452 expect 117 1 tolerance 0.05 m accept 0 0 expect 109.685820833 -0.000173333333333 tolerance 0.05 m accept 1475669.281 673118.573 expect 123 6 tolerance 0.05 m accept 1364854.862 671146.254 expect 122 6 tolerance 0.05 m accept 1254086.173 669446.249 expect 121 6 tolerance 0.05 m accept 1143352.598 668002.074 expect 120 6 tolerance 0.05 m accept 1032643.312 666797.354 expect 119 6 tolerance 0.05 m accept 921947.286 665815.815 expect 118 6 tolerance 0.05 m accept 811253.303 665041.265 expect 117 6 tolerance 0.05 m accept 700549.965 664457.586 expect 116 6 tolerance 0.05 m accept 589825.706 664048.715 expect 115 6 tolerance 0.05 m accept 479068.802 663798.63 expect 114 6 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4742 +inv \ +step +init=epsg:3376 -------------------------------------------------------------------------------- tolerance 0.006 m accept 117 12 roundtrip 1000 tolerance 0.006 m accept 117 10 roundtrip 1000 tolerance 0.006 m accept 117 9 roundtrip 1000 tolerance 0.006 m accept 117 8 roundtrip 1000 tolerance 0.006 m accept 116.846552222 6.87845833333 roundtrip 1000 tolerance 0.006 m accept 117 6 roundtrip 1000 tolerance 0.006 m accept 117 5 roundtrip 1000 tolerance 0.006 m accept 117 4 roundtrip 1000 tolerance 0.006 m accept 115 4 roundtrip 1000 tolerance 0.006 m accept 117 3 roundtrip 1000 tolerance 0.006 m accept 117 2 roundtrip 1000 tolerance 0.006 m accept 117 1 roundtrip 1000 tolerance 0.006 m accept 109.685820833 -0.000173333333333 roundtrip 1000 tolerance 0.006 m accept 123 6 roundtrip 1000 tolerance 0.006 m accept 122 6 roundtrip 1000 tolerance 0.006 m accept 121 6 roundtrip 1000 tolerance 0.006 m accept 120 6 roundtrip 1000 tolerance 0.006 m accept 119 6 roundtrip 1000 tolerance 0.006 m accept 118 6 roundtrip 1000 tolerance 0.006 m accept 117 6 roundtrip 1000 tolerance 0.006 m accept 116 6 roundtrip 1000 tolerance 0.006 m accept 115 6 roundtrip 1000 tolerance 0.006 m accept 114 6 roundtrip 1000 proj-9.8.1/test/gigs/5101.4-jhs-etmerc.gie000664 001750 001750 00000015216 15166171715 017645 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5101 (part 4), Transverse Mercator, v2-0_2011-06-28, recommended JHS formula -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4190 +inv \ +step +proj=etmerc +lat_0=-90 +lon_0=-60 +k=1 +x_0=5500000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m -------------------------------------------------------------------------------- tolerance 0.03 m accept -63.9993433 80.0002644 expect 5422500.0 18889800.0 tolerance 0.03 m accept -63.9998472 60.0001191 expect 5276900.0 16662800.0 tolerance 0.03 m accept -63.9997361 40.0003081 expect 5158399.999 14439199.99 tolerance 0.03 m accept -64.0004605 19.9996448 expect 5081100.0 12219300.0 tolerance 0.03 m accept -63.9996186 0.0003092 expect 5054400.005 10002000.0 tolerance 0.03 m accept -64.0004675 -19.9999283 expect 5081100.017 7784599.993 tolerance 0.03 m accept -63.9997001 -39.9996924 expect 5158400.0 5564800.0 tolerance 0.03 m accept -63.9998814 -60.0004008 expect 5276899.994 3341099.995 tolerance 0.03 m accept -63.9991006 -79.9996521 expect 5422500.0 1114200.0 tolerance 0.03 m accept -70.0002089 -40.000215 expect 4645300.113 5524200.123 tolerance 0.03 m accept -69.0001441 -40.0002935 expect 4730900.0 5533400.0 tolerance 0.03 m accept -67.9995333 -39.9996136 expect 4816500.043 5541700.028 tolerance 0.03 m accept -66.9998073 -39.9999313 expect 4902000.0 5548900.0 tolerance 0.03 m accept -65.9996522 -39.9995894 expect 4987500.009 5555200.001 tolerance 0.03 m accept -64.9992796 -40.000411 expect 5073000.0 5560400.0 tolerance 0.03 m accept -63.9997 -39.9996925 expect 5158400.01 5564799.987 tolerance 0.03 m accept -62.9999842 -40.0002087 expect 5243800.0 5568100.0 tolerance 0.03 m accept -62.0000778 -40.0001803 expect 5329199.995 5570500.009 tolerance 0.03 m accept -61.0000574 -39.9996182 expect 5414600.0 5572000.0 tolerance 0.03 m accept -60.0 -40.0003306 expect 5500000.0 5572399.996 tolerance 0.03 m accept -58.9999426 -39.9996182 expect 5585400.0 5572000.0 tolerance 0.03 m accept -57.9999222 -40.0001803 expect 5670800.005 5570500.009 tolerance 0.03 m accept -57.0000158 -40.0002087 expect 5756200.0 5568100.0 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=etmerc +lat_0=-90 +lon_0=-60 +k=1 +x_0=5500000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +inv \ +step +init=epsg:4190 -------------------------------------------------------------------------------- tolerance 0.03 m accept 5422500.0 18889800.0 expect -63.9993433 80.0002644 tolerance 0.03 m accept 5276900.0 16662800.0 expect -63.9998472 60.0001191 tolerance 0.03 m accept 5158399.999 14439199.99 expect -63.9997361 40.0003081 tolerance 0.03 m accept 5081100.0 12219300.0 expect -64.0004605 19.9996448 tolerance 0.03 m accept 5054400.005 10002000.0 expect -63.9996186 0.0003092 tolerance 0.03 m accept 5081100.017 7784599.993 expect -64.0004675 -19.9999283 tolerance 0.03 m accept 5158400.0 5564800.0 expect -63.9997001 -39.9996924 tolerance 0.03 m accept 5276899.994 3341099.995 expect -63.9998814 -60.0004008 tolerance 0.03 m accept 5422500.0 1114200.0 expect -63.9991006 -79.9996521 tolerance 0.03 m accept 4645300.113 5524200.123 expect -70.0002089 -40.000215 tolerance 0.03 m accept 4730900.0 5533400.0 expect -69.0001441 -40.0002935 tolerance 0.03 m accept 4816500.043 5541700.028 expect -67.9995333 -39.9996136 tolerance 0.03 m accept 4902000.0 5548900.0 expect -66.9998073 -39.9999313 tolerance 0.03 m accept 4987500.009 5555200.001 expect -65.9996522 -39.9995894 tolerance 0.03 m accept 5073000.0 5560400.0 expect -64.9992796 -40.000411 tolerance 0.03 m accept 5158400.01 5564799.987 expect -63.9997 -39.9996925 tolerance 0.03 m accept 5243800.0 5568100.0 expect -62.9999842 -40.0002087 tolerance 0.03 m accept 5329199.995 5570500.009 expect -62.0000778 -40.0001803 tolerance 0.03 m accept 5414600.0 5572000.0 expect -61.0000574 -39.9996182 tolerance 0.03 m accept 5500000.0 5572399.996 expect -60.0 -40.0003306 tolerance 0.03 m accept 5585400.0 5572000.0 expect -58.9999426 -39.9996182 tolerance 0.03 m accept 5670800.005 5570500.009 expect -57.9999222 -40.0001803 tolerance 0.03 m accept 5756200.0 5568100.0 expect -57.0000158 -40.0002087 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4190 +inv \ +step +proj=etmerc +lat_0=-90 +lon_0=-60 +k=1 +x_0=5500000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m -------------------------------------------------------------------------------- tolerance 0.006 m accept -63.9993433 80.0002644 roundtrip 1000 tolerance 0.006 m accept -63.9998472 60.0001191 roundtrip 1000 tolerance 0.006 m accept -63.9997361 40.0003081 roundtrip 1000 tolerance 0.006 m accept -64.0004605 19.9996448 roundtrip 1000 tolerance 0.006 m accept -63.9996186 0.0003092 roundtrip 1000 tolerance 0.006 m accept -64.0004675 -19.9999283 roundtrip 1000 tolerance 0.006 m accept -63.9997001 -39.9996924 roundtrip 1000 tolerance 0.006 m accept -63.9998814 -60.0004008 roundtrip 1000 tolerance 0.006 m accept -63.9991006 -79.9996521 roundtrip 1000 tolerance 0.006 m accept -70.0002089 -40.000215 roundtrip 1000 tolerance 0.006 m accept -69.0001441 -40.0002935 roundtrip 1000 tolerance 0.006 m accept -67.9995333 -39.9996136 roundtrip 1000 tolerance 0.006 m accept -66.9998073 -39.9999313 roundtrip 1000 tolerance 0.006 m accept -65.9996522 -39.9995894 roundtrip 1000 tolerance 0.006 m accept -64.9992796 -40.000411 roundtrip 1000 tolerance 0.006 m accept -63.9997 -39.9996925 roundtrip 1000 tolerance 0.006 m accept -62.9999842 -40.0002087 roundtrip 1000 tolerance 0.006 m accept -62.0000778 -40.0001803 roundtrip 1000 tolerance 0.006 m accept -61.0000574 -39.9996182 roundtrip 1000 tolerance 0.006 m accept -60.0 -40.0003306 roundtrip 1000 tolerance 0.006 m accept -58.9999426 -39.9996182 roundtrip 1000 tolerance 0.006 m accept -57.9999222 -40.0001803 roundtrip 1000 tolerance 0.006 m accept -57.0000158 -40.0002087 roundtrip 1000 proj-9.8.1/test/gigs/5102.1.gie000664 001750 001750 00000010726 15166171715 015605 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5102, Lambert Conic Conformal (1SP), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4230 +inv \ +step +init=epsg:2192 -------------------------------------------------------------------------------- tolerance 0.03 m accept 5 58 expect 760722.92 3457368.68 tolerance 0.03 m accept 5 57 expect 764566.844 3343948.93 tolerance 0.03 m accept 5 56 expect 768396.683 3230944.812 tolerance 0.03 m accept 5 55 expect 772213.973 3118310.947 tolerance 0.03 m accept 5 54 expect 776020.189 3006003.839 tolerance 0.03 m accept 5 53 expect 779816.748 2893981.68 tolerance 0.03 m accept 4 51 expect 717027.292 2668695.784 tolerance 0.03 m accept 4 50 expect 719385.249 2557252.841 tolerance 0.03 m accept 4 49 expect 721740.43 2445941.161 tolerance 0.03 m accept 4 46.8 expect 726915.752 2201342.519 tolerance 0.03 m accept 3 53 expect 644764.905 2891124.195 tolerance 0.03 m accept 4 53 expect 712299.916 2892123.369 tolerance 0.03 m accept 5 53 expect 779816.748 2893981.68 tolerance 0.03 m accept 6 53 expect 847304.473 2896698.827 tolerance 0.03 m accept 7 53 expect 914752.168 2900274.371 tolerance 0.03 m accept 8 53 expect 982148.913 2904707.734 tolerance 0.03 m accept 9 53 expect 1049483.8 2909998.196 tolerance 0.03 m accept 10 53 expect 1116745.929 2916144.902 tolerance 0.03 m accept 11 53 expect 1183924.412 2923146.858 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:2192 +inv \ +step +init=epsg:4230 -------------------------------------------------------------------------------- tolerance 0.03 m accept 760722.92 3457368.68 expect 5 58 tolerance 0.03 m accept 764566.844 3343948.93 expect 5 57 tolerance 0.03 m accept 768396.683 3230944.812 expect 5 56 tolerance 0.03 m accept 772213.973 3118310.947 expect 5 55 tolerance 0.03 m accept 776020.189 3006003.839 expect 5 54 tolerance 0.03 m accept 779816.748 2893981.68 expect 5 53 tolerance 0.03 m accept 717027.292 2668695.784 expect 4 51 tolerance 0.03 m accept 719385.249 2557252.841 expect 4 50 tolerance 0.03 m accept 721740.43 2445941.161 expect 4 49 tolerance 0.03 m accept 726915.752 2201342.519 expect 4 46.8 tolerance 0.03 m accept 644764.905 2891124.195 expect 3 53 tolerance 0.03 m accept 712299.916 2892123.369 expect 4 53 tolerance 0.03 m accept 779816.748 2893981.68 expect 5 53 tolerance 0.03 m accept 847304.473 2896698.827 expect 6 53 tolerance 0.03 m accept 914752.168 2900274.371 expect 7 53 tolerance 0.03 m accept 982148.913 2904707.734 expect 8 53 tolerance 0.03 m accept 1049483.8 2909998.196 expect 9 53 tolerance 0.03 m accept 1116745.929 2916144.902 expect 10 53 tolerance 0.03 m accept 1183924.412 2923146.858 expect 11 53 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4230 +inv \ +step +init=epsg:2192 -------------------------------------------------------------------------------- tolerance 0.006 m accept 5 58 roundtrip 1000 tolerance 0.006 m accept 5 57 roundtrip 1000 tolerance 0.006 m accept 5 56 roundtrip 1000 tolerance 0.006 m accept 5 55 roundtrip 1000 tolerance 0.006 m accept 5 54 roundtrip 1000 tolerance 0.006 m accept 5 53 roundtrip 1000 tolerance 0.006 m accept 4 51 roundtrip 1000 tolerance 0.006 m accept 4 50 roundtrip 1000 tolerance 0.006 m accept 4 49 roundtrip 1000 tolerance 0.006 m accept 4 46.8 roundtrip 1000 tolerance 0.006 m accept 3 53 roundtrip 1000 tolerance 0.006 m accept 4 53 roundtrip 1000 tolerance 0.006 m accept 5 53 roundtrip 1000 tolerance 0.006 m accept 6 53 roundtrip 1000 tolerance 0.006 m accept 7 53 roundtrip 1000 tolerance 0.006 m accept 8 53 roundtrip 1000 tolerance 0.006 m accept 9 53 roundtrip 1000 tolerance 0.006 m accept 10 53 roundtrip 1000 tolerance 0.006 m accept 11 53 roundtrip 1000 proj-9.8.1/test/gigs/5103.3.gie000664 001750 001750 00000005641 15166171715 015610 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5103 (part 3), Lambert Conic Conformal (2SP), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4152 +inv \ +step +init=epsg:3568 -------------------------------------------------------------------------------- tolerance 0.03 m accept -110 49 expect 2003933.27 6452478.8 tolerance 0.03 m accept -110 47 expect 2016617.9 5717717.18 tolerance 0.03 m accept -110 45 expect 2029251.51 4985910.59 tolerance 0.03 m accept -110 43 expect 2041851.0 4256081.23 tolerance 0.03 m accept -110 41 expect 2054432.46 3527295.67 tolerance 0.03 m accept -110 41 expect 2054432.46 3527295.67 tolerance 0.03 m accept -108 41 expect 2606240.3 3543175.46 tolerance 0.03 m accept -106 41 expect 3157536.54 3571750.25 tolerance 0.03 m accept -104 41 expect 3708029.16 3613004.9 tolerance 0.03 m accept -102 41 expect 4257426.54 3666917.56 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:3568 +inv \ +step +init=epsg:4152 -------------------------------------------------------------------------------- tolerance 0.03 m accept 2003933.27 6452478.8 expect -110 49 tolerance 0.03 m accept 2016617.9 5717717.18 expect -110 47 tolerance 0.03 m accept 2029251.51 4985910.59 expect -110 45 tolerance 0.03 m accept 2041851.0 4256081.23 expect -110 43 tolerance 0.03 m accept 2054432.46 3527295.67 expect -110 41 tolerance 0.03 m accept 2054432.46 3527295.67 expect -110 41 tolerance 0.03 m accept 2606240.3 3543175.46 expect -108 41 tolerance 0.03 m accept 3157536.54 3571750.25 expect -106 41 tolerance 0.03 m accept 3708029.16 3613004.9 expect -104 41 tolerance 0.03 m accept 4257426.54 3666917.56 expect -102 41 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4152 +inv \ +step +init=epsg:3568 -------------------------------------------------------------------------------- tolerance 0.006 m accept -110 49 roundtrip 1000 tolerance 0.006 m accept -110 47 roundtrip 1000 tolerance 0.006 m accept -110 45 roundtrip 1000 tolerance 0.006 m accept -110 43 roundtrip 1000 tolerance 0.006 m accept -110 41 roundtrip 1000 tolerance 0.006 m accept -110 41 roundtrip 1000 tolerance 0.006 m accept -108 41 roundtrip 1000 tolerance 0.006 m accept -106 41 roundtrip 1000 tolerance 0.006 m accept -104 41 roundtrip 1000 tolerance 0.006 m accept -102 41 roundtrip 1000 proj-9.8.1/test/gigs/5103.2.gie000664 001750 001750 00000005651 15166171715 015610 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5103 (part 2), Lambert Conic Conformal (2SP), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4152 +inv \ +step +init=epsg:2921 -------------------------------------------------------------------------------- tolerance 0.03 m accept -110 49 expect 2003937.27 6452491.7 tolerance 0.03 m accept -110 47 expect 2016621.93 5717728.61 tolerance 0.03 m accept -110 45 expect 2029255.57 4985920.56 tolerance 0.03 m accept -110 43 expect 2041855.08 4256089.74 tolerance 0.03 m accept -110 41 expect 2054436.57 3527302.73 tolerance 0.03 m accept -110 41 expect 2054436.57 3527302.73 tolerance 0.03 m accept -108 41 expect 2606245.52 3543182.55 tolerance 0.03 m accept -106 41 expect 3157542.86 3571757.39 tolerance 0.03 m accept -104 41 expect 3708036.57 3613012.12 tolerance 0.03 m accept -102 41 expect 4257435.06 3666924.89 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:2921 +inv \ +step +init=epsg:4152 -------------------------------------------------------------------------------- tolerance 0.03 m accept 2003937.27 6452491.7 expect -110 49 tolerance 0.03 m accept 2016621.93 5717728.61 expect -110 47 tolerance 0.03 m accept 2029255.57 4985920.56 expect -110 45 tolerance 0.03 m accept 2041855.08 4256089.74 expect -110 43 tolerance 0.03 m accept 2054436.57 3527302.73 expect -110 41 tolerance 0.03 m accept 2054436.57 3527302.73 expect -110 41 tolerance 0.03 m accept 2606245.52 3543182.55 expect -108 41 tolerance 0.03 m accept 3157542.86 3571757.39 expect -106 41 tolerance 0.03 m accept 3708036.57 3613012.12 expect -104 41 tolerance 0.03 m accept 4257435.06 3666924.89 expect -102 41 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4152 +inv \ +step +init=epsg:2921 -------------------------------------------------------------------------------- tolerance 0.006 m accept -110 49 roundtrip 1000 tolerance 0.006 m accept -110 47 roundtrip 1000 tolerance 0.006 m accept -110 45 roundtrip 1000 tolerance 0.006 m accept -110 43 roundtrip 1000 tolerance 0.006 m accept -110 41 roundtrip 1000 tolerance 0.006 m accept -110 41 roundtrip 1000 tolerance 0.006 m accept -108 41 roundtrip 1000 tolerance 0.006 m accept -106 41 roundtrip 1000 tolerance 0.006 m accept -104 41 roundtrip 1000 tolerance 0.006 m accept -102 41 roundtrip 1000 proj-9.8.1/test/gigs/5105.2.gie000664 001750 001750 00000007320 15166171715 015605 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5105 (part 2), Oblique Mercator (variant B), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4237 +inv \ +step +init=epsg:23700 -------------------------------------------------------------------------------- tolerance 0.05 m accept 16 48.5 expect 424714.235 355124.6 tolerance 0.05 m accept 17.2 48.0 expect 512056.188 296756.716 tolerance 0.05 m accept 17.5826505556 47.6361347222 expect 539847.765 255701.086 tolerance 0.05 m accept 19.0485716667 47.1443936111 expect 650000 200000 tolerance 0.05 m accept 19.2234294444 46.8756683333 expect 663329.053 170142.318 tolerance 0.05 m accept 20.1357405556 46.3703011111 expect 733651.455 114532.099 tolerance 0.05 m accept 21.4 45.7 expect 833148.855 42191.482 tolerance 0.05 m accept 22.3 49.3 expect 886565.935 444656.613 tolerance 0.05 m accept 21.2941986111 48.4899747222 expect 815999.993 351999.998 tolerance 0.05 m accept 19.2234294444 46.8756683333 expect 663329.053 170142.318 tolerance 0.05 m accept 17.6191536111 46.0687463889 expect 539403.958 81440.103 tolerance 0.05 m accept 16.36 45.5 expect 439836.709 20816.456 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:23700 +inv \ +step +init=epsg:4237 -------------------------------------------------------------------------------- tolerance 0.05 m accept 424714.235 355124.6 expect 16 48.5 tolerance 0.05 m accept 512056.188 296756.716 expect 17.2 48.0 tolerance 0.05 m accept 539847.765 255701.086 expect 17.5826505556 47.6361347222 tolerance 0.05 m accept 650000 200000 expect 19.0485716667 47.1443936111 tolerance 0.05 m accept 663329.053 170142.318 expect 19.2234294444 46.8756683333 tolerance 0.05 m accept 733651.455 114532.099 expect 20.1357405556 46.3703011111 tolerance 0.05 m accept 833148.855 42191.482 expect 21.4 45.7 tolerance 0.05 m accept 886565.935 444656.613 expect 22.3 49.3 tolerance 0.05 m accept 815999.993 351999.998 expect 21.2941986111 48.4899747222 tolerance 0.05 m accept 663329.053 170142.318 expect 19.2234294444 46.8756683333 tolerance 0.05 m accept 539403.958 81440.103 expect 17.6191536111 46.0687463889 tolerance 0.05 m accept 439836.709 20816.456 expect 16.36 45.5 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4237 +inv \ +step +init=epsg:23700 -------------------------------------------------------------------------------- tolerance 0.006 m accept 16 48.5 roundtrip 1000 tolerance 0.006 m accept 17.2 48.0 roundtrip 1000 tolerance 0.006 m accept 17.5826505556 47.6361347222 roundtrip 1000 tolerance 0.006 m accept 19.0485716667 47.1443936111 roundtrip 1000 tolerance 0.006 m accept 19.2234294444 46.8756683333 roundtrip 1000 tolerance 0.006 m accept 20.1357405556 46.3703011111 roundtrip 1000 tolerance 0.006 m accept 21.4 45.7 roundtrip 1000 tolerance 0.006 m accept 22.3 49.3 roundtrip 1000 tolerance 0.006 m accept 21.2941986111 48.4899747222 roundtrip 1000 tolerance 0.006 m accept 19.2234294444 46.8756683333 roundtrip 1000 tolerance 0.006 m accept 17.6191536111 46.0687463889 roundtrip 1000 tolerance 0.006 m accept 16.36 45.5 roundtrip 1000 proj-9.8.1/test/gigs/5109.gie000664 001750 001750 00000006641 15166171715 015456 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5109, Albers Equal Area, v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4283 +inv \ +step +init=epsg:3577 -------------------------------------------------------------------------------- tolerance 0.05 m accept 132 0 expect 0 0 tolerance 0.05 m accept 132 -27 expect 0 -2926820.89 tolerance 0.05 m accept 140 0 expect 966973.98 -30285.6 tolerance 0.05 m accept 140 -20 expect 832799.36 -2170181.93 tolerance 0.05 m accept 140 -40 expect 693250.21 -4395794.49 tolerance 0.05 m accept 140 -60 expect 567313.29 -6404311.16 tolerance 0.05 m accept 140 -80 expect 486878.674 -7687130.029 tolerance 0.05 m accept 120 -60 expect -850274.75 -6426505.13 tolerance 0.05 m accept 130 -60 expect -141915.26 -6387653.78 tolerance 0.05 m accept 140 -60 expect 567313.29 -6404311.16 tolerance 0.05 m accept 150 -60 expect 1273067.747 -6476375.276 tolerance 0.05 m accept 160 -60 expect 1971026.26 -6603404.82 tolerance 0.05 m accept 170 -60 expect 2656914.716 -6784621.89 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:3577 +inv \ +step +init=epsg:4283 -------------------------------------------------------------------------------- tolerance 0.05 m accept 0 0 expect 132 0 tolerance 0.05 m accept 0 -2926820.89 expect 132 -27 tolerance 0.05 m accept 966973.98 -30285.6 expect 140 0 tolerance 0.05 m accept 832799.36 -2170181.93 expect 140 -20 tolerance 0.05 m accept 693250.21 -4395794.49 expect 140 -40 tolerance 0.05 m accept 567313.29 -6404311.16 expect 140 -60 tolerance 0.05 m accept 486878.674 -7687130.029 expect 140 -80 tolerance 0.05 m accept -850274.75 -6426505.13 expect 120 -60 tolerance 0.05 m accept -141915.26 -6387653.78 expect 130 -60 tolerance 0.05 m accept 567313.29 -6404311.16 expect 140 -60 tolerance 0.05 m accept 1273067.747 -6476375.276 expect 150 -60 tolerance 0.05 m accept 1971026.26 -6603404.82 expect 160 -60 tolerance 0.05 m accept 2656914.716 -6784621.89 expect 170 -60 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4283 +inv \ +step +init=epsg:3577 -------------------------------------------------------------------------------- tolerance 0.006 m accept 132 0 roundtrip 1000 tolerance 0.006 m accept 132 -27 roundtrip 1000 tolerance 0.006 m accept 140 0 roundtrip 1000 tolerance 0.006 m accept 140 -20 roundtrip 1000 tolerance 0.006 m accept 140 -40 roundtrip 1000 tolerance 0.006 m accept 140 -60 roundtrip 1000 tolerance 0.006 m accept 140 -80 roundtrip 1000 tolerance 0.006 m accept 120 -60 roundtrip 1000 tolerance 0.006 m accept 130 -60 roundtrip 1000 tolerance 0.006 m accept 140 -60 roundtrip 1000 tolerance 0.006 m accept 150 -60 roundtrip 1000 tolerance 0.006 m accept 160 -60 roundtrip 1000 tolerance 0.006 m accept 170 -60 roundtrip 1000 proj-9.8.1/test/gigs/5101.2-jhs.gie000664 001750 001750 00000012767 15166171715 016376 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5101 (part 2), Transverse Mercator, v2-0_2011-06-28, recommended JHS formula -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4326 +inv \ +step +init=epsg:32631 -------------------------------------------------------------------------------- tolerance 0.03 m accept -2.0 80.0 expect 403186.945 8885748.708 tolerance 0.03 m accept -2.0 60.0 expect 221288.77 6661953.041 tolerance 0.03 m accept -2.0 40.0 expect 73106.698 4439746.917 tolerance 0.03 m accept -2.0 20.0 expect -23538.687 2219308.238 tolerance 0.03 m accept -2.0 0.0 expect -57087.12 0.0 tolerance 0.03 m accept -2.0 -20.0 expect -23538.687 -2219308.238 tolerance 0.03 m accept -2.0 -40.0 expect 73106.698 -4439746.917 tolerance 0.03 m accept -2.0 -60.0 expect 221288.77 -6661953.041 tolerance 0.03 m accept -2.0 -80.0 expect 403186.945 -8885748.708 tolerance 0.03 m accept -5.0 60.0 expect 54506.435 6678411.623 tolerance 0.03 m accept -4.0 60.0 expect 110043.299 6672079.494 tolerance 0.03 m accept -3.0 60.0 expect 165640.332 6666593.572 tolerance 0.03 m accept -2.0 60.0 expect 221288.77 6661953.041 tolerance 0.03 m accept -1.0 60.0 expect 276979.926 6658157.202 tolerance 0.03 m accept 0.0 60.0 expect 332705.179 6655205.484 tolerance 0.03 m accept 1.0 60.0 expect 388455.958 6653097.435 tolerance 0.03 m accept 2.0 60.0 expect 444223.733 6651832.735 tolerance 0.03 m accept 3.0 60.0 expect 500000.0 6651411.19 tolerance 0.03 m accept 4.0 60.0 expect 555776.267 6651832.735 tolerance 0.03 m accept 5.0 60.0 expect 611544.042 6653097.435 tolerance 0.03 m accept 6.0 60.0 expect 667294.821 6655205.484 tolerance 0.03 m accept 7.0 60.0 expect 723020.074 6658157.202 tolerance 0.03 m accept 8.0 60.0 expect 778711.23 6661953.041 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:32631 +inv \ +step +init=epsg:4326 -------------------------------------------------------------------------------- tolerance 0.03 m accept 403186.945 8885748.708 expect -2.0 80.0 tolerance 0.03 m accept 221288.77 6661953.041 expect -2.0 60.0 tolerance 0.03 m accept 73106.698 4439746.917 expect -2.0 40.0 tolerance 0.03 m accept -23538.687 2219308.238 expect -2.0 20.0 tolerance 0.03 m accept -57087.12 0.0 expect -2.0 0.0 tolerance 0.03 m accept -23538.687 -2219308.238 expect -2.0 -20.0 tolerance 0.03 m accept 73106.698 -4439746.917 expect -2.0 -40.0 tolerance 0.03 m accept 221288.77 -6661953.041 expect -2.0 -60.0 tolerance 0.03 m accept 403186.945 -8885748.708 expect -2.0 -80.0 tolerance 0.03 m accept 54506.435 6678411.623 expect -5.0 60.0 tolerance 0.03 m accept 110043.299 6672079.494 expect -4.0 60.0 tolerance 0.03 m accept 165640.332 6666593.572 expect -3.0 60.0 tolerance 0.03 m accept 221288.77 6661953.041 expect -2.0 60.0 tolerance 0.03 m accept 276979.926 6658157.202 expect -1.0 60.0 tolerance 0.03 m accept 332705.179 6655205.484 expect 0.0 60.0 tolerance 0.03 m accept 388455.958 6653097.435 expect 1.0 60.0 tolerance 0.03 m accept 444223.733 6651832.735 expect 2.0 60.0 tolerance 0.03 m accept 500000.0 6651411.19 expect 3.0 60.0 tolerance 0.03 m accept 555776.267 6651832.735 expect 4.0 60.0 tolerance 0.03 m accept 611544.042 6653097.435 expect 5.0 60.0 tolerance 0.03 m accept 667294.821 6655205.484 expect 6.0 60.0 tolerance 0.03 m accept 723020.074 6658157.202 expect 7.0 60.0 tolerance 0.03 m accept 778711.23 6661953.041 expect 8.0 60.0 -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4326 +inv \ +step +init=epsg:32631 -------------------------------------------------------------------------------- tolerance 0.006 m accept -2.0 80.0 roundtrip 1000 tolerance 0.006 m accept -2.0 60.0 roundtrip 1000 tolerance 0.006 m accept -2.0 40.0 roundtrip 1000 tolerance 0.006 m accept -2.0 20.0 roundtrip 1000 tolerance 0.006 m accept -2.0 0.0 roundtrip 1000 tolerance 0.006 m accept -2.0 -20.0 roundtrip 1000 tolerance 0.006 m accept -2.0 -40.0 roundtrip 1000 tolerance 0.006 m accept -2.0 -60.0 roundtrip 1000 tolerance 0.006 m accept -2.0 -80.0 roundtrip 1000 tolerance 0.006 m accept -5.0 60.0 roundtrip 1000 tolerance 0.006 m accept -4.0 60.0 roundtrip 1000 tolerance 0.006 m accept -3.0 60.0 roundtrip 1000 tolerance 0.006 m accept -2.0 60.0 roundtrip 1000 tolerance 0.006 m accept -1.0 60.0 roundtrip 1000 tolerance 0.006 m accept 0.0 60.0 roundtrip 1000 tolerance 0.006 m accept 1.0 60.0 roundtrip 1000 tolerance 0.006 m accept 2.0 60.0 roundtrip 1000 tolerance 0.006 m accept 3.0 60.0 roundtrip 1000 tolerance 0.006 m accept 4.0 60.0 roundtrip 1000 tolerance 0.006 m accept 5.0 60.0 roundtrip 1000 tolerance 0.006 m accept 6.0 60.0 roundtrip 1000 tolerance 0.006 m accept 7.0 60.0 roundtrip 1000 tolerance 0.006 m accept 8.0 60.0 roundtrip 1000 proj-9.8.1/test/gigs/5103.1.gie000664 001750 001750 00000007321 15166171715 015603 0ustar00eveneven000000 000000 -------------------------------------------------------------------------------- Test 5103 (part 1), Lambert Conic Conformal (2SP), v2-0_2011-06-28. -------------------------------------------------------------------------------- use_proj4_init_rules true -------------------------------------------------------------------------------- operation +proj=pipeline \ +step +init=epsg:4313 +inv \ +step +init=epsg:31370 tolerance 30 mm -------------------------------------------------------------------------------- accept 5 58 expect 187742.7 969521.653 accept 5 57 expect 188698.877 857277.135 accept 5 56 expect 189652.853 745291.184 accept 5 55 expect 190604.967 633523.672 accept 5 54 expect 191555.55 521935.9 accept 5 53 expect 192504.921 410490.433 accept 5.3876389 52.1561606 expect 219843.841 316827.604 accept 4 51 expect 124202.936 187756.876 accept 4 50 expect 123652.406 76521.628 accept 4 49 expect 123101.889 -34711.068 accept 3.3137281 47.9752611 expect 71254.553 -148236.592 accept 3 53 expect 58108.966 411155.591 accept 4 53 expect 125304.704 410370.504 accept 5 53 expect 192504.921 410490.433 accept 6 53 expect 259697.429 411515.356 accept 7 53 expect 326870.04 413445.087 accept 8 53 expect 394010.571 416279.276 accept 9 53 expect 461106.844 420017.408 accept 10 53 expect 528146.69 424658.807 accept 11 53 expect 595117.95 430202.63 -------------------------------------------------------------------------------- operation proj=pipeline \ step init=epsg:31370 inv \ step init=epsg:4313 tolerance 30 mm -------------------------------------------------------------------------------- accept 187742.7 969521.653 expect 5 58 accept 188698.877 857277.135 expect 5 57 accept 189652.853 745291.184 expect 5 56 accept 190604.967 633523.672 expect 5 55 accept 191555.55 521935.9 expect 5 54 accept 192504.921 410490.433 expect 5 53 accept 219843.841 316827.604 expect 5.3876389 52.1561606 accept 124202.936 187756.876 expect 4 51 accept 123652.406 76521.628 expect 4 50 accept 123101.889 -34711.068 expect 4 49 accept 71254.553 -148236.592 expect 3.3137281 47.9752611 accept 58108.966 411155.591 expect 3 53 accept 125304.704 410370.504 expect 4 53 accept 192504.921 410490.433 expect 5 53 accept 259697.429 411515.356 expect 6 53 accept 326870.04 413445.087 expect 7 53 accept 394010.571 416279.276 expect 8 53 accept 461106.844 420017.408 expect 9 53 accept 528146.69 424658.807 expect 10 53 accept 595117.95 430202.63 expect 11 53 -------------------------------------------------------------------------------- operation +proj=pipeline towgs84=0,0,0 \ # turn off dual datum shift +step +init=epsg:4313 +inv \ +step +init=epsg:31370 tolerance 6 mm -------------------------------------------------------------------------------- accept 5 58 roundtrip 1000 accept 5 57 roundtrip 1000 accept 5 56 roundtrip 1000 accept 5 55 roundtrip 1000 accept 5 54 roundtrip 1000 accept 5 53 roundtrip 1000 accept 5.3876389 52.1561606 roundtrip 1000 accept 4 51 roundtrip 1000 accept 4 50 roundtrip 1000 accept 4 49 roundtrip 1000 accept 3.3137281 47.9752611 roundtrip 1000 accept 3 53 roundtrip 1000 accept 4 53 roundtrip 1000 accept 5 53 roundtrip 1000 accept 6 53 roundtrip 1000 accept 7 53 roundtrip 1000 accept 8 53 roundtrip 1000 accept 9 53 roundtrip 1000 accept 10 53 roundtrip 1000 accept 11 53 roundtrip 1000 proj-9.8.1/test/benchmark/000775 001750 001750 00000000000 15166171735 015366 5ustar00eveneven000000 000000 proj-9.8.1/test/benchmark/CMakeLists.txt000664 001750 001750 00000000425 15166171715 020125 0ustar00eveneven000000 000000 option(BUILD_BENCHMARKS "Build PROJ benchmark applications" ON) if(NOT BUILD_BENCHMARKS) return() endif() add_compile_options(${PROJ_CXX_WARN_FLAGS}) add_executable(bench_proj_trans bench_proj_trans.cpp) target_link_libraries(bench_proj_trans PRIVATE ${PROJ_LIBRARIES}) proj-9.8.1/test/benchmark/bench_proj_trans.cpp000664 001750 001750 00000015032 15166171715 021411 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Benchmark * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2022, Even Rouault, * * 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 "proj.h" #include // rand() #include #include // HUGE_VAL #include #include #include #include static void usage() { printf("Usage: bench_proj_trans [(--source-crs|-s) string]\n"); printf(" [(--target-crs|-t) string]\n"); printf(" [(--pipeline|-p) string]\n"); printf(" [(--loops|-l) number]\n"); printf(" [--noise-x number] [--noise-y number]\n"); printf(" coord_comp_1 coord_comp_2 [coord_comp_3] " "[coord_comp_4]\n"); printf("\n"); printf("Both of --source-crs and --target_crs, or --pipeline must be " "specified.\n"); printf("\n"); printf("Example: bench_proj_trans -s EPSG:4326 -t EPSG:32631 49 2\n"); exit(1); } int main(int argc, char *argv[]) { std::string sourceCRS; std::string targetCRS; std::string pipeline; int loops = 5 * 1000 * 1000; double coord_comp[4] = {0, 0, 0, HUGE_VAL}; int coord_comp_counter = 0; double noiseX = 0; double noiseY = 0; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--source-crs") == 0 || strcmp(argv[i], "-s") == 0) { if (i + 1 >= argc) usage(); sourceCRS = argv[i + 1]; ++i; } else if (strcmp(argv[i], "--target-crs") == 0 || strcmp(argv[i], "-t") == 0) { if (i + 1 >= argc) usage(); targetCRS = argv[i + 1]; ++i; } else if (strcmp(argv[i], "--pipeline") == 0 || strcmp(argv[i], "-p") == 0) { if (i + 1 >= argc) usage(); pipeline = argv[i + 1]; ++i; } else if (strcmp(argv[i], "--loops") == 0 || strcmp(argv[i], "-l") == 0) { if (i + 1 >= argc) usage(); loops = atoi(argv[i + 1]); ++i; } else if (strcmp(argv[i], "--noise-x") == 0) { if (i + 1 >= argc) usage(); noiseX = atof(argv[i + 1]); ++i; } else if (strcmp(argv[i], "--noise-y") == 0) { if (i + 1 >= argc) usage(); noiseY = atof(argv[i + 1]); ++i; } else if (argv[i][0] == '-' && !(argv[i][1] >= '0' && argv[i][1] <= '9')) { usage(); } else if (coord_comp_counter < 4) { coord_comp[coord_comp_counter++] = atof(argv[i]); } else { usage(); } } if (coord_comp_counter < 2) usage(); PJ_CONTEXT *ctxt = proj_context_create(); PJ *P = nullptr; if (!pipeline.empty()) { P = proj_create(ctxt, pipeline.c_str()); } else if (!sourceCRS.empty() && !targetCRS.empty()) { P = proj_create_crs_to_crs(ctxt, sourceCRS.c_str(), targetCRS.c_str(), nullptr); } else { usage(); } if (P == nullptr) { exit(1); } PJ_COORD c; c.v[0] = coord_comp[0]; c.v[1] = coord_comp[1]; c.v[2] = coord_comp[2]; c.v[3] = coord_comp[3]; PJ_COORD c_ori = c; auto res = proj_trans(P, PJ_FWD, c); if (coord_comp_counter == 2) { printf("%.15g %.15g -> %.15g %.15g\n", c.v[0], c.v[1], res.v[0], res.v[1]); } else if (coord_comp_counter == 3) { printf("%.15g %.15g %.15g -> %.15g %.15g %.15g\n", c.v[0], c.v[1], c.v[2], res.v[0], res.v[1], res.v[2]); } else { printf("%.15g %.15g %.15g %.15g -> %.15g %.15g %.15g %.15g\n", c.v[0], c.v[1], c.v[2], c.v[3], res.v[0], res.v[1], res.v[2], res.v[3]); } // Start by timing just noise generation double dummy = 0; auto start_noise = std::chrono::system_clock::now(); for (int i = 0; i < loops; ++i) { if (noiseX != 0) c.v[0] = c_ori.v[0] + noiseX * (2 * double(rand()) / RAND_MAX - 1); if (noiseY != 0) c.v[1] = c_ori.v[1] + noiseY * (2 * double(rand()) / RAND_MAX - 1); dummy += c.v[0]; dummy += c.v[1]; } auto end_noise = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now(); for (int i = 0; i < loops; ++i) { if (noiseX != 0) c.v[0] = c_ori.v[0] + noiseX * (2 * double(rand()) / RAND_MAX - 1); if (noiseY != 0) c.v[1] = c_ori.v[1] + noiseY * (2 * double(rand()) / RAND_MAX - 1); dummy += c.v[0]; dummy += c.v[1]; proj_trans(P, PJ_FWD, c); } auto end = std::chrono::system_clock::now(); proj_destroy(P); proj_context_destroy(ctxt); auto elapsed_ms = std::chrono::duration_cast( (end - start) - (end_noise - start_noise)); printf("Duration: %d ms\n", static_cast(elapsed_ms.count())); printf("Throughput: %.02f million coordinates/s\n", 1e-3 * static_cast(loops) / static_cast(elapsed_ms.count()) + dummy * 1e-300); return 0; } proj-9.8.1/test/gie/000775 001750 001750 00000000000 15166171735 014200 5ustar00eveneven000000 000000 proj-9.8.1/test/gie/more_builtins.gie000664 001750 001750 00000106430 15166171715 017543 0ustar00eveneven000000 000000 =============================================================================== Various test material, mostly converted from selftest entries in PJ_xxx.c Contrary to the material in builtins.gie, this material is handwritten and intends to exercise corner cases. =============================================================================== ------------------------------------------------------------------------------- # Two ob_tran tests from data/testvarious ------------------------------------------------------------------------------- operation +proj=ob_tran +o_proj=moll +R=6378137.0 +o_lon_p=0 +o_lat_p=0 +lon_0=180 ------------------------------------------------------------------------------- tolerance 1 mm direction inverse accept 300000 400000 expect -42.7562158333 85.5911341667 direction forward accept 10 20 expect -1384841.18787 7581707.88240 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Two tests from PJ_molodensky.c ------------------------------------------------------------------------------- operation proj=molodensky a=6378160 rf=298.25 \ da=-23 df=-8.120449e-8 dx=-134 dy=-48 dz=149 \ abridged ------------------------------------------------------------------------------- tolerance 2 m accept 144.9667 -37.8 50 0 expect 144.968 -37.79848 46.378 0 roundtrip 100 1 m ------------------------------------------------------------------------------- # Same thing once more, but this time unabridged ------------------------------------------------------------------------------- operation proj=molodensky a=6378160 rf=298.25 \ da=-23 df=-8.120449e-8 dx=-134 dy=-48 dz=149 ------------------------------------------------------------------------------- tolerance 2 m accept 144.9667 -37.8 50 0 expect 144.968 -37.79848 46.378 0 roundtrip 100 1 m ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Molodensky with all 0 parameters ------------------------------------------------------------------------------- operation proj=molodensky a=6378160 rf=298.25 \ da=0 df=0 dx=0 dy=0 dz=0 ------------------------------------------------------------------------------- tolerance 1 mm accept 144.9667 -37.8 50 0 expect 144.9667 -37.8 50 0 roundtrip 1 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test error cases of molodensky ------------------------------------------------------------------------------- # No arguments operation proj=molodensky a=6378160 rf=298.25 expect failure errno invalid_op_missing_arg # Missing arguments operation proj=molodensky a=6378160 rf=298.25 dx=0 expect failure errno invalid_op_missing_arg ------------------------------------------------------------------------------- # Tests for PJ_bertin1953.c ------------------------------------------------------------------------------- operation proj=bertin1953 +R=1 ------------------------------------------------------------------------------- accept 0 0 expect -0.260206554508 -0.685226058142 accept 16.5 42 expect 0.0 0.0 accept -180 90 expect 0.0 0.813473286152 accept 0 90 expect 0.0 0.813473286152 accept 10 -35 expect -0.138495501548 -1.221408328101 accept -70 -35 expect -1.504967424950 -0.522846035499 accept 80 7 expect 0.929377425352 -0.215443296201 accept 128 35 expect 0.920230566844 0.713170409026 accept 170 -41 expect 2.162845830414 -0.046534568425 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Some tests from PJ_pipeline.c ------------------------------------------------------------------------------- # Forward-reverse geo->utm->geo (4D functions) ------------------------------------------------------------------------------- operation proj=pipeline zone=32 step \ proj=utm ellps=GRS80 step \ proj=utm ellps=GRS80 inv ------------------------------------------------------------------------------- tolerance 0.1 mm accept 12 55 0 0 expect 12 55 0 0 # Now the inverse direction (still same result: the pipeline is symmetrical) direction inverse expect 12 55 0 0 ------------------------------------------------------------------------------- # And now the back-to-back situation utm->geo->utm (4D functions) ------------------------------------------------------------------------------- operation proj=pipeline zone=32 ellps=GRS80 step \ proj=utm inv step \ proj=utm ------------------------------------------------------------------------------- accept 691875.63214 6098907.82501 0 0 expect 691875.63214 6098907.82501 0 0 direction inverse expect 691875.63214 6098907.82501 0 0 ------------------------------------------------------------------------------- # Forward-reverse geo->utm->geo (3D functions) ------------------------------------------------------------------------------- operation proj=pipeline zone=32 step \ proj=utm ellps=GRS80 step \ proj=utm ellps=GRS80 inv ------------------------------------------------------------------------------- tolerance 0.1 mm accept 12 55 0 expect 12 55 0 # Now the inverse direction (still same result: the pipeline is symmetrical) direction inverse expect 12 55 0 ------------------------------------------------------------------------------- # And now the back-to-back situation utm->geo->utm (3D functions) ------------------------------------------------------------------------------- operation proj=pipeline zone=32 ellps=GRS80 step \ proj=utm inv step \ proj=utm ------------------------------------------------------------------------------- accept 691875.63214 6098907.82501 0 expect 691875.63214 6098907.82501 0 direction inverse expect 691875.63214 6098907.82501 0 ------------------------------------------------------------------------------- # Test a corner case: A rather pointless one-step pipeline geo->utm ------------------------------------------------------------------------------- operation proj=pipeline step proj=utm zone=32 ellps=GRS80 ------------------------------------------------------------------------------- accept 12 55 0 0 expect 691875.63214 6098907.82501 0 0 direction inverse accept 691875.63214 6098907.82501 0 0 expect 12 55 0 0 ------------------------------------------------------------------------------- # Finally test a pipeline with more than one init step ------------------------------------------------------------------------------- use_proj4_init_rules true operation proj=pipeline \ step init=epsg:25832 inv \ step init=epsg:25833 \ step init=epsg:25833 inv \ step init=epsg:25832 ------------------------------------------------------------------------------- accept 691875.63214 6098907.82501 0 0 expect 691875.63214 6098907.82501 0 0 direction inverse accept 12 55 0 0 expect 12 55 0 0 ------------------------------------------------------------------------------- # Test a few inversion scenarios (urm5 has no inverse operation) ------------------------------------------------------------------------------- operation proj=pipeline step \ proj=urm5 n=0.5 inv expect failure pjd_err_malformed_pipeline operation proj=pipeline inv step \ proj=urm5 n=0.5 expect failure pjd_err_malformed_pipeline operation proj=pipeline inv step \ proj=urm5 n=0.5 ellps=WGS84 inv accept 12 56 expect 1215663.2814182492 5452209.5424045017 operation proj=pipeline step \ proj=urm5 ellps=WGS84 n=0.5 accept 12 56 expect 1215663.2814182492 5452209.5424045017 ------------------------------------------------------------------------------- # Test various failing scenarios. ------------------------------------------------------------------------------- operation proj=pipeline step \ proj=pipeline step \ proj=merc expect failure pjd_err_malformed_pipeline operation step proj=pipeline step proj=merc expect failure pjd_err_malformed_pipeline operation proj=pipeline expect failure pjd_err_malformed_pipeline ------------------------------------------------------------------------------- # Some tests from PJ_vgridshift.c ------------------------------------------------------------------------------- operation proj=vgridshift grids=egm96_15.gtx ellps=GRS80 ------------------------------------------------------------------------------- tolerance 1 cm accept 12.5 55.5 0 0 expect 12.5 55.5 -36.394090697 0 accept -180.1 0 0 expect -180.1 0 -20.835222268 accept 179.9 0 0 expect 179.9 0 -20.835222268 accept 180 0 0 expect 180 0 -20.756538510 accept 540 0 0 expect 540 0 -20.756538510 accept -180 0 0 expect -180 0 -20.756538510 accept -540 0 0 expect -540 0 -20.756538510 roundtrip 100 1 nm ------------------------------------------------------------------------------- # Fail on purpose: +grids parameter is mandatory operation proj=vgridshift expect failure errno invalid_op_missing_arg # Fail on purpose: open non-existing grid operation proj=vgridshift grids=nonexistinggrid.gtx expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation proj=vgridshift grids=egm96_15.gtx ellps=GRS80 multiplier=0.1 tolerance 15 cm accept 12.5 55.5 0 0 expect 12.5 55.5 3.6021305084228516 0 ------------------------------------------------------------------------------- # Some tests from PJ_hgridshift.c ------------------------------------------------------------------------------- operation proj=hgridshift +grids=ntf_r93.gsb ellps=GRS80 ------------------------------------------------------------------------------- tolerance 1 mm accept 2.250704350387 46.500051597273 expect 2.25 46.5 direction inverse accept 2.25 46.5 expect 2.250704350387 46.500051597273 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Fail on purpose: open non-existing grid: operation proj=hgridshift grids=@nonexistinggrid.gsb,anothernonexistinggrid.gsb expect failure errno invalid_op_file_not_found_or_invalid # Fail on purpose: +grids parameter is mandatory: operation proj=hgridshift expect failure errno invalid_op_missing_arg ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Tests for LCC 2SP Michigan (from PJ_lcc.c) ------------------------------------------------------------------------------- # This test is taken from EPSG guidance note 7-2 (version 54, August 2018, # page 25) ------------------------------------------------------------------------------- operation +proj=lcc +ellps=clrk66 +lat_1=44d11'N +lat_2=45d42'N +x_0=609601.2192 +lon_0=84d20'W +lat_0=43d19'N +k_0=1.0000382 +units=us-ft ------------------------------------------------------------------------------- tolerance 5 mm accept 83d10'W 43d45'N expect 2308335.75 160210.48 direction inverse accept 2308335.75 160210.48 expect 83d10'W 43d45'N ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # A number of tests from PJ_helmert.c ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # This example is from Lotti Jivall: "Simplified transformations from # ITRF2008/IGS08 to ETRS89 for maritime applications" ------------------------------------------------------------------------------- operation proj=helmert convention=coordinate_frame \ x=0.67678 y=0.65495 z=-0.52827 \ rx=-0.022742 ry=0.012667 rz=0.022704 s=-0.01070 ------------------------------------------------------------------------------- tolerance 1 um accept 3565285.00000000 855949.00000000 5201383.00000000 expect 3565285.41342351 855948.67986759 5201382.72939791 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # This example is a random point, transformed from ED50 to ETRS89 using KMStrans2 ------------------------------------------------------------------------------- operation proj=helmert exact convention=coordinate_frame \ x=-081.0703 rx=-0.48488 \ y=-089.3603 ry=-0.02436 \ z=-115.7526 rz=-0.41321 s=-0.540645 ------------------------------------------------------------------------------- tolerance 1 um accept 3494994.30120000 1056601.97250000 5212382.16660000 expect 3494909.84026368 1056506.78938633 5212265.66699761 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # This example is a coordinate from the geodetic observatory in Onsala, # Sweden transformed from ITRF2000 @ 2017.0 to ITRF93 @ 2017.0. # The test coordinate was transformed using GNSStrans, using transformation # parameters published by ITRF: ftp://itrf.ensg.ign.fr/pub/itrf/ITRF.TP ------------------------------------------------------------------------------- operation proj=helmert convention=position_vector \ x = 0.0127 dx = -0.0029 rx = -0.00039 drx = -0.00011 \ y = 0.0065 dy = -0.0002 ry = 0.00080 dry = -0.00019 \ z = -0.0209 dz = -0.0006 rz = -0.00114 drz = 0.00007 \ s = 0.00195 ds = 0.00001 t_epoch = 1988.0 ------------------------------------------------------------------------------- tolerance 0.03 mm accept 3370658.37800 711877.31400 5349787.08600 2017.0 # ITRF2000@2017.0 expect 3370658.18890 711877.42370 5349787.12430 2017.0 # ITRF93@2017.0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # This example is from "A mathematical relationship between NAD27 and NAD83 (91) # State Plane coordinates in Southeastern Wisconsin": # http://www.sewrpc.org/SEWRPCFiles/Publications/TechRep/tr-034-Mathematical-Relationship-Between-NAD27-and-NAD83-91-State-Plane-Coordinates-Southeastern-Wisconsin.pdf # The test data is taken from p. 29. Here we are using point 203 and converting it # from NAD27 (ft) -> NAD83 (m). The paper reports a difference of 0.0014 m from # measured to computed coordinates, hence the test tolerance is set accordingly. ------------------------------------------------------------------------------- operation proj=helmert \ x=-9597.3572 y=.6112 \ s=0.304794780637 theta=-1.244048 ------------------------------------------------------------------------------- tolerance 1 mm accept 2546506.957 542256.609 0 expect 766563.675 165282.277 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Finally test the 4D-capabilities of the proj.h API, especially that the # rotation matrix is updated when necessary. # Test coordinates from GNSStrans. ------------------------------------------------------------------------------- operation proj=helmert convention=position_vector \ x = 0.01270 dx =-0.0029 rx =-0.00039 drx =-0.00011 \ y = 0.00650 dy =-0.0002 ry = 0.00080 dry =-0.00019 \ z =-0.0209 dz =-0.0006 rz =-0.00114 drz = 0.00007 \ s = 0.00195 ds = 0.00001 \ t_epoch=1988.0 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 3370658.378 711877.314 5349787.086 2017.0 expect 3370658.18890 711877.42370 5349787.12430 2017.0 accept 3370658.378 711877.314 5349787.086 2018.0 expect 3370658.18087 711877.42750 5349787.12648 2018.0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test case of https://github.com/OSGeo/PROJ/issues/2333 ------------------------------------------------------------------------------- operation +proj=helmert +x=-0.0019 +y=-0.0017 +z=-0.0105 +s=0.00134 \ +dx=0.0001 +dy=0.0001 +dz=-0.0018 +ds=0.00008 +t_epoch=2000.0 \ +convention=position_vector ------------------------------------------------------------------------------- tolerance 0.1 mm accept 3513638.1938 778956.4525 5248216.4690 2008.75 expect 3513638.1999 778956.4533 5248216.4535 2008.75 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test error cases of helmert ------------------------------------------------------------------------------- # A rotational term implies an explicit convention to be specified operation proj=helmert rx=1 expect failure errno invalid_op_missing_arg operation proj=helmert rx=1 convention=foo expect failure errno invalid_op_illegal_arg_value operation proj=helmert rx=1 convention=1 expect failure errno invalid_op_illegal_arg_value # towgs84 in helmert context should always be position_vector operation proj=helmert towgs84=1,2,3,4,5,6,7 convention=coordinate_frame expect failure errno invalid_op_illegal_arg_value # Transpose no longer accepted operation proj=helmert transpose expect failure errno invalid_op_illegal_arg_value # Use of 2D Helmert interface with 3D Helmert setup operation +proj=ob_tran +o_proj=helmert +o_lat_p=0 direction inverse accept 0 0 expect failure errno no_inverse_op ------------------------------------------------------------------------------- # Molodensky-Badekas from IOGP Guidance 7.2, Transformation from La Canoa to REGVEN # between geographic 2D coordinate reference systems (EPSG Dataset transformation code 1771). # Here just taking the Cartesian step of the transformation. ------------------------------------------------------------------------------- operation proj=molobadekas convention=coordinate_frame \ x=-270.933 y=115.599 z=-360.226 rx=-5.266 ry=-1.238 rz=2.381 \ s=-5.109 px=2464351.59 py=-5783466.61 pz=974809.81 ------------------------------------------------------------------------------- tolerance 1 cm roundtrip 1 accept 2550408.96 -5749912.26 1054891.11 expect 2550138.45 -5749799.87 1054530.82 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test error cases of molobadekas ------------------------------------------------------------------------------- # Missing convention operation proj=molobadekas expect failure errno invalid_op_missing_arg ------------------------------------------------------------------------------- # geocentric latitude ------------------------------------------------------------------------------- operation proj=geoc ellps=GRS80 accept 12 55 0 0 expect 12 54.818973308324573 0 0 roundtrip 1000 accept 12 90 0 0 expect 12 90 0 0 accept 12 -90 0 0 expect 12 -90 0 0 accept 12 89.99999999999 0 0 expect 12 89.999999999989996 0 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # geocentric latitude using old +geoc flag ------------------------------------------------------------------------------- operation proj=pipeline step proj=longlat ellps=GRS80 geoc inv accept 12 55 0 0 expect 12 54.818973308324573 0 0 roundtrip 1 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # some less used options ------------------------------------------------------------------------------- operation proj=utm ellps=GRS80 zone=32 to_meter=0 expect failure errno invalid_op_illegal_arg_value operation proj=utm ellps=GRS80 zone=32 to_meter=10 accept 12 55 expect 69187.5632 609890.7825 operation proj=utm ellps=GRS80 zone=32 to_meter=1/0 expect failure errno invalid_op_illegal_arg_value operation proj=utm ellps=GRS80 zone=32 to_meter=2.0/0.2 accept 12 55 expect 69187.5632 609890.7825 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test that gie can read DMS style coordinates as well as coordinates where _ # is used as a thousands separator. ------------------------------------------------------------------------------- operation +step +proj=latlong +ellps=WGS84 ------------------------------------------------------------------------------- tolerance 1 m accept -64d43'75.34 17d32'45.6 expect -64.737589 17.546000 accept 164d43'75.34 17d32'45.6 expect 164.737589 17.546000 accept 164d43'75.34 17d32'45.6 expect 164d43'75.34 17d32'45.6 accept 164d43'75.34W 17d32'45.6S expect -164.737589 -17.546000 accept 90d00'00.00 0d00'00.00 expect 90.0 0.0 accept 0d00'00.00 0d00'00.00 expect 0.0 0.0 operation +proj=pipeline \ +step +proj=latlong +datum=NAD27 +inv \ +step +units=us-ft +init=nad27:3901 tolerance 1 mm accept -80d32'30.000 34d32'30.000 0.0 expect 2_138_028.224 561_330.721 0.0 accept -81d00'00.000 34d32'30.000 0.0 expect 2_000_000.000 561_019.077 0.0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Some tests from PJ_eqearth.c ------------------------------------------------------------------------------- operation +proj=eqearth +ellps=WGS84 ------------------------------------------------------------------------------- direction forward tolerance 1cm accept 0 0 expect 0 0 accept -180 90 expect -10216474.79 8392927.6 accept 0 90 expect 0 8392927.6 accept 180 90 expect 10216474.79 8392927.6 accept 180 45 expect 14792474.75 5466867.76 accept 180 0 expect 17243959.06 0 accept -70 -31.2 expect -6241081.64 -3907019.16 direction inverse accept -6241081.64 -3907019.16 expect -70 -31.2 accept 17243959.06 0 expect 180 0 accept 14792474.75 5466867.76 expect 180 45 accept 0 0 expect 0 0 accept -10216474.79 8392927.6 expect -180 90 accept 0 8392927.6 expect 0 90 accept 10216474.79 8392927.6 expect 180 90 operation +proj=eqearth +R=6378137 direction forward tolerance 1cm accept 0 0 expect 0 0 accept -180 90 expect -10227908.09 8402320.16 accept 0 90 expect 0.00 8402320.16 accept 180 90 expect 10227908.09 8402320.16 accept 180 45 expect 14795421.79 5486671.72 accept 180 0 expect 17263256.84 0.00 accept -70 -31.2 expect -6244707.88 -3924893.29 direction inverse accept -6244707.88 -3924893.29 expect -70 -31.2 accept 17263256.84 0.00 expect 180 0 accept 14795421.79 5486671.72 expect 180 45 accept 0 0 expect 0 0 accept -10227908.09 8402320.16 expect -180 90 accept 0.00 8402320.16 expect 0 90 accept 10227908.09 8402320.16 expect 180 90 operation +proj=eqearth +R=1 direction inverse # coordinate in valid region accept 0 -1.3 expect 0 -82.318 # coordinate on edge accept 0 -1.3173627591574 expect 0 -90 # coordinate outside valid region, should be clamped accept 0 -1.4 expect 0 -90 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test for PJ_affine ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=geogoffset ------------------------------------------------------------------------------- direction forward tolerance 1mm accept 10 20 expect 10 20 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=geogoffset +dlon=3600 +dlat=-3600 +dh=3 ------------------------------------------------------------------------------- direction forward tolerance 1mm accept 10 20 expect 11 19 roundtrip 1 accept 10 20 30 expect 11 19 33 roundtrip 1 accept 10 20 30 40 expect 11 19 33 40 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=affine ------------------------------------------------------------------------------- direction forward tolerance 1mm accept 10 20 30 40 expect 10 20 30 40 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=affine +xoff=1 +yoff=2 +zoff=3 +toff=4 +s11=11 +s12=12 +s13=13 +s21=21 +s22=22 +s23=23 +s31=-31 +s32=32 +s33=33 +tscale=34 ------------------------------------------------------------------------------- direction forward tolerance 1mm accept 2 49 10 100 expect 741.0000 1352.0000 1839.0000 3404.0000 roundtrip 1 accept 2 49 10 expect 741.0000 1352.0000 1839.0000 roundtrip 1 accept 2 49 expect 611.0000 1122.0000 roundtrip 1 ------------------------------------------------------------------------------- # Non invertible operation +proj=affine +s11=0 +s22=0 +s23=0 ------------------------------------------------------------------------------- direction reverse accept 0 0 0 0 expect failure ------------------------------------------------------------------------------- # Non invertible operation +proj=affine +tscale=0 ------------------------------------------------------------------------------- direction reverse accept 0 0 0 0 expect failure ------------------------------------------------------------------------------- # Test lon_wrap operation +proj=longlat +ellps=WGS84 +lon_wrap=180 ------------------------------------------------------------------------------- direction forward accept -1 10 0 expect 359 10 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test for vertoffset (Vertical Offset And Slope) # Test point for EPSG Guidance note 7.2 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vertoffset +lat_0=46.9166666666666666 +lon_0=8.183333333333334 +dh=-0.245 +slope_lat=-0.210 +slope_lon=-0.032 +ellps=GRS80 ------------------------------------------------------------------------------- direction forward tolerance 1mm accept 9.666666666666666 47.333333333333336 473.000 expect 9.666666666666666 47.333333333333336 472.690 roundtrip 1 ------------------------------------------------------------------------------- # Test NaN handling # When given NaNs, return NaNs ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=laea +lat_0=90 +lon_0=-150 +datum=WGS84 +units=m ------------------------------------------------------------------------------- direction forward tolerance 0 accept NaN NaN NaN NaN expect NaN NaN NaN NaN roundtrip 1 ------------------------------------------------------------------------------- # No-op ------------------------------------------------------------------------------- operation +proj=noop direction forward accept 25 25 expect 25 25 accept 25 25 25 expect 25 25 25 accept 25 25 25 25 expect 25 25 25 25 ------------------------------------------------------------------------------- # Test invalid lat_0 operation +proj=aeqd +R=1 +lat_0=91 expect failure errno invalid_op_illegal_arg_value ------------------------------------------------------------------------------- # cart ------------------------------------------------------------------------------- operation +proj=cart +ellps=GRS80 tolerance 0.001mm accept 0 0 0 expect 6378137 0 0 accept 0 90 0 expect 0 0 6356752.314140347 accept 0 -90 0 expect 0 0 -6356752.314140347 accept 90 0 0 expect 0 6378137 0 accept -90 0 0 expect 0 -6378137 0 accept 180 0 0 expect -6378137 0 0 accept -180 0 0 expect -6378137 0 0 # Center of Earth ! accept 0 0 -6378137 expect 0 0 0 accept 0 90 -6356752.314140347 expect 0 0 0 direction inverse accept 6378137 0 0 expect 0 0 0 accept 0 0 6356752.314140347 expect 0 90 0 accept 0 0 -6356752.314140347 expect 0 -90 0 accept 0 6378137 0 expect 90 0 0 accept 0 -6378137 0 expect -90 0 0 accept -6378137 0 0 expect 180 0 0 # Center of Earth ! accept 0 0 0 expect 0 90 -6356752.314140356 accept 0 0 1e-6 expect 0 90 -6356752.314139356 accept 0 0 -1e-6 expect 0 -90 -6356752.314139356 ------------------------------------------------------------------------------- # Test handling of endianness of NTv2 grids ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_little_endian.gsb ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_big_endian.gsb ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- =============================================================================== # Tests for testing that +omit_fwd, +omit_inv and +inv work together like they # should. # # +omit_fwd specifies that a step should be omitted when running forwards. # # +omit_inv specifies that a step should be omitted when running backwards. # # +inv specifies that a step should be inverted. The forward path would do # inverse operation and inverse path would do forward operation. # # | | invertible step | non-invertible step | # | flags | forward path | inverse path | forward path | inverse path | # | -------------- | ------------ | ------------ | ------------ | ------------ | # | +omit_fwd | omit | inv | omit | runtime err | # | +omit_fwd +inv | omit | fwd | omit | fwd | # | +omit_inv | fwd | omit | fwd | omit | # | +omit_inv +inv | inv | omit | pipeline creation error | # # From the table we can see that invertible steps should work pretty much all # the time. Non-invertible steps on the other hand make either the forward path # or inverse path undefined depending on which flags the step has. # # A non-invertible step is for example an affine transformation where we cannot # calculate the inverse of the matrix. =============================================================================== ------------------------------------------------------------------------------- # Test that +omit_fwd, +omit_inv and +inv work correctly with an invertible step. ------------------------------------------------------------------------------- # Test that forward path does nothing and inverse path does inverse transformation operation proj=pipeline step proj=affine s11=2 omit_fwd direction forward accept 1 2 3 expect 1 2 3 direction inverse accept 1 2 3 expect 0.5 2 3 # Test that forward path does nothing and inverse path does forward transformation operation proj=pipeline step proj=affine s11=2 omit_fwd inv direction forward accept 1 2 3 expect 1 2 3 direction inverse accept 1 2 3 expect 2 2 3 # Test that forward path does forward transformation and inverse path does nothing operation proj=pipeline step proj=affine s11=2 omit_inv direction forward accept 1 2 3 expect 2 2 3 direction inverse accept 1 2 3 expect 1 2 3 # Test that forward path does inverse transformation and inverse path does nothing operation proj=pipeline step proj=affine s11=2 omit_inv inv direction forward accept 1 2 3 expect 0.5 2 3 direction inverse accept 1 2 3 expect 1 2 3 ------------------------------------------------------------------------------- # Test that +omit_fwd, +omit_inv and +inv work correctly with a non-invertible step. ------------------------------------------------------------------------------- # Test that forward path does nothing and inverse path is not defined. # # The affine transformation is not invertible so this pipeline cannot be executed in # reverse. operation proj=pipeline step proj=affine s11=1 s12=1 s13=1 s22=0 s33=0 omit_fwd direction forward accept 1 2 3 expect 1 2 3 direction inverse accept 1 2 3 expect failure errno no_inverse_op # Test that forward path does nothing and inverse path does forward transformation. # # The affine transformation does not have an inverse, but inv specifies that the # step should be done in inverse order relative to our pipeline direction. When # we execute the pipeline in reverse, we should call the forward transformation of # the step which is defined so the pipeline should be valid in reverse. operation proj=pipeline step proj=affine s11=1 s12=1 s13=1 s22=0 s33=0 omit_fwd inv direction forward accept 1 2 3 expect 1 2 3 direction inverse accept 1 2 3 expect 6 0 0 # Test that the forward path does forward transformation and inverse path does nothing. operation proj=pipeline step proj=affine s11=1 s12=1 s13=1 s22=0 s33=0 omit_inv direction forward accept 1 2 3 expect 6 0 0 direction inverse accept 1 2 3 expect 1 2 3 # Test that the forward path is not defined # # When going through the forward path, inv specifies that we should execute the # step in reverse. Because the affine transformation does not have an inverse, # this means that the forward path does not exist. operation proj=pipeline step proj=affine s11=1 s12=1 s13=1 s22=0 s33=0 omit_inv inv expect failure errno no_inverse_op proj-9.8.1/test/gie/unitconvert.gie000664 001750 001750 00000003336 15166171715 017251 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- Tests for the unitconvert operation ------------------------------------------------------------------------------- operation proj=unitconvert xy_in=m xy_out=dm z_in=cm z_out=mm tolerance 0.1 accept 55.25 23.23 45.5 expect 552.5 232.3 455.0 operation proj=unitconvert +xy_in=m +xy_out=m +z_in=m +z_out=m tolerance 0.1 accept 12.3 45.6 7.89 expect 12.3 45.6 7.89 operation proj=unitconvert xy_in=dm xy_out=dm tolerance 0.1 accept 1 1 1 1 expect 1 1 1 1 operation proj=unitconvert xy_in=2.0 xy_out=4.0 tolerance 0.1 accept 1 1 1 1 expect 0.5 0.5 1 1 operation proj=unitconvert xy_in=deg xy_out=rad tolerance 0.0000001 accept 1 1 1 1 expect 1 1 1 1 # gie does a rad->deg conversion behind the scenes operation proj=unitconvert xy_in=grad xy_out=deg tolerance 0.000000000001 accept 50 50 1 1 expect 45 45 1 1 operation proj=unitconvert xy_in=m xy_out=rad accept 1 1 1 1 expect failure operation proj=unitconvert z_in=rad z_out=m accept 1 1 1 1 expect failure operation proj=unitconvert xy_in=0 expect failure operation proj=unitconvert xy_out=0 expect failure operation proj=unitconvert xy_in=1e400 expect failure operation proj=unitconvert xy_out=1e400 expect failure operation proj=unitconvert z_in=0 expect failure operation proj=unitconvert z_out=0 expect failure operation proj=unitconvert z_in=1e400 expect failure operation proj=unitconvert z_out=1e400 expect failure proj-9.8.1/test/gie/epsg_grid.gie000664 001750 001750 00000001671 15166171715 016634 0ustar00eveneven000000 000000 ### This file must contain only (crs_src, crs_dst) tuples where the best transformation ### DOES involve the use a grid/json/etc. resource file ### Otherwise use epsg_no_grid.gie ################################### #### Generic / worldwide scope #### ################################### # Test EGM2008 grid crs_src EPSG:4979 # WGS 84 geographic 3D crs_dst EPSG:9518 # WGS 84 + EGM2008 tolerance 0.1 mm require_grid us_nga_egm08_25.tif accept 55.0 12.0 10.0 expect 55.0 12.0 -27.8257 ################ #### FRANCE #### ################ crs_src EPSG:9785 # RGF93 v2b + NGF-IGN69 height crs_dst EPSG:9781 # RGF93 v2b geographic 3D tolerance 0.1 mm require_grid fr_ign_RAF20.tif accept 43.6109 3.8761 0.0 expect 43.6109 3.8761 49.6904 proj-9.8.1/test/gie/adams_hemi.gie000664 001750 001750 00000203467 15166171715 016767 0ustar00eveneven000000 000000 ------------------------------------------------------------ # This gie file was automatically generated using libproject #where the adams_hemi code was adapted from ------------------------------------------------------------ ------------------------------------------------------------ operation +proj=adams_hemi +R=6370997 tolerance 1 mm ------------------------------------------------------------ accept -179.0512914938 -90.1445918836 expect failure errno coord_transfm_invalid_coord accept -169.5842217825 -89.1738195765 expect failure errno coord_transfm_outside_projection_domain accept -159.8126151474 -88.9303357409 expect failure errno coord_transfm_outside_projection_domain accept -149.8486678837 -88.7598088570 expect failure errno coord_transfm_outside_projection_domain accept -139.3978823413 -88.5424937255 expect failure errno coord_transfm_outside_projection_domain accept -129.4584139907 -88.3017941113 expect failure errno coord_transfm_outside_projection_domain accept -119.6382190434 -88.1579367749 expect failure errno coord_transfm_outside_projection_domain accept -109.1571755829 -87.4911657719 expect failure errno coord_transfm_outside_projection_domain accept -99.4449870732 -87.1707570236 expect failure errno coord_transfm_outside_projection_domain accept -89.9433443609 -87.0825895518 expect -2032451.307 -14670658.595 accept -79.1196204797 -86.6146886067 expect -1972952.703 -14316663.749 accept -69.9188804603 -86.0423885378 expect -1919210.763 -13959619.296 accept -59.2140342623 -85.2062438921 expect -1821078.174 -13499207.260 accept -49.3771402997 -84.6064875075 expect -1632786.696 -13151268.681 accept -39.7523563839 -84.2227887345 expect -1375189.719 -12898281.230 accept -29.1378956446 -84.1916000473 expect -1019955.835 -12777203.156 accept -19.8973002908 -83.3255313811 expect -750646.386 -12420097.177 accept -9.0586917767 -82.5740757930 expect -361764.270 -12130784.486 accept 0.6658150243 -81.6306426596 expect 28245.397 -11833545.201 accept 10.3916472934 -81.3826632020 expect 446703.237 -11781755.067 accept 20.1494345130 -81.0829408522 expect 877880.087 -11752832.089 accept 30.9020031853 -80.1029516039 expect 1408756.445 -11595274.577 accept 40.6784961632 -79.5385879087 expect 1890606.237 -11591903.266 accept 50.0047749416 -79.2557693956 expect 2331531.206 -11694280.098 accept 60.6444819104 -78.7437592966 expect 2853286.437 -11817317.500 accept 70.0847779691 -77.7507873749 expect 3388704.151 -11865524.800 accept 80.5231675121 -77.0660588617 expect 3923735.969 -12068158.388 accept 90.0066580543 -76.6161050638 expect failure errno coord_transfm_outside_projection_domain accept 100.5350349572 -76.3746207928 expect failure errno coord_transfm_outside_projection_domain accept 110.7544963160 -76.2761103137 expect failure errno coord_transfm_outside_projection_domain accept 120.0734970456 -75.8866598636 expect failure errno coord_transfm_outside_projection_domain accept 130.5824490023 -75.8761529489 expect failure errno coord_transfm_outside_projection_domain accept 140.8041765005 -75.3058724059 expect failure errno coord_transfm_outside_projection_domain accept 150.3183273801 -74.4580538960 expect failure errno coord_transfm_outside_projection_domain accept 160.0892519416 -73.7178034782 expect failure errno coord_transfm_outside_projection_domain accept 170.3366715442 -72.7342346131 expect failure errno coord_transfm_outside_projection_domain accept 180.6302993811 -72.6551561090 expect failure errno coord_transfm_outside_projection_domain accept -179.1498353863 -79.8617775679 expect failure errno coord_transfm_outside_projection_domain accept -169.6126454375 -78.9973036997 expect failure errno coord_transfm_outside_projection_domain accept -159.5376706591 -78.3099466224 expect failure errno coord_transfm_outside_projection_domain accept -149.7206409942 -77.9762123582 expect failure errno coord_transfm_outside_projection_domain accept -139.5308439004 -77.1568269429 expect failure errno coord_transfm_outside_projection_domain accept -129.5655598613 -76.3111768828 expect failure errno coord_transfm_outside_projection_domain accept -119.7726048543 -75.3290637518 expect failure errno coord_transfm_outside_projection_domain accept -109.6962230001 -74.3833782562 expect failure errno coord_transfm_outside_projection_domain accept -99.6722260401 -73.9288741111 expect failure errno coord_transfm_outside_projection_domain accept -89.0043085338 -73.3335214021 expect -4844180.674 -11775270.656 accept -79.1663300454 -72.4399400853 expect -4517737.562 -11230872.680 accept -69.4926078623 -71.8461023317 expect -4102914.567 -10768468.225 accept -59.2657055315 -71.2092093538 expect -3613458.773 -10316718.559 accept -49.8213060755 -70.4770080858 expect -3129758.562 -9913931.998 accept -39.4219744153 -70.0556232602 expect -2525390.621 -9588638.786 accept -29.2292837281 -69.7843638102 expect -1896816.371 -9350212.714 accept -19.3122799108 -68.8398158747 expect -1286419.314 -9045554.926 accept -9.6128015323 -68.4874678083 expect -646826.577 -8904263.293 accept 0.2782436804 -67.7652217315 expect 19032.119 -8747432.621 accept 10.0150948909 -66.8884770151 expect 697224.743 -8618447.314 accept 20.3145279457 -66.3089232102 expect 1427884.878 -8604766.655 accept 30.1158431277 -65.8422273022 expect 2129234.668 -8665589.397 accept 40.3813237731 -65.2868591410 expect 2871280.128 -8782328.275 accept 50.3256474182 -64.5263189423 expect 3606565.288 -8925940.412 accept 60.0397335630 -64.2204936386 expect 4289216.964 -9202136.643 accept 70.8347119838 -64.2006837236 expect 4994689.905 -9631301.887 accept 80.1994904845 -64.1621304539 expect 5574892.739 -10059995.184 accept 90.4107676644 -63.6583206132 expect failure errno coord_transfm_outside_projection_domain accept 100.7435829266 -62.9292855324 expect failure errno coord_transfm_outside_projection_domain accept 110.6722999581 -62.8950940661 expect failure errno coord_transfm_outside_projection_domain accept 120.8104324458 -62.4946649855 expect failure errno coord_transfm_outside_projection_domain accept 130.0315355771 -61.8657176607 expect failure errno coord_transfm_outside_projection_domain accept 140.7842333846 -61.7278379595 expect failure errno coord_transfm_outside_projection_domain accept 150.5202047210 -61.0467237730 expect failure errno coord_transfm_outside_projection_domain accept 160.7596965102 -60.2885251988 expect failure errno coord_transfm_outside_projection_domain accept 170.1658638654 -60.0997063537 expect failure errno coord_transfm_outside_projection_domain accept 180.2502374139 -59.7318603713 expect failure errno coord_transfm_outside_projection_domain accept -179.9256771826 -69.2161805164 expect failure errno coord_transfm_outside_projection_domain accept -169.2003519382 -68.5724302106 expect failure errno coord_transfm_outside_projection_domain accept -159.5787597594 -67.9857475830 expect failure errno coord_transfm_outside_projection_domain accept -149.8429607974 -67.2794853256 expect failure errno coord_transfm_outside_projection_domain accept -139.5406690392 -67.1131557084 expect failure errno coord_transfm_outside_projection_domain accept -129.7848559822 -66.2388824806 expect failure errno coord_transfm_outside_projection_domain accept -119.1305416029 -65.8739932573 expect failure errno coord_transfm_outside_projection_domain accept -109.5840686978 -65.1388713461 expect failure errno coord_transfm_outside_projection_domain accept -99.0639263051 -65.0873010186 expect failure errno coord_transfm_outside_projection_domain accept -89.1823111373 -64.6956687932 expect -6023804.986 -10592902.952 accept -79.1164233302 -64.2716519504 expect -5497921.434 -10022147.140 accept -69.2574458208 -63.9075720894 expect -4922828.801 -9520562.405 accept -59.8783718706 -63.9038815291 expect -4304901.029 -9147659.032 accept -49.7289524443 -63.8500661196 expect -3612059.924 -8800325.048 accept -39.1403094008 -63.8422040109 expect -2862966.181 -8516003.259 accept -29.1777068994 -63.1667304050 expect -2169833.127 -8203288.513 accept -19.2977455865 -63.0553679417 expect -1441725.328 -8043555.407 accept -9.9255569976 -62.9647601040 expect -743731.598 -7948394.774 accept 0.1796078505 -62.5085487744 expect 13569.082 -7844710.542 accept 10.1904676971 -61.9758669545 expect 776353.440 -7788440.095 accept 20.6226352769 -61.7448218808 expect 1574820.972 -7846337.019 accept 30.2475627489 -61.1393337334 expect 2327457.261 -7896980.364 accept 40.5688849715 -60.3721116419 expect 3149169.433 -8001812.738 accept 50.3372781690 -60.3371608659 expect 3889524.441 -8275997.858 accept 60.0304323790 -60.0341705989 expect 4627914.366 -8575277.072 accept 70.8357778632 -59.4751248220 expect 5450054.504 -8962420.257 accept 80.4614530617 -58.9769415123 expect 6157836.688 -9386832.221 accept 90.1145386062 -58.4070158299 expect failure errno coord_transfm_outside_projection_domain accept 100.7207579758 -58.0174867644 expect failure errno coord_transfm_outside_projection_domain accept 110.5210303471 -57.2190055708 expect failure errno coord_transfm_outside_projection_domain accept 120.6967482820 -56.4709263993 expect failure errno coord_transfm_outside_projection_domain accept 130.0815389060 -55.4932687347 expect failure errno coord_transfm_outside_projection_domain accept 140.1058922487 -54.7233777967 expect failure errno coord_transfm_outside_projection_domain accept 150.7972249220 -54.0392541094 expect failure errno coord_transfm_outside_projection_domain accept 160.4001197642 -53.4894321537 expect failure errno coord_transfm_outside_projection_domain accept 170.7522870277 -52.8073510172 expect failure errno coord_transfm_outside_projection_domain accept 180.9552557119 -51.9628267761 expect failure errno coord_transfm_outside_projection_domain accept -179.8457680307 -59.7627027888 expect failure errno coord_transfm_outside_projection_domain accept -169.9866964666 -59.3961476674 expect failure errno coord_transfm_outside_projection_domain accept -159.7470584020 -58.4027994525 expect failure errno coord_transfm_outside_projection_domain accept -149.6934351691 -57.7899236032 expect failure errno coord_transfm_outside_projection_domain accept -139.8251656458 -57.1675573730 expect failure errno coord_transfm_outside_projection_domain accept -129.7548669173 -56.7627741202 expect failure errno coord_transfm_outside_projection_domain accept -119.8194582121 -56.1851853006 expect failure errno coord_transfm_outside_projection_domain accept -109.9910713909 -55.2204505294 expect failure errno coord_transfm_outside_projection_domain accept -99.5100607596 -54.5311736441 expect failure errno coord_transfm_outside_projection_domain accept -89.8644038896 -53.9245170355 expect -7342928.224 -9343992.584 accept -79.9635057664 -53.6948723497 expect -6667476.192 -8689307.261 accept -69.9915897328 -53.5080881233 expect -5920106.317 -8113696.635 accept -59.9878237924 -53.1347486229 expect -5137251.168 -7595504.352 accept -49.6910260363 -52.6201247567 expect -4300887.397 -7131351.272 accept -39.7880824067 -52.5359539692 expect -3452174.116 -6823690.285 accept -29.2778055547 -51.6296886545 expect -2568030.082 -6461876.918 accept -19.4306837146 -51.1069811525 expect -1713350.228 -6239079.500 accept -9.3856113762 -50.6351985324 expect -831338.016 -6085131.749 accept 0.8939490530 -50.4873507691 expect 79288.786 -6038354.630 accept 10.1840000169 -50.4621600892 expect 903758.620 -6065670.749 accept 20.0863846360 -50.3906053109 expect 1785191.886 -6146622.254 accept 30.3281308173 -49.6297449138 expect 2720752.037 -6201183.856 accept 40.5225439155 -49.1883193839 expect 3657308.119 -6373152.042 accept 50.0774176880 -48.8514336226 expect 4539427.520 -6619769.484 accept 60.4611757563 -48.8337125195 expect 5475204.679 -7027995.280 accept 70.0359400468 -48.6327927027 expect 6332662.310 -7473372.039 accept 80.9191136756 -48.5703968561 expect 7251379.374 -8116860.291 accept 90.3784337844 -48.2622816670 expect failure errno coord_transfm_outside_projection_domain accept 100.0644440138 -47.5531771971 expect failure errno coord_transfm_outside_projection_domain accept 110.4395693884 -47.1020565364 expect failure errno coord_transfm_outside_projection_domain accept 120.8787420308 -46.5648775386 expect failure errno coord_transfm_outside_projection_domain accept 130.8192016133 -45.7254461234 expect failure errno coord_transfm_outside_projection_domain accept 140.6056889736 -45.1737240013 expect failure errno coord_transfm_outside_projection_domain accept 150.1175388463 -44.6690953878 expect failure errno coord_transfm_outside_projection_domain accept 160.6179845473 -44.6641530409 expect failure errno coord_transfm_outside_projection_domain accept 170.3909883495 -43.8609446159 expect failure errno coord_transfm_outside_projection_domain accept 180.3536945715 -43.4346766235 expect failure errno coord_transfm_outside_projection_domain accept -179.6728016921 -49.7927741248 expect failure errno coord_transfm_outside_projection_domain accept -169.8899091213 -49.5948095572 expect failure errno coord_transfm_outside_projection_domain accept -159.7193834386 -48.9692766397 expect failure errno coord_transfm_outside_projection_domain accept -149.3161804706 -48.3900540417 expect failure errno coord_transfm_outside_projection_domain accept -139.3059860307 -47.5443099911 expect failure errno coord_transfm_outside_projection_domain accept -129.2607005772 -47.3426945690 expect failure errno coord_transfm_outside_projection_domain accept -119.0464442962 -46.3637609990 expect failure errno coord_transfm_outside_projection_domain accept -109.3006137776 -46.2470536112 expect failure errno coord_transfm_outside_projection_domain accept -99.1086587357 -45.3788313212 expect failure errno coord_transfm_outside_projection_domain accept -89.6418391342 -44.9558227898 expect -8329227.919 -8319528.025 accept -79.5922401709 -44.0666175390 expect -7575608.504 -7475396.243 accept -69.7678517742 -43.8414369161 expect -6693103.012 -6832178.393 accept -59.9773741961 -43.6178971244 expect -5769594.561 -6303851.937 accept -49.0224540140 -42.7382586843 expect -4742402.857 -5752204.163 accept -39.0799820528 -42.4267915714 expect -3774284.368 -5417401.690 accept -29.7065845030 -42.3050919341 expect -2859905.278 -5199352.562 accept -19.4093864480 -41.7132409855 expect -1870549.029 -4971910.558 accept -9.5019946065 -41.0671902488 expect -918157.515 -4807120.737 accept 0.7256633705 -40.8863960679 expect 70159.362 -4759033.602 accept 10.1160300689 -40.8493938089 expect 979223.286 -4782540.672 accept 20.7134315844 -40.8364519203 expect 2011034.581 -4873376.186 accept 30.5509837736 -40.3124354704 expect 2991370.345 -4953502.965 accept 40.6811168119 -39.4330828245 expect 4036041.984 -5061633.604 accept 50.5408477188 -39.0992426534 expect 5062748.637 -5317244.256 accept 60.8602526703 -38.6119802236 expect 6165904.838 -5673423.296 accept 70.8028881731 -37.7864477876 expect 7266962.017 -6097356.033 accept 80.6995146991 -37.6252342058 expect 8295075.544 -6755019.382 accept 90.0888221682 -36.9847575823 expect failure errno coord_transfm_outside_projection_domain accept 100.7780137471 -36.6711774624 expect failure errno coord_transfm_outside_projection_domain accept 110.5677826175 -36.0072187827 expect failure errno coord_transfm_outside_projection_domain accept 120.4571639125 -35.5987514678 expect failure errno coord_transfm_outside_projection_domain accept 130.4994689409 -35.0078880309 expect failure errno coord_transfm_outside_projection_domain accept 140.4973246226 -34.7603698284 expect failure errno coord_transfm_outside_projection_domain accept 150.2185553402 -33.7827115000 expect failure errno coord_transfm_outside_projection_domain accept 160.0049830768 -33.6576606654 expect failure errno coord_transfm_outside_projection_domain accept 170.1113538332 -32.7218296414 expect failure errno coord_transfm_outside_projection_domain accept 180.0413323536 -32.6458132046 expect failure errno coord_transfm_outside_projection_domain accept -179.7614880705 -39.3083971924 expect failure errno coord_transfm_outside_projection_domain accept -169.2195161001 -38.8120777840 expect failure errno coord_transfm_outside_projection_domain accept -159.8162149104 -38.0579151517 expect failure errno coord_transfm_outside_projection_domain accept -149.1975646236 -38.0219346503 expect failure errno coord_transfm_outside_projection_domain accept -139.4875552064 -37.4333640809 expect failure errno coord_transfm_outside_projection_domain accept -129.3174577073 -37.3101077795 expect failure errno coord_transfm_outside_projection_domain accept -119.3659376731 -37.0018039210 expect failure errno coord_transfm_outside_projection_domain accept -109.4951561921 -36.7266176768 expect failure errno coord_transfm_outside_projection_domain accept -99.0146344791 -36.1766584469 expect failure errno coord_transfm_outside_projection_domain accept -89.4535006021 -35.4158065520 expect -9377248.574 -7225961.706 accept -79.0387416065 -34.4648659946 expect -8422693.344 -6224371.305 accept -69.2215350809 -34.3740808096 expect -7350614.857 -5544861.367 accept -59.2745531980 -33.8274972528 expect -6265513.674 -4955207.335 accept -49.7169986740 -32.9249999485 expect -5234261.572 -4467029.348 accept -39.3938152675 -32.5464412753 expect -4109883.252 -4132559.187 accept -29.3299550856 -32.1522584471 expect -3038342.418 -3890348.809 accept -19.7672186473 -31.5562360216 expect -2040849.804 -3696547.750 accept -9.6757428807 -31.5381986939 expect -994652.380 -3622557.798 accept 0.9267949652 -30.8907292201 expect 95467.453 -3522841.136 accept 10.2711979528 -30.1415999097 expect 1063857.348 -3456961.986 accept 20.3037759043 -30.0366136211 expect 2114356.936 -3516056.014 accept 30.7604872284 -29.7526562583 expect 3235403.228 -3612623.593 accept 40.6115143350 -28.8411062530 expect 4342408.974 -3683312.580 accept 50.4426160267 -28.7604876417 expect 5473507.294 -3935831.832 accept 60.0667408454 -28.0150409892 expect 6658598.640 -4193291.049 accept 70.8986641601 -27.4846012044 expect 8045113.983 -4691740.858 accept 80.7902728211 -26.9566814760 expect 9340735.895 -5359874.908 accept 90.7774660604 -26.3781900961 expect failure errno coord_transfm_outside_projection_domain accept 100.9677349413 -25.6947295847 expect failure errno coord_transfm_outside_projection_domain accept 110.4045415377 -24.8858012124 expect failure errno coord_transfm_outside_projection_domain accept 120.9106581244 -24.6760450197 expect failure errno coord_transfm_outside_projection_domain accept 130.1070741983 -24.3713625355 expect failure errno coord_transfm_outside_projection_domain accept 140.5983717849 -23.4696540641 expect failure errno coord_transfm_outside_projection_domain accept 150.9695044815 -23.2859623288 expect failure errno coord_transfm_outside_projection_domain accept 160.2904693231 -22.4177486612 expect failure errno coord_transfm_outside_projection_domain accept 170.9786886104 -22.3889418243 expect failure errno coord_transfm_outside_projection_domain accept 180.4135817841 -21.4141834167 expect failure errno coord_transfm_outside_projection_domain accept -179.1529257481 -29.4728615966 expect failure errno coord_transfm_outside_projection_domain accept -169.2570543721 -28.7440325834 expect failure errno coord_transfm_outside_projection_domain accept -159.4124846191 -27.8743740104 expect failure errno coord_transfm_outside_projection_domain accept -149.9984258738 -27.2290510016 expect failure errno coord_transfm_outside_projection_domain accept -139.4767743694 -27.0546870326 expect failure errno coord_transfm_outside_projection_domain accept -129.5641039139 -26.6837015309 expect failure errno coord_transfm_outside_projection_domain accept -119.3705764793 -25.8611684419 expect failure errno coord_transfm_outside_projection_domain accept -109.5378587803 -25.4624789903 expect failure errno coord_transfm_outside_projection_domain accept -99.3734787689 -24.9304945684 expect failure errno coord_transfm_outside_projection_domain accept -89.3465946697 -24.1700440971 expect -10704395.428 -5847361.236 accept -79.7404033820 -23.7748908886 expect -9513574.581 -4810463.511 accept -69.8876497415 -23.5742092130 expect -8180418.594 -4058430.736 accept -59.3891167783 -23.0862162961 expect -6800548.565 -3472782.360 accept -49.0962705404 -23.0040787983 expect -5498406.531 -3127392.477 accept -39.5604939819 -22.8514526819 expect -4357597.539 -2896084.387 accept -29.6190870010 -22.2716044223 expect -3224919.475 -2674683.087 accept -19.2436583074 -22.1221079553 expect -2074925.707 -2558243.836 accept -9.3541697716 -21.2322237727 expect -1006009.651 -2403546.370 accept 0.3565245677 -20.3671378180 expect 38384.574 -2289133.435 accept 10.4893794068 -19.6098192847 expect 1134672.480 -2219784.886 accept 20.1245536415 -19.4921243712 expect 2191535.285 -2254510.891 accept 30.7062162966 -19.3927174348 expect 3384477.986 -2336722.926 accept 40.8666533485 -18.6123732185 expect 4592279.592 -2378596.017 accept 50.0951841039 -17.9250502345 expect 5759194.930 -2468249.825 accept 60.3777794968 -17.5681367463 expect 7155127.802 -2710926.594 accept 70.7020153713 -16.8114869486 expect 8722490.928 -3052901.356 accept 80.8671865869 -16.2424776439 expect 10422654.067 -3712280.432 accept 90.2461291404 -15.5645924718 expect failure errno coord_transfm_outside_projection_domain accept 100.9711143384 -14.9057684714 expect failure errno coord_transfm_outside_projection_domain accept 110.5061775944 -13.9999376124 expect failure errno coord_transfm_outside_projection_domain accept 120.6646869394 -13.8762395278 expect failure errno coord_transfm_outside_projection_domain accept 130.4786384854 -13.6641917166 expect failure errno coord_transfm_outside_projection_domain accept 140.4992250881 -12.7990197875 expect failure errno coord_transfm_outside_projection_domain accept 150.0366235813 -12.4696803942 expect failure errno coord_transfm_outside_projection_domain accept 160.5278707110 -12.2720940753 expect failure errno coord_transfm_outside_projection_domain accept 170.3484529579 -12.2138862792 expect failure errno coord_transfm_outside_projection_domain accept 180.2280212650 -11.3213090122 expect failure errno coord_transfm_outside_projection_domain accept -179.9887698836 -19.8434403334 expect failure errno coord_transfm_outside_projection_domain accept -169.6164294546 -19.0221825561 expect failure errno coord_transfm_outside_projection_domain accept -159.5308654294 -18.1879041212 expect failure errno coord_transfm_outside_projection_domain accept -149.8050966059 -17.4019446265 expect failure errno coord_transfm_outside_projection_domain accept -139.1612469827 -16.6625595025 expect failure errno coord_transfm_outside_projection_domain accept -129.4619391187 -16.4594418864 expect failure errno coord_transfm_outside_projection_domain accept -119.8383311004 -16.1675065524 expect failure errno coord_transfm_outside_projection_domain accept -109.3335459376 -15.6868347454 expect failure errno coord_transfm_outside_projection_domain accept -99.7976668059 -15.6854972046 expect failure errno coord_transfm_outside_projection_domain accept -89.7267294244 -14.8632370454 expect -12053401.390 -4568307.176 accept -79.1896256706 -14.3035788003 expect -10318651.920 -3211294.118 accept -69.8238721868 -13.6606252292 expect -8752793.203 -2483025.217 accept -59.9341884732 -13.0163995934 expect -7239853.279 -2016848.066 accept -49.2718060884 -12.0872143872 expect -5769460.920 -1657984.518 accept -39.0612457278 -11.2146109871 expect -4471191.256 -1415302.828 accept -29.7861799219 -10.9508322090 expect -3354764.734 -1309138.609 accept -19.4145569922 -10.5133139512 expect -2160417.521 -1206521.486 accept -9.5198292959 -9.7964336368 expect -1053132.786 -1099465.773 accept 0.8412200908 -8.9185740153 expect 92973.787 -993763.563 accept 10.4291884083 -8.3013059591 expect 1156683.321 -932334.794 accept 20.1434736553 -7.4540137611 expect 2253230.418 -856389.581 accept 30.9337103769 -7.4330940402 expect 3510477.410 -893006.725 accept 40.1244867612 -6.5706097227 expect 4641586.907 -835473.434 accept 50.1188628580 -6.0246021551 expect 5960644.364 -835727.421 accept 60.8442179646 -5.0882786722 expect 7543977.493 -808597.717 accept 70.9106152137 -5.0040354490 expect 9276405.717 -966062.038 accept 80.6138637845 -4.9732504580 expect 11379290.973 -1327935.989 accept 90.6032354861 -4.1370495786 expect failure errno coord_transfm_outside_projection_domain accept 100.1644532030 -3.7428216159 expect failure errno coord_transfm_outside_projection_domain accept 110.6488410247 -3.6483998900 expect failure errno coord_transfm_outside_projection_domain accept 120.4200133401 -3.5679026480 expect failure errno coord_transfm_outside_projection_domain accept 130.4611248753 -2.8417810196 expect failure errno coord_transfm_outside_projection_domain accept 140.5117693002 -2.1994039429 expect failure errno coord_transfm_outside_projection_domain accept 150.8812291858 -1.4289441365 expect failure errno coord_transfm_outside_projection_domain accept 160.7607399916 -1.4071810906 expect failure errno coord_transfm_outside_projection_domain accept 170.8773135555 -1.0552787291 expect failure errno coord_transfm_outside_projection_domain accept 180.0261501820 -0.4477561568 expect failure errno coord_transfm_outside_projection_domain accept -179.6651296552 -9.8752237332 expect failure errno coord_transfm_outside_projection_domain accept -169.8040567331 -9.5090347854 expect failure errno coord_transfm_outside_projection_domain accept -159.2369236977 -8.7812050100 expect failure errno coord_transfm_outside_projection_domain accept -149.4668694945 -8.7411685014 expect failure errno coord_transfm_outside_projection_domain accept -139.4921425087 -8.4838585832 expect failure errno coord_transfm_outside_projection_domain accept -129.0489643890 -8.3165131291 expect failure errno coord_transfm_outside_projection_domain accept -119.2267713387 -8.2419396541 expect failure errno coord_transfm_outside_projection_domain accept -109.8259504273 -7.2718622518 expect failure errno coord_transfm_outside_projection_domain accept -99.9614377002 -7.1037710855 expect failure errno coord_transfm_outside_projection_domain accept -89.8090238071 -6.4232987368 expect -13640751.484 -2974873.944 accept -79.3641569088 -6.0589975081 expect -11010026.860 -1514839.115 accept -69.4104381605 -5.3415079971 expect -8989236.834 -994787.772 accept -59.1035311505 -4.7601449911 expect -7276854.155 -737312.363 accept -49.7514136971 -4.1714583763 expect -5923537.462 -576783.461 accept -39.2161406618 -3.9369832151 expect -4539636.330 -497351.785 accept -29.7503799768 -2.9883217265 expect -3383505.746 -356659.413 accept -19.3042659436 -2.0693367961 expect -2166506.455 -236875.349 accept -9.8550183317 -1.1339666688 expect -1098434.101 -127035.957 accept 0.3698643768 -0.7850645975 expect 41125.238 -87297.441 accept 10.5309879356 -0.2442969095 expect 1174302.709 -27396.310 accept 20.3483686169 0.5355680008 expect 2286903.911 61502.678 accept 30.9157771281 1.5084105801 expect 3524936.009 181088.864 accept 40.8345369145 1.9690445225 expect 4750388.872 251710.802 accept 50.0083884196 2.0400972654 expect 5968514.060 282935.766 accept 60.5564326069 2.5146830998 expect 7522513.981 398583.466 accept 70.0242529688 2.6387476779 expect 9150344.687 501095.492 accept 80.6476764259 3.6310845229 expect 11462634.636 984489.812 accept 90.5849650699 4.1063917523 expect failure errno coord_transfm_outside_projection_domain accept 100.6238525596 4.1230600723 expect failure errno coord_transfm_outside_projection_domain accept 110.6594697851 5.0495381196 expect failure errno coord_transfm_outside_projection_domain accept 120.0930076215 5.2661796574 expect failure errno coord_transfm_outside_projection_domain accept 130.5000547754 5.3939702157 expect failure errno coord_transfm_outside_projection_domain accept 140.4751452989 5.8876297478 expect failure errno coord_transfm_outside_projection_domain accept 150.7452375246 6.3021488806 expect failure errno coord_transfm_outside_projection_domain accept 160.1359677924 6.9173640629 expect failure errno coord_transfm_outside_projection_domain accept 170.0302177328 7.6378853558 expect failure errno coord_transfm_outside_projection_domain accept 180.8170157951 8.3472735471 expect failure errno coord_transfm_outside_projection_domain accept -179.1943150364 0.7596010223 expect failure errno coord_transfm_outside_projection_domain accept -169.6391212860 1.0795717122 expect failure errno coord_transfm_outside_projection_domain accept -159.2152700578 1.5596096157 expect failure errno coord_transfm_outside_projection_domain accept -149.3566080854 2.3935395760 expect failure errno coord_transfm_outside_projection_domain accept -139.6411690265 2.8792042895 expect failure errno coord_transfm_outside_projection_domain accept -129.0884027419 3.3736512451 expect failure errno coord_transfm_outside_projection_domain accept -119.6412398776 3.5609941446 expect failure errno coord_transfm_outside_projection_domain accept -109.8410023851 4.3613065976 expect failure errno coord_transfm_outside_projection_domain accept -99.5554211656 5.1657533479 expect failure errno coord_transfm_outside_projection_domain accept -89.3773565828 5.9157121060 expect -13652078.995 2749284.275 accept -79.5182008966 5.9890761386 expect -11050370.051 1507839.328 accept -69.5064826337 6.4698255786 expect -8978882.758 1203806.785 accept -59.5169821631 7.3693412472 expect -7304721.114 1145373.248 accept -49.9241519045 7.8049591034 expect -5916373.828 1079808.719 accept -39.3530693426 7.8141712724 expect -4536367.181 988115.206 accept -29.0573163195 8.4892787230 expect -3282841.554 1010642.286 accept -19.3808304987 8.6780787506 expect -2162785.190 995072.997 accept -9.7889836306 9.1980318083 expect -1084005.841 1032425.841 accept 0.7401919089 9.6999375489 expect 81716.177 1081216.951 accept 10.2922798343 10.2080075121 expect 1138278.757 1147216.412 accept 20.8409423708 11.0731980134 expect 2320143.899 1276786.428 accept 30.8315703906 11.4319423563 expect 3474811.307 1373967.408 accept 40.8302365060 12.2114488494 expect 4680441.778 1560453.893 accept 50.8938950488 12.4827231183 expect 5980086.590 1739463.351 accept 60.1452074812 12.8049940379 expect 7276948.599 1990687.541 accept 70.4026750861 13.0768132535 expect 8877755.135 2411628.800 accept 80.2140199400 13.5624272700 expect 10571534.837 3165721.762 accept 90.4576635954 13.9718076506 expect failure errno coord_transfm_outside_projection_domain accept 100.6449925924 14.8800104945 expect failure errno coord_transfm_outside_projection_domain accept 110.2075367326 15.0521133300 expect failure errno coord_transfm_outside_projection_domain accept 120.4913041985 15.0889224537 expect failure errno coord_transfm_outside_projection_domain accept 130.1088876084 15.6744717063 expect failure errno coord_transfm_outside_projection_domain accept 140.6091054692 15.9789322360 expect failure errno coord_transfm_outside_projection_domain accept 150.8827038395 16.1118935785 expect failure errno coord_transfm_outside_projection_domain accept 160.8584892232 16.9655055987 expect failure errno coord_transfm_outside_projection_domain accept 170.7081605352 17.1424915281 expect failure errno coord_transfm_outside_projection_domain accept 180.4875952646 17.7286920734 expect failure errno coord_transfm_outside_projection_domain accept -179.2192819040 10.8569245931 expect failure errno coord_transfm_outside_projection_domain accept -169.0657035958 11.2213297367 expect failure errno coord_transfm_outside_projection_domain accept -159.1659774291 11.5431930234 expect failure errno coord_transfm_outside_projection_domain accept -149.9545173238 12.5029285949 expect failure errno coord_transfm_outside_projection_domain accept -139.3117413904 12.6753804804 expect failure errno coord_transfm_outside_projection_domain accept -129.3194639426 13.3306225335 expect failure errno coord_transfm_outside_projection_domain accept -119.8562785026 13.8682936931 expect failure errno coord_transfm_outside_projection_domain accept -109.1061461002 14.0130001331 expect failure errno coord_transfm_outside_projection_domain accept -99.6079217864 14.7921669533 expect failure errno coord_transfm_outside_projection_domain accept -89.3223757109 14.9467935082 expect -11977848.908 4521084.682 accept -79.5614292778 15.5269071903 expect -10271311.258 3464674.481 accept -69.5919903841 16.3857419518 expect -8573212.789 2923178.600 accept -59.6477980679 16.9839343080 expect -7073184.422 2599846.459 accept -49.1834673639 17.2588986357 expect -5657105.754 2357772.162 accept -39.8262428613 17.9830247197 expect -4476942.024 2281920.882 accept -29.2381466616 18.7499060636 expect -3223466.080 2243030.787 accept -19.1786678435 19.0226677902 expect -2090004.567 2193284.527 accept -9.6334690404 19.4761386210 expect -1042142.311 2201616.494 accept 0.6321060114 20.3870396192 expect 68050.548 2291463.687 accept 10.9421661895 20.4924024395 expect 1180518.031 2323367.738 accept 20.4864473342 21.0164763452 expect 2220079.189 2436668.067 accept 30.1291797254 21.2284428591 expect 3296035.229 2553633.126 accept 40.6200421317 21.5203653800 expect 4508950.471 2745825.410 accept 50.6582942940 21.9446471796 expect 5723200.728 3026932.927 accept 60.9730121398 22.4243870208 expect 7038314.007 3442040.060 accept 70.2091499688 22.5967062068 expect 8288776.818 3931269.871 accept 80.7948402736 23.3693033859 expect 9696270.644 4844753.993 accept 90.9423588456 23.9305098127 expect failure errno coord_transfm_outside_projection_domain accept 100.5427479545 24.6568371578 expect failure errno coord_transfm_outside_projection_domain accept 110.6483359564 25.2988879331 expect failure errno coord_transfm_outside_projection_domain accept 120.6254581560 25.7897168715 expect failure errno coord_transfm_outside_projection_domain accept 130.6198305384 26.5579713234 expect failure errno coord_transfm_outside_projection_domain accept 140.5223099495 27.4665102983 expect failure errno coord_transfm_outside_projection_domain accept 150.1770507578 27.9150551786 expect failure errno coord_transfm_outside_projection_domain accept 160.8757513817 28.5236724280 expect failure errno coord_transfm_outside_projection_domain accept 170.8877826460 29.0924417375 expect failure errno coord_transfm_outside_projection_domain accept 180.6425321330 29.5764205068 expect failure errno coord_transfm_outside_projection_domain accept -179.9444701529 20.8756448666 expect failure errno coord_transfm_outside_projection_domain accept -169.6232517758 21.6942653703 expect failure errno coord_transfm_outside_projection_domain accept -159.5830169036 22.4130383475 expect failure errno coord_transfm_outside_projection_domain accept -149.4619550763 22.6339632787 expect failure errno coord_transfm_outside_projection_domain accept -139.7058201004 23.2553831478 expect failure errno coord_transfm_outside_projection_domain accept -129.7766004320 23.8189681109 expect failure errno coord_transfm_outside_projection_domain accept -119.3228272091 24.7032636217 expect failure errno coord_transfm_outside_projection_domain accept -109.5551540716 24.7745254301 expect failure errno coord_transfm_outside_projection_domain accept -99.9135718461 25.5385802763 expect failure errno coord_transfm_outside_projection_domain accept -89.1841069049 26.2083957447 expect -10431143.612 6091127.422 accept -79.7910279462 26.6431680702 expect -9245823.651 5229352.217 accept -69.8818215995 27.1405016091 expect -7939848.940 4579107.633 accept -59.7043872074 27.3432782139 expect -6646772.640 4084211.760 accept -49.0682511760 28.2506774891 expect -5330443.447 3825676.830 accept -39.4506559191 29.1409725672 expect -4204573.962 3696283.632 accept -29.9015556332 29.7574405692 expect -3142385.818 3600000.101 accept -19.0693346875 30.4852506297 expect -1979564.068 3559098.611 accept -9.6233626432 30.8903459176 expect -992683.054 3544093.220 accept 0.8232394368 31.3744079922 expect 84584.215 3580932.776 accept 10.4236001839 31.4267077934 expect 1072418.207 3612634.442 accept 20.9980760459 31.8605558626 expect 2165806.548 3746351.172 accept 30.8255462270 32.3520551790 expect 3193447.531 3939445.335 accept 40.8324168382 32.9127578763 expect 4256012.184 4213848.131 accept 50.0837143989 33.6950660037 expect 5244572.185 4581526.452 accept 60.4197804189 34.0559290352 expect 6381932.867 5038103.593 accept 70.4867056300 34.8824703453 expect 7453835.177 5690136.852 accept 80.9991967787 34.8943272959 expect 8589604.803 6432883.803 accept 90.5925677009 35.3510670805 expect failure errno coord_transfm_outside_projection_domain accept 100.0148954666 36.2711932097 expect failure errno coord_transfm_outside_projection_domain accept 110.4283207815 36.6017613088 expect failure errno coord_transfm_outside_projection_domain accept 120.6777960225 37.3666811945 expect failure errno coord_transfm_outside_projection_domain accept 130.2891249681 37.8530786142 expect failure errno coord_transfm_outside_projection_domain accept 140.8212942434 38.5395736955 expect failure errno coord_transfm_outside_projection_domain accept 150.7430926180 38.7113483726 expect failure errno coord_transfm_outside_projection_domain accept 160.9509577553 39.6821505727 expect failure errno coord_transfm_outside_projection_domain accept 170.3061167023 40.4192356875 expect failure errno coord_transfm_outside_projection_domain accept 180.9886935244 41.3233715669 expect failure errno coord_transfm_outside_projection_domain accept -179.3343647471 30.6447124879 expect failure errno coord_transfm_outside_projection_domain accept -169.9318029695 30.8088173910 expect failure errno coord_transfm_outside_projection_domain accept -159.9351347433 31.4030041187 expect failure errno coord_transfm_outside_projection_domain accept -149.1603401895 31.7370508292 expect failure errno coord_transfm_outside_projection_domain accept -139.8411358400 31.9883214220 expect failure errno coord_transfm_outside_projection_domain accept -129.6487283950 32.2540792459 expect failure errno coord_transfm_outside_projection_domain accept -119.3377596938 32.5474903700 expect failure errno coord_transfm_outside_projection_domain accept -109.4052121056 32.6499740509 expect failure errno coord_transfm_outside_projection_domain accept -99.9645719240 33.2634048792 expect failure errno coord_transfm_outside_projection_domain accept -89.3430796801 33.3255757200 expect -9605176.890 6972552.471 accept -79.6121040164 33.9110503188 expect -8536182.225 6196716.591 accept -69.4414539606 34.6980659140 expect -7351641.813 5601824.874 accept -59.4871790876 35.1364758272 expect -6218131.412 5141950.349 accept -49.8946468200 36.1100227223 expect -5124886.495 4896415.322 accept -39.8692181364 36.8503178238 expect -4035583.381 4702140.850 accept -29.1761644239 37.2204561349 expect -2922531.225 4529762.914 accept -19.7409728105 37.3611622287 expect -1965561.511 4418245.538 accept -9.2203420334 38.0828885726 expect -910444.166 4426682.810 accept 0.6679908400 38.2706333082 expect 65814.006 4427674.481 accept 10.6257647236 39.2684671305 expect 1040789.082 4583898.094 accept 20.2366080925 40.1629979305 expect 1974657.219 4780940.306 accept 30.5069203139 40.3074104493 expect 2987108.844 4952040.049 accept 40.7821801102 41.0561850499 expect 3990538.047 5279018.731 accept 50.9837826453 42.0495032526 expect 4969682.427 5728168.352 accept 60.0140580662 42.5571623885 expect 5839203.037 6163270.290 accept 70.7254208788 42.7772722042 expect 6868275.710 6748247.633 accept 80.6903986951 43.0741360152 expect 7768806.954 7429525.503 accept 90.6634321726 43.5837411887 expect failure errno coord_transfm_outside_projection_domain accept 100.8304968356 44.3478015738 expect failure errno coord_transfm_outside_projection_domain accept 110.2734219112 44.4218804789 expect failure errno coord_transfm_outside_projection_domain accept 120.7981801963 44.9674252843 expect failure errno coord_transfm_outside_projection_domain accept 130.6206535666 45.4286113875 expect failure errno coord_transfm_outside_projection_domain accept 140.3730391283 46.0714239353 expect failure errno coord_transfm_outside_projection_domain accept 150.9426457881 46.5091534230 expect failure errno coord_transfm_outside_projection_domain accept 160.6115790352 47.4101077572 expect failure errno coord_transfm_outside_projection_domain accept 170.3277640796 48.0638373590 expect failure errno coord_transfm_outside_projection_domain accept 180.8477425796 48.8442357472 expect failure errno coord_transfm_outside_projection_domain accept -179.2598149320 40.1705017627 expect failure errno coord_transfm_outside_projection_domain accept -169.6056153255 40.1727881259 expect failure errno coord_transfm_outside_projection_domain accept -159.3396275493 41.0355246248 expect failure errno coord_transfm_outside_projection_domain accept -149.3430287138 41.0573932198 expect failure errno coord_transfm_outside_projection_domain accept -139.3972998819 41.0866734660 expect failure errno coord_transfm_outside_projection_domain accept -129.2347342883 41.3220478306 expect failure errno coord_transfm_outside_projection_domain accept -119.9384666450 41.9087201484 expect failure errno coord_transfm_outside_projection_domain accept -109.1765297705 42.5137977627 expect failure errno coord_transfm_outside_projection_domain accept -99.7425660165 43.1240737964 expect failure errno coord_transfm_outside_projection_domain accept -89.3631524858 43.6013437171 expect -8456598.089 8145902.918 accept -79.1184854139 44.4465168591 expect -7497596.784 7490230.178 accept -69.3713987852 45.2639704879 expect -6544218.717 6996093.996 accept -59.4171409115 45.4132014431 expect -5602984.158 6519916.313 accept -49.3100677093 45.7526223682 expect -4626331.424 6169411.113 accept -39.3187102974 46.6936897182 expect -3644320.114 5998514.674 accept -29.9742251826 46.9036255354 expect -2766108.322 5819685.981 accept -19.2850914312 47.8240485822 expect -1759883.555 5782937.269 accept -9.4670802435 48.1827130036 expect -859898.761 5747174.232 accept 0.9511694165 49.1770930435 expect 85514.729 5856972.276 accept 10.9297395133 49.6986510413 expect 977791.001 5964314.135 accept 20.6644197363 50.6277034455 expect 1831934.709 6186995.533 accept 30.1619319357 50.8191455831 expect 2670361.605 6364198.402 accept 40.7545399266 51.8106566282 expect 3567452.494 6746371.295 accept 50.2966459346 52.3403869111 expect 4368473.680 7112624.685 accept 60.8853952926 52.6411504842 expect 5247040.469 7565966.065 accept 70.9660756955 53.5899082901 expect 5989886.277 8174678.498 accept 80.1015409967 53.6234824949 expect 6684866.077 8688618.853 accept 90.9390534980 54.4032676618 expect failure errno coord_transfm_outside_projection_domain accept 100.2275975816 54.6540829630 expect failure errno coord_transfm_outside_projection_domain accept 110.2799875321 55.5050863665 expect failure errno coord_transfm_outside_projection_domain accept 120.5746083149 55.8365526658 expect failure errno coord_transfm_outside_projection_domain accept 130.5493283922 55.8603600954 expect failure errno coord_transfm_outside_projection_domain accept 140.1036826064 56.0703761781 expect failure errno coord_transfm_outside_projection_domain accept 150.2683487285 56.7530037053 expect failure errno coord_transfm_outside_projection_domain accept 160.2871251380 57.2145257183 expect failure errno coord_transfm_outside_projection_domain accept 170.1375706366 57.2304506879 expect failure errno coord_transfm_outside_projection_domain accept 180.2551410057 57.5449642224 expect failure errno coord_transfm_outside_projection_domain accept -179.4163113130 50.3425538005 expect failure errno coord_transfm_outside_projection_domain accept -169.9733110986 50.4458299488 expect failure errno coord_transfm_outside_projection_domain accept -159.7576358296 50.9581504113 expect failure errno coord_transfm_outside_projection_domain accept -149.3417213155 51.6042289093 expect failure errno coord_transfm_outside_projection_domain accept -139.0195163151 51.8452436385 expect failure errno coord_transfm_outside_projection_domain accept -129.9037457234 52.0925641350 expect failure errno coord_transfm_outside_projection_domain accept -119.8773360504 52.9710431510 expect failure errno coord_transfm_outside_projection_domain accept -109.3390467545 53.2106639106 expect failure errno coord_transfm_outside_projection_domain accept -99.5444768748 53.6966176349 expect failure errno coord_transfm_outside_projection_domain accept -89.1508332331 54.2933793206 expect -7253089.374 9338825.070 accept -79.5075548350 54.5417649044 expect -6548869.079 8768667.084 accept -69.4795328221 54.7159059038 expect -5776149.289 8249326.048 accept -59.0478968386 55.5204260857 expect -4890946.145 7889320.902 accept -49.1881407070 56.1072337490 expect -4060119.862 7613040.252 accept -39.4101841829 56.8828347097 expect -3227458.953 7444966.031 accept -29.4613810419 57.5239937456 expect -2395806.485 7324970.138 accept -19.1085034175 58.4436055844 expect -1535705.803 7310415.489 accept -9.9177724571 59.0951662260 expect -790135.180 7330448.667 accept 0.8275131479 59.4326708329 expect 65618.406 7353747.937 accept 10.2968451911 60.3586661957 expect 804903.589 7530433.615 accept 20.4902700839 60.9790884371 expect 1584299.446 7722395.231 accept 30.8639040743 61.2280864786 expect 2370997.263 7922432.098 accept 40.0936831128 61.5458193623 expect 3052952.372 8172497.353 accept 50.1049528540 62.3327697572 expect 3741270.462 8574038.080 accept 60.0465199812 62.7356240065 expect 4412884.709 8976470.296 accept 70.2410137014 63.2120813131 expect 5054093.961 9462122.654 accept 80.2667195466 63.4462194528 expect 5659364.075 9965995.331 accept 90.1958404963 64.0831209279 expect failure errno coord_transfm_outside_projection_domain accept 100.5787966151 64.7750993019 expect failure errno coord_transfm_outside_projection_domain accept 110.5253826702 65.0311314831 expect failure errno coord_transfm_outside_projection_domain accept 120.1812962078 65.3475412110 expect failure errno coord_transfm_outside_projection_domain accept 130.6287227669 65.6970336106 expect failure errno coord_transfm_outside_projection_domain accept 140.0181654960 66.0623612621 expect failure errno coord_transfm_outside_projection_domain accept 150.7856466511 66.3591210923 expect failure errno coord_transfm_outside_projection_domain accept 160.9724593003 66.6162174492 expect failure errno coord_transfm_outside_projection_domain accept 170.0946608724 66.6386212957 expect failure errno coord_transfm_outside_projection_domain accept 180.4742461481 67.2758505348 expect failure errno coord_transfm_outside_projection_domain accept -179.3409209668 60.7971917835 expect failure errno coord_transfm_outside_projection_domain accept -169.8189576120 61.1637771517 expect failure errno coord_transfm_outside_projection_domain accept -159.5389911401 61.2887975834 expect failure errno coord_transfm_outside_projection_domain accept -149.8226015684 61.6437737052 expect failure errno coord_transfm_outside_projection_domain accept -139.2361655032 62.2457658007 expect failure errno coord_transfm_outside_projection_domain accept -129.4677006422 63.0421916813 expect failure errno coord_transfm_outside_projection_domain accept -119.2765495979 63.0575080285 expect failure errno coord_transfm_outside_projection_domain accept -109.6674914716 63.2917945558 expect failure errno coord_transfm_outside_projection_domain accept -99.7670971881 63.3834861911 expect failure errno coord_transfm_outside_projection_domain accept -89.8162159435 64.1409584458 expect -6128005.443 10556995.120 accept -79.1187835657 64.5588989031 expect -5465931.391 10061962.599 accept -69.5357774042 64.7792984398 expect -4855109.193 9660585.643 accept -59.4761740671 64.8974706901 expect -4194775.341 9286417.697 accept -49.9280128167 65.6204314486 expect -3502178.047 9090538.544 accept -39.3823903629 65.9211509100 expect -2766684.454 8864998.247 accept -29.7679870260 66.9026625522 expect -2059983.349 8842303.129 accept -19.9897475317 67.3766053532 expect -1374622.796 8788777.482 accept -9.9442064094 68.2526920687 expect -672566.109 8863134.950 accept 0.7509772840 69.1829974388 expect 49780.475 9007858.113 accept 10.6585809296 69.8835157088 expect 694405.780 9169808.493 accept 20.3911658798 70.0037343002 expect 1321293.723 9273335.219 accept 30.6722125463 70.6594377768 expect 1946504.130 9536648.767 accept 40.6299540574 71.5624775878 expect 2501652.005 9893454.187 accept 50.8721494614 71.8337784376 expect 3079786.371 10187475.653 accept 60.0291284696 71.8501773949 expect 3593103.464 10453205.799 accept 70.8431571594 72.0862933562 expect 4145342.784 10857384.689 accept 80.1791569395 72.7183017021 expect 4529430.842 11315638.668 accept 90.8791736411 72.9059905421 expect failure errno coord_transfm_outside_projection_domain accept 100.0478827489 73.6259975169 expect failure errno coord_transfm_outside_projection_domain accept 110.4087249466 74.2803681973 expect failure errno coord_transfm_outside_projection_domain accept 120.3401612468 74.8365621394 expect failure errno coord_transfm_outside_projection_domain accept 130.1826062634 75.5694271641 expect failure errno coord_transfm_outside_projection_domain accept 140.9483568214 75.9187393924 expect failure errno coord_transfm_outside_projection_domain accept 150.4892277622 76.6692051191 expect failure errno coord_transfm_outside_projection_domain accept 160.6382868956 77.4573927636 expect failure errno coord_transfm_outside_projection_domain accept 170.7717121538 77.8061856572 expect failure errno coord_transfm_outside_projection_domain accept 180.3573673565 78.0660690876 expect failure errno coord_transfm_outside_projection_domain accept -179.6675977383 70.0957203052 expect failure errno coord_transfm_outside_projection_domain accept -169.7405185971 70.8584256587 expect failure errno coord_transfm_outside_projection_domain accept -159.5167125707 71.8322204655 expect failure errno coord_transfm_outside_projection_domain accept -149.0042143904 72.1158106531 expect failure errno coord_transfm_outside_projection_domain accept -139.0329602286 72.2542294354 expect failure errno coord_transfm_outside_projection_domain accept -129.7801336775 73.2077231869 expect failure errno coord_transfm_outside_projection_domain accept -119.3119539671 73.8550759705 expect failure errno coord_transfm_outside_projection_domain accept -109.4758176356 74.3266149256 expect failure errno coord_transfm_outside_projection_domain accept -99.6607202273 75.0169362990 expect failure errno coord_transfm_outside_projection_domain accept -89.4753530769 75.9617981968 expect -4457168.999 12206697.987 accept -79.5887435008 76.2786099898 expect -4003489.308 11893940.766 accept -69.5002491752 76.5782690095 expect -3522337.539 11617780.097 accept -59.6915787173 77.1315486475 expect -3007598.216 11449732.407 accept -49.4552848899 77.1769236310 expect -2520444.564 11214194.197 accept -39.2403833701 77.8832488521 expect -1964697.049 11174495.756 accept -29.4506836219 78.3637643336 expect -1456404.136 11143594.629 accept -19.5010832381 78.4638811594 expect -965580.048 11064519.470 accept -9.5727750302 79.0649065844 expect -463128.369 11154178.989 accept 0.3501026786 79.9268552980 expect 16280.988 11359716.588 accept 10.8083498797 80.7734576072 expect 480582.301 11612067.445 accept 20.8098672542 81.2830486223 expect 896200.874 11813880.882 accept 30.3588816681 81.4543384377 expect 1287049.335 11952125.248 accept 40.8217587675 82.0376694327 expect 1655654.948 12249255.684 accept 50.5425294111 82.6836589307 expect 1943629.387 12583714.312 accept 60.1931997991 83.6237398675 expect 2131995.937 13024459.333 accept 70.6113860432 84.0910108055 expect 2365890.947 13363173.126 accept 80.9787521274 84.9230149078 expect 2463890.570 13818835.453 accept 90.9143604315 85.3871192550 expect failure errno coord_transfm_outside_projection_domain accept 100.4734880731 86.0042464514 expect failure errno coord_transfm_outside_projection_domain accept 110.9680822795 86.3859461851 expect failure errno coord_transfm_outside_projection_domain accept 120.8622100732 87.2340544489 expect failure errno coord_transfm_outside_projection_domain accept 130.2558456040 87.3620502284 expect failure errno coord_transfm_outside_projection_domain accept 140.2081210889 87.9608446770 expect failure errno coord_transfm_outside_projection_domain accept 150.4963196965 88.3752399281 expect failure errno coord_transfm_outside_projection_domain accept 160.1141812758 89.1315428504 expect failure errno coord_transfm_outside_projection_domain accept 170.8037412086 89.6150717843 expect failure errno coord_transfm_outside_projection_domain accept 180.1602592915 90.5979873573 expect failure errno coord_transfm_invalid_coord accept -179.4988010038 80.2382292021 expect failure errno coord_transfm_outside_projection_domain accept -169.7323895837 80.2920174872 expect failure errno coord_transfm_outside_projection_domain accept -159.9503807693 80.8196927273 expect failure errno coord_transfm_outside_projection_domain accept -149.0925061081 81.4663152862 expect failure errno coord_transfm_outside_projection_domain accept -139.5716369758 82.4286410234 expect failure errno coord_transfm_outside_projection_domain accept -129.8787197288 82.8456226975 expect failure errno coord_transfm_outside_projection_domain accept -119.4793423714 83.5516191256 expect failure errno coord_transfm_outside_projection_domain accept -109.2664761985 84.4756117750 expect failure errno coord_transfm_outside_projection_domain accept -99.3415573570 85.3222226381 expect failure errno coord_transfm_outside_projection_domain accept -89.5621686999 86.1477384313 expect -2328002.776 14359252.241 accept -79.7459074624 86.3337679988 expect -2066809.293 14230691.128 accept -69.5924016054 86.7873849130 expect -1721998.690 14226866.593 accept -59.3424378796 87.4693622383 expect -1325649.584 14378063.176 accept -49.4008006097 87.4815472674 expect -1116300.148 14277850.382 accept -39.0859175285 87.8616846009 expect -823391.287 14385214.623 accept -29.4922393933 88.2891416248 expect -560419.081 14575750.266 accept -19.1948975670 88.7931017780 expect -308317.578 14881668.127 accept -9.7223365758 89.3832521756 expect -112026.906 15387878.248 accept 0.6641448256 90.0106423220 expect failure errno coord_transfm_invalid_coord accept 10.1860723801 90.3688642972 expect failure errno coord_transfm_invalid_coord accept 20.9490167192 90.7173958262 expect failure errno coord_transfm_invalid_coord accept 30.5649867370 90.9925163187 expect failure errno coord_transfm_invalid_coord accept 40.4458702150 91.4734308311 expect failure errno coord_transfm_invalid_coord accept 50.5856921606 91.9158720484 expect failure errno coord_transfm_invalid_coord accept 60.1363202035 92.2767051552 expect failure errno coord_transfm_invalid_coord accept 70.0710227099 93.2758942081 expect failure errno coord_transfm_invalid_coord accept 80.2222434482 93.6733349750 expect failure errno coord_transfm_invalid_coord accept 90.0483434140 94.4740404396 expect failure errno coord_transfm_invalid_coord accept 100.6823676393 95.0641687155 expect failure errno coord_transfm_invalid_coord accept 110.6403276588 95.6156935259 expect failure errno coord_transfm_invalid_coord accept 120.2581625576 96.0431766104 expect failure errno coord_transfm_invalid_coord accept 130.4609264126 96.4854267472 expect failure errno coord_transfm_invalid_coord accept 140.6441294534 96.7262713213 expect failure errno coord_transfm_invalid_coord accept 150.3252472008 97.1420609214 expect failure errno coord_transfm_invalid_coord accept 160.7668616164 97.4790143988 expect failure errno coord_transfm_invalid_coord accept 170.7465062128 97.6550567817 expect failure errno coord_transfm_invalid_coord accept 180.7542323137 98.2872938097 expect failure errno coord_transfm_invalid_coord accept -179.4933532172 89.0462961156 expect failure errno coord_transfm_outside_projection_domain accept -169.8505428520 89.8903183246 expect failure errno coord_transfm_outside_projection_domain accept -159.6997771019 89.9583403191 expect failure errno coord_transfm_outside_projection_domain accept -149.2613507188 90.8395350848 expect failure errno coord_transfm_invalid_coord accept -139.1842997548 91.1573946186 expect failure errno coord_transfm_invalid_coord accept -129.6568991775 91.9446769249 expect failure errno coord_transfm_invalid_coord accept -119.3115606256 92.6597043587 expect failure errno coord_transfm_invalid_coord accept -109.6274703609 93.6500222571 expect failure errno coord_transfm_invalid_coord accept -99.3955363318 93.6790731121 expect failure errno coord_transfm_invalid_coord accept -89.4902277654 93.7101118679 expect failure errno coord_transfm_invalid_coord accept -79.2055095189 93.8973426839 expect failure errno coord_transfm_invalid_coord accept -69.2694919752 94.5715889948 expect failure errno coord_transfm_invalid_coord accept -59.2096313695 94.8767204910 expect failure errno coord_transfm_invalid_coord accept -49.3346426547 95.7619370286 expect failure errno coord_transfm_invalid_coord accept -39.2940192313 95.9177686800 expect failure errno coord_transfm_invalid_coord accept -29.1265263906 96.4025342806 expect failure errno coord_transfm_invalid_coord accept -19.8149677195 96.5067828223 expect failure errno coord_transfm_invalid_coord accept -9.2806942519 96.8402148749 expect failure errno coord_transfm_invalid_coord accept 0.9337491530 97.5569468760 expect failure errno coord_transfm_invalid_coord accept 10.2800898790 97.9514714385 expect failure errno coord_transfm_invalid_coord accept 20.9290209494 98.9284832763 expect failure errno coord_transfm_invalid_coord accept 30.3939457169 99.5719752144 expect failure errno coord_transfm_invalid_coord accept 40.6958590705 100.0328567981 expect failure errno coord_transfm_invalid_coord accept 50.1473239826 100.6776574030 expect failure errno coord_transfm_invalid_coord accept 60.6429472168 100.7044060498 expect failure errno coord_transfm_invalid_coord accept 70.8017862719 101.2635238404 expect failure errno coord_transfm_invalid_coord accept 80.3144473172 101.9663622692 expect failure errno coord_transfm_invalid_coord accept 90.7459660200 102.3134423218 expect failure errno coord_transfm_invalid_coord accept 100.1161002435 103.1947683448 expect failure errno coord_transfm_invalid_coord accept 110.1928396624 104.0579352787 expect failure errno coord_transfm_invalid_coord accept 120.2928981698 104.8100792607 expect failure errno coord_transfm_invalid_coord accept 130.4257286255 105.4176918707 expect failure errno coord_transfm_invalid_coord accept 140.8110132830 105.4248870814 expect failure errno coord_transfm_invalid_coord accept 150.8802025406 106.2350153626 expect failure errno coord_transfm_invalid_coord accept 160.3540927190 106.6211948814 expect failure errno coord_transfm_invalid_coord accept 170.1006211763 106.7429949781 expect failure errno coord_transfm_invalid_coord accept 180.9080349563 107.0582862622 expect failure errno coord_transfm_invalid_coord proj-9.8.1/test/gie/adams_ws1.gie000664 001750 001750 00000164545 15166171715 016562 0ustar00eveneven000000 000000 ------------------------------------------------------------ # This gie file was automatically generated using libproject # where the adams_ws1 code was adapted from ------------------------------------------------------------ ------------------------------------------------------------ operation +proj=adams_ws1 +R=6370997 tolerance 1 mm ------------------------------------------------------------ accept -179.5170670673 -90.3642618405 expect failure errno coord_transfm_invalid_coord accept -169.6193301609 -90.0089826784 expect failure errno coord_transfm_invalid_coord accept -159.5146913398 -89.9552061084 expect -350717.162 -11748881.092 accept -149.6430387202 -89.4598171312 expect -1198165.030 -11484132.922 accept -139.3420792457 -88.5689205573 expect -1900383.673 -11089869.954 accept -129.5512190326 -88.2843232228 expect -2002093.387 -10839580.396 accept -119.5849519031 -87.8113050000 expect -2149637.897 -10510603.217 accept -109.8164701029 -87.6292839331 expect -2103837.772 -10270387.260 accept -99.0070325468 -87.4860856350 expect -1996207.278 -10030480.328 accept -89.8710202810 -87.0734859278 expect -1978905.833 -9725415.443 accept -79.9177626050 -86.8409810985 expect -1848776.887 -9483178.045 accept -69.6832972131 -86.4913255195 expect -1709649.451 -9206995.664 accept -59.5324497291 -86.3836989068 expect -1493076.994 -9040595.732 accept -49.7885614755 -86.1711430633 expect -1288297.308 -8857404.772 accept -39.2736625770 -85.9311099723 expect -1048159.783 -8676577.940 accept -29.9883661697 -85.1267277663 expect -864602.075 -8328578.049 accept -19.7354612657 -84.2995572008 expect -606385.822 -8009717.908 accept -9.8320712389 -83.8158727667 expect -311874.893 -7833551.758 accept 0.1529149340 -83.6786769068 expect 4891.700 -7783359.345 accept 10.8075764102 -83.0220228165 expect 358525.175 -7610748.071 accept 20.2756666263 -82.4050031966 expect 693297.426 -7478128.281 accept 30.5717416484 -82.1078093242 expect 1059291.615 -7458462.299 accept 40.0217776423 -81.4996688835 expect 1423231.064 -7381432.285 accept 50.4439084395 -80.9178750042 expect 1835831.195 -7348906.778 accept 60.2928202530 -80.8948879488 expect 2195784.908 -7466766.931 accept 70.1422375374 -79.9793295792 expect 2644125.293 -7411053.135 accept 80.0281082734 -79.9307378831 expect 3023172.557 -7577579.052 accept 90.1359566782 -79.0548748136 expect 3514571.202 -7604717.836 accept 100.9983207780 -78.4899667014 expect 4020295.243 -7757494.376 accept 110.7127101938 -77.8772548871 expect 4506704.120 -7915020.711 accept 120.9499015457 -77.6414595729 expect 4975451.712 -8212762.236 accept 130.7914974914 -77.0932634434 expect 5498089.726 -8493219.908 accept 140.6080188527 -77.0119938017 expect 5942639.654 -8934467.649 accept 150.6995918182 -76.9369794197 expect 6388836.279 -9482287.585 accept 160.9763179459 -76.4102449261 expect 6955592.369 -10093789.725 accept 170.5761545704 -76.3749705670 expect 7276212.519 -10898661.639 accept 180.4778073139 -75.5923496826 expect -7735120.306 -11759677.243 accept -179.9594024463 -79.1850172406 expect -6267808.862 -11809346.696 accept -169.3792405914 -78.6847392352 expect -6375466.776 -11010285.716 accept -159.3418105960 -77.9452575038 expect -6399742.566 -10210740.446 accept -149.6506452375 -77.3145139162 expect -6244446.337 -9487525.415 accept -139.5282281225 -76.8064299491 expect -5941042.263 -8840534.760 accept -129.3590935485 -75.8784716103 expect -5652577.184 -8195427.069 accept -119.3829013254 -75.5762740726 expect -5225585.846 -7754976.336 accept -109.0050595456 -74.8490162848 expect -4822333.815 -7284106.174 accept -99.6562286237 -73.8531237046 expect -4472856.920 -6851420.623 accept -89.4153824527 -73.5246290838 expect -4008400.544 -6558443.533 accept -79.6133647306 -73.1453487971 expect -3570120.993 -6303696.366 accept -69.6398208021 -72.3341230538 expect -3146057.888 -6010826.112 accept -59.8706106341 -71.3971340090 expect -2728166.758 -5736410.313 accept -49.3205605438 -70.7335340397 expect -2256529.742 -5524593.729 accept -39.3411926381 -70.6365159073 expect -1795311.675 -5424479.920 accept -29.5083854002 -70.4758592857 expect -1345403.678 -5337336.817 accept -19.0279013677 -70.0084283415 expect -870594.276 -5225973.870 accept -9.2887899729 -69.7394095461 expect -425827.654 -5164538.802 accept 0.8271647968 -69.2722085113 expect 38107.829 -5095338.557 accept 10.4036887646 -68.3592860451 expect 484296.312 -4988000.264 accept 20.2060343192 -68.2238727038 expect 943249.674 -4997881.278 accept 30.5698496420 -67.8806308159 expect 1435688.678 -5002119.817 accept 40.1495377913 -67.4553827915 expect 1899965.927 -5010162.059 accept 50.3602778968 -67.0362523329 expect 2404182.204 -5042085.202 accept 60.9974900881 -66.4077946779 expect 2947890.396 -5072684.473 accept 70.9319955374 -65.8210021247 expect 3471665.520 -5121479.201 accept 80.4863012420 -65.1950301635 expect 3994821.648 -5179465.208 accept 90.6983519178 -64.8794465969 expect 4561938.454 -5308162.118 accept 100.9396101906 -64.2819828297 expect 5168400.393 -5424158.734 accept 110.3740821515 -63.7921797585 expect 5753378.431 -5559987.662 accept 120.9952802105 -63.1814589291 expect 6456367.698 -5728557.242 accept 130.5262638944 -62.9745552631 expect 7113536.253 -5951928.183 accept 140.8208531113 -62.1723630793 expect 7922447.639 -6121121.677 accept 150.0998916715 -61.6016244915 expect 8715442.276 -6298866.943 accept 160.9286198483 -61.2084726717 expect 9734201.834 -6531135.754 accept 170.5710819967 -60.6197664775 expect 10759563.471 -6612968.425 accept 180.0430694902 -60.0407055339 expect -11807509.426 -6558808.816 accept -179.5390947423 -69.0648460645 expect -11698244.368 -9734562.708 accept -169.1901512061 -68.4729959428 expect -9950971.024 -8797882.041 accept -159.9508405867 -68.3071525125 expect -8958082.142 -8161514.970 accept -149.8939906743 -67.9903394603 expect -8122002.603 -7560968.287 accept -139.3084750889 -67.6932300903 expect -7358441.192 -7046235.673 accept -129.4903340896 -66.9738174802 expect -6754279.469 -6566632.394 accept -119.3732549373 -66.1666702364 expect -6167465.568 -6131803.466 accept -109.9122840717 -65.4626199327 expect -5635053.049 -5784623.911 accept -99.0894792417 -64.9328397796 expect -5031244.298 -5474194.879 accept -89.0379071438 -63.9724755049 expect -4506394.832 -5162764.903 accept -79.8512236201 -63.1650388913 expect -4030262.713 -4918826.251 accept -69.0024968791 -62.7959282508 expect -3459473.930 -4731573.132 accept -59.6285901198 -62.7061946763 expect -2970786.932 -4617341.636 accept -49.7082574527 -62.1571694174 expect -2471206.184 -4465370.277 accept -39.4924979763 -61.7459110848 expect -1958931.958 -4346771.831 accept -29.6363032106 -61.6957692779 expect -1464984.333 -4287075.197 accept -19.5739110331 -61.6019886294 expect -965552.532 -4238215.163 accept -9.6576498976 -61.0164571209 expect -477467.663 -4155301.252 accept 0.9700961170 -60.1204628592 expect 48205.814 -4057834.175 accept 10.7639703855 -59.1654390696 expect 538264.315 -3972144.031 accept 20.0541166979 -58.2221709542 expect 1009809.314 -3901769.484 accept 30.8822551157 -57.4323767288 expect 1566666.238 -3866147.789 accept 40.9150221936 -56.7476936019 expect 2091877.250 -3852786.699 accept 50.0130584672 -56.6268047719 expect 2571289.842 -3900890.372 accept 60.1810460294 -56.5806776760 expect 3115889.942 -3978273.297 accept 70.0095293626 -56.4903844848 expect 3655288.464 -4064302.468 accept 80.2965290229 -56.4254295515 expect 4235148.992 -4174494.751 accept 90.0684823849 -55.5094217862 expect 4825173.693 -4205785.231 accept 100.2589575221 -54.7080863095 expect 5463679.888 -4267283.094 accept 110.9472297773 -54.0819702617 expect 6161497.112 -4367866.466 accept 120.8869363890 -53.8315420800 expect 6836546.011 -4509924.510 accept 130.9603256931 -53.6395383232 expect 7559494.803 -4670554.878 accept 140.1257246691 -53.4446807335 expect 8259667.150 -4816997.141 accept 150.2071621174 -52.9722495199 expect 9089603.867 -4934074.676 accept 160.6088986258 -52.1795359483 expect 10009756.247 -4978006.469 accept 170.2183758483 -51.5758826477 expect 10895284.078 -4985209.390 accept 180.3089859974 -51.2476311235 expect -11783292.311 -4970550.366 accept -179.3405213764 -59.1949594465 expect -11740654.113 -6374577.500 accept -169.0421166099 -59.1562784619 expect -10642169.105 -6280796.295 accept -159.3788558458 -58.9714540198 expect -9692814.995 -6062325.471 accept -149.0662979992 -58.4548170890 expect -8795516.955 -5733211.410 accept -139.3706367143 -58.1144715199 expect -8025985.315 -5449082.903 accept -129.1106150079 -57.8422608500 expect -7273101.480 -5175957.712 accept -119.2867946872 -57.5287479364 expect -6602894.857 -4927624.634 accept -109.8617968496 -56.9893254243 expect -6002387.156 -4682302.572 accept -99.4159322812 -56.2424930053 expect -5370022.362 -4421472.280 accept -89.1699101036 -56.1663221081 expect -4755991.161 -4262687.067 accept -79.7513340456 -55.7410486547 expect -4218798.697 -4097773.078 accept -69.3859719372 -55.4898494102 expect -3639082.164 -3958567.253 accept -59.6570132581 -54.5322069534 expect -3118583.817 -3776942.947 accept -49.1598655048 -54.4599218486 expect -2552713.105 -3691062.494 accept -39.1574191031 -54.3046314680 expect -2023877.704 -3616745.668 accept -29.6721934217 -53.6562199308 expect -1531972.573 -3515412.233 accept -19.8662059139 -53.4687850046 expect -1023531.105 -3467209.179 accept -9.5466027093 -53.0271646961 expect -491871.463 -3409466.748 accept 0.5571469345 -53.0212050870 expect 28691.149 -3403138.420 accept 10.2150991679 -52.8267091529 expect 526790.549 -3393151.184 accept 20.9184560187 -52.1945236902 expect 1083680.938 -3360440.100 accept 30.4122790805 -51.8430079370 expect 1582353.162 -3361293.005 accept 40.9466629257 -51.6378144374 expect 2141968.613 -3391259.162 accept 50.9012994718 -51.6157642742 expect 2677989.259 -3447645.551 accept 60.2635223516 -50.8239592102 expect 3201005.434 -3444708.197 accept 70.3461813446 -50.7667566814 expect 3768769.080 -3523960.462 accept 80.9078900718 -50.1766356883 expect 4389398.947 -3573130.194 accept 90.1074711799 -49.9117875315 expect 4944447.058 -3649776.255 accept 100.1252105382 -48.9204454031 expect 5584999.550 -3676552.405 accept 110.8672357396 -48.1515257195 expect 6298134.023 -3741552.205 accept 120.3694000933 -47.4449038605 expect 6960325.242 -3800735.139 accept 130.4622216350 -47.1679496656 expect 7689059.250 -3914199.981 accept 140.5086830302 -47.1237216006 expect 8449190.695 -4050734.799 accept 150.1476683134 -46.3595419843 expect 9231198.383 -4086961.656 accept 160.2556473416 -46.2916624279 expect 10076699.984 -4186410.441 accept 170.8522091884 -46.1574282136 expect 10999670.471 -4243488.850 accept 180.2570720688 -46.0614416637 expect -11789409.557 -4252598.873 accept -179.4511464235 -49.1459116777 expect -11761990.229 -4665890.121 accept -169.9257769945 -49.0470611950 expect -10894262.669 -4621088.416 accept -159.9907938476 -48.8074825653 expect -10017224.172 -4504606.959 accept -149.4585690847 -48.4774901965 expect -9135164.524 -4334099.962 accept -139.2453966436 -47.8692552329 expect -8335827.552 -4118376.932 accept -129.9085174848 -46.8854494051 expect -7654250.412 -3875947.564 accept -119.8893057705 -46.5651334714 expect -6944562.132 -3704152.951 accept -109.0958379349 -45.7583483056 expect -6225548.239 -3485487.807 accept -99.2790124089 -45.4028322295 expect -5592633.486 -3337133.510 accept -89.5363705206 -44.7944152576 expect -4990785.214 -3180396.161 accept -79.5060440658 -44.5224968249 expect -4385934.870 -3064005.444 accept -69.4515180819 -43.5521936119 expect -3804302.240 -2904123.456 accept -59.1288687363 -42.8251932286 expect -3217542.420 -2777920.839 accept -49.5480487278 -42.2232882391 expect -2682493.692 -2679394.683 accept -39.1528197403 -42.0524649460 expect -2108296.850 -2621044.912 accept -29.4378045622 -41.1418430583 expect -1582000.485 -2522351.315 accept -19.7178288379 -40.6492312711 expect -1057685.973 -2464591.380 accept -9.4090896450 -40.5893893271 expect -503836.212 -2446387.785 accept 0.9610981916 -39.6877677601 expect 51540.292 -2379140.432 accept 10.9883708721 -39.3404897159 expect 590154.416 -2360351.587 accept 20.8051789783 -39.1539543744 expect 1120024.260 -2361313.374 accept 30.4192741493 -38.5008875049 expect 1644931.536 -2337542.552 accept 40.3869890035 -37.5480251892 expect 2197952.841 -2301604.532 accept 50.8557015620 -37.0223436721 expect 2787383.069 -2305516.111 accept 60.6790393928 -36.1726044193 expect 3354525.829 -2291237.179 accept 70.2878480471 -35.4333317594 expect 3922441.096 -2289959.081 accept 80.5852221343 -34.7195179750 expect 4547835.373 -2300447.284 accept 90.8953318146 -34.0606127868 expect 5194014.701 -2320692.817 accept 100.1645347304 -33.2806968649 expect 5796224.654 -2327994.783 accept 110.9256034679 -32.3021983595 expect 6521787.796 -2332216.561 accept 120.5411285918 -31.9198181206 expect 7190613.992 -2375521.402 accept 130.2284841486 -31.5333210976 expect 7889339.253 -2417443.002 accept 140.4220823477 -30.6970967059 expect 8655106.833 -2418067.478 accept 150.9227263702 -30.4375246674 expect 9466130.022 -2460197.858 accept 160.0295580243 -29.5659253879 expect 10191991.350 -2424567.041 accept 170.6808093605 -29.1021932503 expect 11052824.977 -2413781.540 accept 180.1099977864 -29.0325047616 expect -11803327.121 -2416468.990 accept -179.6225370823 -39.8953975091 expect -11780081.121 -3517460.085 accept -169.6109651378 -39.4882092166 expect -10930063.239 -3453245.362 accept -159.1192163384 -39.1718824306 expect -10057200.020 -3364669.762 accept -149.1119232566 -38.9176058199 expect -9251011.551 -3261533.260 accept -139.5731057891 -38.9121786221 expect -8508507.983 -3173185.997 accept -129.7628486402 -38.0200948217 expect -7786640.589 -2991489.483 accept -119.7677289390 -37.8134438608 expect -7073265.858 -2874240.367 accept -109.1746441433 -36.9333699237 expect -6356287.199 -2698249.042 accept -99.9602516727 -36.5407037317 expect -5751426.148 -2585442.621 accept -89.8203767870 -35.5930072217 expect -5112766.564 -2430581.015 accept -79.3526596151 -35.2401507154 expect -4468536.158 -2331237.714 accept -69.9140939731 -34.4711213307 expect -3906901.211 -2218161.925 accept -59.5564783999 -33.9615774789 expect -3302614.749 -2129304.529 accept -49.4856159772 -33.3921969402 expect -2727641.771 -2047863.180 accept -39.5601849133 -33.2219810003 expect -2168968.599 -2003059.820 accept -29.6206503521 -32.2387656553 expect -1619499.618 -1912794.298 accept -19.8260643144 -31.2411754077 expect -1082312.059 -1831061.273 accept -9.6980379834 -31.1493347964 expect -528492.040 -1814844.632 accept 0.1021998532 -30.2109056883 expect 5573.607 -1752134.034 accept 10.1241618952 -30.0590713215 expect 552610.811 -1745957.315 accept 20.7210235248 -29.1582457860 expect 1134768.700 -1699801.253 accept 30.8569113997 -28.5816818553 expect 1696753.177 -1680040.760 accept 40.8690942845 -28.2388918529 expect 2258557.806 -1680827.372 accept 50.7976512101 -28.0352374493 expect 2824229.726 -1696198.158 accept 60.6880476492 -27.3593892155 expect 3400671.784 -1686010.997 accept 70.3971704737 -26.5193483221 expect 3980673.819 -1668358.964 accept 80.7537743358 -25.6284840207 expect 4616331.727 -1653268.127 accept 90.9008404807 -25.6193405937 expect 5253601.247 -1701776.097 accept 100.6019282805 -25.1073764643 expect 5885059.255 -1715983.750 accept 110.2074536086 -24.7371256820 expect 6529985.921 -1741419.588 accept 120.8735021013 -24.6200753121 expect 7269663.058 -1792634.308 accept 130.3337268181 -24.1982546793 expect 7950714.246 -1812019.053 accept 140.0292803756 -24.1036603231 expect 8668942.211 -1854994.840 accept 150.2932445511 -23.9158664525 expect 9452746.490 -1886388.628 accept 160.0080010509 -23.0912677563 expect 10215210.151 -1850466.772 accept 170.5235832568 -22.1201165276 expect 11053033.448 -1789486.248 accept 180.6560052356 -22.0885409872 expect -11759672.392 -1793318.350 accept -179.4804785664 -29.6306479674 expect -11769827.502 -2472486.935 accept -169.5969137916 -29.4470720507 expect -10963965.497 -2443636.516 accept -159.7905342089 -29.3575038499 expect -10173853.235 -2404475.146 accept -149.0451837035 -29.3191918216 expect -9326864.232 -2349131.811 accept -139.2634124047 -29.2174079662 expect -8578812.250 -2281416.940 accept -129.4780322813 -29.1095444632 expect -7854782.176 -2207222.010 accept -119.0212234055 -28.1534517213 expect -7115133.213 -2058739.607 accept -109.3989071392 -27.9851170141 expect -6453382.370 -1982663.067 accept -99.1605926399 -27.3804651341 expect -5776201.695 -1874182.968 accept -89.1612152313 -27.3765954441 expect -5132743.215 -1817444.101 accept -79.5274133946 -26.6685619062 expect -4535248.349 -1719079.927 accept -69.2474535764 -26.1885685364 expect -3913186.952 -1641486.835 accept -59.9970146255 -26.1431540924 expect -3365047.885 -1603639.182 accept -49.7059448492 -25.7380450818 expect -2769264.371 -1544962.373 accept -39.1015835447 -24.9244904554 expect -2167463.036 -1467333.079 accept -29.0839025659 -24.0290019262 expect -1606736.238 -1393727.229 accept -19.0559621671 -23.5834403073 expect -1050009.512 -1354116.104 accept -9.4796954418 -22.7008051540 expect -521897.404 -1294226.360 accept 0.8467653094 -22.2437237249 expect 46612.070 -1264810.191 accept 10.1002957100 -21.4069947198 expect 556780.318 -1217517.853 accept 20.6390095332 -21.0427895850 expect 1140438.287 -1203323.480 accept 30.9472401536 -20.1902202762 expect 1717070.228 -1164328.832 accept 40.6934322431 -20.0098914684 expect 2268176.645 -1168727.630 accept 50.7817131415 -19.2822125520 expect 2848736.826 -1144124.118 accept 60.5453409920 -18.5217559886 expect 3421842.893 -1119498.292 accept 70.9306917526 -17.5496804837 expect 4046179.805 -1085105.639 accept 80.9437168988 -16.8999064340 expect 4663411.774 -1071645.011 accept 90.2760894813 -16.3256791261 expect 5254544.808 -1062173.444 accept 100.3952892244 -15.3687880606 expect 5915778.505 -1029396.074 accept 110.8716860238 -15.1135676547 expect 6620486.296 -1045435.781 accept 120.2754329194 -14.7635849177 expect 7273908.238 -1050938.104 accept 130.9205644215 -13.8870075430 expect 8038711.991 -1019018.441 accept 140.5279151075 -13.8598832588 expect 8747404.377 -1043857.108 accept 150.1724830315 -13.4001676763 expect 9478091.722 -1031350.442 accept 160.7119668695 -12.8946537206 expect 10293471.165 -1010309.510 accept 170.7445455520 -12.4806498073 expect 11080986.451 -987879.955 accept 180.0860875981 -12.0984902614 expect -11805496.888 -960233.297 accept -179.3916734336 -19.7014586301 expect -11763712.607 -1588768.200 accept -169.6454904074 -19.4996536478 expect -10986824.032 -1564928.001 accept -159.3116700662 -18.9771320814 expect -10172088.280 -1502117.477 accept -149.2431319392 -18.5239809302 expect -9392904.689 -1437107.722 accept -139.9543854131 -18.2509646291 expect -8690226.526 -1383821.794 accept -129.7568071343 -17.3990196444 expect -7942027.033 -1280164.910 accept -119.1614046516 -16.8619769504 expect -7188040.064 -1200364.046 accept -109.2977430033 -16.3891311149 expect -6508726.059 -1130383.184 accept -99.9990872016 -15.4549241428 expect -5889334.034 -1034038.536 accept -89.1925625691 -14.8368103445 expect -5189840.029 -960130.042 accept -79.0808010606 -14.6385640035 expect -4553964.924 -920479.551 accept -69.2443866324 -14.1456687273 expect -3952562.317 -866676.412 accept -59.0066198816 -13.5814519067 expect -3341600.979 -812246.810 accept -49.6855331551 -13.0942516406 expect -2796564.477 -768323.810 accept -39.1133813450 -12.2378694514 expect -2189428.295 -705006.114 accept -29.7742120709 -11.7887487551 expect -1660234.233 -670669.573 accept -19.9711155501 -11.6767395032 expect -1110226.679 -658130.627 accept -9.9497447301 -11.3258048935 expect -552158.854 -634498.402 accept 0.5509033260 -11.1626324486 expect 30555.312 -624081.026 accept 10.5660459562 -10.2780961917 expect 586666.653 -575356.975 accept 20.2035769485 -9.2879705389 expect 1124313.735 -522411.222 accept 30.1804649932 -8.9615586081 expect 1685072.306 -508721.741 accept 40.7493880077 -8.5624561325 expect 2286291.364 -492777.586 accept 50.2789516772 -8.3745080127 expect 2836608.706 -489680.573 accept 60.8385203257 -8.2963597858 expect 3457824.160 -495534.087 accept 70.9443512805 -8.0934149371 expect 4066101.521 -494895.161 accept 80.2007278148 -7.3297477878 expect 4637580.892 -458880.962 accept 90.2330138853 -6.3835404074 expect 5273690.558 -410871.266 accept 100.0849613278 -5.7704772952 expect 5916391.051 -382372.515 accept 110.4993556234 -5.2096342892 expect 6616996.779 -356342.641 accept 120.8300166158 -4.8886947314 expect 7334310.897 -345088.331 accept 130.0108888901 -4.6741162050 expect 7990761.455 -338928.560 accept 140.0244639639 -4.5796883247 expect 8726333.645 -341133.044 accept 150.2062193251 -3.8424983415 expect 9493863.039 -292863.578 accept 160.9138221527 -3.6801453162 expect 10317707.231 -285699.001 accept 170.8709639263 -3.0673481366 expect 11095020.712 -240557.828 accept 180.1668965748 -2.1055585251 expect -11799179.686 -165599.448 accept -179.1485401294 -9.0050262787 expect -11745147.622 -711689.062 accept -169.6824431551 -8.1575200565 expect -11000115.042 -641511.177 accept -159.6715880943 -7.6221099815 expect -10218824.936 -592292.988 accept -149.3051183917 -7.4397903953 expect -9422033.576 -567343.940 accept -139.2134827169 -6.9710994505 expect -8663402.254 -519006.901 accept -129.2596978253 -6.6939068551 expect -7934011.410 -484946.005 accept -119.6918931722 -6.2610035871 expect -7252563.925 -440785.572 accept -109.4245072379 -5.7027048567 expect -6543136.053 -388878.776 accept -99.0230201699 -5.3407381847 expect -5846751.038 -352675.119 accept -89.3070231490 -5.1039965720 expect -5215688.995 -327406.437 accept -79.7971543944 -4.4916537572 expect -4615264.044 -280462.038 accept -69.3324157511 -3.9437385342 expect -3971856.878 -239654.016 accept -59.5648134684 -3.8977574735 expect -3385627.891 -231608.529 accept -49.0877260741 -3.6480235522 expect -2770346.534 -212306.063 accept -39.8261302552 -3.1309666481 expect -2236162.052 -179431.131 accept -29.9044251723 -2.1430339033 expect -1671913.963 -121205.794 accept -19.0798027396 -1.2466271084 expect -1063209.017 -69795.001 accept -9.7521679387 -1.0322328827 expect -542511.865 -57496.172 accept 0.9430496675 -0.6908437587 expect 52430.968 -38410.610 accept 10.9467094331 -0.6297633697 expect 609067.346 -35093.759 accept 20.4348082857 0.1325785640 expect 1139135.679 7429.710 accept 30.7578824509 0.9786091010 expect 1720312.179 55393.172 accept 40.1304133943 1.3184899313 expect 2253938.856 75567.836 accept 50.6339926465 1.7803646722 expect 2860936.487 103859.561 accept 60.7806464601 1.9656672403 expect 3458637.676 117050.207 accept 70.1424618467 2.0349290359 expect 4021938.226 123840.505 accept 80.3515740385 2.0389166997 expect 4651228.515 127409.428 accept 90.5030006919 2.6127341537 expect 5294231.959 168032.745 accept 100.0361988664 2.7788325854 expect 5915751.683 183883.148 accept 110.4290203898 3.4171783192 expect 6613830.450 233508.030 accept 120.0868391150 4.1107421068 expect 7282720.013 289418.148 accept 130.8358443481 4.7269030447 expect 8050540.773 343566.765 accept 140.4771116425 4.7459157253 expect 8759896.875 353947.419 accept 150.6050881294 4.8179867316 expect 9523608.686 367693.374 accept 160.2822850737 4.8901762814 expect 10268142.143 379540.358 accept 170.8847288206 5.6488167761 expect 11095489.666 443644.671 accept 180.4963027772 6.1617137742 expect -11773225.028 485642.177 accept -179.7711795627 0.4973936135 expect -11794312.860 39108.932 accept -169.1314097124 1.1380806183 expect -10958975.952 89092.114 accept -159.9956088875 1.3785514183 expect -10247153.847 106804.510 accept -149.5081205584 1.7169216947 expect -9441654.288 130579.933 accept -139.3350755264 2.6978007803 expect -8676365.930 200450.001 accept -129.9999093970 3.1714349364 expect -7991182.579 229813.581 accept -119.2025789331 3.1937958085 expect -7221314.251 224181.555 accept -109.2754065272 3.9790540663 expect -6534751.689 270991.754 accept -99.2176596815 4.4421245388 expect -5860471.809 293385.606 accept -89.3282286284 4.7715139132 expect -5217353.706 306050.669 accept -79.7812956950 5.1451934064 expect -4613741.333 321348.393 accept -69.9603490759 5.4631533571 expect -4008936.444 332711.261 accept -59.2124036536 5.9473211127 expect -3363430.499 353449.079 accept -49.1946821277 6.3240504109 expect -2775155.416 368554.729 accept -39.5188729974 7.1419837480 expect -2216820.895 409863.534 accept -29.1883557585 7.8744650207 expect -1629648.240 446150.067 accept -19.1594428055 8.7442679237 expect -1066133.170 491231.789 accept -9.5636574635 9.2026796862 expect -531158.530 514475.669 accept 0.0205997186 9.2921681107 expect 1143.393 518613.658 accept 10.6474003585 9.3325749918 expect 591403.379 522011.049 accept 20.0440697721 9.7920834357 expect 1115183.582 550935.839 accept 30.1210940174 10.3126985648 expect 1680869.076 586066.073 accept 40.9703333564 10.5122361845 expect 2297293.386 606203.052 accept 50.1242819935 10.7592956093 expect 2825113.486 630238.088 accept 60.5237375468 11.0581832240 expect 3435579.925 661638.044 accept 70.0186459905 11.8235504151 expect 4004121.295 723779.234 accept 80.3592940251 12.0855798348 expect 4639465.013 760177.897 accept 90.5376970710 12.9622135342 expect 5281027.214 840054.684 accept 100.6214357026 12.9800321873 expect 5937912.442 867143.481 accept 110.6668058012 13.4013916345 expect 6611924.171 924135.479 accept 120.8660619260 13.6620144115 expect 7319051.165 972687.117 accept 130.0979222590 14.6001907539 expect 7976726.161 1070012.208 accept 140.4720834014 14.9027374310 expect 8740255.766 1124185.971 accept 150.7487344905 15.7591910245 expect 9516541.903 1219443.082 accept 160.7309215820 16.6928847546 expect 10288334.979 1317274.424 accept 170.7171183093 17.1256054857 expect 11074727.024 1367681.457 accept 180.0522042065 17.7180717900 expect -11808147.730 1421784.637 accept -179.7714315704 10.4228351993 expect -11794256.988 825223.859 accept -169.5070586751 10.6771709914 expect -10984834.587 842079.978 accept -159.4580308434 11.3886412993 expect -10198009.570 888630.633 accept -149.0300160729 12.0779530345 expect -9393645.594 925474.868 accept -139.9457181785 13.0366592822 expect -8706116.490 979150.471 accept -129.2494581925 13.4021731716 expect -7918892.896 978007.743 accept -119.6924991127 13.6081016459 expect -7236591.544 965299.698 accept -109.9549521935 14.0589477898 expect -6561371.199 968205.406 accept -99.7560216207 14.8951827161 expect -5875106.633 995021.878 accept -89.4929629341 14.9878946198 expect -5208590.397 970965.202 accept -79.7718646359 15.7860515996 expect -4593786.261 996113.294 accept -69.1787032576 16.1396339481 expect -3943904.709 991435.170 accept -59.5415515235 16.9323624559 expect -3366393.956 1018559.978 accept -49.4027735513 17.7467818790 expect -2772187.794 1047604.534 accept -39.4592345235 18.6907045458 expect -2200328.007 1087110.348 accept -29.3138791949 19.4420397181 expect -1626432.141 1117581.487 accept -19.0263697499 20.4143066442 expect -1051476.817 1164568.814 accept -9.3715816928 21.0493545179 expect -516728.380 1196014.238 accept 0.8183051284 21.2475093875 expect 45086.676 1205740.085 accept 10.2436153128 21.9004386412 expect 564436.455 1246884.830 accept 20.9411485051 22.8923059091 expect 1155233.946 1314342.262 accept 30.8909786046 23.0333873010 expect 1709465.640 1335784.571 accept 40.6622924750 23.1458260628 expect 2259937.246 1360334.792 accept 50.2165426895 23.2813833636 expect 2805957.899 1390914.743 accept 60.2763462023 24.1180873989 expect 3388956.040 1473204.648 accept 70.9671286324 24.5104273631 expect 4023912.266 1536627.432 accept 80.1106583316 25.2622593363 expect 4578414.346 1625284.387 accept 90.1352434237 25.7041000035 expect 5204358.499 1703872.872 accept 100.7659799608 26.3884977784 expect 5887972.662 1810511.107 accept 110.3627285832 26.5971550981 expect 6528536.584 1882676.289 accept 120.5747336187 27.3361344725 expect 7230359.893 2003955.277 accept 130.0334936740 27.5388791002 expect 7906998.039 2080891.049 accept 140.6618802190 27.9206554725 expect 8693452.038 2178619.988 accept 150.8057175364 28.8815698894 expect 9466666.514 2319595.955 accept 160.9794787031 29.3286919896 expect 10269064.515 2406494.682 accept 170.8635008015 30.0881534347 expect 11065436.193 2506392.939 accept 180.7801778816 30.7919429173 expect -11748279.400 2582524.107 accept -179.3089746696 20.7349232283 expect -11757005.796 1676812.324 accept -169.3508199207 21.5333791040 expect -10960347.629 1737356.209 accept -159.4055152547 22.3252032326 expect -10170125.304 1783292.652 accept -149.8454884475 22.4734039129 expect -9424453.386 1763417.112 accept -139.3941906099 23.1082873232 expect -8626570.035 1770372.898 accept -129.6731808706 24.0100345748 expect -7903751.181 1793442.280 accept -119.5653357447 24.9154429650 expect -7175704.391 1808079.370 accept -109.4049571753 25.6530253182 expect -6469736.987 1805594.553 accept -99.2573928302 26.1761469511 expect -5790274.499 1786445.122 accept -89.1936009216 26.8478033007 expect -5137966.077 1779983.008 accept -79.9319952951 27.0219083563 expect -4558285.962 1745405.889 accept -69.7891755607 27.9650852011 expect -3937249.698 1763328.521 accept -59.9224508827 28.7378521521 expect -3349925.184 1774568.738 accept -49.2934775668 29.7032670105 expect -2731923.368 1800437.192 accept -39.7585640043 30.6226892016 expect -2188798.477 1831989.503 accept -29.7786441638 31.0043819908 expect -1631282.400 1832903.391 accept -19.9767642691 31.2638249380 expect -1090543.436 1832723.009 accept -9.4508778589 31.3720263871 expect -514837.245 1828891.334 accept 0.9277524134 31.4797046691 expect 50503.011 1832688.099 accept 10.8523072346 31.6410734176 expect 591047.237 1847096.134 accept 20.9701082341 32.0077728355 expect 1143775.602 1882087.570 accept 30.4584904014 32.2524388516 expect 1665803.096 1915504.146 accept 40.6480244298 32.2809624159 expect 2233201.781 1943617.167 accept 50.8580656871 32.6668467052 expect 2809043.442 2003759.375 accept 60.0108771272 33.5702558206 expect 3331147.200 2104097.315 accept 70.5034398056 33.6252927324 expect 3947557.082 2160701.818 accept 80.8803135822 34.2081349569 expect 4569870.780 2264387.101 accept 90.6363798105 35.0454420959 expect 5169067.110 2394326.391 accept 100.7095824201 35.0925204648 expect 5814871.525 2475230.813 accept 110.4928526153 35.3747862433 expect 6462291.225 2579639.068 accept 120.4119184030 36.0949196788 expect 7138879.871 2729237.832 accept 130.3776922680 36.6637391210 expect 7848033.791 2872450.680 accept 140.7776214259 37.0211653163 expect 8622838.330 3001065.131 accept 150.9988070526 37.4312106652 expect 9416320.865 3126629.034 accept 160.1799904005 37.7026348392 expect 10156009.588 3217300.330 accept 170.7924717408 38.0391644104 expect 11035694.135 3300682.151 accept 180.2988991065 38.8552843618 expect -11786931.659 3402926.729 accept -179.1544921899 30.5865766668 expect -11742964.963 2562920.940 accept -169.3849428190 30.6760590330 expect -10943452.496 2558666.832 accept -159.4339156279 30.6775291474 expect -10138949.929 2524905.799 accept -149.5559422097 31.4197140165 expect -9352568.633 2541799.021 accept -139.0897465732 32.2480021869 expect -8541501.925 2546249.903 accept -129.2432705270 33.1672095397 expect -7802297.946 2550865.482 accept -119.6591996700 33.2608566981 expect -7115874.023 2480432.270 accept -109.9331600482 33.2921667980 expect -6445294.904 2404312.074 accept -99.3485704747 34.1878235263 expect -5734796.733 2392907.698 accept -89.8642843810 35.1664811803 expect -5119362.394 2398013.740 accept -79.2554014378 36.1141613383 expect -4455555.225 2396032.770 accept -69.1149563301 37.0376191064 expect -3841378.648 2400749.345 accept -59.1032039467 37.4528523762 expect -3255415.569 2375467.923 accept -49.2271359812 37.5837083768 expect -2692342.061 2338370.137 accept -39.6916503252 37.7843555404 expect -2158326.616 2315768.979 accept -29.8938804478 38.4387691365 expect -1616406.889 2331809.199 accept -19.6362961239 39.2643368046 expect -1056535.053 2366905.008 accept -9.4374584268 39.5396992359 expect -506540.235 2372796.930 accept 0.3678100982 40.0842150295 expect 19706.992 2406799.601 accept 10.7782532604 40.3498890996 expect 577565.478 2430780.292 accept 20.3511128947 41.0134441926 expect 1090910.801 2491722.447 accept 30.4333653857 41.5670977874 expect 1634463.912 2555980.666 accept 40.6127074692 41.8606872838 expect 2189539.048 2612578.512 accept 50.1387387629 41.9862708713 expect 2717075.610 2664468.878 accept 60.0313937859 42.2155826206 expect 3273928.502 2736271.447 accept 70.3375377370 42.9334405924 expect 3862149.979 2861074.643 accept 80.5122630521 43.3972517538 expect 4459635.506 2979024.926 accept 90.2998133187 44.2542302837 expect 5045422.749 3141116.429 accept 100.0647551775 44.4815518568 expect 5657295.328 3262535.857 accept 110.6066455294 44.8720342864 expect 6341643.269 3419834.403 accept 120.1724623585 45.4033516879 expect 6986444.476 3591181.437 accept 130.7707977218 46.0009809664 expect 7735330.463 3793501.018 accept 140.0357246677 46.4664188350 expect 8425607.646 3969868.107 accept 150.3890392442 46.5461497362 expect 9247658.444 4111964.848 accept 160.9163061323 46.7398186016 expect 10127353.584 4248204.315 accept 170.1397977064 47.5954473956 expect 10925947.772 4426508.240 accept 180.2351988738 48.0474540668 expect -11790980.142 4514546.436 accept -179.6179355770 40.1429577199 expect -11779642.671 3545074.378 accept -169.3994668623 40.9727631907 expect -10904739.639 3617207.971 accept -159.0744411429 41.2851805506 expect -10034213.428 3592885.528 accept -149.8423978548 41.7994923976 expect -9274861.042 3569932.623 accept -139.3764020016 42.6414085288 expect -8441459.370 3548024.189 accept -129.8834678056 43.3974758995 expect -7717279.673 3512850.000 accept -119.2273385573 43.9271323417 expect -6946608.719 3434404.081 accept -109.0271864056 44.8644031164 expect -6236376.125 3400177.924 accept -99.2759252254 45.1195335801 expect -5596913.783 3311405.157 accept -89.6168562261 45.3669011811 expect -4987642.638 3231158.191 accept -79.1400549649 45.8028162130 expect -4348106.593 3168953.470 accept -69.1272034531 45.8946112129 expect -3760612.414 3092810.191 accept -59.5914817326 46.6423497334 expect -3209252.229 3085015.399 accept -49.7829967571 47.4970577378 expect -2655798.711 3093450.893 accept -39.2258939434 48.1273210284 expect -2076130.612 3090330.169 accept -29.0459159714 48.7731368364 expect -1527463.826 3102071.708 accept -19.4563259884 49.0147747663 expect -1019421.009 3094379.980 accept -9.2974861965 49.6391756401 expect -485234.163 3127485.443 accept 0.8002954696 50.4949000479 expect 41618.406 3191862.866 accept 10.5449464536 50.7566160438 expect 548218.021 3220010.469 accept 20.2148839237 51.7062683926 expect 1049059.717 3317338.698 accept 30.3826788909 51.7914032104 expect 1581115.436 3356787.020 accept 40.4566276504 52.4962620813 expect 2108580.613 3463100.568 accept 50.7914497367 53.3942556548 expect 2652555.049 3604990.575 accept 60.6693710568 53.6277898414 expect 3186853.012 3700944.562 accept 70.8983905024 54.4541071246 expect 3741818.938 3872995.556 accept 80.0921298391 54.7777207304 expect 4258319.040 4004738.180 accept 90.8264889463 55.1332494774 expect 4879297.684 4176785.355 accept 100.4617594180 55.2946750932 expect 5460672.139 4333801.395 accept 110.3843836904 56.2425900762 expect 6060244.396 4604234.950 accept 120.9357632899 56.9127950267 expect 6737581.209 4883511.960 accept 130.5662521586 57.0909748951 expect 7408708.016 5106858.830 accept 140.5130223329 57.4425849230 expect 8144289.862 5377321.687 accept 150.1055981470 58.0701974311 expect 8901145.374 5695571.850 accept 160.5298175128 58.2923369725 expect 9829016.530 5963428.705 accept 170.7716226761 58.3931749475 expect 10840592.637 6151742.796 accept 180.3673574365 58.5523508398 expect -11773057.974 6240881.263 accept -179.1819571823 50.5244358587 expect -11736162.165 4863184.254 accept -169.7451954874 51.1460877943 expect -10856391.238 4918273.752 accept -159.2560731724 51.1659719222 expect -9910743.214 4816238.037 accept -149.9672222696 51.3661570651 expect -9112154.958 4711683.179 accept -139.8591282059 52.1411020613 expect -8277768.355 4644021.152 accept -129.7970972042 52.4993373959 expect -7508721.894 4510492.172 accept -119.8747965281 52.7309515249 expect -6798679.387 4365407.674 accept -109.1716302230 53.6677897735 expect -6057181.907 4293188.653 accept -99.0064373966 53.8072556867 expect -5408756.759 4153094.469 accept -89.2833040728 54.6106266193 expect -4799568.155 4102012.106 accept -79.5985918526 55.1027737429 expect -4223329.559 4031502.907 accept -69.2277148796 55.7622851795 expect -3625399.148 3983778.291 accept -59.7190082964 55.9016291208 expect -3101569.537 3908067.342 accept -49.8523842515 55.9381520101 expect -2571672.615 3833924.898 accept -39.0306006434 56.2379702434 expect -1998881.092 3793906.979 accept -29.4386358660 56.7861006916 expect -1497679.518 3798815.953 accept -19.4990519776 57.6029946160 expect -985017.408 3841130.737 accept -9.7556385919 58.2846118408 expect -490207.195 3885471.247 accept 0.2410422753 58.3298871859 expect 12102.460 3882913.235 accept 10.5236020030 58.9487998679 expect 526883.142 3950657.847 accept 20.5151795661 59.2605128471 expect 1027091.833 4004099.328 accept 30.5940481955 60.2189305606 expect 1527348.143 4138882.981 accept 40.7900054843 61.1999722752 expect 2031933.355 4297245.113 accept 50.5958144484 62.0053970071 expect 2519299.307 4456118.987 accept 60.8762100151 62.4317915615 expect 3041726.936 4599170.199 accept 70.4775836656 63.4288167842 expect 3520271.382 4823700.333 accept 80.4849589897 63.7092243462 expect 4046810.715 4994145.001 accept 90.9223489138 64.1528226880 expect 4604230.542 5218233.343 accept 100.8413924261 65.0793496826 expect 5124544.719 5529805.227 accept 110.4292321534 65.5632643921 expect 5660125.988 5811449.898 accept 120.3403456476 66.5295382674 expect 6202409.500 6215253.879 accept 130.8994551518 67.0090839235 expect 6844751.778 6619354.810 accept 140.8028440361 67.1546072874 expect 7520909.896 7003616.860 accept 150.1461657011 67.4794680766 expect 8205195.583 7463336.596 accept 160.1948043463 67.9727812220 expect 9033005.154 8088126.442 accept 170.2236262055 68.9001498060 expect 10002490.427 9028299.844 accept 180.4510871253 69.6301310123 expect -11672105.068 10174167.501 accept -179.6928502294 60.7606385646 expect -11777314.779 6722857.577 accept -169.1226136208 61.3072659363 expect -10577772.207 6741718.347 accept -159.3917581951 61.6554659753 expect -9558954.738 6580581.575 accept -149.9215059659 62.2899028506 expect -8655627.202 6419091.419 accept -139.4229348418 63.2282931041 expect -7745441.279 6255362.342 accept -129.6145782219 63.6056278696 expect -7010285.489 6024060.105 accept -119.4455633048 63.7880033811 expect -6321410.142 5777303.154 accept -109.2885142234 64.7874725222 expect -5635268.808 5674008.141 accept -99.8106343476 65.7546653268 expect -5032017.479 5601114.476 accept -89.7332690529 65.9905292469 expect -4461000.004 5437610.544 accept -79.7061602957 66.5799499364 expect -3900842.013 5346675.577 accept -69.8078719866 67.2349525670 expect -3366364.151 5288124.095 accept -59.9278061065 67.4707153106 expect -2863723.058 5195929.041 accept -49.6499709362 68.0783549484 expect -2344265.721 5168345.324 accept -39.7785355635 68.6589802179 expect -1858546.630 5161258.347 accept -29.2733130411 69.2346381936 expect -1354425.808 5169177.366 accept -19.7520445226 69.7447199105 expect -906653.264 5193341.582 accept -9.4546150406 70.4233082073 expect -429913.564 5256559.645 accept 0.4048610966 70.6236052708 expect 18357.153 5275509.126 accept 10.1670362925 70.8976936786 expect 459603.254 5322872.830 accept 20.8647562320 71.3607521955 expect 938809.251 5419110.193 accept 30.9620313275 72.3532797491 expect 1376843.290 5613329.925 accept 40.2436883822 72.6448737185 expect 1786361.071 5722579.786 accept 50.0800145195 72.7023402174 expect 2228603.224 5821470.902 accept 60.8106370898 73.3031896004 expect 2692182.927 6039226.272 accept 70.6025437547 73.5707955278 expect 3126302.133 6221575.805 accept 80.4428196695 74.5629968499 expect 3514896.350 6555403.995 accept 90.9705345155 75.3895826260 expect 3928491.141 6916551.843 accept 100.7042769293 76.1944062131 expect 4287723.041 7301533.036 accept 110.6051178518 77.0216158345 expect 4624306.800 7743103.312 accept 120.5528704261 77.5805939292 expect 4968887.256 8186300.038 accept 130.0043694164 78.1214527556 expect 5261992.760 8661662.569 accept 140.4219455785 78.5676823388 expect 5567546.429 9220412.007 accept 150.9607701203 78.6584076186 expect 5913255.760 9797358.275 accept 160.8565770466 78.9854274121 expect 6094257.816 10453353.669 accept 170.4873432082 79.5034695093 expect 6085224.011 11149120.017 accept 180.3649630983 79.8293737054 expect -6018407.263 11787437.041 accept -179.3073985765 70.8163938435 expect -10817280.529 11465490.561 accept -169.9897278030 71.2934701929 expect -9276875.736 9863919.805 accept -159.2365467427 71.8590802946 expect -8184178.135 9023921.272 accept -149.5868788879 72.5692211678 expect -7360942.175 8539328.713 accept -139.1323352132 73.1055180323 expect -6629486.281 8091534.479 accept -129.7742695161 73.8196458860 expect -6000140.533 7815451.358 accept -119.7041959034 74.7579440452 expect -5353400.778 7611475.920 accept -109.7008776088 75.3901899809 expect -4792518.394 7404785.570 accept -99.2314898828 75.9111536135 expect -4252036.425 7210319.564 accept -89.5888146775 76.2792517885 expect -3785659.112 7048200.366 accept -79.3077687162 76.5665040629 expect -3313318.429 6890921.119 accept -69.4337987883 76.6621432071 expect -2883922.823 6737102.185 accept -59.0261934568 76.9594965901 expect -2426688.222 6640668.630 accept -49.3115987100 77.8323229554 expect -1979275.955 6688559.534 accept -39.4459740795 78.6210665264 expect -1547696.694 6747296.835 accept -29.2899383303 78.8023227358 expect -1142238.026 6708292.313 accept -19.1326451258 79.5725050055 expect -728892.599 6814228.120 accept -9.5853469917 80.5460704598 expect -353566.181 6996661.780 accept 0.8129663669 80.7466782310 expect 29772.656 7032318.930 accept 10.8102522601 81.0089111161 expect 392092.798 7104986.336 accept 20.8020824140 81.8177026408 expect 730326.797 7332200.488 accept 30.3227561837 81.8668144912 expect 1061996.648 7396680.294 accept 40.1192116699 82.5591024355 expect 1359653.273 7647195.746 accept 50.7225485024 83.3689210619 expect 1642887.530 7969766.384 accept 60.1580162716 83.8192679216 expect 1890049.083 8210844.252 accept 70.1731315267 84.1714314114 expect 2143235.010 8455663.246 accept 80.1231823107 85.0909307831 expect 2259322.517 8893244.179 accept 90.1299725257 85.3982223970 expect 2445628.938 9170008.600 accept 100.9345367116 86.3293017336 expect 2430413.592 9671881.713 accept 110.8967536886 86.5596667511 expect 2542278.654 9949902.835 accept 120.1922834115 86.7776311985 expect 2614369.629 10217813.641 accept 130.8551063009 87.2011682062 expect 2577941.936 10571347.314 accept 140.5883122989 87.9808140533 expect 2273302.478 10967527.442 accept 150.7238822230 88.4956617003 expect 2017487.009 11270794.839 accept 160.6971562328 89.4208328233 expect 1268680.941 11594325.039 accept 170.3991577317 90.3170479552 expect failure errno coord_transfm_invalid_coord accept 180.5908903286 90.7796418583 expect failure errno coord_transfm_invalid_coord accept -179.6672737749 80.9670130376 expect -5579975.368 11792148.700 accept -169.9437203572 81.8738988668 expect -5187300.642 11262109.091 accept -159.3629518885 82.2036176232 expect -4941095.530 10742757.395 accept -149.7114004837 82.3010483165 expect -4738960.212 10299021.779 accept -139.8871282560 82.3318724399 expect -4514031.774 9880652.443 accept -129.1103803259 83.1345581099 expect -4013186.362 9627836.752 accept -119.1303161403 83.9704390214 expect -3530043.701 9489966.226 accept -109.5992779696 84.5102002899 expect -3148868.130 9358019.001 accept -99.5340303810 84.8783360216 expect -2804982.087 9214341.492 accept -89.9555755860 85.8080149104 expect -2340101.503 9294021.159 accept -79.4199716924 86.1128031464 expect -2020424.674 9213221.394 accept -69.1379478051 86.2852242994 expect -1740870.664 9123538.176 accept -59.4862558744 86.8907264218 expect -1395372.231 9240228.437 accept -49.7224425586 87.3050907988 expect -1102476.832 9323956.320 accept -39.0166782385 88.0295172740 expect -756214.155 9606239.667 accept -29.9931677373 88.8762393331 expect -449807.586 10100826.611 accept -19.5461864449 89.0119403988 expect -277206.486 10175661.602 accept -9.5857052257 89.5105564736 expect -97172.766 10643561.020 accept 0.2546693029 89.8300875130 expect 1535.284 11119443.552 accept 10.1878191712 89.8306694493 expect 61235.456 11123325.873 accept 20.3202271141 90.0891258483 expect failure errno coord_transfm_invalid_coord accept 30.4076531874 90.1655009485 expect failure errno coord_transfm_invalid_coord accept 40.4697816170 90.3073014721 expect failure errno coord_transfm_invalid_coord accept 50.0046280736 90.9081150272 expect failure errno coord_transfm_invalid_coord accept 60.2882853272 91.3234973005 expect failure errno coord_transfm_invalid_coord accept 70.1983790734 92.0487871293 expect failure errno coord_transfm_invalid_coord accept 80.2016298087 92.7883781050 expect failure errno coord_transfm_invalid_coord accept 90.1380649347 93.4193661081 expect failure errno coord_transfm_invalid_coord accept 100.9727810821 94.0036131508 expect failure errno coord_transfm_invalid_coord accept 110.2124518442 94.5588879261 expect failure errno coord_transfm_invalid_coord accept 120.9027437830 95.4070516655 expect failure errno coord_transfm_invalid_coord accept 130.4370128971 95.6659394457 expect failure errno coord_transfm_invalid_coord accept 140.5025123065 95.8655824422 expect failure errno coord_transfm_invalid_coord accept 150.4051276696 96.5597126498 expect failure errno coord_transfm_invalid_coord accept 160.6794810904 97.5509003421 expect failure errno coord_transfm_invalid_coord accept 170.0804614782 97.7962770171 expect failure errno coord_transfm_invalid_coord accept 180.7350988655 97.9297507566 expect failure errno coord_transfm_invalid_coord accept -179.6509243766 89.9594690425 expect -339016.824 11811270.987 accept -169.1462360158 90.2259103522 expect failure errno coord_transfm_invalid_coord accept -159.1237371257 91.0966186475 expect failure errno coord_transfm_invalid_coord accept -149.3864893992 92.0018593591 expect failure errno coord_transfm_invalid_coord accept -139.8836795793 92.7310580140 expect failure errno coord_transfm_invalid_coord accept -129.2447556879 93.0874941458 expect failure errno coord_transfm_invalid_coord accept -119.5691328385 94.0016439421 expect failure errno coord_transfm_invalid_coord accept -109.5375733271 94.6275672079 expect failure errno coord_transfm_invalid_coord accept -99.6767551820 94.9362396518 expect failure errno coord_transfm_invalid_coord accept -89.0765267674 95.5300020136 expect failure errno coord_transfm_invalid_coord accept -79.7404375474 95.8979843707 expect failure errno coord_transfm_invalid_coord accept -69.2186423862 96.1670313079 expect failure errno coord_transfm_invalid_coord accept -59.3116550382 97.0176191327 expect failure errno coord_transfm_invalid_coord accept -49.2194140278 97.9598960425 expect failure errno coord_transfm_invalid_coord accept -39.4423952109 97.9675789614 expect failure errno coord_transfm_invalid_coord accept -29.2846350035 98.7361321642 expect failure errno coord_transfm_invalid_coord accept -19.5599253582 98.8248133304 expect failure errno coord_transfm_invalid_coord accept -9.7751102831 99.2905137713 expect failure errno coord_transfm_invalid_coord accept 0.4459912613 99.9348079001 expect failure errno coord_transfm_invalid_coord accept 10.2556901904 100.0679369696 expect failure errno coord_transfm_invalid_coord accept 20.2129499464 100.4339484435 expect failure errno coord_transfm_invalid_coord accept 30.5120716507 100.8173987820 expect failure errno coord_transfm_invalid_coord accept 40.9186217475 101.3362169174 expect failure errno coord_transfm_invalid_coord accept 50.4499309674 101.7519777648 expect failure errno coord_transfm_invalid_coord accept 60.9618169609 101.9681616248 expect failure errno coord_transfm_invalid_coord accept 70.5136938969 102.7263743079 expect failure errno coord_transfm_invalid_coord accept 80.1112365376 103.4930742925 expect failure errno coord_transfm_invalid_coord accept 90.0656487088 104.2687818917 expect failure errno coord_transfm_invalid_coord accept 100.4737302350 105.1672304167 expect failure errno coord_transfm_invalid_coord accept 110.0826941374 105.9578331609 expect failure errno coord_transfm_invalid_coord accept 120.5038465053 106.4653112353 expect failure errno coord_transfm_invalid_coord accept 130.1946022249 107.2836000330 expect failure errno coord_transfm_invalid_coord accept 140.1612060920 108.2561565502 expect failure errno coord_transfm_invalid_coord accept 150.8381796528 108.3785139275 expect failure errno coord_transfm_invalid_coord accept 160.5344804952 109.2381208530 expect failure errno coord_transfm_invalid_coord accept 170.6816130052 110.0255190870 expect failure errno coord_transfm_invalid_coord accept 180.9197969760 110.0988929845 expect failure errno coord_transfm_invalid_coord proj-9.8.1/test/gie/axisswap.gie000664 001750 001750 00000005004 15166171715 016522 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- Tests for the axisswap operation ------------------------------------------------------------------------------- operation proj=axisswap order=1,2,3,4 tolerance 0.000001 m accept 1 2 3 4 expect 1 2 3 4 roundtrip 100 operation proj=axisswap order=4,3,2,1 tolerance 0.000001 m accept 1 2 3 4 expect 4 3 2 1 roundtrip 100 operation proj=axisswap order=-1,-2,-3,-4 tolerance 0.000001 m accept 1 2 3 4 expect -1 -2 -3 -4 roundtrip 100 operation proj=axisswap order=1,2,-3,4 tolerance 0.000001 m accept 1 2 3 4 expect 1 2 -3 4 roundtrip 100 operation proj=axisswap order=-1,2,3,4 tolerance 0.000001 m accept 1 2 3 4 expect -1 2 3 4 roundtrip 100 operation proj=axisswap order=1,2,3,-4 tolerance 0.000001 m accept 1 2 3 4 expect 1 2 3 -4 roundtrip 100 operation proj=axisswap order=-2,1 tolerance 0.000001 m accept 1 2 3 4 expect -2 1 3 4 roundtrip 100 operation proj=axisswap order=3,-2,1 tolerance 0.000001 m accept 1 2 3 4 expect 3 -2 1 4 roundtrip 100 operation proj=axisswap axis=neu tolerance 0 m accept 1 2 3 expect 2 1 3 # when using the +axis parameter we specify the order of the INPUT coordinate, # as opposed to +order which relates to the OUTPUT coordinate. Here we test # that n(1), u(2) and e(3) are swapped correctly to enu ordering. operation proj=axisswap axis=nue tolerance 0 m accept 1 2 3 expect 2 3 1 operation proj=axisswap axis=swd tolerance 0.000001 m accept 1 2 3 4 expect -2 -1 -3 4 operation proj=pipeline \ step proj=latlong +ellps=WGS84 \ step proj=axisswap \ order=1,2,3,4 tolerance 0.00001 m accept 12 55 0 0 expect 12 55 0 0 operation proj=pipeline \ step proj=latlong +ellps=WGS84 \ step proj=axisswap \ order=-2,-1,3,4 tolerance 0.00001 m accept 12 55 0 0 expect -55 -12 0 0 operation proj=axisswap order=1,2,3,4 axis=enu expect failure pjd_err_axis operation proj=axisswap expect failure pjd_err_axis operation proj=axisswap order=1,2,1,4 expect failure pjd_err_axis operation proj=axisswap order=2,3 expect failure pjd_err_axis operation proj=axisswap order=2,3,4 expect failure pjd_err_axis operation proj=axisswap order=1,2,3,5 expect failure pjd_err_axis proj-9.8.1/test/gie/defmodel.gie000664 001750 001750 00000007430 15166171715 016447 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- =============================================================================== Test +proj=defmodel =============================================================================== # Missing +model operation +proj=defmodel expect failure errno invalid_op_missing_arg # +model doesn't point to an existing file operation +proj=defmodel +model=i_do_not_exist expect failure errno invalid_op_file_not_found_or_invalid # Not a JSON file operation +proj=defmodel +model=proj.ini expect failure errno invalid_op_file_not_found_or_invalid # Missing time operation +proj=defmodel +model=tests/simple_model_degree_horizontal.json accept 2 49 30 HUGE_VAL expect failure errno coord_transfm_missing_time operation +proj=defmodel +model=tests/simple_model_degree_horizontal.json direction inverse accept 2 49 30 HUGE_VAL expect failure errno coord_transfm_missing_time # Horizontal deformation with horizontal unit = degree operation +proj=defmodel +model=tests/simple_model_degree_horizontal.json tolerance 0.1 mm accept 2 49 30 2020 expect 3 51 30 2020 roundtrip 1 # 3D deformation with horizontal unit = degree operation +proj=defmodel +model=tests/simple_model_degree_3d.json tolerance 0.1 mm accept 2 49 30 2020 expect 3 51 33 2020 roundtrip 1 # Horizontal deformation with horizontal unit = metre operation +proj=pipeline +step +inv +proj=merc +step +proj=defmodel +model=tests/simple_model_metre_horizontal.json +step +proj=merc tolerance 0.1 mm accept 10 20 30 2020 expect 11 22 30 2020 roundtrip 1 # 3D deformation with horizontal unit = metre operation +proj=pipeline +step +inv +proj=merc +step +proj=defmodel +model=tests/simple_model_metre_3d.json +step +proj=merc tolerance 0.1 mm accept 10 20 30 2020 expect 11 22 33 2020 roundtrip 1 # 3D deformation with horizontal unit = metre and a projeced grid operation +proj=pipeline +step +proj=defmodel +model=tests/simple_model_projected.json tolerance 0.1 mm accept 1500200.0 5400400.0 30 2020 expect 1500200.588 5400399.722 30.6084 2020 roundtrip 1 # South-west corner accept 1500000.0 5400000.0 30 2020 expect 1500000.4 5399999.8 30.84 2020 roundtrip 1 # South-east corner accept 1501000.0 5400000.0 30 2020 expect 1501000.5 5399999.75 30.75 2020 roundtrip 1 # North-west corner accept 1500000.0 5401000.0 30 2020 expect 1500000.8 5400999.6 30.36 2020 roundtrip 1 # North-east corner accept 1501000.0 5401000.0 30 2020 expect 1501001.0 5400999.7 30 2020 roundtrip 1 # Test geocentric addition of components operation +proj=pipeline +step +inv +proj=merc +step +proj=defmodel +model=tests/simple_model_metre_3d_geocentric.json +step +proj=merc tolerance 0.1 mm accept 10 20 30 2020 expect 11 22 33 2020 roundtrip 1 # Vertical deformation with vertical unit = metre operation +proj=defmodel +model=tests/simple_model_metre_vertical.json tolerance 0.1 mm accept 2 49 30 2020 expect 2 49 33 2020 roundtrip 1 # Adjust for 360 degree longitude offsets operation +proj=defmodel +model=tests/simple_model_metre_vertical.json tolerance 0.1 mm accept 362 49 30 2020 expect 2 49 33 2020 operation +proj=defmodel +model=tests/simple_model_wrap_east.json accept 165.9 -37.3 10 2020 expect 165.9 -37.3 10.4525 2020 operation +proj=defmodel +model=tests/simple_model_wrap_west.json accept 165.9 -37.3 10 2020 expect 165.9 -37.3 10.4525 2020 # Test geocentric bilinear interpolation method operation +proj=defmodel +model=tests/simple_model_polar.json tolerance 0.1 mm accept 20 -90 15 2020 expect 27.4743245365 -89.9999747721 18.0000 2020 accept 120 -90 15 2020 expect 27.4737934098 -89.9999747718 18.0000 2020 accept 235 -89.5 15 2020 expect -124.9986638571 -89.5000223708 17.3750 2020 accept 45 -89.5 15 2020 expect 44.9991295392 -89.4999759438 18.5469 2020 proj-9.8.1/test/gie/nkg.gie000664 001750 001750 00000020100 15166171715 015434 0ustar00eveneven000000 000000 # ------------------------------------------------------------------------------- # NKG # ------------------------------------------------------------------------------- operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_NKG_ETRF00 tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9311 948983.7980 5201383.2227 2020.5 #------------------------------------------------------------------------------- # DENMARK #------------------------------------------------------------------------------- # 2008 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_DK tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9362 948983.7825 5201383.2292 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_DK tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.3829 948984.2188 5201383.5296 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_DK tolerance 0.1 mm # BUDD accept 3513638.0964 778956.5470 5248216.5248 2015.0 expect 3513638.5607 778956.1875 5248216.2477 2015.0 #ESBC accept 3582104.8458 532590.0946 5232755.0863 2015.0 expect 3582105.2916 532589.7310 5232754.8057 2015.0 operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_NKG_ETRF14 tolerance 0.1 mm # BUDD accept 3513638.0964 778956.5470 5248216.5248 2015.0 expect 3513638.5071 778956.1528 5248216.2870 2015.0 # ESBC accept 3582104.8458 532590.0946 5232755.0863 2015.0 expect 3582105.2401 532589.6950 5232754.8507 2015.0 operation urn:ogc:def:coordinateOperation:NKG::ETRF14_TO_DK tolerance 0.1 mm # BUDD accept 3513638.5071 778956.1528 5248216.2870 2015.0 expect 3513638.5607 778956.1875 5248216.2477 2015.0 # ESBC accept 3582105.2401 532589.6950 5232754.8507 2015.0 expect 3582105.2916 532589.7310 5232754.8057 2015.0 # ------------------------------------------------------------------------------- # ESTONIA # ------------------------------------------------------------------------------- # 2008 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_EE tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9395 948983.8006 5201383.2242 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_EE tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.3862 948984.2370 5201383.5246 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_EE tolerance 0.1 mm # AJOE accept 2922027.7409 1516183.8589 5444680.6502 2015.0 expect 2922028.2730 1516183.5457 5444680.4094 2015.0 # ------------------------------------------------------------------------------- # FINLAND # ------------------------------------------------------------------------------- # 2008 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_FI tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9522 948983.7911 5201383.2230 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_FI_EUREF-FIN tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9522 948983.7911 5201383.2230 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_FI tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.3989 948984.2274 5201383.5235 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_FI tolerance 0.1 mm # DEGE accept 2994012.0569 1112559.9272 5502272.0863 2015.0 expect 2994012.5170 1112559.5902 5502271.7683 2015.0 operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_FI_EUREF-FIN tolerance 0.1 mm # DEGE accept 2994012.0569 1112559.9272 5502272.0863 2015.0 expect 2994012.5170 1112559.5902 5502271.7683 2015.0 # ------------------------------------------------------------------------------- # LATVIA # ------------------------------------------------------------------------------- # 2008 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_LV tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9806 948983.8606 5201383.3118 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_LV tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.4273 948984.2970 5201383.6122 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_LV tolerance 0.1 mm # BAUS accept 3226814.4746 1449250.4615 5289639.6134 2015.0 expect 3226814.9950 1449250.1841 5289639.3779 2015.0 # ------------------------------------------------------------------------------- # LITHUANIA # ------------------------------------------------------------------------------- # 2008 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_LT tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9358 948983.8042 5201383.2294 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_LT tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.3826 948984.2405 5201383.5299 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_LT tolerance 0.1 mm # VLNS accept 3343600.4221 1580417.8797 5179337.3696 2015.0 expect 3343600.9945 1580417.5661 5179337.1637 2015.0 # ------------------------------------------------------------------------------- # NORWAY # ------------------------------------------------------------------------------- # 2008 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_NO tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9204 948983.8049 5201383.2054 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_NO tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.3671 948984.2412 5201383.5058 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_NO tolerance 0.1 mm # STAS accept 3275753.4135 321111.2481 5445042.2134 2020.0 expect 3275753.9094 321110.8626 5445041.8818 2020.0 # BOD3 accept 2391773.9918 615615.1837 5860966.1279 2020.0 expect 2391774.5481 615614.9063 5860965.8185 2020.0 # KAUS accept 2107888.9134 895603.4769 5933242.6269 2020.0 expect 2107889.5014 895603.2055 5933242.3208 2020.0 # ------------------------------------------------------------------------------- # SWEDEN # ------------------------------------------------------------------------------- operation urn:ogc:def:coordinateOperation:NKG::ITRF2000_TO_SE tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.9223 948983.7912 5201383.2025 2020.5 operation urn:ogc:def:coordinateOperation:NKG::ETRF00_TO_SE tolerance 1 mm accept 3541657.3778 948984.2343 5201383.5231 2020.5 expect 3541657.3690 948984.2275 5201383.5030 2020.5 # 2020 Transformations operation urn:ogc:def:coordinateOperation:NKG::ITRF2014_TO_SE tolerance 0.1 mm # ARJ0 accept 2441774.9791 799268.3078 5818729.4941 2015.0 expect 2441775.4338 799268.0336 5818729.1635 2015.0 # BOD3 accept 2391774.0738 615615.1324 5860966.0796 2015.0 expect 2391774.5409 615614.8770 5860965.8078 2015.0 # KIR0 accept 2248123.0276 865686.7906 5886425.8928 2015.0 expect 2248123.5028 865686.5301 5886425.5928 2015.0 proj-9.8.1/test/gie/deformation.gie000664 001750 001750 00000015747 15166171715 017211 0ustar00eveneven000000 000000 =============================================================================== Test for the deformation operation - Kinematic Gridshifting For all the deformation tests the alaska and egm96_15.gtx grids are used even though they are not parts of a deformation model, they are in the proper format and for testing purposes it doesn't really matter all that much... The input coordinate is located at long=60, lam=-160 - somewhere in Alaska. =============================================================================== ------------------------------------------------------------------------------- # Test with an extract of nkgrf03vel_realigned with ctable2+gtx ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=cart +ellps=GRS80 \ +step +proj=deformation \ +xy_grids=tests/nkgrf03vel_realigned_xy_extract.ct2 \ +z_grids=tests/nkgrf03vel_realigned_z_extract.gtx +ellps=GRS80 +dt=1 \ +step +proj=cart +ellps=GRS80 +inv ------------------------------------------------------------------------------- tolerance 0.05 mm accept 21.5 63 0 expect 21.5000000049 62.9999999937 0.0083 roundtrip 5 ------------------------------------------------------------------------------- # Test with an extract of nkgrf03vel_realigned with GeoTIFF ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=cart +ellps=GRS80 \ +step +proj=deformation \ +grids=tests/nkgrf03vel_realigned_extract.tif +ellps=GRS80 +dt=1 \ +step +proj=cart +ellps=GRS80 +inv ------------------------------------------------------------------------------- tolerance 0.05 mm accept 21.5 63 0 expect 21.5000000049 62.9999999937 0.0083 roundtrip 5 ------------------------------------------------------------------------------- # Test the +dt parameter ------------------------------------------------------------------------------- operation +proj=deformation +xy_grids=alaska +z_grids=egm96_15.gtx \ +ellps=GRS80 +dt=16.0 # 2016.0 - 2000.0 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -3004295.5882503074 -1093474.1690603832 5500477.1338251457 expect -3004295.7000 -1093474.2097 5500477.3397 roundtrip 5 # Test that errors are reported for coordinates outside the grid. # Here we test 120W 40N which is well outside the alaska grid. accept -2446353.8001 -4237209.0750 4077985.572 expect failure errno coord_transfm_outside_grid accept -2446353.8001 -4237209.0750 4077985.572 expect failure errno coord_transfm_outside_grid ------------------------------------------------------------------------------- # Test using both horizontal and vertical grids ------------------------------------------------------------------------------- operation +proj=deformation \ +xy_grids=alaska +z_grids=egm96_15.gtx +t_epoch=2016.0 +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm direction inverse accept -3004295.5882503074 -1093474.1690603832 5500477.1338251457 2000.0 expect -3004295.7000 -1093474.2097 5500477.3397 2000.0 roundtrip 5 # Missing time direction forward accept -3004295.5882503074 -1093474.1690603832 5500477.1338251457 HUGE_VAL expect failure errno coord_transfm_missing_time direction inverse accept -3004295.5882503074 -1093474.1690603832 5500477.1338251457 HUGE_VAL expect failure errno coord_transfm_missing_time ------------------------------------------------------------------------------- operation proj=deformation xy_grids=alaska +dt=1.0 ellps=GRS80 expect failure errno invalid_op_missing_arg operation proj=deformation z_grids=egm96_15.gtx +dt=1.0 ellps=GRS80 expect failure errno invalid_op_missing_arg operation proj=deformation xy_grids=nonexisting z_grids=egm96_15.gtx \ +dt=1.0 ellps=GRS80 expect failure errno invalid_op_file_not_found_or_invalid operation proj=deformation xy_grids=alaska z_grids=nonexisting \ +dt=1.0 ellps=GRS80 expect failure errno invalid_op_file_not_found_or_invalid operation proj=deformation xy_grids=alaska z_grids=nonexisting ellps=GRS80 expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=egm96_15.gtx +t_epoch=2010.0 +t_final=2018.0 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 12 56 0.0 2000.0 expect 12 56 -36.9960 2000.0 roundtrip 100 accept 12 56 0.0 2011.0 expect 12 56 0.0 2011.0 roundtrip 100 accept 12 56 0.0 2019.0 expect 12 56 0.0 2019.0 roundtrip 100 accept 12 56 0.0 expect 12 56 -36.9960 roundtrip 100 ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=egm96_15.gtx +t_epoch=2010.0 +t_final=now ------------------------------------------------------------------------------- tolerance 0.1 mm accept 12 56 0.0 2000.0 expect 12 56 -36.9960 2000.0 roundtrip 100 accept 12 56 0.0 2011.0 expect 12 56 0.0 2011.0 roundtrip 1000 accept 12 56 0.0 3011.0 expect 12 56 0.0 3011.0 roundtrip 100 ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=alaska +t_epoch=2010.0 +t_final=2018.0 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -147.0 64.0 0.0 2000.0 expect -147.0023233121 63.9995792119 0.0 2000.0 roundtrip 100 accept -147.0 64.0 0.0 2011.0 expect -147.0 64.0 0.0 2011.0 roundtrip 100 accept -147.0 64.0 0.0 2011.0 expect -147.0 64.0 0.0 2011.0 roundtrip 100 ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=alaska +t_epoch=2010.0 +t_final=now ------------------------------------------------------------------------------- tolerance 0.1 mm accept -147.0 64.0 0.0 2000.0 expect -147.0023233121 63.9995792119 0.0 2000.0 roundtrip 100 accept -147.0 64.0 0.0 2011.0 expect -147.0 64.0 0.0 2011.0 roundtrip 100 accept -147.0 64.0 0.0 3011.0 expect -147.0 64.0 0.0 3011.0 roundtrip 100 proj-9.8.1/test/gie/tinshift.gie000664 001750 001750 00000003277 15166171715 016525 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- =============================================================================== Test +proj=tinshift =============================================================================== # Missing +file operation +proj=tinshift expect failure errno invalid_op_missing_arg # +file doesn't point to an existing file operation +proj=tinshift +file=i_do_not_exist expect failure errno invalid_op_file_not_found_or_invalid # Not a JSON file operation +proj=tinshift +file=proj.ini expect failure errno invalid_op_file_not_found_or_invalid # Tests on a file without explicit CRS operation +proj=tinshift +file=tests/tinshift_crs_implicit.json accept 2 49 expect 2.1 49.1 roundtrip 1 accept 0 0 expect failure direction inverse accept 0 0 expect failure # Tests on a file with explicit CRS operation +proj=tinshift +file=tests/tinshift_simplified_kkj_etrs.json tolerance 0.1 mm # Verified with https://kartta.paikkatietoikkuna.fi/?lang=en with EPSG:2393 to EPSG:3067 accept 3210000.0000 6700000.0000 expect 209948.3217 6697187.0009 roundtrip 1 operation +proj=tinshift +file=tests/tinshift_simplified_n60_n2000.json tolerance 0.1 mm accept 3210000.0000 6700000.0000 10.0 expect 3210000.0000 6700000.0000 10.2886 roundtrip 1 # Test fallback strategy nearest_side operation +proj=tinshift +file=tests/tinshift_fallback_nearest_side.json accept 2 3 expect 4 6 roundtrip 1 # Test fallback strategy nearest_centroid operation +proj=tinshift +file=tests/tinshift_fallback_nearest_centroid.json accept 3 0 expect 3 0 roundtrip 1 proj-9.8.1/test/gie/DHDN_ETRS89.gie000664 001750 001750 00000051364 15166171715 016430 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- operation proj=latlong datum=potsdam ellps=bessel ------------------------------------------------------------------------------- #DE_DHDN (BeTA, 2007) to ETRS89 using NTv2 grid. epsg:15948 ------------------------------------------------------------------------------- tolerance 1 mm accept 7.482506019176 53.498461143331 # ETRS89_Lat-Lon expect 7.483333333333 53.500000000000 # DE_DHDN_Lat-Lon direction inverse accept 7.483333333333 53.500000000000 # DE_DHDN_Lat-Lon expect 7.482506019176 53.498461143331 # ETRS89_Lat-Lon accept 10.333333333333 48.833333333333 # DE_DHDN_Lat-Lon expect 10.332117283303 48.832327188640 # ETRS89_Lat-Lon accept 8.000000000000 50.083333333333 # DE_DHDN_Lat-Lon expect 7.999097344043 50.082172046476 # ETRS89_Lat-Lon accept 10.016666666667 51.033333333333 # DE_DHDN_Lat-Lon expect 10.015460839103 51.032075951188 # ETRS89_Lat-Lon accept 10.466666666667 54.333333333333 # DE_DHDN_Lat-Lon expect 10.465373788153 54.331696254077 # ETRS89_Lat-Lon accept 10.750000000000 53.583333333333 # DE_DHDN_Lat-Lon expect 10.748659705929 53.581781243436 # ETRS89_Lat-Lon accept 10.016666666667 53.500000000000 # DE_DHDN_Lat-Lon expect 10.015444367463 53.498457503620 # ETRS89_Lat-Lon accept 11.000000000000 53.466666666667 # DE_DHDN_Lat-Lon expect 10.998619309575 53.465127257963 # ETRS89_Lat-Lon accept 13.466666666667 53.766666666667 # DE_DHDN_Lat-Lon expect 13.464877774631 53.765109112396 # ETRS89_Lat-Lon accept 10.983333333333 52.766666666667 # DE_DHDN_Lat-Lon expect 10.981965431979 52.765211787713 # ETRS89_Lat-Lon accept 13.000000000000 51.783333333333 # DE_DHDN_Lat-Lon expect 12.998336654827 51.782006921265 # ETRS89_Lat-Lon accept 10.466666666667 52.500000000000 # DE_DHDN_Lat-Lon expect 10.465380298337 52.498573633365 # ETRS89_Lat-Lon accept 10.550000000000 51.466666666667 # DE_DHDN_Lat-Lon expect 10.548711467380 51.465361979987 # ETRS89_Lat-Lon accept 10.450000000000 50.583333333333 # DE_DHDN_Lat-Lon expect 10.448735275612 50.582129474187 # ETRS89_Lat-Lon accept 10.416666666667 49.666666666667 # DE_DHDN_Lat-Lon expect 10.415423634267 49.665566047661 # ETRS89_Lat-Lon accept 10.550000000000 47.750000000000 # DE_DHDN_Lat-Lon expect 10.548775945187 47.749120260296 # ETRS89_Lat-Lon accept 13.450000000000 50.666666666667 # DE_DHDN_Lat-Lon expect 13.448283429558 50.665476385913 # ETRS89_Lat-Lon accept 13.550000000000 51.333333333333 # DE_DHDN_Lat-Lon expect 13.548264242652 51.332063317958 # ETRS89_Lat-Lon accept 13.566666666667 52.050000000000 # DE_DHDN_Lat-Lon expect 13.564906713066 52.048646469731 # ETRS89_Lat-Lon accept 13.433333333333 53.166666666667 # DE_DHDN_Lat-Lon expect 13.431569610583 53.165185284138 # ETRS89_Lat-Lon accept 13.466666666667 52.483333333333 # DE_DHDN_Lat-Lon expect 13.464913254978 52.481930297429 # ETRS89_Lat-Lon accept 13.133333333333 49.066666666667 # DE_DHDN_Lat-Lon expect 13.131706947050 49.065661709281 # ETRS89_Lat-Lon accept 8.666666666667 53.116666666667 # DE_DHDN_Lat-Lon expect 8.665654272188 53.115169791635 # ETRS89_Lat-Lon accept 12.950000000000 47.650000000000 # DE_DHDN_Lat-Lon expect 12.948437185277 47.649155713893 # ETRS89_Lat-Lon accept 8.500000000000 54.716666666667 # DE_DHDN_Lat-Lon expect 8.499027339833 54.714992333813 # ETRS89_Lat-Lon accept 7.483333333333 51.983333333333 # DE_DHDN_Lat-Lon expect 7.482494584516 51.981965147975 # ETRS89_Lat-Lon accept 7.516666666667 51.016666666667 # DE_DHDN_Lat-Lon expect 7.515823996992 51.015402184493 # ETRS89_Lat-Lon accept 7.466666666667 50.500000000000 # DE_DHDN_Lat-Lon expect 7.465834308888 50.498791390585 # ETRS89_Lat-Lon accept 7.533333333333 49.333333333333 # DE_DHDN_Lat-Lon expect 7.532503616986 49.332250779407 # ETRS89_Lat-Lon accept 7.250000000000 49.333333333333 # DE_DHDN_Lat-Lon expect 7.249209260581 49.332249456364 # ETRS89_Lat-Lon accept 7.533333333333 47.666666666667 # DE_DHDN_Lat-Lon expect 7.532530252396 47.665765608135 # ETRS89_Lat-Lon ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation proj=latlong \ towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 ellps=bessel ------------------------------------------------------------------------------- # DE_DHDN to ETRS89 using deprecated 7 parameter Helmert transform. The results # agree at the 3 m level. ------------------------------------------------------------------------------- require_grid BETA2007.gsb tolerance 3 m accept 7.482506019176 53.498461143331 # ETRS89_Lat-Lon expect 7.483333333333 53.500000000000 # DE_DHDN_Lat-Lon direction inverse accept 7.483333333333 53.500000000000 # DE_DHDN_Lat-Lon expect 7.482506019176 53.498461143331 # ETRS89_Lat-Lon accept 10.333333333333 48.833333333333 # DE_DHDN_Lat-Lon expect 10.332117283303 48.832327188640 # ETRS89_Lat-Lon accept 8.000000000000 50.083333333333 # DE_DHDN_Lat-Lon expect 7.999097344043 50.082172046476 # ETRS89_Lat-Lon accept 10.016666666667 51.033333333333 # DE_DHDN_Lat-Lon expect 10.015460839103 51.032075951188 # ETRS89_Lat-Lon accept 10.466666666667 54.333333333333 # DE_DHDN_Lat-Lon expect 10.465373788153 54.331696254077 # ETRS89_Lat-Lon accept 10.750000000000 53.583333333333 # DE_DHDN_Lat-Lon expect 10.748659705929 53.581781243436 # ETRS89_Lat-Lon accept 10.016666666667 53.500000000000 # DE_DHDN_Lat-Lon expect 10.015444367463 53.498457503620 # ETRS89_Lat-Lon accept 11.000000000000 53.466666666667 # DE_DHDN_Lat-Lon expect 10.998619309575 53.465127257963 # ETRS89_Lat-Lon accept 13.466666666667 53.766666666667 # DE_DHDN_Lat-Lon expect 13.464877774631 53.765109112396 # ETRS89_Lat-Lon accept 10.983333333333 52.766666666667 # DE_DHDN_Lat-Lon expect 10.981965431979 52.765211787713 # ETRS89_Lat-Lon accept 13.000000000000 51.783333333333 # DE_DHDN_Lat-Lon expect 12.998336654827 51.782006921265 # ETRS89_Lat-Lon accept 10.466666666667 52.500000000000 # DE_DHDN_Lat-Lon expect 10.465380298337 52.498573633365 # ETRS89_Lat-Lon accept 10.550000000000 51.466666666667 # DE_DHDN_Lat-Lon expect 10.548711467380 51.465361979987 # ETRS89_Lat-Lon accept 10.450000000000 50.583333333333 # DE_DHDN_Lat-Lon expect 10.448735275612 50.582129474187 # ETRS89_Lat-Lon accept 10.416666666667 49.666666666667 # DE_DHDN_Lat-Lon expect 10.415423634267 49.665566047661 # ETRS89_Lat-Lon accept 10.550000000000 47.750000000000 # DE_DHDN_Lat-Lon expect 10.548775945187 47.749120260296 # ETRS89_Lat-Lon accept 13.450000000000 50.666666666667 # DE_DHDN_Lat-Lon expect 13.448283429558 50.665476385913 # ETRS89_Lat-Lon accept 13.550000000000 51.333333333333 # DE_DHDN_Lat-Lon expect 13.548264242652 51.332063317958 # ETRS89_Lat-Lon accept 13.566666666667 52.050000000000 # DE_DHDN_Lat-Lon expect 13.564906713066 52.048646469731 # ETRS89_Lat-Lon accept 13.433333333333 53.166666666667 # DE_DHDN_Lat-Lon expect 13.431569610583 53.165185284138 # ETRS89_Lat-Lon accept 13.466666666667 52.483333333333 # DE_DHDN_Lat-Lon expect 13.464913254978 52.481930297429 # ETRS89_Lat-Lon accept 13.133333333333 49.066666666667 # DE_DHDN_Lat-Lon expect 13.131706947050 49.065661709281 # ETRS89_Lat-Lon accept 8.666666666667 53.116666666667 # DE_DHDN_Lat-Lon expect 8.665654272188 53.115169791635 # ETRS89_Lat-Lon accept 12.950000000000 47.650000000000 # DE_DHDN_Lat-Lon expect 12.948437185277 47.649155713893 # ETRS89_Lat-Lon accept 8.500000000000 54.716666666667 # DE_DHDN_Lat-Lon expect 8.499027339833 54.714992333813 # ETRS89_Lat-Lon accept 7.483333333333 51.983333333333 # DE_DHDN_Lat-Lon expect 7.482494584516 51.981965147975 # ETRS89_Lat-Lon accept 7.516666666667 51.016666666667 # DE_DHDN_Lat-Lon expect 7.515823996992 51.015402184493 # ETRS89_Lat-Lon accept 7.466666666667 50.500000000000 # DE_DHDN_Lat-Lon expect 7.465834308888 50.498791390585 # ETRS89_Lat-Lon accept 7.533333333333 49.333333333333 # DE_DHDN_Lat-Lon expect 7.532503616986 49.332250779407 # ETRS89_Lat-Lon accept 7.250000000000 49.333333333333 # DE_DHDN_Lat-Lon expect 7.249209260581 49.332249456364 # ETRS89_Lat-Lon accept 7.533333333333 47.666666666667 # DE_DHDN_Lat-Lon expect 7.532530252396 47.665765608135 # ETRS89_Lat-Lon ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- The numerical material in this file is based on the contents of the BKG test data file over at http://crs.bkg.bund.de/crseu/crs/descrtrans/BeTA/BETA2007testdaten.csv The conversion was carried out as follows: set insertkey=gawk 'BEGIN {FS=","}; {print $3","$0} set reformat=gawk 'BEGIN {FS=","}; {print "accept " $6 " " $5 " # " $4 "\nexpect " $9 " " $8 " # " $7}' cat BETA2007testdaten.csv | %insertkey% | sort | %reformat% >DHDN_ETRS89.gie ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- Tests for GK system zones to UTM32/33 not implemented yet ------------------------------------------------------------------------------- accept 2598417.333192 5930677.980308 # DE_DHDN_3GK2 expect 399340.601863 5928794.177992 # ETRS89_UTM32 accept 2643120.946052 5551463.861308 # DE_DHDN_3GK2 expect 428391.209209 5548246.766868 # ETRS89_UTM32 accept 2678509.791823 5890320.494547 # DE_DHDN_3GK2 expect 477621.722498 5885134.566909 # ETRS89_UTM32 accept 2661073.960381 6067930.993896 # DE_DHDN_3GK2 expect 467726.896146 6063191.974102 # ETRS89_UTM32 accept 2601895.024514 5761935.671777 # DE_DHDN_3GK2 expect 395783.496871 5760119.715259 # ETRS89_UTM32 accept 2606412.760026 5654454.411797 # DE_DHDN_3GK2 expect 395892.865206 5652585.895428 # ETRS89_UTM32 accept 2604044.332230 5596917.811668 # DE_DHDN_3GK2 expect 391195.030128 5595215.127880 # ETRS89_UTM32 accept 2611430.565041 5467270.623504 # DE_DHDN_3GK2 expect 393381.121595 5465427.351346 # ETRS89_UTM32 accept 2590840.678885 5466891.206854 # DE_DHDN_3GK2 expect 372799.647928 5465865.755414 # ETRS89_UTM32 accept 2615145.447136 5281966.148083 # DE_DHDN_3GK2 expect 389829.267589 5280195.601333 # ETRS89_UTM32 accept 3399371.190396 5930724.531323 # DE_DHDN_3GK3 expect 399340.601862 5928794.177992 # ETRS89_UTM32 accept 3597874.421966 5411397.512092 # DE_DHDN_3GK3 expect 597759.898637 5409672.239612 # ETRS89_UTM32 accept 3428437.612810 5550026.645035 # DE_DHDN_3GK3 expect 428391.209209 5548246.766869 # ETRS89_UTM32 accept 3571307.006323 5655705.338031 # DE_DHDN_3GK3 expect 571204.563344 5653882.476948 # ETRS89_UTM32 accept 3595392.782000 6023387.959898 # DE_DHDN_3GK3 expect 595286.044398 6021417.376973 # ETRS89_UTM32 accept 3615881.001454 5940351.727710 # DE_DHDN_3GK3 expect 615764.364007 5938413.819150 # ETRS89_UTM32 accept 3615881.001454 5940351.727710 # DE_DHDN_3GK3 expect 218617.111391 5945399.220269 # ETRS89_UTM33 accept 3567455.742115 5930134.904864 # DE_DHDN_3GK3 expect 567358.390548 5928201.976543 # ETRS89_UTM32 accept 3632798.076882 5927807.051283 # DE_DHDN_3GK3 expect 632674.379672 5925873.747901 # ETRS89_UTM32 accept 3632798.076882 5927807.051283 # DE_DHDN_3GK3 expect 234423.486615 5931470.592457 # ETRS89_UTM33 accept 3633848.721200 5849896.198513 # DE_DHDN_3GK3 expect 633723.734075 5847994.536970 # ETRS89_UTM32 accept 3633848.721200 5849896.198513 # DE_DHDN_3GK3 expect 228947.171966 5853725.067987 # ETRS89_UTM33 accept 3599586.686397 5819391.659845 # DE_DHDN_3GK3 expect 599474.934168 5817502.626999 # ETRS89_UTM32 accept 3607695.214682 5704557.217497 # DE_DHDN_3GK3 expect 607578.857121 5702714.405562 # ETRS89_UTM32 accept 3607695.214682 5704557.217497 # DE_DHDN_3GK3 expect 190859.292094 5710978.842070 # ETRS89_UTM33 accept 3602680.921862 5606162.921133 # DE_DHDN_3GK3 expect 602565.455313 5604359.618990 # ETRS89_UTM32 accept 3602680.921862 5606162.921133 # DE_DHDN_3GK3 expect 177845.139712 5613251.897383 # ETRS89_UTM33 accept 3602255.364740 5504172.212483 # DE_DHDN_3GK3 expect 602139.527314 5502409.680191 # ETRS89_UTM32 accept 3602255.364740 5504172.212483 # DE_DHDN_3GK3 expect 169220.450101 5511545.700292 # ETRS89_UTM33 accept 3616211.566778 5291255.078896 # DE_DHDN_3GK3 expect 616089.408439 5289578.131826 # ETRS89_UTM32 accept 3616211.566778 5291255.078896 # DE_DHDN_3GK3 expect 166384.067958 5298018.237122 # ETRS89_UTM33 accept 3477684.063162 5887048.676718 # DE_DHDN_3GK3 expect 477621.722499 5885134.566914 # ETRS89_UTM32 accept 3467781.947036 6065176.417740 # DE_DHDN_3GK3 expect 467726.896147 6063191.974105 # ETRS89_UTM32 accept 3395815.326925 5761982.907482 # DE_DHDN_3GK3 expect 395783.496872 5760119.715259 # ETRS89_UTM32 accept 3395925.872234 5654406.808724 # DE_DHDN_3GK3 expect 395892.865206 5652585.895428 # ETRS89_UTM32 accept 3391226.589718 5597013.366086 # DE_DHDN_3GK3 expect 391195.030128 5595215.127881 # ETRS89_UTM32 accept 3393414.080125 5467174.397245 # DE_DHDN_3GK3 expect 393381.121595 5465427.351346 # ETRS89_UTM32 accept 3372824.499428 5467612.907301 # DE_DHDN_3GK3 expect 372799.647928 5465865.755413 # ETRS89_UTM32 accept 3389860.774004 5281869.239226 # DE_DHDN_3GK3 expect 389829.267590 5280195.601333 # ETRS89_UTM32 accept 4377657.794741 5411879.839992 # DE_DHDN_3GK4 expect 597759.898636 5409672.239612 # ETRS89_UTM32 accept 4360897.154310 5657085.679344 # DE_DHDN_3GK4 expect 571204.563343 5653882.476947 # ETRS89_UTM32 accept 4400271.505998 6023480.198072 # DE_DHDN_3GK4 expect 595286.044399 6021417.376972 # ETRS89_UTM32 accept 4417225.999425 5939654.081375 # DE_DHDN_3GK4 expect 615764.364007 5938413.819151 # ETRS89_UTM32 accept 4417225.999425 5939654.081375 # DE_DHDN_3GK4 expect 218617.111391 5945399.220269 # ETRS89_UTM33 accept 4368411.664264 5931484.902370 # DE_DHDN_3GK4 expect 567358.390548 5928201.976543 # ETRS89_UTM32 accept 4433598.021986 5926410.006980 # DE_DHDN_3GK4 expect 632674.379671 5925873.747901 # ETRS89_UTM32 accept 4433598.021986 5926410.006980 # DE_DHDN_3GK4 expect 234423.486614 5931470.592457 # ETRS89_UTM33 accept 4596699.814954 5960328.296681 # DE_DHDN_3GK4 expect 794226.051532 5966642.993890 # ETRS89_UTM32 accept 4596699.814954 5960328.296681 # DE_DHDN_3GK4 expect 398811.452821 5958481.617326 # ETRS89_UTM33 accept 4431385.771953 5848536.122437 # DE_DHDN_3GK4 expect 633723.734074 5847994.536971 # ETRS89_UTM32 accept 4431385.771953 5848536.122437 # DE_DHDN_3GK4 expect 228947.171966 5853725.067987 # ETRS89_UTM33 accept 4568999.833703 5739119.060681 # DE_DHDN_3GK4 expect 775766.817929 5744357.999264 # ETRS89_UTM32 accept 4568999.833703 5739119.060681 # DE_DHDN_3GK4 expect 361924.813552 5738688.111797 # ETRS89_UTM33 accept 4395886.918912 5819485.694352 # DE_DHDN_3GK4 expect 599474.934169 5817502.626999 # ETRS89_UTM32 accept 4399252.521454 5704414.901133 # DE_DHDN_3GK4 expect 607578.857121 5702714.405563 # ETRS89_UTM32 accept 4399252.521454 5704414.901133 # DE_DHDN_3GK4 expect 190859.292094 5710978.842070 # ETRS89_UTM33 accept 4390237.957560 5606306.171667 # DE_DHDN_3GK4 expect 602565.455313 5604359.618990 # ETRS89_UTM32 accept 4390237.957560 5606306.171667 # DE_DHDN_3GK4 expect 177845.139712 5613251.897384 # ETRS89_UTM33 accept 4385715.060070 5504412.338975 # DE_DHDN_3GK4 expect 602139.527314 5502409.680191 # ETRS89_UTM32 accept 4385715.060070 5504412.338975 # DE_DHDN_3GK4 expect 169220.450101 5511545.700292 # ETRS89_UTM33 accept 4391285.796869 5291109.755123 # DE_DHDN_3GK4 expect 616089.408439 5289578.131827 # ETRS89_UTM32 accept 4391285.796869 5291109.755123 # DE_DHDN_3GK4 expect 166384.067958 5298018.237122 # ETRS89_UTM33 accept 4602499.566145 5615431.379860 # DE_DHDN_3GK4 expect 814311.364242 5622071.326313 # ETRS89_UTM32 accept 4602499.566145 5615431.379860 # DE_DHDN_3GK4 expect 390338.211462 5613774.353256 # ETRS89_UTM33 accept 4608008.855658 5689725.987089 # DE_DHDN_3GK4 expect 816793.461724 5696579.298817 # ETRS89_UTM32 accept 4608008.855658 5689725.987089 # DE_DHDN_3GK4 expect 398863.493307 5687753.129020 # ETRS89_UTM33 accept 4607459.254388 5769472.054323 # DE_DHDN_3GK4 expect 812962.846098 5776288.882564 # ETRS89_UTM32 accept 4607459.254388 5769472.054323 # DE_DHDN_3GK4 expect 401589.388273 5767420.751372 # ETRS89_UTM33 accept 4595844.509596 5893520.178529 # DE_DHDN_3GK4 expect 796184.889876 5899821.806119 # ETRS89_UTM32 accept 4595844.509596 5893520.178529 # DE_DHDN_3GK4 expect 395147.893839 5891795.036022 # ETRS89_UTM33 accept 4599624.347102 5817537.418158 # DE_DHDN_3GK4 expect 803137.012417 5824018.671556 # ETRS89_UTM32 accept 4599624.347102 5817537.418158 # DE_DHDN_3GK4 expect 395754.092849 5815749.835902 # ETRS89_UTM33 accept 4582806.457775 5437104.667215 # DE_DHDN_3GK4 expect 801769.133341 5442981.626260 # ETRS89_UTM32 accept 4582806.457775 5437104.667215 # DE_DHDN_3GK4 expect 363531.446507 5436436.282581 # ETRS89_UTM33 accept 4571363.304563 5279411.440581 # DE_DHDN_3GK4 expect 796505.582915 5284862.664428 # ETRS89_UTM32 accept 4571363.304563 5279411.440581 # DE_DHDN_3GK4 expect 345930.907036 5279345.459526 # ETRS89_UTM33 accept 5398905.047545 5960421.130827 # DE_DHDN_3GK5 expect 794226.051532 5966642.993889 # ETRS89_UTM32 accept 5398905.047545 5960421.130827 # DE_DHDN_3GK5 expect 398811.452821 5958481.617326 # ETRS89_UTM33 accept 5362005.247500 5740538.568445 # DE_DHDN_3GK5 expect 775766.817929 5744357.999262 # ETRS89_UTM32 accept 5362005.247500 5740538.568445 # DE_DHDN_3GK5 expect 361924.813551 5738688.111796 # ETRS89_UTM33 accept 5390431.824773 5615574.548074 # DE_DHDN_3GK5 expect 814311.364241 5622071.326313 # ETRS89_UTM32 accept 5390431.824773 5615574.548074 # DE_DHDN_3GK5 expect 390338.211462 5613774.353256 # ETRS89_UTM33 accept 5398959.121385 5689583.521018 # DE_DHDN_3GK5 expect 816793.461724 5696579.298817 # ETRS89_UTM32 accept 5398959.121385 5689583.521018 # DE_DHDN_3GK5 expect 398863.493307 5687753.129020 # ETRS89_UTM33 accept 5401685.729154 5769283.220752 # DE_DHDN_3GK5 expect 812962.846098 5776288.882564 # ETRS89_UTM32 accept 5401685.729154 5769283.220752 # DE_DHDN_3GK5 expect 401589.388272 5767420.751372 # ETRS89_UTM33 accept 5395240.318989 5893707.029636 # DE_DHDN_3GK5 expect 796184.889876 5899821.806119 # ETRS89_UTM32 accept 5395240.318989 5893707.029636 # DE_DHDN_3GK5 expect 395147.893840 5891795.036022 # ETRS89_UTM33 accept 5395847.545864 5817631.467237 # DE_DHDN_3GK5 expect 803137.012417 5824018.671556 # ETRS89_UTM32 accept 5395847.545864 5817631.467237 # DE_DHDN_3GK5 expect 395754.092849 5815749.835902 # ETRS89_UTM33 accept 5363615.032963 5438164.610427 # DE_DHDN_3GK5 expect 801769.133341 5442981.626260 # ETRS89_UTM32 accept 5363615.032963 5438164.610427 # DE_DHDN_3GK5 expect 363531.446506 5436436.282581 # ETRS89_UTM33 accept 5346007.854521 5281010.564511 # DE_DHDN_3GK5 expect 796505.582915 5284862.664427 # ETRS89_UTM32 accept 5346007.854521 5281010.564511 # DE_DHDN_3GK5 expect 345930.907036 5279345.459525 # ETRS89_UTM33 ------------------------------------------------------------------------------- proj-9.8.1/test/gie/adams_ws2.gie000664 001750 001750 00000167312 15166171715 016556 0ustar00eveneven000000 000000 ------------------------------------------------------------ # This gie file was initially generated from "random" test points # got by using libproject where the adams_ws2 code was adapted from # It can be edited. ------------------------------------------------------------ ------------------------------------------------------------ operation +proj=adams_ws2 +R=6370997 tolerance 1 mm ------------------------------------------------------------ accept -179.7092450238 -90.0290393775 expect failure errno coord_transfm_invalid_coord accept -169.9316998581 -89.6983443874 expect -2757243.603 -13694037.516 accept -159.9839735761 -89.3853376439 expect -3135302.955 -12966682.177 accept -149.0127901515 -88.4812940559 expect -3702020.595 -11830322.541 accept -139.1470666283 -87.4944214849 expect -3951802.110 -10998803.477 accept -129.1647510809 -86.6732359620 expect -3966830.515 -10396244.146 accept -119.8872640416 -86.0311019572 expect -3870042.307 -9946966.226 accept -109.1071943611 -85.4385747937 expect -3666639.936 -9527264.855 accept -99.1726204643 -85.1105131986 expect -3405494.414 -9249877.167 accept -89.9699823716 -84.7014602402 expect -3161293.332 -8969222.654 accept -79.0142432052 -84.6183360959 expect -2796763.716 -8803380.868 accept -69.3746588999 -83.8694127295 expect -2538194.969 -8434335.838 accept -59.1116247628 -83.4382281692 expect -2201397.875 -8194239.985 accept -49.9832824786 -82.7776695863 expect -1905027.516 -7914064.933 accept -39.2446354074 -82.2684783422 expect -1520355.973 -7692624.558 accept -29.1591843717 -81.2712614892 expect -1160852.682 -7367506.867 accept -19.7518374124 -80.8288036882 expect -795019.539 -7219158.095 accept -9.9255150926 -80.7634661276 expect -400180.112 -7180839.863 accept 0.1884288619 -80.2758766316 expect 7681.127 -7053424.532 accept 10.3463227493 -79.3698907818 expect 429721.976 -6850344.331 accept 20.5976448498 -78.7037640543 expect 866348.961 -6727849.736 accept 30.3082205513 -78.5268124103 expect 1278848.718 -6727598.466 accept 40.4562460745 -78.1491297266 expect 1718510.989 -6703423.050 accept 50.9458720501 -77.1822355756 expect 2199782.851 -6583628.613 accept 60.7726090586 -77.0349803149 expect 2630476.462 -6641198.553 accept 70.5081156610 -76.3842150797 expect 3083737.575 -6619890.177 accept 80.2365671846 -75.4841051853 expect 3557787.939 -6575918.481 accept 90.5217667884 -74.7807003525 expect 4055523.822 -6599908.601 accept 100.8213322615 -74.0327498320 expect 4564995.375 -6643366.399 accept 110.5063711430 -73.2571688604 expect 5056479.354 -6698955.863 accept 120.3163752614 -72.4393741465 expect 5563998.804 -6778747.641 accept 130.6066962791 -72.2383125997 expect 6050103.198 -6992464.809 accept 140.9842139999 -72.0604912080 expect 6533723.092 -7241474.496 accept 150.7407483672 -71.8220094179 expect 6988261.604 -7493795.727 accept 160.2333513818 -71.3988285471 expect 7442114.989 -7741103.770 accept 170.2174368056 -70.4342119870 expect 7967287.710 -7968253.770 accept 180.4880362185 -69.4423545276 expect -8459022.028 -8206959.559 accept -179.6765866679 -79.9829089371 expect -6985694.074 -9698942.155 accept -169.3697449847 -79.1533653138 expect -6788658.846 -9227367.628 accept -159.3398127142 -78.6158200646 expect -6518830.741 -8830410.965 accept -149.2529018703 -77.9038978934 expect -6242199.759 -8415628.859 accept -139.1270212047 -77.3123241499 expect -5919170.448 -8040244.161 accept -129.9719878520 -77.1799395743 expect -5563633.984 -7792039.926 accept -119.8002013346 -76.4574860662 expect -5211249.405 -7431807.092 accept -109.5059723182 -75.7415088186 expect -4829803.114 -7094468.982 accept -99.6093198617 -75.2549327376 expect -4430664.339 -6828988.008 accept -89.7930338075 -74.9639973582 expect -4012235.208 -6620268.234 accept -79.4586895446 -74.2118180651 expect -3586854.329 -6347250.691 accept -69.3436462673 -74.1262304089 expect -3131838.678 -6210695.025 accept -59.8993905791 -73.3233934105 expect -2730484.498 -5982995.653 accept -49.7522174506 -73.2363687589 expect -2268190.621 -5882547.028 accept -39.2685447989 -72.6142200904 expect -1801294.844 -5714042.490 accept -29.0364706467 -72.5237014490 expect -1332211.352 -5647707.712 accept -19.8296434180 -72.1886326428 expect -912551.389 -5563488.488 accept -9.8077325645 -71.7629261845 expect -453152.448 -5478207.867 accept 0.4315853824 -71.5575178388 expect 19979.237 -5441012.049 accept 10.8411958509 -70.9073113064 expect 505138.396 -5355652.524 accept 20.7152781983 -70.1134571802 expect 972838.442 -5266833.183 accept 30.0599851854 -69.8795545612 expect 1415773.291 -5269134.697 accept 40.5438377621 -69.1450433006 expect 1924403.608 -5223807.773 accept 50.0307069046 -68.4333233270 expect 2392850.994 -5193157.214 accept 60.5578535026 -67.8341598034 expect 2917014.530 -5202936.346 accept 70.5486167487 -67.0039300601 expect 3429776.377 -5197720.065 accept 80.6104642107 -66.2045341279 expect 3955430.510 -5217814.475 accept 90.2841546043 -65.8422559530 expect 4456136.396 -5308137.385 accept 100.1453713101 -65.8321633336 expect 4958133.076 -5466790.857 accept 110.3255223842 -65.6999143010 expect 5484630.668 -5641098.630 accept 120.2695614591 -65.4373463883 expect 6008718.351 -5822579.879 accept 130.1617028260 -64.7707999563 expect 6557379.569 -5983944.031 accept 140.4485877356 -64.1566350257 expect 7128044.250 -6198238.582 accept 150.3105224098 -63.5579868289 expect 7676672.874 -6441225.256 accept 160.5091429791 -62.6650596939 expect 8262531.987 -6705693.227 accept 170.8981621301 -61.7219845816 expect 8855010.278 -7022513.736 accept 180.3558199030 -61.1527410473 expect -9320622.179 -7351806.789 accept -179.1949344790 -69.7752745189 expect -8408251.537 -8232691.330 accept -169.9844730348 -68.9207602047 expect -8120078.404 -7775444.464 accept -159.6630800816 -68.8474900484 expect -7673728.921 -7392368.231 accept -149.1514601079 -68.2067669189 expect -7247339.088 -6962408.917 accept -139.0879724776 -67.2990645502 expect -6835622.742 -6542934.347 accept -129.7364301888 -67.0709630928 expect -6389191.557 -6259081.373 accept -119.2525297571 -66.7130216826 expect -5885127.012 -5959615.515 accept -109.9942126502 -65.9323791506 expect -5456298.788 -5663479.323 accept -99.3718189580 -65.0050609815 expect -4952897.215 -5351531.121 accept -89.2126537463 -64.8003181492 expect -4438651.521 -5164919.484 accept -79.9699918529 -64.7417604712 expect -3968262.754 -5031339.112 accept -69.2852704168 -64.0719899001 expect -3442756.784 -4828682.323 accept -59.7713311869 -63.5883281794 expect -2971057.023 -4681634.618 accept -49.4591068015 -63.3603315571 expect -2455101.158 -4574825.541 accept -39.9221210763 -62.7166029149 expect -1985110.513 -4444066.132 accept -29.4033816775 -62.2459162224 expect -1463165.847 -4343593.454 accept -19.4562997110 -62.1057544781 expect -967613.655 -4296313.938 accept -9.6356219787 -61.1466320407 expect -481338.112 -4176929.015 accept 0.6588822473 -60.1858259817 expect 33069.831 -4072773.374 accept 10.9123408994 -59.3942535459 expect 550076.991 -4001105.274 accept 20.9249231576 -58.4264590285 expect 1060832.702 -3926177.072 accept 30.8182477174 -57.5436346739 expect 1571551.690 -3873152.034 accept 40.8062160819 -57.3120408186 expect 2088248.634 -3896213.274 accept 50.5783407631 -57.2502847616 expect 2597056.641 -3948016.223 accept 60.3795380424 -57.2275967096 expect 3112185.488 -4018270.594 accept 70.5341633320 -56.6610699237 expect 3662178.471 -4054556.527 accept 80.2621021518 -56.0832576961 expect 4200156.553 -4102899.263 accept 90.9924788950 -55.3105280599 expect 4810349.300 -4164560.690 accept 100.1850294843 -54.9771464696 expect 5337043.742 -4270248.554 accept 110.2445797630 -54.0658285398 expect 5943289.082 -4356935.761 accept 120.6069857779 -53.3088500534 expect 6579954.717 -4496181.899 accept 130.2789945294 -52.8143911635 expect 7180795.395 -4682156.275 accept 140.3311899960 -52.3084536190 expect 7816505.414 -4918688.318 accept 150.9519295109 -52.0098336142 expect 8481836.319 -5247130.328 accept 160.6630418532 -51.6702008173 expect 9089733.809 -5596578.481 accept 170.5027767145 -51.5189598167 expect 9675637.514 -6026539.807 accept 180.8758506742 -51.2201444439 expect -10177185.136 -6434755.421 accept -179.8411044403 -59.9393178899 expect -9443976.994 -7246273.236 accept -169.3270885266 -59.1985019770 expect -8995948.520 -6701742.553 accept -159.0120452761 -59.1242618883 expect -8464180.924 -6272112.895 accept -149.5670531797 -58.7286202895 expect -7979472.299 -5884420.870 accept -139.0970719139 -58.1489537744 expect -7430300.432 -5483894.856 accept -129.1196020868 -57.2913818657 expect -6907494.210 -5112397.388 accept -119.7410720497 -56.9161549702 expect -6387194.447 -4845091.157 accept -109.7214510325 -55.9267756586 expect -5851170.964 -4534131.372 accept -99.1928708683 -55.4401681713 expect -5267711.955 -4300230.895 accept -89.2702698097 -55.2997481025 expect -4714550.947 -4139814.556 accept -79.4864123616 -54.8136229276 expect -4183440.927 -3971202.629 accept -69.3453324893 -54.6311145369 expect -3632724.847 -3848910.729 accept -59.8339416819 -53.6386169191 expect -3133296.168 -3676377.089 accept -49.3010973212 -52.9141596041 expect -2577599.457 -3539651.763 accept -39.8985039869 -52.8539647879 expect -2079543.627 -3483996.852 accept -29.3961250522 -52.3417298211 expect -1530564.484 -3397269.368 accept -19.5014059179 -51.3773597952 expect -1016792.801 -3288257.869 accept -9.9116288496 -50.4755131648 expect -517724.688 -3198035.336 accept 0.9743282127 -49.9184212313 expect 50962.745 -3147322.949 accept 10.6508090388 -49.0463066485 expect 558876.345 -3082894.951 accept 20.2170315032 -48.8012100804 expect 1063007.281 -3078773.024 accept 30.9229311437 -48.2225550224 expect 1632723.266 -3061426.633 accept 40.7489150310 -47.7931114403 expect 2161132.006 -3064760.741 accept 50.9882474140 -47.6183917612 expect 2717126.369 -3102179.138 accept 60.4951083293 -46.6454001087 expect 3249189.082 -3083060.891 accept 70.9554669962 -45.9763718998 expect 3843598.409 -3108377.147 accept 80.8848766764 -45.7846392844 expect 4415566.167 -3184255.726 accept 90.7402232455 -45.6088952189 expect 4996535.515 -3278794.703 accept 100.8696089733 -45.3585308636 expect 5610895.632 -3392002.899 accept 110.9170212513 -44.9478951297 expect 6242312.338 -3516148.140 accept 120.9353901019 -44.7761977319 expect 6885637.054 -3692702.792 accept 130.5310034140 -44.6491174665 expect 7518026.186 -3902292.936 accept 140.4911819383 -44.5558487119 expect 8188523.026 -4170828.177 accept 150.4583065377 -44.0055960197 expect 8893017.639 -4454795.754 accept 160.9810806536 -43.8499619826 expect 9618889.130 -4871178.683 accept 170.9230729547 -43.0316016948 expect 10335543.716 -5284759.370 accept 180.3381763034 -42.9767512080 expect -10889181.304 -5775308.758 accept -179.6530173733 -49.3872071390 expect -10359694.674 -6307512.780 accept -169.2223492311 -49.3351161906 expect -9766541.338 -5765269.396 accept -159.0508327192 -48.4914027020 expect -9200440.534 -5224400.981 accept -149.1969312324 -48.3204060368 expect -8581410.142 -4822470.693 accept -139.4509254495 -48.2401083822 expect -7956818.463 -4491703.439 accept -129.3953824841 -47.8590661722 expect -7326697.432 -4175808.479 accept -119.8005497638 -46.8591030615 expect -6750164.464 -3860038.014 accept -109.6371217071 -46.1165362624 expect -6133381.272 -3598151.680 accept -99.7572070447 -45.2128579221 expect -5546071.645 -3363379.628 accept -89.9756152550 -44.7940088251 expect -4964897.006 -3200898.955 accept -79.8767538149 -44.0954679134 expect -4380500.181 -3035551.265 accept -69.7070595303 -43.9155165619 expect -3796215.985 -2932502.304 accept -59.4162002941 -43.4002905582 expect -3219205.574 -2819636.846 accept -49.0092629276 -43.1455519878 expect -2642338.036 -2741942.445 accept -39.2149367905 -43.1396428570 expect -2105326.203 -2698721.256 accept -29.1962960438 -42.8806359633 expect -1563179.016 -2646665.138 accept -19.9520584705 -41.9858293563 expect -1068083.171 -2560330.542 accept -9.1048749080 -41.9710426109 expect -486662.244 -2545041.394 accept 0.7345999991 -41.2273949585 expect 39313.660 -2488176.296 accept 10.3358537029 -40.3051838201 expect 554536.579 -2427518.000 accept 20.9085122254 -40.0048024560 expect 1124375.953 -2420626.128 accept 30.8006774663 -39.6693852781 expect 1661863.596 -2419220.994 accept 40.8129777308 -39.2404566099 expect 2212297.195 -2420473.575 accept 50.5680928423 -38.6381409742 expect 2757518.213 -2417613.121 accept 60.1150468046 -38.1919404446 expect 3299910.135 -2434340.337 accept 70.2009887167 -37.7503319244 expect 3885091.635 -2465277.945 accept 80.9433725042 -37.6092654477 expect 4521685.524 -2537447.427 accept 90.5450445832 -36.8932371421 expect 5114490.299 -2572080.702 accept 100.2810830549 -36.2548338054 expect 5735525.829 -2629983.784 accept 110.9555828878 -35.3701130040 expect 6448221.387 -2701332.745 accept 120.1071621348 -34.4239368091 expect 7092033.959 -2769933.898 accept 130.3959151631 -33.5728740994 expect 7849212.306 -2898252.702 accept 140.3966149747 -33.1007607627 expect 8616443.259 -3102144.372 accept 150.7804103414 -32.1498899431 expect 9474137.117 -3341084.536 accept 160.1294242200 -31.8677972120 expect 10255119.633 -3696347.819 accept 170.4926366887 -31.4760758031 expect 11130499.187 -4208944.238 accept 180.0855468805 -30.6845915110 expect -11924104.915 -4768423.647 accept -179.7273734266 -39.1658927524 expect -11204917.679 -5465459.947 accept -169.0184062476 -39.0055263654 expect -10493158.815 -4820337.657 accept -159.3652618381 -38.5207051121 expect -9823706.550 -4298001.622 accept -149.2442235779 -37.7437811442 expect -9109090.826 -3817330.153 accept -139.7582139899 -37.0859345868 expect -8430885.041 -3451397.102 accept -129.6848433059 -36.3175720184 expect -7723246.989 -3125416.425 accept -119.9915203582 -35.8537888060 expect -7052847.569 -2889355.259 accept -109.3741418826 -35.0916931594 expect -6347630.931 -2655717.550 accept -99.4860284168 -34.9974460643 expect -5702866.657 -2521419.373 accept -89.2610432155 -34.3775651948 expect -5064932.880 -2367691.750 accept -79.8980045489 -33.8084333270 expect -4496443.808 -2246842.786 accept -69.7744908122 -33.0535274429 expect -3897431.224 -2124030.733 accept -59.1741793523 -33.0245833220 expect -3279277.127 -2064611.925 accept -49.3252940718 -32.7520928002 expect -2718021.990 -2003727.901 accept -39.9837216743 -32.6820581131 expect -2193100.779 -1967477.081 accept -29.6634260976 -32.4559533116 expect -1621044.860 -1926376.015 accept -19.0425557421 -31.6151992048 expect -1038939.460 -1853758.302 accept -9.3464940981 -30.7676922872 expect -509764.792 -1790359.701 accept 0.2271197201 -29.8880719395 expect 12396.124 -1731908.926 accept 10.3155486517 -29.7265492803 expect 563481.490 -1725116.999 accept 20.1839066538 -29.2623085092 expect 1105114.379 -1705401.818 accept 30.7346251189 -29.0674505554 expect 1688443.872 -1710201.130 accept 40.3481516035 -28.9492608062 expect 2225766.274 -1724893.934 accept 50.0990389861 -28.2777777134 expect 2780666.281 -1710825.486 accept 60.6952552496 -28.1677069370 expect 3393248.667 -1743873.889 accept 70.5261359994 -27.5835336013 expect 3977728.481 -1751038.589 accept 80.1654103496 -27.3421123535 expect 4564865.482 -1789330.795 accept 90.0545702452 -26.7993470554 expect 5189164.780 -1818900.217 accept 100.4846205812 -26.3321377541 expect 5873417.372 -1871564.464 accept 110.1454350755 -25.9649549970 expect 6535803.844 -1941828.510 accept 120.7008393450 -25.1526201253 expect 7304855.220 -2009386.765 accept 130.2389402517 -24.5243234665 expect 8043003.839 -2105873.386 accept 140.5571385114 -23.6272475940 expect 8905477.819 -2232473.683 accept 150.7016104068 -22.6662177106 expect 9831511.358 -2411871.251 accept 160.2968564724 -22.4382538673 expect 10756906.363 -2747796.799 accept 170.0748311294 -22.3860117924 expect 11733461.723 -3272290.561 accept 180.6506603946 -22.3728965349 expect -12617064.805 -3974167.403 accept -179.6121212366 -29.7691402382 expect -11979921.495 -4667118.695 accept -169.7119531770 -29.2443283665 expect -11222154.909 -3947901.934 accept -159.9169552740 -28.8913170762 expect -10396184.341 -3393588.505 accept -149.4387461093 -28.3593158878 expect -9515015.598 -2929955.771 accept -139.1669991056 -27.7889137233 expect -8680487.806 -2582510.633 accept -129.3433138149 -27.6725871069 expect -7909885.992 -2361735.805 accept -119.4082102166 -27.0902195884 expect -7179377.517 -2149357.741 accept -109.1270697537 -26.4319377776 expect -6459215.181 -1966619.626 accept -99.5563015601 -26.1428020328 expect -5813582.857 -1849104.872 accept -89.7150051747 -25.6767220293 expect -5176646.600 -1736438.788 accept -79.8755275705 -25.5910332550 expect -4558775.834 -1666806.999 accept -69.2123495570 -25.4383454527 expect -3910739.366 -1600921.909 accept -59.5658576308 -24.9358234812 expect -3341553.359 -1527967.578 accept -49.6169420229 -24.6444229699 expect -2765720.514 -1476703.148 accept -39.6482793765 -24.5549160001 expect -2198313.929 -1445718.653 accept -29.9777355053 -24.4560828087 expect -1655580.275 -1421139.862 accept -19.1713792157 -23.8256788800 expect -1056119.979 -1368757.459 accept -9.7201400616 -22.9994139064 expect -535034.881 -1312183.508 accept 0.5529890539 -22.3142121451 expect 30441.381 -1269018.253 accept 10.1921781407 -21.7066906983 expect 561728.285 -1235326.876 accept 20.3795548881 -21.4584354172 expect 1125554.829 -1227765.197 accept 30.9486641454 -20.5179662552 expect 1716375.316 -1183995.648 accept 40.8135756255 -19.5974702869 expect 2275225.833 -1144577.227 accept 50.6799989922 -19.0578134683 expect 2842703.974 -1132076.757 accept 60.3954481899 -18.9092744312 expect 3411747.962 -1147888.925 accept 70.4453750849 -18.1833498293 expect 4016856.952 -1133502.787 accept 80.7669954500 -18.0082544937 expect 4655570.265 -1161429.470 accept 90.3894774181 -17.1937702409 expect 5275686.661 -1150673.533 accept 100.6108192539 -16.6801050032 expect 5961256.060 -1170474.059 accept 110.0973977891 -16.5576038471 expect 6627078.423 -1224759.106 accept 120.1744128824 -16.1385356593 expect 7378167.388 -1276012.928 accept 130.9958002425 -15.9988390562 expect 8242075.942 -1381187.077 accept 140.7561364481 -15.5140276310 expect 9095834.646 -1478681.653 accept 150.6609169826 -14.6810594541 expect 10062376.739 -1592616.483 accept 160.4729986277 -13.9641207869 expect 11146505.848 -1801865.434 accept 170.2580177319 -13.4019318955 expect 12380828.321 -2216425.290 accept 180.9723789714 -13.2206420941 expect -13517837.926 -2963599.665 accept -179.3731911762 -19.9754368483 expect -12847531.905 -3741083.544 accept -169.7161399275 -19.5104972567 expect -11896617.727 -2931048.939 accept -159.8730648764 -19.1040998843 expect -10868152.967 -2367829.374 accept -149.7374470773 -18.6126354884 expect -9867014.825 -1975013.098 accept -139.9375452903 -17.7675516184 expect -8981245.860 -1675376.283 accept -129.7815708228 -17.6801059141 expect -8119555.541 -1510161.646 accept -119.9989025368 -16.8565473881 expect -7357650.403 -1331626.610 accept -109.4668005307 -16.5581522944 expect -6581807.300 -1220164.823 accept -99.7153754334 -15.6910883516 expect -5906017.650 -1094954.342 accept -89.4547033980 -15.0712716331 expect -5224831.274 -1002148.542 accept -79.7184992899 -14.3419936196 expect -4603946.126 -917271.900 accept -69.1303448651 -14.1075269728 expect -3949591.288 -871282.018 accept -59.3174344116 -13.5400304440 expect -3361462.250 -813740.055 accept -49.5741035561 -13.3755321856 expect -2789994.364 -786454.715 accept -39.3784173894 -12.8080837456 expect -2203910.826 -739131.430 accept -29.0461086285 -11.9729370492 expect -1619004.622 -680848.917 accept -19.4469305321 -11.4082175114 expect -1081054.702 -642575.882 accept -9.8174680795 -10.9835914719 expect -544889.181 -615082.754 accept 0.5832005675 -10.8244025391 expect 32351.518 -604970.269 accept 10.1821355116 -10.3112442518 expect 565312.650 -577140.866 accept 20.2673282337 -9.6748015367 expect 1127704.663 -544392.415 accept 30.6144784184 -9.1704395398 expect 1709477.558 -521063.652 accept 40.9538042404 -8.8387995357 expect 2298042.701 -509435.004 accept 50.3542282310 -8.5281632402 expect 2841826.819 -499951.327 accept 60.3333585361 -8.0618147294 expect 3430944.123 -483342.239 accept 70.6491293618 -7.1912559947 expect 4056498.671 -443543.575 accept 80.6385279900 -6.8438270481 expect 4680976.678 -436529.709 accept 90.2779063923 -5.9477536652 expect 5306879.206 -394211.155 accept 100.1664295698 -5.3498071015 expect 5976711.329 -371637.191 accept 110.7526797683 -5.0501900022 expect 6732766.131 -372725.699 accept 120.9395236376 -4.1674482190 expect 7511218.339 -330114.528 accept 130.9122756981 -3.3925202280 expect 8335031.912 -292646.657 accept 140.8622591730 -2.6308107403 expect 9239878.756 -252678.754 accept 150.8989218927 -2.4542899296 expect 10271402.485 -272075.289 accept 160.6447981803 -1.8622854664 expect 11459900.021 -252282.512 accept 170.0641946050 -1.8083280762 expect 12937298.765 -340309.531 accept 180.2204067090 -0.9025887443 expect -15802698.304 -708580.364 accept -179.6574960805 -9.4004926352 expect -14071690.323 -2539687.191 accept -169.9633593449 -9.3485472828 expect -12600921.441 -1621732.042 accept -159.8318959140 -9.0791301932 expect -11229195.403 -1182926.612 accept -149.3318176481 -8.2240809358 expect -10048132.890 -884380.748 accept -139.7502220731 -8.1745353165 expect -9100377.717 -773484.631 accept -129.6865020082 -7.4201521250 expect -8212868.503 -632791.243 accept -119.8341799056 -7.3634864983 expect -7413306.708 -578750.081 accept -109.3372260355 -6.6955324842 expect -6624672.935 -490071.015 accept -99.2316583050 -6.5126588093 expect -5909647.259 -450435.980 accept -89.1084063299 -5.8865293624 expect -5229890.468 -388173.334 accept -79.5816448608 -4.9451058817 expect -4616528.397 -313937.264 accept -69.7454406840 -4.1892409654 expect -4004251.255 -257320.628 accept -59.1123214226 -3.4679775330 expect -3362108.879 -206832.813 accept -49.6901763540 -2.4894793557 expect -2807061.182 -145333.833 accept -39.7692356409 -2.0816660918 expect -2233545.578 -119370.078 accept -29.1740796470 -2.0439733787 expect -1630731.974 -115537.821 accept -19.4550859282 -1.5656815613 expect -1084212.955 -87689.967 accept -9.7922127612 -1.0233548058 expect -544742.779 -57002.643 accept 0.9474400360 -0.4509174844 expect 52675.338 -25070.513 accept 10.5907531926 0.4791919877 expect 589235.938 26699.153 accept 20.4649033892 1.2637472104 expect 1140802.061 70831.592 accept 30.7336324422 1.4750458750 expect 1719011.039 83523.161 accept 40.5647880296 1.7200843296 expect 2279244.812 98755.090 accept 50.0975358484 2.4206654883 expect 2830862.498 141431.370 accept 60.5029363117 2.7469759008 expect 3445462.137 164373.737 accept 70.2466016765 3.0425721977 expect 4035802.541 187111.823 accept 80.9459783007 3.1839332730 expect 4704897.806 203036.380 accept 90.5736386070 3.8575317925 expect 5329437.218 255822.534 accept 100.2061055405 4.6282142532 expect 5980767.943 321504.886 accept 110.3986145890 5.0514461622 expect 6706729.320 371990.508 accept 120.2464205180 5.7344268609 expect 7451933.549 451953.904 accept 130.8487160288 6.0228137911 expect 8319391.922 519216.608 accept 140.7982222585 6.8874824898 expect 9210050.487 660196.582 accept 150.4954910508 7.7970420656 expect 10177774.848 854370.803 accept 160.4294052652 8.2748648014 expect 11325245.938 1096444.978 accept 170.1405366440 9.1299598915 expect 12640596.907 1598916.038 accept 180.3816637148 9.5293212197 expect -14048784.308 2552595.319 accept -179.7984994410 0.1294010891 expect -16146197.188 164013.021 accept -169.9075691637 0.6099770177 expect -12921454.488 114297.725 accept -159.8158750237 0.7517410096 expect -11353744.941 99828.675 accept -149.2779203136 0.9860535478 expect -10098753.673 106507.361 accept -139.0081254157 1.7045245488 expect -9066221.814 160142.630 accept -129.4952290080 2.3401856085 expect -8216193.058 199202.767 accept -119.6801874031 2.7438994101 expect -7415046.859 215250.709 accept -109.8061814794 3.3602853603 expect -6666538.461 246453.448 accept -99.4072778605 4.2027372999 expect -5926167.742 290710.723 accept -89.4794192866 4.3761667210 expect -5256523.244 288879.125 accept -79.0507721337 5.0765421386 expect -4582771.048 321680.244 accept -69.6180300324 6.0161102274 expect -3994695.190 369666.719 accept -59.1757895169 6.6698983501 expect -3363484.198 398396.059 accept -49.5137470044 7.3709595382 expect -2793929.460 431038.915 accept -39.8808038489 7.5057731743 expect -2237570.835 431453.557 accept -29.1954492510 7.5302153709 expect -1630244.834 426646.570 accept -19.1423038522 7.9594801079 expect -1065438.676 446891.077 accept -9.1680642851 8.1530010191 expect -509342.190 455357.512 accept 0.1739948339 8.7651393303 expect 9659.433 488991.424 accept 10.6214035743 9.0690282061 expect 590010.824 507153.456 accept 20.6145954060 9.2785749606 expect 1147299.618 522064.856 accept 30.7022010899 9.4344357510 expect 1714266.145 536235.946 accept 40.6920436024 9.6859857008 expect 2282298.409 558407.819 accept 50.0779641219 10.4703837993 expect 2823489.061 614432.905 accept 60.7862954507 10.8675657776 expect 3453869.707 653748.275 accept 70.7316745409 11.5482616810 expect 4053689.320 714785.183 accept 80.5969392822 11.6377813068 expect 4667778.775 744691.155 accept 90.7414911957 12.5339468232 expect 5319510.529 835981.260 accept 100.1954199623 13.4045312190 expect 5951099.489 935712.315 accept 110.4203894507 14.1556288162 expect 6667631.291 1047159.283 accept 120.0358650247 14.9021123434 expect 7379023.541 1176295.202 accept 130.5020570647 15.6380412243 expect 8205734.023 1344014.139 accept 140.0437745429 16.3999265763 expect 9015988.290 1549714.704 accept 150.1545875606 17.2596103884 expect 9945182.384 1847801.749 accept 160.8486565585 17.2719148256 expect 11052859.415 2202730.719 accept 170.5318144721 17.4322460420 expect 12132375.221 2742993.334 accept 180.3687704920 18.1343689010 expect -13055638.227 3577371.152 accept -179.0206911010 10.9105813395 expect -13789973.226 2666800.487 accept -169.3809207620 11.7944347582 expect -12368824.571 1941584.924 accept -159.6203666385 12.0516777085 expect -11114668.475 1542870.752 accept -149.3198962805 12.6359180213 expect -9971639.315 1349414.366 accept -139.5233350739 13.5860975895 expect -9014835.165 1278723.421 accept -129.7243722634 14.3491243406 expect -8156662.238 1224564.849 accept -119.1501250857 14.5216128511 expect -7314420.668 1138694.060 accept -109.0371133259 15.0539906817 expect -6561854.668 1105088.504 accept -99.9179969843 15.7258631745 expect -5919649.304 1098546.238 accept -89.9773066160 16.3601816158 expect -5252963.299 1091861.766 accept -79.9963611264 16.9875171909 expect -4611500.971 1091043.258 accept -69.8357048214 17.5002106365 expect -3982187.532 1087834.694 accept -59.7680108732 18.2166379428 expect -3376669.886 1102916.942 accept -49.6200663069 18.3027049517 expect -2783116.693 1083573.630 accept -39.6880697045 18.9352023262 expect -2212450.852 1102685.583 accept -29.7767726581 19.4447171460 expect -1652161.342 1118356.380 accept -19.4001109678 19.4907667291 expect -1073007.877 1110251.090 accept -9.2283971215 19.7074346080 expect -509423.729 1116879.960 accept 0.4114648584 20.3715526988 expect 22689.511 1154092.449 accept 10.2069053765 20.5961272596 expect 563089.088 1169589.074 accept 20.2755153863 21.5012785791 expect 1119736.801 1230222.492 accept 30.7133762328 21.6331993543 expect 1701464.196 1250677.021 accept 40.8056510679 21.9458705640 expect 2269913.206 1287337.418 accept 50.1343812652 22.7322724754 expect 2801331.228 1358020.376 accept 60.8279717815 23.6042650645 expect 3421001.843 1446782.481 accept 70.8396198168 23.6161521840 expect 4017785.556 1487799.299 accept 80.0150193753 24.3176168344 expect 4575413.559 1580619.042 accept 90.0092672617 25.2892002351 expect 5198545.082 1711055.973 accept 100.9112248813 25.3488952172 expect 5911825.956 1802460.981 accept 110.6217357170 26.3130477559 expect 6564636.348 1974281.767 accept 120.8513137656 27.2915307690 expect 7281361.160 2187092.016 accept 130.4863483330 27.3717431335 expect 8004199.764 2357290.762 accept 140.8500841767 27.7063050682 expect 8819787.218 2617012.258 accept 150.1012624908 28.3597926319 expect 9571394.994 2952268.869 accept 160.6866246900 28.7569504248 expect 10470505.204 3415900.295 accept 170.4254637193 28.9327804979 expect 11304554.466 3961238.714 accept 180.5169825410 29.4454995234 expect -11997898.864 4629333.323 accept -179.9618334794 20.6361252181 expect -12837832.007 3860315.017 accept -169.0544424157 21.5377240973 expect -11689194.203 3115133.126 accept -159.5209982370 22.3343317585 expect -10685600.977 2702674.102 accept -149.4879824588 23.0136475051 expect -9708605.390 2409089.845 accept -139.0901904339 23.0833518677 expect -8794463.480 2149634.537 accept -129.5571592628 23.7534110208 expect -8003843.252 2027714.928 accept -119.3172537231 24.1047564008 expect -7218181.243 1905508.759 accept -109.6905533732 24.4734381465 expect -6521952.824 1821700.764 accept -99.8910561910 25.1938730946 expect -5845160.963 1781991.443 accept -89.4462296168 25.8863398753 expect -5157803.362 1749387.629 accept -79.8103593109 26.1645676522 expect -4550997.853 1705809.308 accept -69.6977621502 26.6287263876 expect -3933504.408 1682619.842 accept -59.8282049770 27.3537990267 expect -3346444.748 1686544.376 accept -49.1454058737 27.7787492517 expect -2728032.304 1675388.053 accept -39.3139281426 28.6345630085 expect -2168591.423 1702020.943 accept -29.4615301172 29.5694962171 expect -1616690.922 1739665.855 accept -19.8075997700 29.9373943713 expect -1083433.316 1747604.085 accept -9.6080286962 30.8618954465 expect -523974.378 1796510.976 accept 0.2364142241 31.2844282188 expect 12878.465 1820358.353 accept 10.8007276009 32.1455048270 expect 588003.896 1879462.172 accept 20.9914890120 32.9845571828 expect 1143393.118 1945198.869 accept 30.5649011670 33.3185372073 expect 1668534.713 1985021.750 accept 40.9928890043 33.9545285454 expect 2244749.779 2055570.711 accept 50.5960695735 34.5487194112 expect 2781374.579 2130923.541 accept 60.7989209340 34.6635486089 expect 3363023.887 2186540.557 accept 70.4270746519 35.2029582655 expect 3919596.726 2281175.829 accept 80.5548872020 35.6698817700 expect 4518398.282 2388864.639 accept 90.4057879600 36.1979951235 expect 5114553.759 2517121.960 accept 100.9114892728 36.5847249808 expect 5770875.600 2663935.228 accept 110.7309087130 37.4367897060 expect 6394670.813 2868606.494 accept 120.3064304930 38.2603562874 expect 7018617.432 3101989.836 accept 130.7451971478 39.2489093279 expect 7711939.115 3412678.003 accept 140.5750239477 39.4115866274 expect 8403813.025 3690760.565 accept 150.9679439696 39.5896177639 expect 9149846.161 4055027.485 accept 160.6524332599 40.0167951770 expect 9830011.049 4497209.502 accept 170.4722281589 40.1946244182 expect 10510426.457 5006824.562 accept 180.6722342584 40.4624940717 expect -11073707.514 5547471.043 accept -179.2831936865 30.3756797354 expect -11903513.624 4695492.667 accept -169.3418436736 30.6105366204 expect -11096888.315 4057764.336 accept -159.1006283258 31.5893799475 expect -10184786.130 3622256.417 accept -149.1140289956 31.8719516081 expect -9350613.109 3255936.184 accept -139.6996779260 31.9484140590 expect -8599676.794 2977225.067 accept -129.5556705121 32.3449943633 expect -7818577.975 2772332.434 accept -119.9724472555 32.9392845832 expect -7113428.652 2642433.850 accept -109.7343311582 33.1372998541 expect -6404617.901 2502642.905 accept -99.9994915413 33.6225969013 expect -5754979.598 2420019.593 accept -89.2957244981 34.5626482766 expect -5064999.094 2381966.292 accept -79.0486888283 34.5979570645 expect -4437892.568 2298043.032 accept -69.3692667775 34.8321767388 expect -3860329.808 2247797.371 accept -59.5430097857 35.6428903265 expect -3284552.144 2248836.044 accept -49.1028389556 36.0212581738 expect -2689697.467 2226461.965 accept -39.5707710224 36.0991805410 expect -2157256.671 2196683.027 accept -29.2950741715 36.7864032717 expect -1588948.254 2214928.423 accept -19.8971059232 37.5191207820 expect -1075080.881 2246281.883 accept -9.1855199225 38.0327790292 expect -495017.437 2268788.188 accept 0.3119584346 38.1079667572 expect 16801.964 2270513.428 accept 10.9783425364 38.7495700245 expect 590921.384 2319580.204 accept 20.7896762683 39.3106928548 expect 1119567.208 2371677.736 accept 30.1643265779 40.2243862141 expect 1625278.990 2456916.428 accept 40.5328985021 41.0696713596 expect 2188008.492 2551119.506 accept 50.2300300588 41.3865813447 expect 2721620.789 2616277.410 accept 60.6521695116 42.3133443814 expect 3298047.285 2744030.130 accept 70.7309222598 43.1274592749 expect 3863356.586 2878383.982 accept 80.8866702938 43.7702855800 expect 4443854.438 3018951.902 accept 90.5499599785 44.5851564761 expect 5002600.615 3190179.530 accept 100.5821513570 44.6219279790 expect 5608351.970 3324185.783 accept 110.0283698987 45.0603376392 expect 6183798.502 3510751.426 accept 120.9059581322 45.6167638758 expect 6858556.379 3768914.576 accept 130.8382094225 46.2623868954 expect 7479609.418 4061298.725 accept 140.3050870951 46.3222134245 expect 8098901.115 4333302.419 accept 150.7692866141 46.9256636829 expect 8759282.574 4745456.067 accept 160.6423483126 47.3983214513 expect 9371550.773 5189253.049 accept 170.3660263984 48.1704941243 expect 9921744.591 5715878.904 accept 180.1728485128 48.3869676380 expect -10452810.767 6233145.886 accept -179.3366303542 40.5764857623 expect -11064985.137 5557446.981 accept -169.9016120813 40.9858722485 expect -10415017.640 5046984.188 accept -159.8421069000 41.3352033138 expect -9693683.857 4584354.161 accept -149.9878180879 41.8238911228 expect -8970272.939 4230268.129 accept -139.8603273594 42.3826076660 expect -8236029.698 3946841.156 accept -129.0166066782 42.7333243644 expect -7482247.804 3687948.837 accept -119.3388991582 43.6373394362 expect -6814813.949 3556870.147 accept -109.3805129308 43.9116610208 expect -6170341.281 3398585.005 accept -99.7947564839 44.3361622416 expect -5565812.101 3288505.423 accept -89.9466466666 45.2719659306 expect -4955176.241 3240762.956 accept -79.0227489515 46.1464400004 expect -4302718.131 3195776.905 accept -69.8148943004 46.5943287210 expect -3771570.667 3149477.147 accept -59.7413792811 47.1807640935 expect -3202028.794 3121345.514 accept -49.8815279751 47.1898937016 expect -2660293.312 3061471.431 accept -39.6116669497 47.2588276162 expect -2103308.236 3017259.465 accept -29.2023113842 48.0824833396 expect -1541812.718 3044777.159 accept -19.5149453395 48.6918139540 expect -1026306.480 3068551.199 accept -9.6336458263 49.0434719530 expect -505461.907 3081586.975 accept 0.4543975995 49.7836456546 expect 23777.504 3136347.935 accept 10.9700141403 50.3323164289 expect 573327.428 3187475.308 accept 20.2945040946 50.5294422839 expect 1061285.060 3219471.341 accept 30.9938167603 50.9444135429 expect 1622296.392 3284285.467 accept 40.8850179873 51.0892523800 expect 2145326.320 3336819.857 accept 50.8160896333 51.7186327718 expect 2670455.247 3444230.679 accept 60.5975018363 51.7472739987 expect 3198356.358 3513374.312 accept 70.9211780937 52.2961472542 expect 3755656.007 3649240.500 accept 80.4886876286 52.4940883205 expect 4283198.775 3765359.201 accept 90.6063621158 52.8917619975 expect 4845216.723 3926850.931 accept 100.0213726456 53.0072065012 expect 5381474.017 4075643.010 accept 110.7666710262 53.1761320615 expect 6002387.050 4279156.043 accept 120.4133749558 53.6140202991 expect 6556934.536 4522304.309 accept 130.4689014096 54.3464457214 expect 7124048.584 4841940.061 accept 140.1319502152 55.1999540459 expect 7653482.073 5205936.544 accept 150.0406833955 55.2259750645 expect 8231914.631 5536917.537 accept 160.6632656727 55.6152942863 expect 8813145.718 5981687.838 accept 170.1022885868 55.6436461375 expect 9328803.345 6391660.249 accept 180.5533384236 56.5237207562 expect -9735738.829 6914894.208 accept -179.2247622429 50.9111169957 expect -10208607.447 6413665.425 accept -169.7924972767 51.7536923319 expect -9617111.948 6014189.958 accept -159.6893113052 52.2420602240 expect -8992940.568 5611018.295 accept -149.2250378095 52.6608417101 expect -8338627.076 5249471.462 accept -139.5235597836 52.6880191938 expect -7748063.996 4931880.210 accept -129.8722920081 52.7891165561 expect -7157279.641 4668965.541 accept -119.6585401358 53.3027824621 expect -6523737.589 4474430.220 accept -109.4717350642 53.5457403772 expect -5914835.972 4290989.912 accept -99.3049585904 53.8711625087 expect -5317309.229 4147683.328 accept -89.1067456005 53.9601597136 expect -4736479.726 4008096.136 accept -79.6629562504 54.6547978698 expect -4196286.812 3958062.567 accept -69.8279241005 55.2371531832 expect -3648862.266 3910629.370 accept -59.9635334446 55.6192322641 expect -3113545.379 3860627.969 accept -49.9264269173 56.5572332986 expect -2571251.312 3877332.211 accept -39.0317197762 56.9105729373 expect -2000116.599 3848938.560 accept -29.8485414955 57.1405754683 expect -1524573.629 3831116.411 accept -19.7173893031 57.8895638096 expect -1001946.186 3871323.208 accept -9.5284598326 58.3169856731 expect -482748.075 3893995.092 accept 0.5519708680 59.1698787655 expect 27843.757 3971499.805 accept 10.3102576934 59.9070126591 expect 518383.957 4051426.264 accept 20.2471682871 60.1178484569 expect 1017884.749 4092029.150 accept 30.7186291790 60.9667316568 expect 1539950.301 4213756.217 accept 40.5361666024 61.0726031598 expect 2035176.439 4271402.857 accept 50.1593218026 61.5209991783 expect 2518306.294 4378497.037 accept 60.6426706803 61.6933237333 expect 3051461.747 4479017.707 accept 70.0635629430 62.5101492783 expect 3519070.146 4659116.189 accept 80.7525125683 62.7158368686 expect 4066565.729 4805766.003 accept 90.3486210746 63.2666959744 expect 4549151.501 5000629.808 accept 100.3103261370 63.6630728713 expect 5055042.336 5206928.884 accept 110.4333486366 64.5625075877 expect 5544588.666 5503654.246 accept 120.2948792100 65.1058556815 expect 6028251.873 5782340.030 accept 130.5927832047 65.6748652993 expect 6522940.319 6105850.437 accept 140.6012624076 66.3132526636 expect 6983093.737 6463380.696 accept 150.8560042350 67.2695080089 expect 7407700.123 6899834.886 accept 160.4312759692 67.7825785445 expect 7809736.690 7288594.640 accept 170.6182278472 67.8589725624 expect 8257493.223 7675010.400 accept 180.6346540849 67.9886371340 expect -8615169.523 8037735.613 accept -179.3862724658 60.0765397412 expect -9409858.860 7237943.395 accept -169.8319368721 60.7048897911 expect -8892462.330 6874089.420 accept -159.7586035106 60.8099423764 expect -8373658.128 6477781.193 accept -149.4451039042 60.8141416010 expect -7830654.586 6105061.877 accept -139.0047357806 61.2863951253 expect -7238746.216 5824802.025 accept -129.8203349111 61.6531963428 expect -6721103.700 5609206.409 accept -119.9476298931 62.1392105751 expect -6163537.132 5422258.227 accept -109.5579719203 62.6892094638 expect -5582691.055 5264366.147 accept -99.2658471038 63.6397776749 expect -5001335.481 5186237.501 accept -89.3190408834 64.2596249008 expect -4462613.903 5101850.571 accept -79.8712042695 64.9388669541 expect -3957350.660 5053673.793 accept -69.5143458832 64.9984151350 expect -3431555.358 4940124.081 accept -59.7549744076 65.5198053592 expect -2930531.346 4907235.702 accept -49.4522584709 65.6036762180 expect -2417789.843 4834516.760 accept -39.6862901889 66.0522555435 expect -1930302.252 4825742.348 accept -29.0676277333 66.9411073706 expect -1402365.485 4882358.630 accept -19.2947838600 67.6760455583 expect -924758.269 4940769.295 accept -9.8908486233 68.4759462496 expect -470791.969 5023788.451 accept 0.7684849115 68.4820083161 expect 36570.245 5017696.478 accept 10.9619070788 69.1271846096 expect 518988.202 5110914.008 accept 20.8669367778 69.4597522731 expect 985678.393 5178041.292 accept 30.9015874823 70.2228485919 expect 1450977.848 5320350.646 accept 40.3644592677 70.4447947660 expect 1893313.797 5401286.949 accept 50.4426463863 70.9370001765 expect 2357733.911 5540352.544 accept 60.8571701571 71.2042128565 expect 2840863.185 5668499.100 accept 70.2207367933 71.7513035300 expect 3263071.274 5846454.476 accept 80.9197701425 72.2949102709 expect 3742012.412 6060269.554 accept 90.9968622399 72.8817626201 expect 4182037.591 6296350.257 accept 100.6140322356 73.0673811891 expect 4615350.939 6484139.751 accept 110.0937440863 73.5055307300 expect 5020529.626 6730333.834 accept 120.1366082758 74.4237054204 expect 5401515.154 7088596.371 accept 130.7310123929 74.8225035758 expect 5829427.956 7399976.843 accept 140.7557697839 75.3904414554 expect 6198183.044 7749882.172 accept 150.8261707368 75.4797965998 expect 6601790.754 8045032.409 accept 160.3617186765 76.0800713976 expect 6904851.750 8427523.609 accept 170.3723095047 76.9487430385 expect 7160097.253 8883978.785 accept 180.8353501206 77.0774046082 expect -7451033.788 9196865.430 accept -179.7332624578 70.4393887237 expect -8352576.455 8331537.300 accept -169.3475485875 71.1990434220 expect -7846291.115 8032842.415 accept -159.2878573247 71.7560324305 expect -7364104.304 7757743.784 accept -149.0633836490 72.7063403889 expect -6827251.647 7569191.962 accept -139.6942131577 73.5163807564 expect -6341247.119 7423166.084 accept -129.8134586050 73.9643420027 expect -5867435.003 7238589.421 accept -119.5111667574 74.4925642590 expect -5368260.354 7086271.451 accept -109.3071655658 74.6108323823 expect -4906478.988 6896010.407 accept -99.5978241183 75.1275985390 expect -4438705.543 6806711.516 accept -89.2137798701 75.6445071712 expect -3946153.966 6730726.096 accept -79.1346028255 76.0354738901 expect -3479800.008 6660516.066 accept -69.0638837521 76.5148306463 expect -3014377.593 6628057.905 accept -59.1693658687 77.4423799967 expect -2543880.527 6705578.610 accept -49.4634180765 78.1864565763 expect -2099555.264 6773693.535 accept -39.8508153072 78.5203765703 expect -1681563.114 6777405.489 accept -29.6114412219 78.7797877191 expect -1243660.143 6778541.162 accept -19.1867860823 79.6454776348 expect -792468.866 6932699.289 accept -9.9582630982 80.1780113525 expect -406784.347 7037337.973 accept 0.8045877956 80.9823584569 expect 32275.315 7229013.962 accept 10.6198537389 81.4079136814 expect 421565.709 7348340.200 accept 20.7119981828 82.2150056342 expect 804489.400 7594469.330 accept 30.3505244935 82.6478465607 expect 1163485.615 7757650.909 accept 40.8655993799 82.9850228503 expect 1548903.569 7915659.478 accept 50.7222864861 83.6349635667 expect 1878353.123 8190395.486 accept 60.9484292190 84.1578297365 expect 2209073.061 8452785.649 accept 70.9281849579 84.2418711822 expect 2556434.364 8578888.109 accept 80.6700039410 84.7042458603 expect 2843072.410 8854459.520 accept 90.2195191862 85.4123304310 expect 3062193.052 9248148.617 accept 100.8308186795 86.2993989176 expect 3233512.836 9778762.722 accept 110.2610774864 87.2488149984 expect 3271018.801 10404221.588 accept 120.0282638556 87.9552070544 expect 3287869.350 10991486.485 accept 130.8896115077 88.8068669405 expect 3109942.957 11856621.688 accept 140.8963110473 89.7228156694 expect 2304894.417 13439579.058 accept 150.4318097094 90.4391164019 expect failure errno coord_transfm_invalid_coord accept 160.7127363327 90.6605815936 expect failure errno coord_transfm_invalid_coord accept 170.7492804294 90.8176634524 expect failure errno coord_transfm_invalid_coord accept 180.4583398460 90.9109976584 expect failure errno coord_transfm_invalid_coord accept -179.7591323884 80.4424115493 expect -6903393.952 9786682.886 accept -169.8822070628 80.7500588713 expect -6531575.755 9548129.356 accept -159.9945746863 80.9496010459 expect -6169272.314 9309595.559 accept -149.9417464353 81.4902616414 expect -5735389.987 9165799.932 accept -139.6978190662 82.2239127957 expect -5260169.587 9097892.645 accept -129.3012716885 83.0429415409 expect -4766013.283 9092128.730 accept -119.1898998617 83.3279294780 expect -4372133.574 8977351.665 accept -109.9862107641 83.5308131176 expect -4021828.937 8875471.473 accept -99.5860433196 84.1789855873 expect -3565728.493 8919632.792 accept -89.2677089488 84.1934914271 expect -3206581.559 8779174.807 accept -79.3268775666 84.9722014413 expect -2762678.704 8940646.845 accept -69.8212102616 85.0076410054 expect -2434287.034 8852607.599 accept -59.3204702001 85.5856170742 expect -2014364.675 8995544.515 accept -49.6961911803 86.4721128657 expect -1603812.213 9344086.957 accept -39.6159279805 86.7872101641 expect -1252848.694 9454262.547 accept -29.6502801920 87.1555761043 expect -912288.029 9626048.356 accept -19.4496938483 87.5951755895 expect -575538.601 9883989.014 accept -9.7937838350 88.5280609570 expect -257529.241 10651674.770 accept 0.9510780379 88.6715513574 expect 24396.904 10799028.318 accept 10.4788996031 89.0882607425 expect 245024.182 11333021.357 accept 20.0454282778 89.3798616914 expect 425823.124 11838688.204 accept 30.8762585995 89.9894216623 expect 237234.260 14954822.615 accept 40.2272544657 90.2854737833 expect failure errno coord_transfm_invalid_coord accept 50.3342208278 90.6201781373 expect failure errno coord_transfm_invalid_coord accept 60.0620171885 91.1323497706 expect failure errno coord_transfm_invalid_coord accept 70.7871678571 91.2021231110 expect failure errno coord_transfm_invalid_coord accept 80.7237355733 91.8207335323 expect failure errno coord_transfm_invalid_coord accept 90.5359055804 91.8495346522 expect failure errno coord_transfm_invalid_coord accept 100.2370378259 92.3201685216 expect failure errno coord_transfm_invalid_coord accept 110.7018248262 92.7082390660 expect failure errno coord_transfm_invalid_coord accept 120.4791498907 92.8320642395 expect failure errno coord_transfm_invalid_coord accept 130.7292413039 93.7863129954 expect failure errno coord_transfm_invalid_coord accept 140.1002623482 94.2304861566 expect failure errno coord_transfm_invalid_coord accept 150.7401582820 94.4002034978 expect failure errno coord_transfm_invalid_coord accept 160.9690930362 95.0432445572 expect failure errno coord_transfm_invalid_coord accept 170.5238000008 95.9332496636 expect failure errno coord_transfm_invalid_coord accept 180.5593997844 96.9295538910 expect failure errno coord_transfm_invalid_coord accept -179.2788799656 89.7588830795 expect -2720982.745 13966946.704 accept -169.2920835775 90.4241930348 expect failure errno coord_transfm_invalid_coord accept -159.9895526197 91.4107597532 expect failure errno coord_transfm_invalid_coord accept -149.2463523987 92.1662912669 expect failure errno coord_transfm_invalid_coord accept -139.5441662785 93.0270602663 expect failure errno coord_transfm_invalid_coord accept -129.2489121030 93.8575161591 expect failure errno coord_transfm_invalid_coord accept -119.2218964968 94.3245277139 expect failure errno coord_transfm_invalid_coord accept -109.6391371233 94.8605218216 expect failure errno coord_transfm_invalid_coord accept -99.7022656135 95.4188372117 expect failure errno coord_transfm_invalid_coord accept -89.9236110047 96.1459344102 expect failure errno coord_transfm_invalid_coord accept -79.3053114467 96.4284727639 expect failure errno coord_transfm_invalid_coord accept -69.9403123372 97.1204524330 expect failure errno coord_transfm_invalid_coord accept -59.8919954903 98.0976036625 expect failure errno coord_transfm_invalid_coord accept -49.4360361183 99.0132534146 expect failure errno coord_transfm_invalid_coord accept -39.1296215520 99.7082663882 expect failure errno coord_transfm_invalid_coord accept -29.7429317093 100.3804719805 expect failure errno coord_transfm_invalid_coord accept -19.9518617483 100.7090523427 expect failure errno coord_transfm_invalid_coord accept -9.8094666546 100.7731576636 expect failure errno coord_transfm_invalid_coord accept 0.6789399156 101.1890038152 expect failure errno coord_transfm_invalid_coord accept 10.3002789517 101.6303167682 expect failure errno coord_transfm_invalid_coord accept 20.3122591998 101.9746447499 expect failure errno coord_transfm_invalid_coord accept 30.8403181671 102.1868812356 expect failure errno coord_transfm_invalid_coord accept 40.5913833272 102.9224326125 expect failure errno coord_transfm_invalid_coord accept 50.4094599185 103.4226263618 expect failure errno coord_transfm_invalid_coord accept 60.2807542048 103.6337815648 expect failure errno coord_transfm_invalid_coord accept 70.1179652096 103.9375646519 expect failure errno coord_transfm_invalid_coord accept 80.0709906538 104.9002368302 expect failure errno coord_transfm_invalid_coord accept 90.4905546467 104.9051484801 expect failure errno coord_transfm_invalid_coord accept 100.8677613905 105.1778053719 expect failure errno coord_transfm_invalid_coord accept 110.7875504667 106.1651152124 expect failure errno coord_transfm_invalid_coord accept 120.3531685812 106.2777498204 expect failure errno coord_transfm_invalid_coord accept 130.5786379336 106.8735960149 expect failure errno coord_transfm_invalid_coord accept 140.2390732913 107.1035922681 expect failure errno coord_transfm_invalid_coord accept 150.2230766093 107.9665006223 expect failure errno coord_transfm_invalid_coord accept 160.0106199737 108.8731447468 expect failure errno coord_transfm_invalid_coord accept 170.2666681817 109.4591245928 expect failure errno coord_transfm_invalid_coord accept 180.2003062924 109.9750225424 expect failure errno coord_transfm_invalid_coord ------------------------------------------------------------------------------- # Test inverse ------------------------------------------------------------------------------- operation +proj=adams_ws2 +ellps=WGS84 ------------------------------------------------------------------------------- direction forward tolerance 1 mm accept 0 0 expect 0 0 roundtrip 1 accept 40 60 expect 2021909.611 4162291.966 roundtrip 1 accept 179.999 0 expect 16686159.356 0.000 roundtrip 1 accept -179.999 0 expect -16686159.356 0.000 roundtrip 1 accept 0 89.999 expect 0 15743336.122 roundtrip 1 accept 0 -89.999 expect 0 -15743336.122 roundtrip 1 # Results a bit different on x86 tolerance 3 mm accept 179.999 89.999 expect 693320.704 16030515.906 roundtrip 1 accept 179.999 -89.999 expect 693320.702 -16030515.904 roundtrip 1 accept -179.999 89.999 expect -693320.702 16030515.904 roundtrip 1 # This test fails with "roundtrip deviation: inf mm, expected: 3.000000 mm" on MacOS 13 x64 / clang 16.0.6 #accept -179.999 -89.999 #expect -693320.704 -16030515.906 #roundtrip 1 direction inverse accept 0.000005801264 16722285.492330472916 expect failure errno coord_transfm_outside_projection_domain proj-9.8.1/test/gie/peirce_q.gie000664 001750 001750 00000140533 15166171715 016461 0ustar00eveneven000000 000000 ------------------------------------------------------------ # This gie file was originally automatically generated using libproject # from where the peirce_q code was adapted ------------------------------------------------------------ ------------------------------------------------------------ # These test values were selected from 60 points # based on the from the original libproj test. Note that # Peirce_q distances include considerable distortion away # from axes, a feature of the projection to allow # tessellation but means that there is no uniform scale # across the coordinate space, so tolerance can be high. ------------------------------------------------------------ ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=square tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect -16684778.66 16659858.26 accept -139.6233037328 -87.8821294926 expect -16686136.62 16470363.98 accept -119.6070748182 -86.3003323104 expect -16595886.22 -16308355.92 accept -99.5789095738 -85.2121814625 expect -16396383.18 -16271023.42 accept -79.2799350968 -83.8692118030 expect -16141287.47 -16320788.59 accept -59.1007316490 -82.6429522913 expect -15910611.60 -16505541.92 accept -39.7694988813 -81.1240616181 expect 15720298.10 -16614965.01 accept -19.4219986373 -80.4596653260 expect 15746034.38 -16246050.72 accept 0.0372192405 -79.1830001774 expect 15852642.31 -15851534.09 accept 10.8072116146 -78.5286202425 expect 15985877.10 -15646515.07 accept 30.6949420481 -77.5251356225 expect 16361000.46 -15355648.15 accept 50.8838172783 -77.2075414406 expect 16558682.76 15284231.71 accept 70.9123606882 -75.6020670736 expect 16001764.31 15257501.18 accept 90.9423960847 -75.1187688922 expect 15509280.35 15547980.96 accept 110.1071594840 -74.5087602471 expect 15133049.74 15975564.05 accept 130.0104641839 -73.3709657282 expect 14849947.11 16543126.46 accept 150.5931126765 -73.1105693985 expect -14882991.09 16196524.65 accept 170.3454770498 -72.3687427114 expect -15093352.82 15561916.35 accept -179.4283603569 -79.2780817976 expect -15868107.22 15851237.11 accept -159.6140419013 -78.9064235928 expect -16189722.98 15580148.89 accept -139.5485387788 -78.1437071317 expect -16600189.56 15386190.03 accept -119.3040589991 -77.1517896758 expect -16316973.21 -15323941.76 accept -99.1733290138 -76.2249333909 expect -15804208.98 -15457233.95 accept -79.5627247417 -75.2438944725 expect -15346437.66 -15769094.64 accept -59.2065888779 -74.4787205556 expect -15021689.81 -16278889.61 accept -39.4027491539 -73.6693258790 expect 14885488.22 -16526762.78 accept -19.3829588944 -72.8068670782 expect 14968262.72 -15872213.73 accept 0.5951648774 -71.1110965727 expect 15222108.42 -15190983.68 accept 10.0089578122 -71.1061903043 expect 15489314.77 -14968374.05 accept 30.2470524798 -69.7155529444 expect 16124447.67 -14500816.44 accept 50.0855077954 -69.3112248587 expect 16498844.48 14388231.66 accept 70.3496352985 -68.4437853274 expect 15666358.72 14513249.85 accept 90.4893702644 -66.7652070914 expect 14837383.82 14868997.15 accept 110.3952984186 -65.8768496172 expect 14229937.28 15571030.23 accept 130.3231133025 -64.8242212878 expect 13868599.66 16472840.15 accept 150.8384853690 -63.8483921906 expect -13857834.28 15896552.84 accept 170.7991687984 -62.8345530934 expect -14209244.97 14904320.39 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect -1542258.49 1501537.48 accept -159.9563893280 71.5181717183 expect -1879442.18 874815.08 accept -139.1100287608 72.5807306253 expect -1947072.52 139940.96 accept -119.6844932150 73.8160145972 expect -1747316.08 -478587.18 accept -99.2835557046 74.0204664516 expect -1452001.59 -1044052.58 accept -79.7589672970 75.0775591038 expect -951397.60 -1370919.39 accept -59.3247479059 76.5851745566 expect -370785.20 -1451930.29 accept -39.7902268868 77.0657936158 expect 131161.56 -1438411.11 accept -19.7666667754 78.0413206451 expect 568957.13 -1207234.94 accept 0.2731990430 79.0490755914 expect 867766.11 -859530.19 accept 10.9115419099 79.7152157770 expect 949655.50 -642692.40 accept 30.7420972758 80.5901686038 expect 1016382.12 -258281.56 accept 50.1425480782 81.1493341375 expect 982144.24 88390.48 accept 70.9517084975 81.7646094071 expect 824812.58 401430.41 accept 90.1847984189 82.9573597164 expect 552645.55 556222.02 accept 110.4406311958 83.6703375043 expect 292833.81 640803.17 accept 130.8429657164 84.7838043163 expect 42074.56 578888.43 accept 150.8488588999 85.8764976286 expect -125274.18 441272.71 accept 170.3972098164 87.6222109978 expect -153172.25 215556.52 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=diamond tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect -17621.38 23578218.89 accept -139.6233037328 -87.8821294926 expect -152574.30 23445186.42 accept -119.6070748182 -86.3003323104 expect -23266812.75 203314.63 accept -99.5789095738 -85.2121814625 expect -23099344.73 88642.74 accept -79.2799350968 -83.8692118030 expect -22954154.12 -126926.46 accept -59.1007316490 -82.6429522913 expect -22921681.97 -420679.26 accept -39.7694988813 -81.1240616181 expect -632625.04 -22864483.82 accept -19.4219986373 -80.4596653260 expect -353564.94 -22621820.31 accept 0.0372192405 -79.1830001774 expect 783.63 -22418238.13 accept 10.8072116146 -78.5286202425 expect 239965.19 -22367479.01 accept 30.6949420481 -77.5251356225 expect 710891.44 -22427057.31 accept 50.8838172783 -77.2075414406 expect 22516340.76 -901172.98 accept 70.9123606882 -75.6020670736 expect 22103638.61 -526273.50 accept 90.9423960847 -75.1187688922 expect 21960800.08 27365.47 accept 110.1071594840 -74.5087602471 expect 21997111.76 595747.59 accept 130.0104641839 -73.3709657282 expect 22198255.20 1197258.60 accept 150.5931126765 -73.1105693985 expect 928808.49 21976536.34 accept 170.3454770498 -72.3687427114 expect 331324.45 21676548.71 accept -179.4283603569 -79.2780817976 expect -11928.97 22428963.47 accept -159.6140419013 -78.9064235928 expect -431033.97 22464691.83 accept -139.5485387788 -78.1437071317 expect -858427.30 22617785.91 accept -119.3040589991 -77.1517896758 expect -22373505.54 702179.27 accept -99.1733290138 -76.2249333909 expect -22105178.28 245348.39 accept -79.5627247417 -75.2438944725 expect -22002003.89 -298863.61 accept -59.2065888779 -74.4787205556 expect -22132851.96 -888974.50 accept -39.4027491539 -73.6693258790 expect -1160556.37 -22211815.70 accept -19.3829588944 -72.8068670782 expect -639189.89 -21807510.03 accept 0.5951648774 -71.1110965727 expect 22008.52 -21505303.66 accept 10.0089578122 -71.1061903043 expect 368360.71 -21536838.30 accept 30.2470524798 -69.7155529444 expect 1148080.66 -21655331.93 accept 50.0855077954 -69.3112248587 expect 21840460.99 -1492428.63 accept 70.3496352985 -68.4437853274 expect 21340205.88 -815371.10 accept 90.4893702644 -66.7652070914 expect 21005583.43 22354.00 accept 110.3952984186 -65.8768496172 expect 21072466.21 948295.92 accept 130.3231133025 -64.8242212878 expect 21454637.84 1841476.11 accept 150.8384853690 -63.8483921906 expect 1441591.72 21039528.90 accept 170.7991687984 -62.8345530934 expect 491492.54 20586399.49 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect -28794.10 2152288.77 accept -159.9563893280 71.5181717183 expect -710378.63 1947553.99 accept -139.1100287608 72.5807306253 expect -1277834.99 1475741.38 accept -119.6844932150 73.8160145972 expect -1573951.28 897126.81 accept -99.2835557046 74.0204664516 expect -1764976.83 288463.51 accept -79.7589672970 75.0775591038 expect -1642126.09 -296646.71 accept -59.3247479059 76.5851745566 expect -1288854.48 -764485.02 accept -39.7902268868 77.0657936158 expect -924365.02 -1109855.48 accept -19.7666667754 78.0413206451 expect -451330.57 -1255957.46 accept 0.2731990430 79.0490755914 expect 5823.67 -1221382.92 accept 10.9115419099 79.7152157770 expect 217055.69 -1125960.00 accept 30.7420972758 80.5901686038 expect 536058.05 -901323.33 accept 50.1425480782 81.1493341375 expect 756982.36 -631979.34 accept 70.9517084975 81.7646094071 expect 867084.74 -299376.41 accept 90.1847984189 82.9573597164 expect 784087.78 2528.94 accept 110.4406311958 83.6703375043 expect 660181.04 246051.50 accept 130.8429657164 84.7838043163 expect 439087.14 379584.73 accept 150.8488588999 85.8764976286 expect 223444.70 400609.15 accept 170.3972098164 87.6222109978 expect 44112.34 260730.62 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=horizontal tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect 11829925.59 46389.53 accept -139.6233037328 -87.8821294926 expect 11964878.50 179422.00 accept -119.6070748182 -86.3003323104 expect 12170099.87 203314.63 accept -99.5789095738 -85.2121814625 expect 12337567.89 88642.74 accept -79.2799350968 -83.8692118030 expect 12482758.50 -126926.46 accept -59.1007316490 -82.6429522913 expect 12515230.65 -420679.26 accept -39.7694988813 -81.1240616181 expect 12444929.25 -760124.60 accept -19.4219986373 -80.4596653260 expect 12165869.15 -1002788.10 accept 0.0372192405 -79.1830001774 expect 11811520.58 -1206370.29 accept 10.8072116146 -78.5286202425 expect 11572339.01 -1257129.40 accept 30.6949420481 -77.5251356225 expect 11101412.77 -1197551.10 accept 50.8838172783 -77.2075414406 expect 10704036.55 -901172.98 accept 70.9123606882 -75.6020670736 expect 10291334.40 -526273.50 accept 90.9423960847 -75.1187688922 expect 10148495.87 27365.47 accept 110.1071594840 -74.5087602471 expect 10184807.56 595747.59 accept 130.0104641839 -73.3709657282 expect 10385951.00 1197258.60 accept 150.5931126765 -73.1105693985 expect 10883495.72 1648072.07 accept 170.3454770498 -72.3687427114 expect 11480979.75 1948059.70 accept -179.4283603569 -79.2780817976 expect 11824233.18 1195644.94 accept -159.6140419013 -78.9064235928 expect 12243338.18 1159916.58 accept -139.5485387788 -78.1437071317 expect 12670731.51 1006822.50 accept -119.3040589991 -77.1517896758 expect 13063407.08 702179.27 accept -99.1733290138 -76.2249333909 expect 13331734.34 245348.39 accept -79.5627247417 -75.2438944725 expect 13434908.73 -298863.61 accept -59.2065888779 -74.4787205556 expect 13304060.66 -888974.50 accept -39.4027491539 -73.6693258790 expect 12972860.58 -1412792.72 accept -19.3829588944 -72.8068670782 expect 12451494.09 -1817098.38 accept 0.5951648774 -71.1110965727 expect 11790295.69 -2119304.75 accept 10.0089578122 -71.1061903043 expect 11443943.49 -2087770.11 accept 30.2470524798 -69.7155529444 expect 10664223.55 -1969276.48 accept 50.0855077954 -69.3112248587 expect 10028156.79 -1492428.63 accept 70.3496352985 -68.4437853274 expect 9527901.67 -815371.10 accept 90.4893702644 -66.7652070914 expect 9193279.23 22354.00 accept 110.3952984186 -65.8768496172 expect 9260162.01 948295.92 accept 130.3231133025 -64.8242212878 expect 9642333.63 1841476.11 accept 150.8384853690 -63.8483921906 expect 10370712.49 2585079.51 accept 170.7991687984 -62.8345530934 expect 11320811.66 3038208.93 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect -11841098.31 2152288.77 accept -159.9563893280 71.5181717183 expect -12522682.84 1947553.99 accept -139.1100287608 72.5807306253 expect -13090139.19 1475741.38 accept -119.6844932150 73.8160145972 expect -13386255.49 897126.81 accept -99.2835557046 74.0204664516 expect -13577281.04 288463.51 accept -79.7589672970 75.0775591038 expect -13454430.30 -296646.71 accept -59.3247479059 76.5851745566 expect -13101158.69 -764485.02 accept -39.7902268868 77.0657936158 expect -12736669.23 -1109855.48 accept -19.7666667754 78.0413206451 expect -12263634.78 -1255957.46 accept 0.2731990430 79.0490755914 expect -11806480.53 -1221382.92 accept 10.9115419099 79.7152157770 expect -11595248.52 -1125960.00 accept 30.7420972758 80.5901686038 expect -11276246.16 -901323.33 accept 50.1425480782 81.1493341375 expect -11055321.85 -631979.34 accept 70.9517084975 81.7646094071 expect -10945219.47 -299376.41 accept 90.1847984189 82.9573597164 expect -11028216.43 2528.94 accept 110.4406311958 83.6703375043 expect -11152123.17 246051.50 accept 130.8429657164 84.7838043163 expect -11373217.07 379584.73 accept 150.8488588999 85.8764976286 expect -11588859.50 400609.15 accept 170.3972098164 87.6222109978 expect -11768191.87 260730.62 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=horizontal +scrollx=0.75 tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect 17621.38 46389.53 accept -139.6233037328 -87.8821294926 expect 152574.30 179422.00 accept -119.6070748182 -86.3003323104 expect 357795.67 203314.63 accept -99.5789095738 -85.2121814625 expect 525263.69 88642.74 accept -79.2799350968 -83.8692118030 expect 670454.30 -126926.46 accept -59.1007316490 -82.6429522913 expect 702926.44 -420679.26 accept -39.7694988813 -81.1240616181 expect 632625.04 -760124.60 accept -19.4219986373 -80.4596653260 expect 353564.94 -1002788.10 accept 0.0372192405 -79.1830001774 expect -783.63 -1206370.29 accept 10.8072116146 -78.5286202425 expect -239965.19 -1257129.40 accept 30.6949420481 -77.5251356225 expect -710891.44 -1197551.10 accept 50.8838172783 -77.2075414406 expect -1108267.66 -901172.98 accept 70.9123606882 -75.6020670736 expect -1520969.81 -526273.50 accept 90.9423960847 -75.1187688922 expect -1663808.33 27365.47 accept 110.1071594840 -74.5087602471 expect -1627496.65 595747.59 accept 130.0104641839 -73.3709657282 expect -1426353.21 1197258.60 accept 150.5931126765 -73.1105693985 expect -928808.49 1648072.07 accept 170.3454770498 -72.3687427114 expect -331324.45 1948059.70 accept -179.4283603569 -79.2780817976 expect 11928.97 1195644.94 accept -159.6140419013 -78.9064235928 expect 431033.97 1159916.58 accept -139.5485387788 -78.1437071317 expect 858427.30 1006822.50 accept -119.3040589991 -77.1517896758 expect 1251102.88 702179.27 accept -99.1733290138 -76.2249333909 expect 1519430.13 245348.39 accept -79.5627247417 -75.2438944725 expect 1622604.52 -298863.61 accept -59.2065888779 -74.4787205556 expect 1491756.45 -888974.50 accept -39.4027491539 -73.6693258790 expect 1160556.37 -1412792.72 accept -19.3829588944 -72.8068670782 expect 639189.89 -1817098.38 accept 0.5951648774 -71.1110965727 expect -22008.52 -2119304.75 accept 10.0089578122 -71.1061903043 expect -368360.71 -2087770.11 accept 30.2470524798 -69.7155529444 expect -1148080.66 -1969276.48 accept 50.0855077954 -69.3112248587 expect -1784147.42 -1492428.63 accept 70.3496352985 -68.4437853274 expect -2284402.54 -815371.10 accept 90.4893702644 -66.7652070914 expect -2619024.98 22354.00 accept 110.3952984186 -65.8768496172 expect -2552142.20 948295.92 accept 130.3231133025 -64.8242212878 expect -2169970.58 1841476.11 accept 150.8384853690 -63.8483921906 expect -1441591.72 2585079.51 accept 170.7991687984 -62.8345530934 expect -491492.54 3038208.93 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect 23595814.31 2152288.77 accept -159.9563893280 71.5181717183 expect 22914229.78 1947553.99 accept -139.1100287608 72.5807306253 expect 22346773.43 1475741.38 accept -119.6844932150 73.8160145972 expect 22050657.13 897126.81 accept -99.2835557046 74.0204664516 expect 21859631.58 288463.51 accept -79.7589672970 75.0775591038 expect 21982482.33 -296646.71 accept -59.3247479059 76.5851745566 expect 22335753.93 -764485.02 accept -39.7902268868 77.0657936158 expect 22700243.39 -1109855.48 accept -19.7666667754 78.0413206451 expect 23173277.84 -1255957.46 accept 0.2731990430 79.0490755914 expect -23618784.74 -1221382.92 accept 10.9115419099 79.7152157770 expect -23407552.72 -1125960.00 accept 30.7420972758 80.5901686038 expect -23088550.36 -901323.33 accept 50.1425480782 81.1493341375 expect -22867626.05 -631979.34 accept 70.9517084975 81.7646094071 expect -22757523.68 -299376.41 accept 90.1847984189 82.9573597164 expect -22840520.64 2528.94 accept 110.4406311958 83.6703375043 expect -22964427.38 246051.50 accept 130.8429657164 84.7838043163 expect -23185521.28 379584.73 accept 150.8488588999 85.8764976286 expect -23401163.71 400609.15 accept 170.3972098164 87.6222109978 expect -23580496.07 260730.62 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=vertical tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect -17621.38 11765914.68 accept -139.6233037328 -87.8821294926 expect -152574.30 11632882.21 accept -119.6070748182 -86.3003323104 expect -357795.67 11608989.58 accept -99.5789095738 -85.2121814625 expect -525263.69 11723661.47 accept -79.2799350968 -83.8692118030 expect -670454.30 11939230.67 accept -59.1007316490 -82.6429522913 expect -702926.44 12232983.46 accept -39.7694988813 -81.1240616181 expect -632625.04 12572428.81 accept -19.4219986373 -80.4596653260 expect -353564.94 12815092.31 accept 0.0372192405 -79.1830001774 expect 783.63 13018674.49 accept 10.8072116146 -78.5286202425 expect 239965.19 13069433.61 accept 30.6949420481 -77.5251356225 expect 710891.44 13009855.31 accept 50.8838172783 -77.2075414406 expect 1108267.66 12713477.19 accept 70.9123606882 -75.6020670736 expect 1520969.81 12338577.71 accept 90.9423960847 -75.1187688922 expect 1663808.33 11784938.74 accept 110.1071594840 -74.5087602471 expect 1627496.65 11216556.62 accept 130.0104641839 -73.3709657282 expect 1426353.21 10615045.61 accept 150.5931126765 -73.1105693985 expect 928808.49 10164232.13 accept 170.3454770498 -72.3687427114 expect 331324.45 9864244.50 accept -179.4283603569 -79.2780817976 expect -11928.97 10616659.27 accept -159.6140419013 -78.9064235928 expect -431033.97 10652387.63 accept -139.5485387788 -78.1437071317 expect -858427.30 10805481.70 accept -119.3040589991 -77.1517896758 expect -1251102.88 11110124.94 accept -99.1733290138 -76.2249333909 expect -1519430.13 11566955.81 accept -79.5627247417 -75.2438944725 expect -1622604.52 12111167.82 accept -59.2065888779 -74.4787205556 expect -1491756.45 12701278.71 accept -39.4027491539 -73.6693258790 expect -1160556.37 13225096.92 accept -19.3829588944 -72.8068670782 expect -639189.89 13629402.59 accept 0.5951648774 -71.1110965727 expect 22008.52 13931608.96 accept 10.0089578122 -71.1061903043 expect 368360.71 13900074.32 accept 30.2470524798 -69.7155529444 expect 1148080.66 13781580.69 accept 50.0855077954 -69.3112248587 expect 1784147.42 13304732.84 accept 70.3496352985 -68.4437853274 expect 2284402.54 12627675.31 accept 90.4893702644 -66.7652070914 expect 2619024.98 11789950.21 accept 110.3952984186 -65.8768496172 expect 2552142.20 10864008.28 accept 130.3231133025 -64.8242212878 expect 2169970.58 9970828.10 accept 150.8384853690 -63.8483921906 expect 1441591.72 9227224.69 accept 170.7991687984 -62.8345530934 expect 491492.54 8774095.28 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect -28794.10 -9660015.44 accept -159.9563893280 71.5181717183 expect -710378.63 -9864750.22 accept -139.1100287608 72.5807306253 expect -1277834.99 -10336562.82 accept -119.6844932150 73.8160145972 expect -1573951.28 -10915177.40 accept -99.2835557046 74.0204664516 expect -1764976.83 -11523840.70 accept -79.7589672970 75.0775591038 expect -1642126.09 -12108950.91 accept -59.3247479059 76.5851745566 expect -1288854.48 -12576789.23 accept -39.7902268868 77.0657936158 expect -924365.02 -12922159.68 accept -19.7666667754 78.0413206451 expect -451330.57 -13068261.66 accept 0.2731990430 79.0490755914 expect 5823.67 -13033687.13 accept 10.9115419099 79.7152157770 expect 217055.69 -12938264.21 accept 30.7420972758 80.5901686038 expect 536058.05 -12713627.54 accept 50.1425480782 81.1493341375 expect 756982.36 -12444283.55 accept 70.9517084975 81.7646094071 expect 867084.74 -12111680.61 accept 90.1847984189 82.9573597164 expect 784087.78 -11809775.26 accept 110.4406311958 83.6703375043 expect 660181.04 -11566252.71 accept 130.8429657164 84.7838043163 expect 439087.14 -11432719.48 accept 150.8488588999 85.8764976286 expect 223444.70 -11411695.05 accept 170.3972098164 87.6222109978 expect 44112.34 -11551573.59 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=vertical +scrolly=-0.25 tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect -17621.38 -46389.53 accept -139.6233037328 -87.8821294926 expect -152574.30 -179422.00 accept -119.6070748182 -86.3003323104 expect -357795.67 -203314.63 accept -99.5789095738 -85.2121814625 expect -525263.69 -88642.74 accept -79.2799350968 -83.8692118030 expect -670454.30 126926.46 accept -59.1007316490 -82.6429522913 expect -702926.44 420679.26 accept -39.7694988813 -81.1240616181 expect -632625.04 760124.60 accept -19.4219986373 -80.4596653260 expect -353564.94 1002788.10 accept 0.0372192405 -79.1830001774 expect 783.63 1206370.29 accept 10.8072116146 -78.5286202425 expect 239965.19 1257129.40 accept 30.6949420481 -77.5251356225 expect 710891.44 1197551.10 accept 50.8838172783 -77.2075414406 expect 1108267.66 901172.98 accept 70.9123606882 -75.6020670736 expect 1520969.81 526273.50 accept 90.9423960847 -75.1187688922 expect 1663808.33 -27365.47 accept 110.1071594840 -74.5087602471 expect 1627496.65 -595747.59 accept 130.0104641839 -73.3709657282 expect 1426353.21 -1197258.60 accept 150.5931126765 -73.1105693985 expect 928808.49 -1648072.07 accept 170.3454770498 -72.3687427114 expect 331324.45 -1948059.70 accept -179.4283603569 -79.2780817976 expect -11928.97 -1195644.94 accept -159.6140419013 -78.9064235928 expect -431033.97 -1159916.58 accept -139.5485387788 -78.1437071317 expect -858427.30 -1006822.50 accept -119.3040589991 -77.1517896758 expect -1251102.88 -702179.27 accept -99.1733290138 -76.2249333909 expect -1519430.13 -245348.39 accept -79.5627247417 -75.2438944725 expect -1622604.52 298863.61 accept -59.2065888779 -74.4787205556 expect -1491756.45 888974.50 accept -39.4027491539 -73.6693258790 expect -1160556.37 1412792.72 accept -19.3829588944 -72.8068670782 expect -639189.89 1817098.38 accept 0.5951648774 -71.1110965727 expect 22008.52 2119304.75 accept 10.0089578122 -71.1061903043 expect 368360.71 2087770.11 accept 30.2470524798 -69.7155529444 expect 1148080.66 1969276.48 accept 50.0855077954 -69.3112248587 expect 1784147.42 1492428.63 accept 70.3496352985 -68.4437853274 expect 2284402.54 815371.10 accept 90.4893702644 -66.7652070914 expect 2619024.98 -22354.00 accept 110.3952984186 -65.8768496172 expect 2552142.20 -948295.92 accept 130.3231133025 -64.8242212878 expect 2169970.58 -1841476.11 accept 150.8384853690 -63.8483921906 expect 1441591.72 -2585079.51 accept 170.7991687984 -62.8345530934 expect 491492.54 -3038208.93 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect -28794.10 -21472319.64 accept -159.9563893280 71.5181717183 expect -710378.63 -21677054.43 accept -139.1100287608 72.5807306253 expect -1277834.99 -22148867.03 accept -119.6844932150 73.8160145972 expect -1573951.28 -22727481.60 accept -99.2835557046 74.0204664516 expect -1764976.83 -23336144.90 accept -79.7589672970 75.0775591038 expect -1642126.09 23327961.71 accept -59.3247479059 76.5851745566 expect -1288854.48 22860123.39 accept -39.7902268868 77.0657936158 expect -924365.02 22514752.94 accept -19.7666667754 78.0413206451 expect -451330.57 22368650.96 accept 0.2731990430 79.0490755914 expect 5823.67 22403225.49 accept 10.9115419099 79.7152157770 expect 217055.69 22498648.41 accept 30.7420972758 80.5901686038 expect 536058.05 22723285.08 accept 50.1425480782 81.1493341375 expect 756982.36 22992629.07 accept 70.9517084975 81.7646094071 expect 867084.74 23325232.01 accept 90.1847984189 82.9573597164 expect 784087.78 -23622079.47 accept 110.4406311958 83.6703375043 expect 660181.04 -23378556.92 accept 130.8429657164 84.7838043163 expect 439087.14 -23245023.69 accept 150.8488588999 85.8764976286 expect 223444.70 -23223999.26 accept 170.3972098164 87.6222109978 expect 44112.34 -23363877.80 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=nhemisphere tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect failure errno coord_transfm_outside_projection_domain accept -139.6233037328 -87.8821294926 expect failure errno coord_transfm_outside_projection_domain accept -119.6070748182 -86.3003323104 expect failure errno coord_transfm_outside_projection_domain accept -99.5789095738 -85.2121814625 expect failure errno coord_transfm_outside_projection_domain accept -79.2799350968 -83.8692118030 expect failure errno coord_transfm_outside_projection_domain accept -59.1007316490 -82.6429522913 expect failure errno coord_transfm_outside_projection_domain accept -39.7694988813 -81.1240616181 expect failure errno coord_transfm_outside_projection_domain accept -19.4219986373 -80.4596653260 expect failure errno coord_transfm_outside_projection_domain accept 0.0372192405 -79.1830001774 expect failure errno coord_transfm_outside_projection_domain accept 10.8072116146 -78.5286202425 expect failure errno coord_transfm_outside_projection_domain accept 30.6949420481 -77.5251356225 expect failure errno coord_transfm_outside_projection_domain accept 50.8838172783 -77.2075414406 expect failure errno coord_transfm_outside_projection_domain accept 70.9123606882 -75.6020670736 expect failure errno coord_transfm_outside_projection_domain accept 90.9423960847 -75.1187688922 expect failure errno coord_transfm_outside_projection_domain accept 110.1071594840 -74.5087602471 expect failure errno coord_transfm_outside_projection_domain accept 130.0104641839 -73.3709657282 expect failure errno coord_transfm_outside_projection_domain accept 150.5931126765 -73.1105693985 expect failure errno coord_transfm_outside_projection_domain accept 170.3454770498 -72.3687427114 expect failure errno coord_transfm_outside_projection_domain accept -179.4283603569 -79.2780817976 expect failure errno coord_transfm_outside_projection_domain accept -159.6140419013 -78.9064235928 expect failure errno coord_transfm_outside_projection_domain accept -139.5485387788 -78.1437071317 expect failure errno coord_transfm_outside_projection_domain accept -119.3040589991 -77.1517896758 expect failure errno coord_transfm_outside_projection_domain accept -99.1733290138 -76.2249333909 expect failure errno coord_transfm_outside_projection_domain accept -79.5627247417 -75.2438944725 expect failure errno coord_transfm_outside_projection_domain accept -59.2065888779 -74.4787205556 expect failure errno coord_transfm_outside_projection_domain accept -39.4027491539 -73.6693258790 expect failure errno coord_transfm_outside_projection_domain accept -19.3829588944 -72.8068670782 expect failure errno coord_transfm_outside_projection_domain accept 0.5951648774 -71.1110965727 expect failure errno coord_transfm_outside_projection_domain accept 10.0089578122 -71.1061903043 expect failure errno coord_transfm_outside_projection_domain accept 30.2470524798 -69.7155529444 expect failure errno coord_transfm_outside_projection_domain accept 50.0855077954 -69.3112248587 expect failure errno coord_transfm_outside_projection_domain accept 70.3496352985 -68.4437853274 expect failure errno coord_transfm_outside_projection_domain accept 90.4893702644 -66.7652070914 expect failure errno coord_transfm_outside_projection_domain accept 110.3952984186 -65.8768496172 expect failure errno coord_transfm_outside_projection_domain accept 130.3231133025 -64.8242212878 expect failure errno coord_transfm_outside_projection_domain accept 150.8384853690 -63.8483921906 expect failure errno coord_transfm_outside_projection_domain accept 170.7991687984 -62.8345530934 expect failure errno coord_transfm_outside_projection_domain accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect -28794.10 2152288.77 accept -159.9563893280 71.5181717183 expect -710378.63 1947553.99 accept -139.1100287608 72.5807306253 expect -1277834.99 1475741.38 accept -119.6844932150 73.8160145972 expect -1573951.28 897126.81 accept -99.2835557046 74.0204664516 expect -1764976.83 288463.51 accept -79.7589672970 75.0775591038 expect -1642126.09 -296646.71 accept -59.3247479059 76.5851745566 expect -1288854.48 -764485.02 accept -39.7902268868 77.0657936158 expect -924365.02 -1109855.48 accept -19.7666667754 78.0413206451 expect -451330.57 -1255957.46 accept 0.2731990430 79.0490755914 expect 5823.67 -1221382.92 accept 10.9115419099 79.7152157770 expect 217055.69 -1125960.00 accept 30.7420972758 80.5901686038 expect 536058.05 -901323.33 accept 50.1425480782 81.1493341375 expect 756982.36 -631979.34 accept 70.9517084975 81.7646094071 expect 867084.74 -299376.41 accept 90.1847984189 82.9573597164 expect 784087.78 2528.94 accept 110.4406311958 83.6703375043 expect 660181.04 246051.50 accept 130.8429657164 84.7838043163 expect 439087.14 379584.73 accept 150.8488588999 85.8764976286 expect 223444.70 400609.15 accept 170.3972098164 87.6222109978 expect 44112.34 260730.62 ------------------------------------------------------------ operation +proj=peirce_q +R=6370997 +shape=shemisphere tolerance 10 mm ------------------------------------------------------------ accept -179.6126302052 -90.2440064745 expect failure errno coord_transfm_invalid_coord accept -159.2003712209 -89.5537263306 expect -17621.38 46389.53 accept -139.6233037328 -87.8821294926 expect -152574.30 179422.00 accept -119.6070748182 -86.3003323104 expect -357795.67 203314.63 accept -99.5789095738 -85.2121814625 expect -525263.69 88642.74 accept -79.2799350968 -83.8692118030 expect -670454.30 -126926.46 accept -59.1007316490 -82.6429522913 expect -702926.44 -420679.26 accept -39.7694988813 -81.1240616181 expect -632625.04 -760124.60 accept -19.4219986373 -80.4596653260 expect -353564.94 -1002788.10 accept 0.0372192405 -79.1830001774 expect 783.63 -1206370.29 accept 10.8072116146 -78.5286202425 expect 239965.19 -1257129.40 accept 30.6949420481 -77.5251356225 expect 710891.44 -1197551.10 accept 50.8838172783 -77.2075414406 expect 1108267.66 -901172.98 accept 70.9123606882 -75.6020670736 expect 1520969.81 -526273.50 accept 90.9423960847 -75.1187688922 expect 1663808.33 27365.47 accept 110.1071594840 -74.5087602471 expect 1627496.65 595747.59 accept 130.0104641839 -73.3709657282 expect 1426353.21 1197258.60 accept 150.5931126765 -73.1105693985 expect 928808.49 1648072.07 accept 170.3454770498 -72.3687427114 expect 331324.45 1948059.70 accept -179.4283603569 -79.2780817976 expect -11928.97 1195644.94 accept -159.6140419013 -78.9064235928 expect -431033.97 1159916.58 accept -139.5485387788 -78.1437071317 expect -858427.30 1006822.50 accept -119.3040589991 -77.1517896758 expect -1251102.88 702179.27 accept -99.1733290138 -76.2249333909 expect -1519430.13 245348.39 accept -79.5627247417 -75.2438944725 expect -1622604.52 -298863.61 accept -59.2065888779 -74.4787205556 expect -1491756.45 -888974.50 accept -39.4027491539 -73.6693258790 expect -1160556.37 -1412792.72 accept -19.3829588944 -72.8068670782 expect -639189.89 -1817098.38 accept 0.5951648774 -71.1110965727 expect 22008.52 -2119304.75 accept 10.0089578122 -71.1061903043 expect 368360.71 -2087770.11 accept 30.2470524798 -69.7155529444 expect 1148080.66 -1969276.48 accept 50.0855077954 -69.3112248587 expect 1784147.42 -1492428.63 accept 70.3496352985 -68.4437853274 expect 2284402.54 -815371.10 accept 90.4893702644 -66.7652070914 expect 2619024.98 22354.00 accept 110.3952984186 -65.8768496172 expect 2552142.20 948295.92 accept 130.3231133025 -64.8242212878 expect 2169970.58 1841476.11 accept 150.8384853690 -63.8483921906 expect 1441591.72 2585079.51 accept 170.7991687984 -62.8345530934 expect 491492.54 3038208.93 accept 170.6832502669 -105.0174505020 expect failure errno coord_transfm_invalid_coord accept 180.7137917600 105.8174218935 expect failure errno coord_transfm_invalid_coord accept -179.2332724818 70.8217746040 expect failure errno coord_transfm_outside_projection_domain accept -159.9563893280 71.5181717183 expect failure errno coord_transfm_outside_projection_domain accept -139.1100287608 72.5807306253 expect failure errno coord_transfm_outside_projection_domain accept -119.6844932150 73.8160145972 expect failure errno coord_transfm_outside_projection_domain accept -99.2835557046 74.0204664516 expect failure errno coord_transfm_outside_projection_domain accept -79.7589672970 75.0775591038 expect failure errno coord_transfm_outside_projection_domain accept -59.3247479059 76.5851745566 expect failure errno coord_transfm_outside_projection_domain accept -39.7902268868 77.0657936158 expect failure errno coord_transfm_outside_projection_domain accept -19.7666667754 78.0413206451 expect failure errno coord_transfm_outside_projection_domain accept 0.2731990430 79.0490755914 expect failure errno coord_transfm_outside_projection_domain accept 10.9115419099 79.7152157770 expect failure errno coord_transfm_outside_projection_domain accept 30.7420972758 80.5901686038 expect failure errno coord_transfm_outside_projection_domain accept 50.1425480782 81.1493341375 expect failure errno coord_transfm_outside_projection_domain accept 70.9517084975 81.7646094071 expect failure errno coord_transfm_outside_projection_domain accept 90.1847984189 82.9573597164 expect failure errno coord_transfm_outside_projection_domain accept 110.4406311958 83.6703375043 expect failure errno coord_transfm_outside_projection_domain accept 130.8429657164 84.7838043163 expect failure errno coord_transfm_outside_projection_domain accept 150.8488588999 85.8764976286 expect failure errno coord_transfm_outside_projection_domain accept 170.3972098164 87.6222109978 expect failure errno coord_transfm_outside_projection_domain # Test inverse ------------------------------------------------------------ operation +proj=peirce_q +shape=square ------------------------------------------------------------ #tolerance 1 mm # has to bump to this for i386 tolerance 150 mm accept 0 90 expect 0 0 roundtrip 1 accept 0 0 expect 8361921.234827487729 -8361921.234827487729 roundtrip 1 accept 0 -90 expect 16723842.303160080686 -16723842.303160080686 #tolerance 2 mm roundtrip 1 #tolerance 1 mm accept 0 45 expect 3725360.212758612353 -3725360.212758612353 roundtrip 1 accept 0 -45 expect 12998482.090401465073 -12998482.090401465073 roundtrip 1 accept 45 0 tolerance 200 mm expect 16723842.564696932212 -0.095041956369 roundtrip 1 tolerance 150 mm accept -45 0 expect 0 -16723842.469654975459 #roundtrip 1 accept 90 0 expect 8361921.329869444482 8361921.329869444482 roundtrip 1 accept -90 0 expect -8361921.234827487729 -8361921.234827487729 roundtrip 1 accept 135 0 expect 0.095041956369 16723842.564696932212 roundtrip 1 accept -135 0 expect -16723842.430287310854 -0.039367665210 #roundtrip 1 accept 179.99 0 expect -8360808.039828131907 8363034.429826845415 #roundtrip 1 accept -179.99 0 expect -8363034.429826845415 8360808.039828131907 #roundtrip 1 accept 45 45 expect 5299570.257319082506 0 roundtrip 1 accept -45 45 expect 0 -5299570.257319079712 roundtrip 1 accept 90 45 expect 3725360.212758610491 3725360.212758610491 roundtrip 1 accept -90 45 expect -3725360.212758613285 -3725360.212758613285 roundtrip 1 accept 135 45 expect 0 5299570.257319079712 roundtrip 1 accept -135 45 expect -5299570.257319079712 0 #roundtrip 1 accept 179.99 45 expect -3724717.456456150394 3726002.863303491380 roundtrip 1 accept -179.99 45 expect -3726002.863303492777 3724717.456456151791 roundtrip 1 accept 45 -45 expect 16723842.303160080686 11424272.045840997249 roundtrip 1 accept -45 -45 expect 11424272.045840999112 -16723842.303160080686 roundtrip 1 accept 90 -45 expect 12998482.090401468799 12998482.090401468799 roundtrip 1 accept -90 -45 expect -12998482.090401465073 -12998482.090401465073 roundtrip 1 accept 135 -45 expect -11424272.045840999112 16723842.303160080686 roundtrip 1 accept -135 -45 expect -16723842.303160080686 -11424272.045840999112 roundtrip 1 accept 179.99 -45 expect -12997839.439856586978 12999124.846703927964 roundtrip 1 accept -179.99 -45 expect -12999124.846703927964 12997839.439856585115 roundtrip 1 accept 45 -89.999 expect 16723842.303160080686 16723730.983657168224 #roundtrip 1 accept -45 -89.999 expect 16723730.983657168224 -16723842.303160080686 #roundtrip 1 accept 90 -89.999 expect 16723763.588384689763 16723763.588384689763 roundtrip 1 accept -90 -89.999 expect -16723763.588384689763 -16723763.588384689763 roundtrip 1 accept 135 -89.999 expect -16723730.983657168224 16723842.303160080686 #roundtrip 1 accept -135 -89.999 expect -16723842.303160080686 -16723730.983657168224 #roundtrip 1 accept 179.99 -89.999 expect -16723763.588384689763 16723763.588384689763 #roundtrip 1 accept -179.99 -89.999 expect -16723763.588384689763 16723763.588384689763 #roundtrip 1 # Test inverse ------------------------------------------------------------ operation +proj=peirce_q +shape=diamond ------------------------------------------------------------ #tolerance 1 mm # has to bump to this for i386 tolerance 150 mm accept 0 90 expect 0 0 roundtrip 1 accept 0 -90 #tolerance 10 mm expect 0 -23651084.600117880851 roundtrip 1 #tolerance 1 mm accept 0 45 expect 0 -5268454.937608348206 roundtrip 1 accept 0 -45 expect 0 -18382629.662509534508 roundtrip 1 accept 45 0 tolerance 200 mm expect 11825542.417788611725 -11825542.552198234946 roundtrip 1 tolerance 150 mm accept -45 0 expect -11825542.417788611725 -11825542.417788611725 roundtrip 1 accept 90 0 expect 11825542.552198234946 0.000000000000 roundtrip 1 accept -90 0 expect -11825542.417788611725 0.000000000000 #tolerance 20 mm #roundtrip 1 #tolerance 1 mm accept 135 0 expect 11825542.552198234946 11825542.417788611725 roundtrip 1 accept -135 0 expect -11825542.417788611725 11825542.362114325166 roundtrip 1 accept 179.99 0 expect 1574.295465656175 11825542.417788611725 #tolerance 200 mm #roundtrip 1 #tolerance 1 mm accept -179.99 0 expect -1574.295465656175 11825542.417788611725 #tolerance 30 mm #roundtrip 1 #tolerance 1 mm accept 45 45 expect 3747362.066324858926 -3747362.066324859392 roundtrip 1 accept -45 45 expect -3747362.066324857529 -3747362.066324857995 roundtrip 1 accept 90 45 expect 5268454.937608345412 0.000000000000 #roundtrip 1 accept -90 45 expect -5268454.937608350068 0.000000000000 #roundtrip 1 accept 135 45 expect 3747362.066324858926 3747362.066324857995 roundtrip 1 accept -135 45 expect -3747362.066324857529 3747362.066324857529 roundtrip 1 accept 179.99 45 expect 908.919898338959 5268454.862826444209 roundtrip 1 accept -179.99 45 expect -908.919898338959 5268454.862826446071 roundtrip 1 accept 45 -45 expect 19903722.533793020993 -3747362.066324859392 roundtrip 1 accept -45 -45 expect -3747362.066324857529 -19903722.533793020993 roundtrip 1 accept 90 -45 expect 18382629.662509534508 0.000000000000 roundtrip 1 accept -90 -45 expect -18382629.662509530783 0.000000000000 #tolerance 3 mm #roundtrip 1 #tolerance 1 mm accept 135 -45 expect 3747362.066324858926 19903722.533793020993 roundtrip 1 accept -135 -45 expect -19903722.533793020993 3747362.066324857529 roundtrip 1 accept 179.99 -45 expect 908.919898338959 18382629.737291436642 #roundtrip 1 accept -179.99 -45 expect -908.919898338959 18382629.737291432917 roundtrip 1 accept 45 -89.999 expect 23651005.885342493653 -78.714775386137 #roundtrip 1 accept -45 -89.999 expect -78.714775386137 -23651005.885342493653 #roundtrip 1 accept 90 -89.999 expect 23650973.280614964664 0.000000000000 #roundtrip 1 accept -90 -89.999 expect -23650973.280614964664 0.000000000000 #roundtrip 1 accept 135 -89.999 expect 78.714775386137 23651005.885342493653 #roundtrip 1 accept -135 -89.999 expect -23651005.885342493653 78.714775386137 #roundtrip 1 accept 179.99 -89.999 expect 0.000000000000 23650973.280614964664 #roundtrip 1 accept -179.99 -89.999 expect 0.000000000000 23650973.280614964664 #roundtrip 1 proj-9.8.1/test/gie/builtins.gie000664 001750 001750 00000762056 15166171715 016535 0ustar00eveneven000000 000000 =============================================================================== Test material, mostly converted from selftest entries in PJ_xxx.c Most of this material was autogenerated, and does not attempt to exercise corner cases etc. See more_builtins.gie for some test cases with a more human touch. =============================================================================== # First test non strict gie operation +proj=aea +ellps=GRS80 +lat_1=0 +lat_2=2 tolerance 0.1 mm accept 2 1 expect 222571.608757106 110653.326743030 unknown_keyword =============================================================================== # Albers Equal Area # Conic Sph&Ell # lat_1= lat_2= =============================================================================== ------------------------------------------------------------------------------- operation +proj=aea +ellps=GRS80 +lat_1=0 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222571.608757106 110653.326743030 accept 2 -1 expect 222706.306508391 -110484.267144400 accept -2 1 expect -222571.608757106 110653.326743030 accept -2 -1 expect -222706.306508391 -110484.267144400 accept 150 50 expect 16468399.3582 5275043.9815 direction inverse accept 200 100 expect 0.001796631 0.000904369 accept 200 -100 expect 0.001796630 -0.000904370 accept -200 100 expect -0.001796631 0.000904369 accept -200 -100 expect -0.001796630 -0.000904370 accept 16468399.3582 5275043.9815 expect 150 50 ------------------------------------------------------------------------------- operation +proj=aea +R=6400000 +lat_1=0 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223334.085170885 111780.431884472 accept 2 -1 expect 223470.154991687 -111610.339430990 accept -2 1 expect -223334.085170885 111780.431884472 accept -2 -1 expect -223470.154991687 -111610.339430990 direction inverse accept 200 100 expect 0.001790494 0.000895246 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790494 0.000895246 accept -200 -100 expect -0.001790493 -0.000895247 operation +proj=aea +ellps=GRS80 +lat_1=900 expect failure errno invalid_op_illegal_arg_value operation +proj=aea +ellps=GRS80 +lat_2=900 expect failure errno invalid_op_illegal_arg_value operation +proj=aea +R=6400000 +lat_1=1 +lat_2=-1 expect failure errno invalid_op_illegal_arg_value ------------------------------------------------------------------------------- operation +proj=aea +a=9999999 +b=.9 +lat_2=1 ------------------------------------------------------------------------- expect failure errno invalid_op_illegal_arg_value =============================================================================== # Azimuthal Equidistant # Azi, Sph&Ell # lat_0 guam =============================================================================== ------------------------------------------------------------------------------- # Test equatorial aspect of the spherical azimuthal equidistant. Test data from # Snyder pp. 196-197, table 30. ------------------------------------------------------------------------------- operation +proj=aeqd +R=1 +lat_0=0 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 0 roundtrip 100 accept 0 90 expect 0 1.57080 roundtrip 100 accept 10 80 expect 0.04281 1.39829 roundtrip 100 accept 40 30 expect 0.62896 0.56493 roundtrip 100 accept 90 0 expect 1.57080 0 roundtrip 100 accept 90 90 expect 0 1.57080 roundtrip 100 # point opposite projection center is undefined accept 180 0 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test equatorial aspect of the ellipsoidal azimuthal equidistant. Test data from # Snyder pp. 196-197, table 30. ------------------------------------------------------------------------------- operation +proj=aeqd +ellps=GRS80 +lat_0=0 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 90 expect 0 10001965.7292 roundtrip 100 accept 0 0 expect 0 0 roundtrip 100 accept 90 0 expect 10_018_754.1714 0 roundtrip 100 accept 90 0 expect 10_018_754.1714 0 roundtrip 100 accept 45 45 expect 3_860_398.3783 5_430_089.0490 roundtrip 100 # Test oblique aeqd with point very close lon_0, lat_0, on a perfect sphere operation +proj=aeqd +a=6371008.771415 +b=6371008.771415 +lat_0=30.2345 +lon_0=-120.2345 tolerance 1 mm accept -120.234501 30.234501 expect -0.096 0.111 roundtrip 1 accept -120.2345 30.2345 expect 0.000 0.000 roundtrip 1 # Same on an ellipsoid very close to the sphere operation +proj=aeqd +a=6371008.771415 +b=6371008.771414 +lat_0=30.2345 +lon_0=-120.2345 tolerance 1 mm accept -120.234501 30.234501 expect -0.096 0.111 roundtrip 1 accept -120.2345 30.2345 expect 0.000 0.000 roundtrip 1 ------------------------------------------------------------------------------- # Test the Modified Azimuthal Equidistant / EPSG 9832. Test data from the EPSG # Guidance Note 7 part 2, April 2018, p. 85 ------------------------------------------------------------------------------- operation +proj=aeqd +ellps=clrk66 +lat_0=9.546708325068591 +lon_0=138.1687444500492 +x_0=40000.00 +y_0=60000.00 ------------------------------------------------------------------------------- tolerance 1 cm accept 138.19303001104092 9.596525859439623 expect 42665.90 65509.82 roundtrip 100 direction inverse accept 42665.90 65509.82 expect 138.19303001104092 9.596525859439623 ------------------------------------------------------------------------------- # Test the azimuthal equidistant modified for Guam. Test data from the EPSG # Guidance Note 7 part 2, September 2016, p. 85 ------------------------------------------------------------------------------- operation +proj=aeqd +guam +ellps=clrk66 +x_0=50000.00 +y_0=50000.00 \ +lon_0=144.74875069444445 +lat_0=13.47246633333333 ------------------------------------------------------------------------------- tolerance 1 cm accept 144.635331291666660 13.33903846111111 expect 37712.48 35242.00 roundtrip 100 direction inverse accept 37712.48 35242.00 expect 144.635331291666660 13.33903846111111 ------------------------------------------------------------------------------- # Test northern polar aspect of the ellipsoidal azimuthal equidistant. Test data # from Snyder p. 198, table 31. ------------------------------------------------------------------------------- operation +proj=aeqd +ellps=intl +lat_0=90 ------------------------------------------------------------------------------- tolerance 0.1 m accept 0 90 expect 0 0 roundtrip 100 accept 0 85 expect 0 -558_485.4 roundtrip 100 accept 0 80 expect 0 -1_116_885.2 roundtrip 100 accept 0 70 expect 0 -2_233_100.9 roundtrip 100 ------------------------------------------------------------------------------- # Test southern polar aspect of the ellipsoidal azimuthal equidistant. Test data # from Snyder p. 198, table 31. ------------------------------------------------------------------------------- operation +proj=aeqd +ellps=intl +lat_0=-90 ------------------------------------------------------------------------------- tolerance 0.1 m accept 0 -90 expect 0 0 roundtrip 100 accept 0 -85 expect 0 558_485.4 roundtrip 100 accept 0 -80 expect 0 1_116_885.2 roundtrip 100 accept 0 -70 expect 0 2_233_100.9 roundtrip 100 ------------------------------------------------------------------------------- # Test northern polar aspect of the spherical azimuthal equidistant. ------------------------------------------------------------------------------- operation +proj=aeqd +R=1 +lat_0=90 ------------------------------------------------------------------------------- tolerance 0.1 m accept 0 0 expect 0 -1.5708 roundtrip 100 accept 0 90 expect 0 0 roundtrip 100 accept 90 90 expect 0 0 roundtrip 100 accept 90 0 expect 1.5708 0 roundtrip 100 accept 45 45 expect 0.5554 -0.5554 roundtrip 100 #point opposite of projection center is undefined accept 0 -90 expect failure errno coord_transfm_outside_projection_domain direction inverse accept 0 5 expect failure errno coord_transfm_outside_projection_domain accept 0 3.14159265359 expect 180 -90 ------------------------------------------------------------------------------- # Test sourthnern polar aspect of the spherical azimuthal equidistant. ------------------------------------------------------------------------------- operation +proj=aeqd +R=1 +lat_0=-90 ------------------------------------------------------------------------------- tolerance 0.1 m accept 0 0 expect 0 1.5708 roundtrip 100 accept 0 -90 expect 0 0 roundtrip 100 accept 90 -90 expect 0 0 roundtrip 100 accept 90 0 expect 1.5708 0 roundtrip 100 accept 45 -45 expect 0.5554 0.5554 roundtrip 100 #point opposite of projection center is undefined accept 0 90 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test oblique aspect of the spherical azimuthal equidistant. ------------------------------------------------------------------------------- operation +proj=aeqd +R=1 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 m accept 0 0 expect 0.0000 -0.7854 roundtrip 100 accept 0 45 expect 0.0000 0.0000 roundtrip 100 accept 0 90 expect 0.0000 0.7854 roundtrip 100 accept 90 0 expect 1.5708 -0.0000 roundtrip 100 accept 90 45 expect 0.8550 0.6046 #roundtrip 100 # roundtrip performs badly for this test on some platforms accept 90 90 expect 0.0000 0.7854 roundtrip 100 ------------------------------------------------------------------------------- # Test oblique aspect of the ellipsoidal azimuthal equidistant. ------------------------------------------------------------------------------- operation +proj=aeqd +ellps=GRS80 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 m accept 0 0 expect 0.0000 -4984944.3779 roundtrip 100 accept 0 45 expect 0.0000 0.0000 roundtrip 100 accept 0 90 expect 0.0000 5017021.3514 roundtrip 100 accept 90 0 expect 10010351.5666 26393.3781 roundtrip 100 accept 90 45 expect 5461910.9128 3863514.7047 roundtrip 100 accept 90 90 expect 0.0000 5017021.3514 roundtrip 100 =============================================================================== # Airy # Misc Sph, no inv. # no_cut lat_b= =============================================================================== ------------------------------------------------------------------------------- operation +proj=airy +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 189109.886908621 94583.752387504 accept 2 -1 expect 189109.886908621 -94583.752387504 accept -2 1 expect -189109.886908621 94583.752387504 accept -2 -1 expect -189109.886908621 -94583.752387504 ------------------------------------------------------------------------------- # Test north polar aspect ------------------------------------------------------------------------------- operation +proj=airy +R=1 +lat_0=90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 -1.3863 accept 0 90 expect 0 0 accept 0 -90 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test south polar aspect ------------------------------------------------------------------------------- operation +proj=airy +R=1 +lat_0=-90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 1.3863 accept 0 -90 expect 0 0 accept 0 90 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test oblique aspect ------------------------------------------------------------------------------- operation +proj=airy +R=1 +lon_0=45 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 45 45 expect 0 0 accept 0 0 expect -0.7336 -0.5187 accept -45 -45 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test that coordinates on the opposing hemisphere are projected when using # +no_cut. ------------------------------------------------------------------------------- operation +proj=airy +R=1 +lat_0=-90 +no_cut ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 10 expect 0 1.5677 ------------------------------------------------------------------------------- # Test the +lat_b parameter ------------------------------------------------------------------------------- operation +proj=airy +R=1 +lat_b=89.99999999 # check tolerance ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 0 ------------------------------------------------------------------------------- operation +proj=airy +R=1 +lat_b=30 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 0 accept 25 25 expect 0.3821 0.4216 ------------------------------------------------------------------------------- operation +proj=airy +R=1 +no_cut ------------------------------------------------------------------------------- accept -180 0 expect failure errno coord_transfm_outside_projection_domain =============================================================================== # Aitoff # Misc Sph =============================================================================== ------------------------------------------------------------------------------- operation +proj=aitoff +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223379.458811696 111706.742883853 accept 2 -1 expect 223379.458811696 -111706.742883853 accept -2 1 expect -223379.458811696 111706.742883853 accept -2 -1 expect -223379.458811696 -111706.742883853 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Mod. Stereographic of Alaska # Azi(mod) =============================================================================== ------------------------------------------------------------------------------- operation +proj=alsk +ellps=clrk66 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -160.000000000 55.000000000 expect -513253.146950842 -968928.031867943 accept -160.000000000 70.000000000 expect -305001.133897637 687494.464958651 accept -145.000000000 70.000000000 expect 266454.305088600 683423.477493031 accept -145.000000000 60.000000000 expect 389141.322439244 -423913.251230397 direction inverse accept -500000.000000000 -950000.000000000 expect -159.830804303 55.183195262 accept -305000.000000000 700000.000000000 expect -160.042203156 70.111086864 accept 250000.000000000 700000.000000000 expect -145.381043551 70.163900908 accept 400000.000000000 -400000.000000000 expect -144.758985461 60.202929201 ------------------------------------------------------------------------------- operation +proj=alsk +R=6370997 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -160.000000000 55.000000000 expect -511510.319410844 -967150.991676078 accept -160.000000000 70.000000000 expect -303744.771290369 685439.745941123 accept -145.000000000 70.000000000 expect 265354.974019663 681386.892874573 accept -145.000000000 60.000000000 expect 387711.995394027 -422980.685505463 direction inverse accept -500000.000000000 -950000.000000000 expect -159.854014458 55.165653849 accept -305000.000000000 700000.000000000 expect -160.082332372 70.128307618 accept 250000.000000000 700000.000000000 expect -145.347827407 70.181566919 accept 400000.000000000 -400000.000000000 expect -144.734239827 60.193564733 =============================================================================== # Apian Globular I # Misc Sph, no inv. =============================================================================== ------------------------------------------------------------------------------- operation +proj=apian +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223374.577355253 111701.072127637 accept 2 -1 expect 223374.577355253 -111701.072127637 accept -2 1 expect -223374.577355253 111701.072127637 accept -2 -1 expect -223374.577355253 -111701.072127637 =============================================================================== # August Epicycloidal # Misc Sph, no inv. =============================================================================== ------------------------------------------------------------------------------- operation +proj=august +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223404.978180972 111722.340289763 accept 2 -1 expect 223404.978180972 -111722.340289763 accept -2 1 expect -223404.978180972 111722.340289763 accept -2 -1 expect -223404.978180972 -111722.340289763 =============================================================================== # Bacon Globular # Misc Sph, no inv. =============================================================================== ------------------------------------------------------------------------------- operation +proj=bacon +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223334.132555965 175450.725922666 accept 2 -1 expect 223334.132555965 -175450.725922666 accept -2 1 expect -223334.132555965 175450.725922666 accept -2 -1 expect -223334.132555965 -175450.725922666 =============================================================================== # Bipolar conic of western hemisphere # Conic Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=bipc +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 2452160.217725756 -14548450.759654747 accept 2 -1 expect 2447915.213725341 -14763427.212798730 accept -2 1 expect 2021695.522934909 -14540413.695283702 accept -2 -1 expect 2018090.503004699 -14755620.651414108 direction inverse accept 200 100 expect -73.038700285 17.248118466 accept 200 -100 expect -73.037303739 17.249414978 accept -200 100 expect -73.035893173 17.245536403 accept -200 -100 expect -73.034496627 17.246832896 ------------------------------------------------------------------------------- operation +proj=bipc +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 2460565.740974965 -14598319.989330800 accept 2 -1 expect 2456306.185935200 -14814033.339502094 accept -2 1 expect 2028625.497819099 -14590255.375482792 accept -2 -1 expect 2025008.120589143 -14806200.018759441 direction inverse accept 200 100 expect -73.038693105 17.248116270 accept 200 -100 expect -73.037301330 17.249408353 accept -200 100 expect -73.035895582 17.245543028 accept -200 -100 expect -73.034503807 17.246835092 =============================================================================== # Boggs Eumorphic # PCyl., no inv., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=boggs +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 211949.700808182 117720.998305411 accept 2 -1 expect 211949.700808182 -117720.998305411 accept -2 1 expect -211949.700808182 117720.998305411 accept -2 -1 expect -211949.700808182 -117720.998305411 =============================================================================== # Bonne (Werner lat_1=90) # Conic Sph&Ell # lat_1= =============================================================================== ------------------------------------------------------------------------------- operation +proj=bonne +ellps=GRS80 +lat_1=0.5 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222605.296097157 55321.139565495 accept 2 -1 expect 222605.296099239 -165827.647799052 accept -2 1 expect -222605.296097157 55321.139565495 accept -2 -1 expect -222605.296099239 -165827.647799052 direction inverse accept 200 100 expect 0.001796699 0.500904369 accept 200 -100 expect 0.001796698 0.499095631 accept -200 100 expect -0.001796699 0.500904369 accept -200 -100 expect -0.001796698 0.499095631 ------------------------------------------------------------------------------- operation +proj=bonne +ellps=GRS80 +lat_1=-0.5 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222605.2961 165827.6478 roundtrip 1 accept 2 -1 expect 222605.2961 -55321.1396 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=bonne +ellps=GRS80 +lat_1=90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 90 expect 0 0 direction inverse accept 0 0 expect 0 90 ------------------------------------------------------------------------------- operation +proj=bonne +ellps=GRS80 +lat_1=-90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 -90 expect 0 0 direction inverse accept 0 0 expect 0 -90 ------------------------------------------------------------------------------- operation +proj=bonne +R=6400000 +lat_1=0.5 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223368.115572528 55884.555246394 accept 2 -1 expect 223368.115574632 -167517.599369694 accept -2 1 expect -223368.115572528 55884.555246394 accept -2 -1 expect -223368.115574632 -167517.599369694 direction inverse accept 200 100 expect 0.001790562 0.500895246 accept 200 -100 expect 0.001790561 0.499104753 accept -200 100 expect -0.001790562 0.500895246 accept -200 -100 expect -0.001790561 0.499104753 ------------------------------------------------------------------------------- operation +proj=bonne +R=6400000 +lat_1=-0.5 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223368.1156 167517.5994 roundtrip 1 accept 2 -1 expect 223368.1156 -55884.5552 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=bonne +R=6400000 +lat_1=90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 90 expect 0 0 direction inverse accept 0 0 expect 0 90 ------------------------------------------------------------------------------- operation +proj=bonne +R=6400000 +lat_1=-90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 -90 expect 0 0 direction inverse accept 0 0 expect 0 -90 =============================================================================== # Cal Coop Ocean Fish Invest Lines/Stations # Cyl, Sph&Ell =============================================================================== ------------------------------------------------------------------------------- operation +proj=calcofi +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 508.444872150 -1171.764860418 accept 2 -1 expect 514.999168152 -1145.821981468 accept -2 1 expect 500.685384125 -1131.445377920 accept -2 -1 expect 507.369719137 -1106.178201483 direction inverse accept 200 100 expect -110.363307925 12.032056976 accept 200 -100 expect -98.455008863 18.698723643 accept -200 100 expect -207.447024504 81.314089279 accept -200 -100 expect -62.486322854 87.980755945 ------------------------------------------------------------------------------- operation +proj=calcofi +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 507.090507488 -1164.727375198 accept 2 -1 expect 513.686136375 -1138.999268217 accept -2 1 expect 499.336261476 -1124.435130997 accept -2 -1 expect 506.060570393 -1099.375665067 direction inverse accept 200 100 expect -110.305190410 12.032056976 accept 200 -100 expect -98.322360950 18.698723643 accept -200 100 expect -207.544906814 81.314089279 accept -200 -100 expect -62.576950372 87.980755945 operation +proj=calcofi +lon_0=50 +ellps=WGS84 accept 10 50 expect 303.525850 -1576.974388 roundtrip 100 operation +proj=calcofi +ellps=GRS80 +lon_0=50 accept 10 50 expect 303.525850 -1576.974388 roundtrip 100 operation +proj=calcofi +R=400 +lon_0=50 +x_0=10000 +y_0=500000 accept 10 50 expect 301.769827 -1567.849822 roundtrip 100 =============================================================================== # Cassini # Cyl, Sph&Ell =============================================================================== ------------------------------------------------------------------------------- operation +proj=cass +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222605.285776991 110642.229253999 roundtrip 1 accept 2 -1 expect 222605.285776991 -110642.229253999 accept -2 1 expect -222605.285776991 110642.229253999 accept -2 -1 expect -222605.285776991 -110642.229253999 direction inverse accept 200 100 expect 0.001796631 0.000904369 accept 200 -100 expect 0.001796631 -0.000904369 accept -200 100 expect -0.001796631 0.000904369 accept -200 -100 expect -0.001796631 -0.000904369 ------------------------------------------------------------------------------- operation +proj=cass +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223368.105203484 111769.145040586 accept 2 -1 expect 223368.105203484 -111769.145040586 accept -2 1 expect -223368.105203484 111769.145040586 accept -2 -1 expect -223368.105203484 -111769.145040586 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 ------------------------------------------------------------------------------- # test point from EPSG Guidance Note 7.2 operation +proj=cass +lat_0=10.4416666666667 +lon_0=-61.3333333333333 \ +x_0=86501.46392052 +y_0=65379.0134283 \ +a=6378293.64520876 +b=6356617.98767984 +to_meter=0.201166195164 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -62 10 expect 66644.94040882 82536.21873655 roundtrip 1 ------------------------------------------------------------------------------- # Hyperbolic variant: test point from EPSG Guidance Note 7.2 operation +proj=cass +hyperbolic +a=6378306.376305601 +rf=293.466307 \ +lat_0=-16.25 +lon_0=179.33333333333333 +to_meter=20.1168 \ +x_0=251727.9155424 +y_0=334519.953768 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 179.99433652777776 -16.841456527777776 expect 16015.28901692 13369.66005367 roundtrip 1 ------------------------------------------------------------------------------- # Scenario of https://github.com/OSGeo/PROJ/issues/4385 ------------------------------------------------------------------------------- operation +proj=cass +lat_0=50.6177 +lon_0=-1.19725 +x_0=500000 +y_0=100000 +ellps=airy +units=m tolerance 0.1 mm direction inverse accept 300000 100000 expect -4.022094267169 50.583438725252 accept 500000 100000 expect -1.19725 50.6177 =============================================================================== # Central Conic # Sph # lat_1 =============================================================================== ------------------------------------------------------------------------------- operation +proj=pipeline +R=6390000 \ +step +proj=ccon +lat_1=52 +lat_0=52 +lon_0=19 +x_0=330000 +y_0=-350000 \ +step +proj=axisswap +order=1,-2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 24 55 expect 650031.54109413219363 4106.1617770643609028 accept 15 49 expect 37074.189007307473069 676826.23559270039774 accept 24 49 expect 696053.36061617843913 672294.56795827199940 accept 19 52 expect 330000.00000000000000 350000.00000000000000 direction inverse accept 0 0 expect 13.840227318521004431 55.030403993648806391 accept 0 700000 expect 14.514453594615022781 48.773847834747808675 accept 700000 0 expect 24.782707184271129766 55.003515505218481835 accept 700000 700000 expect 24.027610763560529927 48.750476070495021286 accept 330000 350000 expect 19.000000000000000000 52.000000000000000000 =============================================================================== # Central Cylindrical # Cyl, Sph =============================================================================== ------------------------------------------------------------------------------- operation +proj=cc +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 111712.415540593 accept 2 -1 expect 223402.144255274 -111712.415540593 accept -2 1 expect -223402.144255274 111712.415540593 accept -2 -1 expect -223402.144255274 -111712.415540593 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Equal Area Cylindrical # Cyl, Sph&Ell # lat_ts= =============================================================================== ------------------------------------------------------------------------------- operation +proj=cea +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222638.981586547 110568.812396267 accept 2 -1 expect 222638.981586547 -110568.812396266 accept -2 1 expect -222638.981586547 110568.812396267 accept -2 -1 expect -222638.981586547 -110568.812396266 accept 150 50 expect 16697923.6190 4865983.5552 direction inverse accept 200 100 expect 0.001796631 0.000904369 accept 200 -100 expect 0.001796631 -0.000904369 accept -200 100 expect -0.001796631 0.000904369 accept -200 -100 expect -0.001796631 -0.000904369 accept 16697923.6190 4865983.5552 expect 150 50 ------------------------------------------------------------------------------- operation +proj=cea +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 111695.401198614 accept 2 -1 expect 223402.144255274 -111695.401198614 accept -2 1 expect -223402.144255274 111695.401198614 accept -2 -1 expect -223402.144255274 -111695.401198614 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Chamberlin Trimetric # Misc Sph, no inv. # lat_1= lon_1= lat_2= lon_2= lat_3= lon_3= =============================================================================== ------------------------------------------------------------------------------- operation +proj=chamb +R=6400000 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 2.5 mm accept 2 1 expect -27864.779586801 -223364.324593274 accept 2 -1 expect -251312.283053493 -223402.145526208 accept -2 1 expect -27864.785649105 223364.327328827 accept -2 -1 expect -251312.289116443 223402.142197287 =============================================================================== # Collignon # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=collg +a=6400000 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 249872.921577930 99423.174788460 accept 2 -1 expect 254272.532301245 -98559.307760743 accept -2 1 expect -249872.921577930 99423.174788460 accept -2 -1 expect -254272.532301245 -98559.307760743 direction inverse accept 200 100 expect 0.001586797 0.001010173 accept 200 -100 expect 0.001586769 -0.001010182 accept -200 100 expect -0.001586797 0.001010173 accept -200 -100 expect -0.001586769 -0.001010182 =============================================================================== # Compact Miller # Cyl., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=comill +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 110611.859089459 accept 2 -1 expect 223402.144255274 -110611.859089459 accept -2 1 expect -223402.144255274 110611.859089459 accept -2 -1 expect -223402.144255274 -110611.859089459 direction inverse accept 200 100 expect 0.001790493 0.000904107 accept 200 -100 expect 0.001790493 -0.000904107 accept -200 100 expect -0.001790493 0.000904107 accept -200 -100 expect -0.001790493 -0.000904107 =============================================================================== # Craster Parabolic (Putnins P4) # PCyl., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=crast +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 218280.142056781 114306.045604280 accept 2 -1 expect 218280.142056781 -114306.045604280 accept -2 1 expect -218280.142056781 114306.045604280 accept -2 -1 expect -218280.142056781 -114306.045604280 direction inverse accept 200 100 expect 0.001832259 0.000874839 accept 200 -100 expect 0.001832259 -0.000874839 accept -200 100 expect -0.001832259 0.000874839 accept -200 -100 expect -0.001832259 -0.000874839 =============================================================================== # Denoyer Semi-Elliptical # PCyl., no inv., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=denoy +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223377.422876954 111701.072127637 accept 2 -1 expect 223377.422876954 -111701.072127637 accept -2 1 expect -223377.422876954 111701.072127637 accept -2 -1 expect -223377.422876954 -111701.072127637 =============================================================================== # Airocean # Sph., Ellps. # (Each of the 23 faces tested separately around their center, inverse included) =============================================================================== ------------------------------------------------------------------------------- operation +proj=airocean +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 23 28 expect 13572113.73386754 23493648.55327798 accept 71 46 expect 9714915.991790695 23488176.361173604 accept 147 75 expect 7723484.49359606 20087141.837650128 accept -77 61 expect 9679376.816000767 16802749.593532257 accept -26 35 expect 15458567.83864155 20091165.592037637 accept 29 -13 expect 15471813.400558881 26802282.415074058 accept 71 -25 expect 9737210.823606653 30219178.19260869 accept 97 10 expect 7670302.042847798 26816601.848991044 accept 169 35 expect 3883710.702444233 20135415.72144515 accept -151 13 expect 3859776.9744116343 13387384.422000753 accept -109 24 expect 7674343.074326526 13366009.083146008 accept -84 -9 expect 9673007.441581018 10144952.26955531 accept -42 -4 expect 13562062.520622183 10107761.706502315 accept -11 -34 expect 13627060.52678455 3383645.5697278716 accept 155 -35 expect 1873264.8705730252 30211340.763352156 accept -158 -28 expect 1871227.8450291778 10115901.323020123 accept -109 -46 expect 7708744.672461299 6722251.06988263 accept -36 -75 expect 9665810.798055789 3381177.9821538515 accept 98 -49 expect 4806946.337586326 33007546.454859577 accept 114 -72 expect 7708905.600709579 1101689.019137724 accept 143 -9 expect 3219027.0687154396 27948068.75709961 accept 123 7 expect 5239165.493429321 26821978.017945066 accept 147 16 expect 2635947.740851659 22373572.978527334 direction inverse accept 13600000 23500000 expect 22.77346472511832 27.745464601997153 accept 9700000 23500000 expect 71.26673004703193 45.89205035111361 accept 7700000 20100000 expect 146.99339940860168 74.69909794660227 accept 9700000 16800000 expect -76.55528563752168 60.90966578454296 accept 15500000 20100000 expect -26.125789701735282 34.531335035632864 accept 15500000 26800000 expect 28.72566754254401 -13.176397846758185 accept 9700000 30200000 expect 71.49135806675328 -24.84162689595362 accept 7700000 26800000 expect 96.67476470896398 10.214265110489109 accept 3900000 20100000 expect 169.4467058181239 35.245717462371594 accept 3900000 13400000 expect -150.6222299120939 13.304599775998279 accept 7700000 13400000 expect -108.74281284723317 24.422067806064522 accept 9700000 10100000 expect -83.65325201216521 -9.486900253798344 accept 13600000 10100000 expect -41.56143010477453 -4.013493146314863 accept 13600000 3400000 expect -11.279582965366556 -34.27261608163502 accept 1900000 30200000 expect 154.64715194333021 -34.84574824559832 accept 1900000 10100000 expect -157.58387651437764 -28.052389289696965 accept 7700000 6700000 expect -109.19369493541197 -46.23421830648926 accept 9700000 3400000 expect -35.93009713541779 -74.56175824137314 accept 4800000 33000000 expect 98.172013849367 -49.00298561868703 accept 7700000 1100000 expect 114.26109340373671 -71.94195405675616 accept 3200000 27900000 expect 143.30076636407907 -8.522097079186306 accept 5200000 26800000 expect 123.44730422061694 7.179239072128023 accept 2600000 22400000 expect 146.8547812565557 15.542304306692937 accept 0 0 expect failure ------------------------------------------------------------------------------- operation +proj=airocean +orient=horizontal +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 23 28 expect 13391387.087562159 13572113.73386754 accept 71 46 expect 13396859.279666536 9714915.991790695 accept 147 75 expect 16797893.80319001 7723484.49359606 accept -77 61 expect 20082286.04730788 9679376.816000767 accept -26 35 expect 16793870.048802502 15458567.83864155 accept 29 -13 expect 10082753.22576608 15471813.400558881 accept 71 -25 expect 6665857.448231446 9737210.823606653 accept 97 10 expect 10068433.791849095 7670302.042847798 accept 169 35 expect 16749619.919394989 3883710.702444233 accept -151 13 expect 23497651.218839385 3859776.9744116343 accept -109 24 expect 23519026.557694133 7674343.074326526 accept -84 -9 expect 26740083.371284828 9673007.441581018 accept -42 -4 expect 26777273.934337825 13562062.520622183 accept -11 -34 expect 33501390.07111227 13627060.52678455 accept 155 -35 expect 6673694.877487984 1873264.8705730252 accept -158 -28 expect 26769134.317820016 1871227.8450291778 accept -109 -46 expect 30162784.570957504 7708744.672461299 accept -36 -75 expect 33503857.658686288 9665810.798055789 accept 98 -49 expect 3877489.1859805635 4806946.337586326 accept 114 -72 expect 35783346.62170241 7708905.600709579 accept 143 -9 expect 8936966.883740531 3219027.0687154396 accept 123 7 expect 10063057.622895071 5239165.493429321 accept 147 16 expect 14511462.662312808 2635947.740851659 direction inverse accept 13400000 13600000 expect 22.653513921934305 27.877587719075937 accept 13400000 9700000 expect 71.23213038171733 46.05944622180928 accept 16800000 7700000 expect 147.55671447322464 74.77832986646499 accept 20100000 9700000 expect -76.64598925873727 60.747020624548 accept 16800000 15500000 expect -26.3124065099563 34.601485830443536 accept 10100000 15500000 expect 28.619135182474427 -13.042018999526977 accept 6700000 9700000 expect 71.5162610671907 -24.673252485600123 accept 10100000 7700000 expect 96.68789658312737 10.383985604100156 accept 16800000 3900000 expect 169.65090726985764 35.27199233196341 accept 23500000 3900000 expect -150.55720908958426 13.14679150488858 accept 23500000 7700000 expect -108.71768234825969 24.253726008211544 accept 26800000 9700000 expect -83.64031642722364 -9.65664821408901 accept 26800000 13600000 expect -41.53248336979641 -4.181271680064457 accept 33500000 13600000 expect -11.077997959623605 -34.30009883727707 accept 6700000 1900000 expect 154.6653022651957 -34.676851253860285 accept 26800000 1900000 expect -157.5153533577128 -28.210938432335496 accept 30200000 7700000 expect -109.22990606962236 -46.40145478927908 accept 33500000 9700000 expect -35.386955975332214 -74.64821453762985 accept 3900000 4800000 expect 98.362008540559 -48.89629332838504 accept 35800000 7700000 expect 114.04215001020711 -71.79634907735154 accept 9000000 3200000 expect 143.33006363443351 -8.36301544647104 accept 10100000 5200000 expect 123.47123951316074 7.342196526699235 accept 14500000 2600000 expect 147.01335056698537 15.59184037944909 accept 0 0 expect failure =============================================================================== # Eckert I # PCyl., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=eck1 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 204680.888202951 102912.178426065 accept 2 -1 expect 204680.888202951 -102912.178426065 accept -2 1 expect -204680.888202951 102912.178426065 accept -2 -1 expect -204680.888202951 -102912.178426065 direction inverse accept 200 100 expect 0.001943415 0.000971702 accept 200 -100 expect 0.001943415 -0.000971702 accept -200 100 expect -0.001943415 0.000971702 accept -200 -100 expect -0.001943415 -0.000971702 =============================================================================== # Eckert II # PCyl. Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=eck2 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 204472.870907960 121633.734975242 accept 2 -1 expect 204472.870907960 -121633.734975242 accept -2 1 expect -204472.870907960 121633.734975242 accept -2 -1 expect -204472.870907960 -121633.734975242 direction inverse accept 200 100 expect 0.001943415 0.000824804 accept 200 -100 expect 0.001943415 -0.000824804 accept -200 100 expect -0.001943415 0.000824804 accept -200 -100 expect -0.001943415 -0.000824804 =============================================================================== # Eckert III # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=eck3 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 188652.015721538 94328.919337031 accept 2 -1 expect 188652.015721538 -94328.919337031 accept -2 1 expect -188652.015721538 94328.919337031 accept -2 -1 expect -188652.015721538 -94328.919337031 direction inverse accept 200 100 expect 0.002120241 0.001060120 accept 200 -100 expect 0.002120241 -0.001060120 accept -200 100 expect -0.002120241 0.001060120 accept -200 -100 expect -0.002120241 -0.001060120 =============================================================================== # Eckert IV # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=eck4 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 188646.389356416 132268.540174065 accept 2 -1 expect 188646.389356416 -132268.540174065 accept -2 1 expect -188646.389356416 132268.540174065 accept -2 -1 expect -188646.389356416 -132268.540174065 accept -180 90 expect -8489602.7403 8489602.7403 accept 180 90 expect 8489602.7403 8489602.7403 accept -180 0 expect -16979205.4807 0 accept 180 0 expect 16979205.4807 0 accept -180 -90 expect -8489602.7403 -8489602.7403 accept 180 -90 expect 8489602.7403 -8489602.7403 direction inverse accept 200 100 expect 0.002120241 0.000756015 accept 200 -100 expect 0.002120241 -0.000756015 accept -200 100 expect -0.002120241 0.000756015 accept -200 -100 expect -0.002120241 -0.000756015 accept -8489602.74033281 8489602.74033281 expect -180 90 accept -8489602.75 8489602.74033281 expect failure errno coord_transfm_outside_projection_domain accept 8489602.74033281 8489602.74033281 expect 180 90 accept 8489602.75 8489602.74033281 expect failure errno coord_transfm_outside_projection_domain accept 0 8489602.75 expect failure errno coord_transfm_outside_projection_domain accept -16979205.4807 0 expect -180 0 accept -16979205.49 0 expect failure errno coord_transfm_outside_projection_domain accept 16979205.4807 0 expect 180 0 accept 16979205.49 0 expect failure errno coord_transfm_outside_projection_domain accept -8489602.74033281 -8489602.74033281 expect -180 -90 accept -8489602.75 -8489602.74033281 expect failure errno coord_transfm_outside_projection_domain accept 8489602.74033281 -8489602.74033281 expect 180 -90 accept 8489602.75 -8489602.74033281 expect failure errno coord_transfm_outside_projection_domain accept 0 -8489602.75 expect failure errno coord_transfm_outside_projection_domain =============================================================================== # Eckert V # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=eck5 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 197031.392134061 98523.198847227 accept 2 -1 expect 197031.392134061 -98523.198847227 accept -2 1 expect -197031.392134061 98523.198847227 accept -2 -1 expect -197031.392134061 -98523.198847227 direction inverse accept 200 100 expect 0.002029979 0.001014989 accept 200 -100 expect 0.002029979 -0.001014989 accept -200 100 expect -0.002029979 0.001014989 accept -200 -100 expect -0.002029979 -0.001014989 =============================================================================== # Eckert VI # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=eck6 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 197021.605628992 126640.420733174 accept 2 -1 expect 197021.605628992 -126640.420733174 accept -2 1 expect -197021.605628992 126640.420733174 accept -2 -1 expect -197021.605628992 -126640.420733174 direction inverse accept 200 100 expect 0.002029979 0.000789630 accept 200 -100 expect 0.002029979 -0.000789630 accept -200 100 expect -0.002029979 0.000789630 accept -200 -100 expect -0.002029979 -0.000789630 =============================================================================== # Equidistant Cylindrical (Plate Carree) # Cyl, Sph&Ell # lat_ts=[, lat_0=0] =============================================================================== ------------------------------------------------------------------------------- # Spherical case operation +proj=eqc +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 111701.072127637 accept 2 -1 expect 223402.144255274 -111701.072127637 accept -2 1 expect -223402.144255274 111701.072127637 accept -2 -1 expect -223402.144255274 -111701.072127637 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 ------------------------------------------------------------------------------- # Ellipsoidal case (EPSG:1028) # Test values from IOGP Guidance Note 7-2, Section 3.2.5 # WGS84 ellipsoid: a=6378137, 1/f=298.257223563 # Standard parallel: 0° (lat_ts=0) # Input: lat=55°, lon=10° # Expected: E=1113194.91, N=6097230.31 operation +proj=eqc +ellps=WGS84 +lat_ts=0 ------------------------------------------------------------------------------- tolerance 0.01 m accept 10 55 expect 1113194.91 6097230.31 direction inverse accept 1113194.91 6097230.31 expect 10 55 ------------------------------------------------------------------------------- # Ellipsoidal case - high latitude test # WGS84 ellipsoid # Input: lat=71.9993230521°, lon=-143.9999611505° # Expected: # E=-16030002.350, N=7992053.817 operation +proj=eqc +ellps=WGS84 +lat_ts=0 ------------------------------------------------------------------------------- tolerance 0.1 m accept -143.9999611505 71.9993230521 expect -16030002.350 7992053.817 direction inverse accept -16030002.350 7992053.817 expect -143.9999611505 71.9993230521 ------------------------------------------------------------------------------- # Ellipsoidal case - Edge cases # WGS84 ellipsoid, lat_ts=0 # Tests for origin, hemispheres, date line, near-pole operation +proj=eqc +ellps=WGS84 +lat_ts=0 ------------------------------------------------------------------------------- tolerance 0.001 m # Origin (0, 0) accept 0 0 expect 0.0 0.0 # Southern hemisphere (10, -45) accept 10 -45 expect 1113194.90793 -4984944.37798 # Date line (180, 30) accept 180 30 expect 20037508.34279 3320113.39794 # Near north pole (0, 89) accept 0 89 expect 0.0 9890271.86440 # San Francisco (-122.4194, 37.7749) accept -122.4194 37.7749 expect -13627665.27122 4182513.19136 direction inverse # Inverse tests for edge cases accept 0.0 0.0 expect 0 0 accept 1113194.90793 -4984944.37798 expect 10 -45 accept 20037508.34279 3320113.39794 expect 180 30 accept 0.0 9890271.86440 expect 0 89 accept -13627665.27122 4182513.19136 expect -122.4194 37.7749 ------------------------------------------------------------------------------- # Ellipsoidal case with non-zero standard parallel (lat_ts=45) # WGS84 ellipsoid # Tests the ν₁ cos(φ₁) scaling factor for easting operation +proj=eqc +ellps=WGS84 +lat_ts=45 ------------------------------------------------------------------------------- tolerance 0.01 m # Paris region (2, 49) accept 2 49 expect 157693.670 5429627.632 # Origin (0, 0) - northing should still be 0 accept 0 0 expect 0.0 0.0 # High latitude (10, 70) accept 10 70 expect 788468.351 7768980.728 direction inverse accept 157693.670 5429627.632 expect 2 49 accept 0.0 0.0 expect 0 0 accept 788468.351 7768980.728 expect 10 70 ------------------------------------------------------------------------------- # Ellipsoidal case with lat_ts=30 and lat_0=45 (non-zero origin) # Tests meridional arc offset M₀ operation +proj=eqc +ellps=WGS84 +lat_ts=30 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.01 m # At the origin latitude, northing should be 0 accept 0 45 expect 0.0 0.0 # Above origin accept 0 60 expect 0.0 1669128.442 # Below origin accept 0 30 expect 0.0 -1664830.980 direction inverse accept 0.0 0.0 expect 0 45 accept 0.0 1669128.442 expect 0 60 accept 0.0 -1664830.980 expect 0 30 =============================================================================== # Equidistant Conic # Conic, Sph&Ell # lat_1= lat_2= =============================================================================== ------------------------------------------------------------------------------- operation +proj=eqdc +ellps=GRS80 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222588.440269286 110659.134907347 accept 2 -1 expect 222756.836702042 -110489.578087221 accept -2 1 expect -222588.440269286 110659.134907347 accept -2 -1 expect -222756.836702042 -110489.578087221 direction inverse accept 200 100 expect 0.001796359 0.000904369 accept 200 -100 expect 0.001796358 -0.000904370 accept -200 100 expect -0.001796359 0.000904369 accept -200 -100 expect -0.001796358 -0.000904370 ------------------------------------------------------------------------------- operation +proj=eqdc +R=6400000 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223351.088175114 111786.108747174 accept 2 -1 expect 223521.200266735 -111615.970741241 accept -2 1 expect -223351.088175114 111786.108747174 accept -2 -1 expect -223521.200266735 -111615.970741241 direction inverse accept 200 100 expect 0.001790221 0.000895246 accept 200 -100 expect 0.001790220 -0.000895247 accept -200 100 expect -0.001790221 0.000895246 accept -200 -100 expect -0.001790220 -0.000895247 operation +proj=eqdc +a=9999999 +b=.9 +lat_2=1 expect failure operation +proj=eqdc +R=6400000 +lat_1=1 +lat_2=-1 expect failure errno invalid_op_illegal_arg_value operation +proj=eqdc +R=6400000 +lat_1=91 expect failure errno invalid_op_illegal_arg_value operation +proj=eqdc +R=6400000 +lat_2=91 expect failure errno invalid_op_illegal_arg_value operation +proj=eqdc +R=1 +lat_1=1e-9 expect failure errno invalid_op_illegal_arg_value operation +proj=eqdc +lat_1=1 +ellps=GRS80 +b=.1 expect failure errno invalid_op_illegal_arg_value =============================================================================== # Euler # Conic, Sph # lat_1= and lat_2= =============================================================================== ------------------------------------------------------------------------------- operation +proj=euler +ellps=GRS80 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222597.634659108 111404.240549919 accept 2 -1 expect 222767.165631876 -111234.676491018 accept -2 1 expect -222597.634659108 111404.240549919 accept -2 -1 expect -222767.165631876 -111234.676491018 direction inverse accept 200 100 expect 0.001796281 0.000898315 accept 200 -100 expect 0.001796279 -0.000898316 accept -200 100 expect -0.001796281 0.000898315 accept -200 -100 expect -0.001796279 -0.000898316 ------------------------------------------------------------------------------- operation +proj=euler +a=6400000 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223360.655598694 111786.112389791 accept 2 -1 expect 223530.767690316 -111615.967098624 accept -2 1 expect -223360.655598694 111786.112389791 accept -2 -1 expect -223530.767690316 -111615.967098624 direction inverse accept 200 100 expect 0.001790144 0.000895246 accept 200 -100 expect 0.001790143 -0.000895247 accept -200 100 expect -0.001790144 0.000895246 accept -200 -100 expect -0.001790143 -0.000895247 =============================================================================== # Extended Transverse Mercator # Cyl, Sph # lat_ts=(0) # lat_0=(0) =============================================================================== ------------------------------------------------------------------------------- operation +proj=etmerc +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 50 nm accept 2 1 expect 222650.796797586 110642.229411933 accept 2 -1 expect 222650.796797586 -110642.229411933 accept -2 1 expect -222650.796797586 110642.229411933 accept -2 -1 expect -222650.796797586 -110642.229411933 # near pole accept 30 89.9999 expect 5.584698978 10001956.056248082 # 3900 km from central meridian accept 44.69 35.37 expect 4168136.489446198 4985511.302287407 direction inverse accept 200 100 expect 0.00179663056816 0.00090436947663 accept 200 -100 expect 0.00179663056816 -0.00090436947663 accept -200 100 expect -0.00179663056816 0.00090436947663 accept -200 -100 expect -0.00179663056816 -0.00090436947663 # near pole accept 6 1.0001e7 expect 0.35596960759234 89.99135362646302 # 3900 km from central meridian accept 4168136.489446198 4985511.302287407 expect 44.69 35.37 =============================================================================== # Fahey # Pcyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=fahey +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 182993.344649124 101603.193569884 accept 2 -1 expect 182993.344649124 -101603.193569884 accept -2 1 expect -182993.344649124 101603.193569884 accept -2 -1 expect -182993.344649124 -101603.193569884 direction inverse accept 200 100 expect 0.002185789 0.000984246 accept 200 -100 expect 0.002185789 -0.000984246 accept -200 100 expect -0.002185789 0.000984246 accept -200 -100 expect -0.002185789 -0.000984246 =============================================================================== # Foucaut # PCyl., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=fouc +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222588.120675892 111322.316700694 accept 2 -1 expect 222588.120675892 -111322.316700694 accept -2 1 expect -222588.120675892 111322.316700694 accept -2 -1 expect -222588.120675892 -111322.316700694 direction inverse accept 200 100 expect 0.001796631 0.000898315 accept 200 -100 expect 0.001796631 -0.000898315 accept -200 100 expect -0.001796631 0.000898315 accept -200 -100 expect -0.001796631 -0.000898315 ------------------------------------------------------------------------------- operation +proj=fouc +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223351.109003414 111703.907721713 accept 2 -1 expect 223351.109003414 -111703.907721713 accept -2 1 expect -223351.109003414 111703.907721713 accept -2 -1 expect -223351.109003414 -111703.907721713 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Foucaut Sinusoidal # PCyl., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=fouc_s +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 111695.401198614 accept 2 -1 expect 223402.144255274 -111695.401198614 accept -2 1 expect -223402.144255274 111695.401198614 accept -2 -1 expect -223402.144255274 -111695.401198614 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Gall (Gall Stereographic) # Cyl, Sph =============================================================================== ------------------------------------------------------------------------------- operation +proj=gall +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 157969.171134520 95345.249178386 accept 2 -1 expect 157969.171134520 -95345.249178386 accept -2 1 expect -157969.171134520 95345.249178386 accept -2 -1 expect -157969.171134520 -95345.249178386 direction inverse accept 200 100 expect 0.002532140 0.001048847 accept 200 -100 expect 0.002532140 -0.001048847 accept -200 100 expect -0.002532140 0.001048847 accept -200 -100 expect -0.002532140 -0.001048847 =============================================================================== # Geocentric =============================================================================== ------------------------------------------------------------------------------- operation +proj=geocent +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 0 expect 6373287.27950247 222560.09599219 110568.77482092 accept 2 -1 0 expect 6373287.27950247 222560.09599219 -110568.77482092 accept -2 1 0 expect 6373287.27950247 -222560.09599219 110568.77482092 accept -2 -1 0 expect 6373287.27950247 -222560.09599219 -110568.77482092 direction inverse accept 6373287.27950247 222560.09599219 110568.77482092 expect 2 1 0 ------------------------------------------------------------------------------- operation +proj=geocent +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm =============================================================================== # Geostationary Satellite View # Azi, Sph&Ell # h= =============================================================================== ------------------------------------------------------------------------------- operation +proj=geos +ellps=GRS80 +h=35785831 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222527.070365800 110551.303413329 accept 2 -1 expect 222527.070365800 -110551.303413329 accept -2 1 expect -222527.070365800 110551.303413329 accept -2 -1 expect -222527.070365800 -110551.303413329 direction inverse accept 200 100 expect 0.001796631 0.000904369 accept 200 -100 expect 0.001796631 -0.000904369 accept -200 100 expect -0.001796631 0.000904369 accept -200 -100 expect -0.001796631 -0.000904369 ------------------------------------------------------------------------------- operation +proj=geos +R=6400000 +h=35785831 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223289.457635795 111677.657456537 accept 2 -1 expect 223289.457635795 -111677.657456537 accept -2 1 expect -223289.457635795 111677.657456537 accept -2 -1 expect -223289.457635795 -111677.657456537 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 ------------------------------------------------------------------------------- operation +proj=geos +R=1 +h=0 ------------------------------------------------------------------------------- expect failure errno invalid_op_illegal_arg_value ------------------------------------------------------------------------------- operation +proj=geos +R=1 +h=1e11 ------------------------------------------------------------------------------- expect failure errno invalid_op_illegal_arg_value =============================================================================== # Ginsburg VIII (TsNIIGAiK) # PCyl, Sph., no inv. =============================================================================== ------------------------------------------------------------------------------- operation +proj=gins8 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 194350.250939590 111703.907635335 accept 2 -1 expect 194350.250939590 -111703.907635335 accept -2 1 expect -194350.250939590 111703.907635335 accept -2 -1 expect -194350.250939590 -111703.907635335 =============================================================================== # General Sinusoidal Series # PCyl, Sph. # m= n= =============================================================================== ------------------------------------------------------------------------------- operation +proj=gn_sinu +a=6400000 +m=1 +n=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223385.132504696 111698.236447187 accept 2 -1 expect 223385.132504696 -111698.236447187 accept -2 1 expect -223385.132504696 111698.236447187 accept -2 -1 expect -223385.132504696 -111698.236447187 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Gnomonic # Azi, Sph*Ell =============================================================================== ------------------------------------------------------------------------------- # Test material from Snyder p. 168, table 26. Repeat tests with ellispoid of # flattening 1/200. # Tests the equatorial aspect of the projection. ------------------------------------------------------------------------------- operation +proj=gnom +R=1 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 0 roundtrip 100 accept 10 80 expect 0.1763 5.7588 roundtrip 100 accept 20 70 expect 0.3640 2.9238 roundtrip 100 accept 30 60 expect 0.5774 2.0000 roundtrip 100 accept 40 50 expect 0.8391 1.5557 roundtrip 100 accept 50 40 expect 1.1918 1.3054 roundtrip 100 accept 60 30 expect 1.7321 1.1547 roundtrip 100 accept 70 20 expect 2.7475 1.0642 roundtrip 100 accept 80 10 expect 5.6713 1.0154 roundtrip 100 accept 80 80 expect 5.6713 32.6596 roundtrip 100 accept 0 90 expect failure errno coord_transfm_outside_projection_domain # test that extreme northings are mapped to the sphere direction inverse accept 0 1e8 expect 0 90 ------------------------------------------------------------------------------- operation +proj=gnom +a=1 +rf=200 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 0 roundtrip 100 accept 10 80 expect 0.1763 5.7232 roundtrip 100 accept 20 70 expect 0.3641 2.9037 roundtrip 100 accept 30 60 expect 0.5778 1.9861 roundtrip 100 accept 40 50 expect 0.8405 1.5459 roundtrip 100 accept 50 40 expect 1.1958 1.2994 roundtrip 100 accept 60 30 expect 1.7435 1.1534 roundtrip 100 accept 70 20 expect 2.7852 1.0711 roundtrip 100 accept 80 10 expect 5.8813 1.0465 roundtrip 100 accept 80 80 expect 5.7134 32.7298 roundtrip 100 accept 0 89.99 expect 0 5700.9222 roundtrip 100 accept 180 89.99 expect failure errno coord_transfm_outside_projection_domain # test that extreme northings are mapped to the sphere direction inverse accept 0 1e8 expect 0 90 ------------------------------------------------------------------------------- # Test the northern polar aspect of the gnonomic projection ------------------------------------------------------------------------------- operation +proj=gnom +R=1 +lat_0=90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 90 expect 0 0 roundtrip 100 accept 45 45 expect 0.7071 -0.7071 roundtrip 100 accept 0 0 expect failure errno coord_transfm_outside_projection_domain accept 90 0 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- operation +proj=gnom +a=1 +rf=200 +lat_0=90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 90 expect 0 0 roundtrip 100 accept 45 45 expect 0.7079 -0.7079 roundtrip 100 accept 0 0 expect 0 -127.4835 roundtrip 100 accept 90 0 expect 127.4835 0 roundtrip 100 accept 0 -0.5 expect failure errno coord_transfm_outside_projection_domain accept 90 -0.5 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test the southern polar aspect of the gnonomic projection ------------------------------------------------------------------------------- operation +proj=gnom +R=1 +lat_0=-90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 -90 expect 0 0 roundtrip 100 accept 45 -45 expect 0.7071 0.7071 roundtrip 100 accept 0 0 expect failure errno coord_transfm_outside_projection_domain accept 90 0 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- operation +proj=gnom +a=1 +rf=200 +lat_0=-90 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 -90 expect 0 0 roundtrip 100 accept 45 -45 expect 0.7079 0.7079 roundtrip 100 accept 0 0 expect 0 127.4835 roundtrip 100 accept 90 0 expect 127.4835 0 roundtrip 100 accept 0 0.5 expect failure errno coord_transfm_outside_projection_domain accept 90 0.5 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test the oblique aspect of the gnonomic projection ------------------------------------------------------------------------------- operation +proj=gnom +R=1 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 45 expect 0 0 roundtrip 100 accept 0 0 expect 0 -1 roundtrip 100 accept 0 90 expect 0 1 roundtrip 100 accept 0 -45 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- operation +proj=gnom +a=1 +rf=200 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 45 expect 0 0 roundtrip 100 accept 0 0 expect 0 -0.9897 roundtrip 100 accept 0 90 expect 0 1.0025 roundtrip 100 accept 0 -45 expect 0 -154.8623 roundtrip 100 accept 0 -45.5 expect failure errno coord_transfm_outside_projection_domain =============================================================================== # Goode Homolosine # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=goode +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223368.119026632 111701.072127637 accept 2 -1 expect 223368.119026632 -111701.072127637 accept -2 1 expect -223368.119026632 111701.072127637 accept -2 -1 expect -223368.119026632 -111701.072127637 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 =============================================================================== # Mod. Stereographic of 48 U.S. # Azi(mod) =============================================================================== ------------------------------------------------------------------------------- operation +proj=gs48 +R=6370997 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -119.000000000 40.000000000 expect -1923908.446529346 355874.658944479 accept -70.000000000 64.000000000 expect 1354020.375109298 3040846.007866525 accept -80.000000000 25.000000000 expect 1625139.160484320 -1413614.894029108 accept -95.000000000 35.000000000 expect 90241.658071458 -439595.048485902 direction inverse accept -1923000.000000000 355000.000000000 expect -118.987112613 39.994449789 accept 1354000.000000000 3040000.000000000 expect -70.005208999 63.993387836 accept 1625000.000000000 -1413000.000000000 expect -80.000346610 25.005602547 accept 90000.000000000 -439000.000000000 expect -95.002606473 35.005424705 =============================================================================== # Mod. Stereographic of 50 U.S. # Azi(mod) =============================================================================== ------------------------------------------------------------------------------- operation +proj=gs50 +ellps=clrk66 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -160.000000000 65.000000000 expect -1874628.537740233 2660907.942291015 accept -130.000000000 45.000000000 expect -771831.518853336 48465.166491305 accept -65.000000000 45.000000000 expect 4030931.833981509 1323687.864777399 accept -80.000000000 36.000000000 expect 3450764.261536101 -175619.041820732 # For some reason, does not fail on MacOSX #accept 60 -45 #expect failure errno coord_transfm_outside_projection_domain direction inverse accept -1800000.000000000 2600000.000000000 expect -157.989285000 64.851559610 accept -800000.000000000 500000.000000000 expect -131.171390467 49.084969746 accept 4000000.000000000 1300000.000000000 expect -65.491568685 44.992837924 accept 3900000.000000000 -170000.000000000 expect -75.550660091 34.191114076 ------------------------------------------------------------------------------- operation +proj=gs50 +R=6370997 ------------------------------------------------------------------------------- tolerance 0.1 mm accept -160.000000000 65.000000000 expect -1867268.253460009 2656506.230401823 accept -130.000000000 45.000000000 expect -769572.189672994 48324.312440864 accept -65.000000000 45.000000000 expect 4019393.068680791 1320191.309350289 accept -80.000000000 36.000000000 expect 3442685.615172346 -178760.423489429 direction inverse accept -1800000.000000000 2600000.000000000 expect -158.163295045 64.854288365 accept -800000.000000000 500000.000000000 expect -131.206816960 49.082915351 accept 4000000.000000000 1300000.000000000 expect -65.348945221 44.957292682 accept 3900000.000000000 -170000.000000000 expect -75.446820242 34.185406226 =============================================================================== # Hammer & Eckert-Greifendorff # Misc Sph, # W= M= =============================================================================== ------------------------------------------------------------------------------- operation +proj=hammer +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223373.788703241 111703.907397767 accept 2 -1 expect 223373.788703241 -111703.907397767 accept -2 1 expect -223373.788703241 111703.907397767 accept -2 -1 expect -223373.788703241 -111703.907397767 direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 ------------------------------------------------------------------------------- operation +proj=hammer +a=6400000 +W=1 ------------------------------------------------------------------------------- accept -180 0 expect failure errno coord_transfm_outside_projection_domain =============================================================================== # Hatano Asymmetrical Equal Area # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=hatano +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 189878.878946528 131409.802440626 accept 2 -1 expect 189881.081952445 -131409.142276074 accept -2 1 expect -189878.878946528 131409.802440626 accept -2 -1 expect -189881.081952445 -131409.142276074 direction inverse accept 200 100 expect 0.002106462 0.000760957 accept 200 -100 expect 0.002106462 -0.000760958 accept -200 100 expect -0.002106462 0.000760957 accept -200 -100 expect -0.002106462 -0.000760958 =============================================================================== # HEALPix # Sph., Ellps. =============================================================================== ------------------------------------------------------------------------------- operation +proj=healpix +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222390.103949239 130406.588664482 accept 2 -1 expect 222390.103949239 -130406.588664481 accept -2 1 expect -222390.103949239 130406.588664482 accept -2 -1 expect -222390.103949239 -130406.588664481 direction inverse accept 200 100 expect 0.001798641 0.000766795 accept 200 -100 expect 0.001798641 -0.000766795 accept -200 100 expect -0.001798641 0.000766795 accept -200 -100 expect -0.001798641 -0.000766795 ------------------------------------------------------------------------------- operation +proj=healpix +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 131588.044441999 accept 2 -1 expect 223402.144255274 -131588.044441999 accept -2 1 expect -223402.144255274 131588.044441999 accept -2 -1 expect -223402.144255274 -131588.044441999 direction inverse accept 200 100 expect 0.001790493 0.000759909 accept 200 -100 expect 0.001790493 -0.000759909 accept -200 100 expect -0.001790493 0.000759909 accept -200 -100 expect -0.001790493 -0.000759909 ------------------------------------------------------------------------------- operation +proj=healpix +R=6400000 +lat_1=0.5 +lat_2=2 +rot_xy=42 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 254069.735470912856 -51696.237925639456 accept 2 -1 expect 77970.559536809917 -247274.186569161975 accept -2 1 expect -77970.559536809917 247274.186569161975 accept -2 -1 expect -254069.735470912856 51696.237925639456 direction inverse accept 254069.735470912856 -51696.237925639456 expect 2 1 accept 77970.559536809917 -247274.186569161975 expect 2 -1 accept -77970.559536809917 247274.186569161975 expect -2 1 accept -254069.735470912856 51696.237925639456 expect -2 -1 =============================================================================== # rHEALPix # Sph., Ellps. # north_square= south_square= =============================================================================== ------------------------------------------------------------------------------- operation +proj=rhealpix +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222390.103949239 130406.588664482 accept 2 -1 expect 222390.103949239 -130406.588664481 accept -2 1 expect -222390.103949239 130406.588664482 accept -2 -1 expect -222390.103949239 -130406.588664481 direction inverse accept 200 100 expect 0.001798641 0.000766795 accept 200 -100 expect 0.001798641 -0.000766795 accept -200 100 expect -0.001798641 0.000766795 accept -200 -100 expect -0.001798641 -0.000766795 ------------------------------------------------------------------------------- operation +proj=rhealpix +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223402.144255274 131588.044441999 accept 2 -1 expect 223402.144255274 -131588.044441999 accept -2 1 expect -223402.144255274 131588.044441999 accept -2 -1 expect -223402.144255274 -131588.044441999 direction inverse accept 200 100 expect 0.001790493 0.000759909 accept 200 -100 expect 0.001790493 -0.000759909 accept -200 100 expect -0.001790493 0.000759909 accept -200 -100 expect -0.001790493 -0.000759909 ------------------------------------------------------------------------------- operation +proj=rhealpix +south_square=2 +north_square=3 +ellps=WGS84 ------------------------------------------------------------------------------- tolerance 1 m accept 45 50 expect 10806592 10007554 accept 45 -50 expect 5003777 -5802815 accept 135 50 expect 15011332 5802815 direction inverse accept 10806592 10007554 expect 45 50 accept 5003777 -5802815 expect 45 -50 accept 15011332 5802815 expect 135 50 =============================================================================== # Interrupted Goode Homolosine # PCyl, Sph. # (Each of the 12 sub-projections tested separately) =============================================================================== ------------------------------------------------------------------------------- operation +proj=igh +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223878.497456271 111701.072127637 roundtrip 1 accept 2 -1 expect 223708.371313058 -111701.072127637 roundtrip 1 accept -2 1 expect -222857.740596992 111701.072127637 roundtrip 1 accept -2 -1 expect -223027.866740205 -111701.072127637 roundtrip 1 accept -100.0 22.0 expect -11170107.212763708 2457423.5868080168 roundtrip 1 accept -30.0 22.0 expect -2863013.673043605 2457423.586808016 roundtrip 1 accept -100.0 67.0 expect -11170107.212763708 7205942.523056464 roundtrip 1 accept -30.0 67.0 expect 17045.719482862 7205942.523056464 roundtrip 1 accept -160.0 -22.0 expect -17872171.540421933 -2457423.586808016 roundtrip 1 accept -60.0 -22.0 expect -6702064.327658225 -2457423.586808016 roundtrip 1 accept 20.0 -22.0 expect 2234021.442552742 -2457423.586808016 roundtrip 1 accept 140.0 -22.0 expect 15638150.097869191 -2457423.586808016 roundtrip 1 accept -160.0 -67.0 expect -17872171.540421933 -7205942.523056464 roundtrip 1 accept -60.0 -67.0 expect -6702064.327658225 -7205942.523056464 roundtrip 1 accept 20.0 -67.0 expect 2234021.442552742 -7205942.523056464 roundtrip 1 accept 140.0 -67.0 expect 15638150.097869191 -7205942.523056464 roundtrip 1 direction inverse accept 200 100 expect 0.001790489 0.000895247 accept 200 -100 expect 0.001790491 -0.000895247 accept -200 100 expect -0.001790497 0.000895247 accept -200 -100 expect -0.001790496 -0.000895247 =============================================================================== # Interrupted Goode Homolosine Ocean View # PCyl, Sph. # (Each of the 12 sub-projections tested separately) =============================================================================== ------------------------------------------------------------------------------- operation +proj=igh_o +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223197.992883418 111701.072127637 roundtrip 1 accept 2 -1 expect 223708.371313058 -111701.072127637 roundtrip 1 accept -2 1 expect -223538.245169845 111701.072127637 roundtrip 1 accept -2 -1 expect -223027.866740205 -111701.072127637 roundtrip 1 accept -140.0 22.0 expect -15638150.097869192 2457423.586808016 roundtrip 1 accept 170.0 70.0 expect 16560870.317293623 7463176.386461447 roundtrip 1 accept -10.0 22.0 expect -1117010.721276371 2457423.586808016 roundtrip 1 accept 130.0 22.0 expect 14521139.376592822 2457423.586808016 roundtrip 1 accept -170.0 70.0 expect -17167948.303394791 7463176.386461447 roundtrip 1 accept -140.0 67.0 expect -15638150.097869191 7205942.523056464 roundtrip 1 accept -10.0 67.0 expect -1117010.721276371 7205942.523056464 roundtrip 1 accept 130.0 67.0 expect 14521139.376592822 7205942.523056464 roundtrip 1 accept -110.0 -22.0 expect -12287117.934040081 -2457423.586808016 roundtrip 1 accept 20.0 -22.0 expect 2234021.442552742 -2457423.586808016 roundtrip 1 accept 150.0 -22.0 expect 16755160.819145568 -2457423.586808016 roundtrip 1 accept -110.0 -67.0 expect -12287117.934040081 -7205942.523056464 roundtrip 1 accept 20.0 -67.0 expect 2234021.442552742 -7205942.523056464 roundtrip 1 accept 95.0 -67.0 expect 13699006.578494834 -7205942.523056464 roundtrip 1 accept 150.0 -67.0 expect 16755160.819145564 -7205942.523056464 roundtrip 1 direction inverse accept 200 100 expect 0.001790494 0.000895247 accept 200 -100 expect 0.001790491 -0.000895247 accept -200 100 expect -0.001790492 0.000895247 accept -200 -100 expect -0.001790496 -0.000895247 =============================================================================== # Interrupted Mollweide # PCyl, Sph. # (Each of the 6 sub-projections tested separately) =============================================================================== ------------------------------------------------------------------------------- operation +proj=imoll +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect -912080.283811148372 124066.283433859542 roundtrip 1 accept 2 -1 expect -912174.768289615284 -124066.283433859542 roundtrip 1 accept -2 1 expect -1314307.681094774744 124066.283433859542 roundtrip 1 accept -2 -1 expect -1314402.165573241888 -124066.283433859542 roundtrip 1 accept -39.99 0.1 expect -5135117.0707450127 12406.8672748194 roundtrip 1 accept -40.01 0.1 expect -5137140.6776947584 12406.8672748194 roundtrip 1 accept -99.99 -0.1 expect -11169097.7713819221 -12406.8672748194 roundtrip 1 accept -100.01 -0.1 expect -11171118.5438199658 -12406.8672748194 roundtrip 1 accept -19.99 -0.1 expect -3123793.9498816459 -12406.8672748194 roundtrip 1 accept -20.01 -0.1 expect -3125812.8326452221 -12406.8672748194 roundtrip 1 accept 79.99 -0.1 expect 6930815.0545556545 -12406.8672748194 roundtrip 1 accept 80.01 -0.1 expect 6932837.7166681662 -12406.8672748194 roundtrip 1 accept -100.0 22.0 expect -11170107.212763708085 2703699.326638640370 roundtrip 1 accept -30.0 22.0 expect -3854960.906400005333 2703699.326638640370 roundtrip 1 accept -160.0 -22.0 expect -17204085.078888915479 -2703699.326638640370 roundtrip 1 accept -60.0 -22.0 expect -7147455.302013571374 -2703699.326638640370 roundtrip 1 accept 20.0 -22.0 expect 897848.519486704026 -2703699.326638640370 roundtrip 1 accept 140.0 -22.0 expect 12965804.251737114042 -2703699.326638640370 roundtrip 1 direction inverse accept 200 100 expect 11.074062190626 0.000806005080 accept 200 -100 expect 11.074062191236 -0.000806005080 accept -200 100 expect 11.070084714982 0.000806005080 accept -200 -100 expect 11.070084715592 -0.000806005080 =============================================================================== # Interrupted Mollweide Ocean View # PCyl, Sph. # (Each of the 6 sub-projections tested separately) =============================================================================== ------------------------------------------------------------------------------- operation +proj=imoll_o +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect -1357849.196080365917 124066.283433859542 roundtrip 1 accept 2 -1 expect -1357565.742644961691 -124066.283433859542 roundtrip 1 accept -2 1 expect -1760076.593363992404 124066.283433859542 roundtrip 1 accept -2 -1 expect -1759793.139928588411 -124066.283433859542 roundtrip 1 accept -89.99 0.1 expect -10608821.9887007959 12406.8672748194 roundtrip 1 accept -90.01 0.1 expect -10610845.5956505425 12406.8672748194 roundtrip 1 accept 59.99 0.1 expect 4474097.1799880061 12406.8672748194 roundtrip 1 accept 60.01 0.1 expect 4476121.7317749839 12406.8672748194 roundtrip 1 accept -59.99 -0.1 expect -7591833.0556381932 -12406.8672748194 roundtrip 1 accept -60.01 -0.1 expect -7593856.6625879407 -12406.8672748194 roundtrip 1 accept 89.99 -0.1 expect 7491086.1130506080 -12406.8672748194 roundtrip 1 accept 90.01 -0.1 expect 7493109.7200003546 -12406.8672748194 roundtrip 1 accept -140.0 22.0 expect -15638150.097869191319 2703699.326638640370 roundtrip 1 accept -10.0 22.0 expect -2564531.387931245379 2703699.326638640370 roundtrip 1 accept 130.0 22.0 expect 11514750.299694234505 2703699.326638640370 roundtrip 1 accept -110.0 -22.0 expect -12621161.164806591347 -2703699.326638640370 roundtrip 1 accept 20.0 -22.0 expect 452457.545131357736 -2703699.326638640370 roundtrip 1 accept 150.0 -22.0 expect 13526076.255069304258 -2703699.326638640370 roundtrip 1 direction inverse accept 200 100 expect 15.502891574921 0.000806005080 accept 200 -100 expect 15.502891573090 -0.000806005080 accept -200 100 expect 15.498914099277 0.000806005080 accept -200 -100 expect 15.498914097446 -0.000806005080 =============================================================================== # International Map of the World Polyconic # Mod. Polyconic, Ell # lat_1= and lat_2= [lon_1=] =============================================================================== ------------------------------------------------------------------------------- operation +proj=imw_p +ellps=GRS80 +lat_1=0.5 +lat_2=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222588.441139376 55321.128653810 accept 2 -1 expect 222756.906377687 -165827.584288324 accept -2 1 expect -222588.441139376 55321.128653810 accept -2 -1 expect -222756.906377687 -165827.584288324 direction inverse accept 200 100 expect 0.001796699 0.500904924 accept 200 -100 expect 0.001796698 0.499095076 accept -200 100 expect -0.001796699 0.500904924 accept -200 -100 expect -0.001796698 0.499095076 ------------------------------------------------------------------------------- operation +proj=imw_p +ellps=GRS80 +lat_1=0 +lat_2=10 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 0 expect 0 0 accept 0.000000000000 0.000904928485 expect 0 100 accept 0.000898315284 0.000000000000 expect 100 0 direction inverse accept 0 0 expect 0 0 accept 0 100 expect 0.000000000000 0.000904928485 accept 100 0 expect 0.000898315284 0.000000000000 =============================================================================== # Icosahedral Snyder Equal Area # Sph =============================================================================== ------------------------------------------------------------------------------- operation +proj=isea +a=6400000 ------------------------------------------------------------------------------- tolerance 0.2 mm accept 2 1 expect -1097074.948153475765139 3442909.309747453313321 roundtrip 1 accept 2 -1 expect -1097074.948149705072865 3233611.728292400948703 roundtrip 1 accept -2 1 expect -1575486.353775786235929 3442168.342736063525081 roundtrip 1 accept -2 -1 expect -1575486.353772019501776 3234352.695310209877789 roundtrip 1 operation +proj=isea +mode=hex +resolution=31 accept 0 0 expect failure ------------------------------------------------------------------------------- operation +proj=isea +R=6371007.18091875 ------------------------------------------------------------------------------- tolerance 0.2 mm accept -168.75 58.282525588539 expect -19186144.870842020958662 3323137.771944524254650 roundtrip 1 accept 11.25 58.282525588539 expect -15348915.896747918799520 9969413.315350906923413 roundtrip 1 accept -110 54 expect -15321401.505530973896384 3338358.859094056300819 roundtrip 1 accept -75 45 expect -12774358.709073608741164 4373188.646695702336729 roundtrip 1 accept 2 49 expect -642252.939347098814324 8796229.009143760427833 roundtrip 1 accept 0 0 expect -1331454.074623266700655 3323137.771634854841977 roundtrip 1 accept 90 0 expect 8564460.639100870117545 593869.297485541785136 roundtrip 1 accept 0 45 expect -837334.699958428042009 8323409.759132191538811 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=isea +R=6371007.18091875 +orient=pole ------------------------------------------------------------------------------- tolerance 0.2 mm accept -168.75 58.282525588539 expect -16702163.549901897087693 6386395.630649688653648 roundtrip 1 accept 11.25 58.282525588539 expect 619648.646531744743697 6212947.536539182066917 roundtrip 1 accept -110 54 expect -13285649.857057726010680 6149501.348902118392289 roundtrip 1 accept -75 45 expect -7921366.529368571005762 4728387.055336073972285 roundtrip 1 accept 2 49 expect 152616.434999307675753 5152048.791301283054054 roundtrip 1 accept 0 0 expect 0 -195097.133640714135254 roundtrip 1 accept 90 0 expect 9593072.435467451811 0 roundtrip 1 accept 0 45 expect 0 4726854.770339427515864 roundtrip 1 =============================================================================== # Kavrayskiy V # PCyl., Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=kav5 +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 200360.905308829 123685.082476998 accept 2 -1 expect 200360.905308829 -123685.082476998 accept -2 1 expect -200360.905308829 123685.082476998 accept -2 -1 expect -200360.905308829 -123685.082476998 direction inverse accept 200 100 expect 0.001996259 0.000808483 accept 200 -100 expect 0.001996259 -0.000808483 accept -200 100 expect -0.001996259 0.000808483 accept -200 -100 expect -0.001996259 -0.000808483 ------------------------------------------------------------------------------- operation +proj=kav5 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 201047.703110878 124109.050629171 accept 2 -1 expect 201047.703110878 -124109.050629171 accept -2 1 expect -201047.703110878 124109.050629171 accept -2 -1 expect -201047.703110878 -124109.050629171 direction inverse accept 200 100 expect 0.001989440 0.000805721 accept 200 -100 expect 0.001989440 -0.000805721 accept -200 100 expect -0.001989440 0.000805721 accept -200 -100 expect -0.001989440 -0.000805721 =============================================================================== # Kavrayskiy VII # PCyl, Sph. =============================================================================== ------------------------------------------------------------------------------- operation +proj=kav7 +a=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 193462.974943729 111701.072127637 accept 2 -1 expect 193462.974943729 -111701.072127637 accept -2 1 expect -193462.974943729 111701.072127637 accept -2 -1 expect -193462.974943729 -111701.072127637 direction inverse accept 200 100 expect 0.002067483 0.000895247 accept 200 -100 expect 0.002067483 -0.000895247 accept -200 100 expect -0.002067483 0.000895247 accept -200 -100 expect -0.002067483 -0.000895247 =============================================================================== # Krovak # PCyl., Ellps. =============================================================================== ------------------------------------------------------------------------------- operation +proj=krovak +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect -3196535.232563641 -6617878.867551444 accept 2 -1 expect -3260035.440552109 -6898873.614878031 accept -2 1 expect -3756305.328869175 -6478142.561571511 accept -2 -1 expect -3831703.658501982 -6759107.170155395 accept 24.833333333333 59.757598563058 expect 0 0 direction inverse accept 200 100 expect 24.836218919 59.758403933 accept 200 -100 expect 24.836315485 59.756888426 accept -200 100 expect 24.830447748 59.758403933 accept -200 -100 expect 24.830351182 59.756888426 accept 0 0 expect 24.833333333333 59.757598563058 ------------------------------------------------------------------------------- operation +proj=krovak +lat_0=-90 ------------------------------------------------------------------------------- expect failure errno invalid_op_illegal_arg_value # Test point from EPSG Guidance Note 7-2 ------------------------------------------------------------------------------- operation +proj=krovak +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro ------------------------------------------------------------------------------- tolerance 1.1 cm # 16°50'59.179"E, 50°12'32.442"N accept 16.849771944444445 50.20901166666667 expect -568991.00 -1050538.64 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=krovak +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +czech ------------------------------------------------------------------------------- tolerance 1.1 cm # 16°50'59.179"E, 50°12'32.442"N accept 16.849771944444445 50.20901166666667 expect 568991.00 1050538.64 roundtrip 1 =============================================================================== # Krovak Modified # PCyl., Ellps. =============================================================================== # Test point from EPSG Guidance Note 7-2 # Note: all longitudes below are east of Ferro ------------------------------------------------------------------------------- operation +proj=mod_krovak +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=5000000 +y_0=5000000 +ellps=bessel ------------------------------------------------------------------------------- tolerance 1 cm # 34°30'59.179"E of Ferro, 50°12'32.442"N accept 34.51643861111111 50.20901166666667 expect -5568990.91 -6050538.71 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=mod_krovak +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=5000000 +y_0=5000000 +ellps=bessel +czech ------------------------------------------------------------------------------- tolerance 1 cm accept 34.51643861111111 50.20901166666667 expect 5568990.91 6050538.71 roundtrip 1 =============================================================================== # Laborde # Cyl, Sph # Special for Madagascar =============================================================================== ------------------------------------------------------------------------------- operation +proj=labrd +ellps=GRS80 +lon_0=0.5 +lat_0=2 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 166973.166090228 -110536.912730266 accept 2 -1 expect 166973.168287157 -331761.993650884 accept -2 1 expect -278345.500519976 -110469.032642032 accept -2 -1 expect -278345.504185270 -331829.870790275 direction inverse accept 200 100 expect 0.501797719 2.000904357 accept 200 -100 expect 0.501797717 1.999095641 accept -200 100 expect 0.498202281 2.000904357 accept -200 -100 expect 0.498202283 1.999095641 ------------------------------------------------------------------------------- operation +proj=labrd +ellps=GRS80 +lat_0=0 accept 0 0 expect failure errno invalid_op_illegal_arg_value =============================================================================== # Lambert Azimuthal Equal Area # Azi, Sph&Ell =============================================================================== ------------------------------------------------------------------------------- operation +proj=laea +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 222602.471450095 110589.827224410 accept 2 -1 expect 222602.471450095 -110589.827224409 accept -2 1 expect -222602.471450095 110589.827224410 accept -2 -1 expect -222602.471450095 -110589.827224409 accept 150 50 expect 4372597.1888 10352365.4614 accept 180 0 expect failure errno coord_transfm_outside_projection_domain direction inverse accept 200 100 expect 0.001796631 0.000904369 accept 200 -100 expect 0.001796631 -0.000904369 accept -200 100 expect -0.001796631 0.000904369 accept -200 -100 expect -0.001796631 -0.000904369 accept 4372597.1888 10352365.4614 expect 150 50 accept 13000000 0 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- operation +proj=laea +R=6400000 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 2 1 expect 223365.281370125 111716.668072916 accept 2 -1 expect 223365.281370125 -111716.668072916 accept -2 1 expect -223365.281370125 111716.668072916 accept -2 -1 expect -223365.281370125 -111716.668072916 accept 180 0 expect failure errno coord_transfm_outside_projection_domain direction inverse accept 200 100 expect 0.001790493 0.000895247 accept 200 -100 expect 0.001790493 -0.000895247 accept -200 100 expect -0.001790493 0.000895247 accept -200 -100 expect -0.001790493 -0.000895247 ------------------------------------------------------------------------------- # Test oblique aspect of the spherical form ------------------------------------------------------------------------------- operation +proj=laea +R=1 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 45 expect 0 0 accept 0 0 expect 0 -0.7654 accept 0 90 expect 0 0.7654 accept 0 -45 expect 0 -1.4142 accept 45 45 expect 0.5194 0.1521 tolerance 0.1 mm accept 45 45 roundtrip 100 # error when waaay outside the sphere direction inverse accept 0 10 expect failure errno coord_transfm_outside_projection_domain ------------------------------------------------------------------------------- # Test oblique aspect of the ellipsoidal form ------------------------------------------------------------------------------- operation +proj=laea +ellps=GRS80 +lat_0=45 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 0 45 expect 0 0 accept 0 0 expect 0 -4860248.8602 accept 0 -45 expect 0 -8984728.0442 accept 45 45 expect 3318800.8682 968788.2336 # Passes 0.1 mm except on i386. Cf https://github.com/OSGeo/PROJ/pull/4441#issuecomment-2744141103 tolerance 50 mm accept 0 90 expect 0 4886594.2207 tolerance 10 cm accept 45 45 roundtrip 100 # test rho proj-9.8.1/test/gie/geotiff_grids.gie000664 001750 001750 00000044341 15166171715 017505 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- =============================================================================== Test GeoTIFF grids =============================================================================== # Those first tests using +proj=vgridshift only test the capability of reading # correctly a value from various formulations of GeoTIFF file, hence only the # forward path is tested (reverse path is tested in other files) ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_pixelispoint.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_pixelisarea.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_deflate.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_deflate_floatingpointpredictor.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_uint16.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_uint16_with_scale_offset.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_int16.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_int32.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_uint32.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_float64.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- # The overview should be ignored ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_with_overview.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_in_second_channel.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_bigtiff.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_bigendian.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_bigendian_bigtiff.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_bottomup_with_scale.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_bottomup_with_matrix.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_with_subgrid.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.5 52.5 0 expect 4.5 52.5 11.5 # In subgrid accept 5.5 53.5 0 expect 5.5 53.5 110.0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_nodata.tif +multiplier=1 ------------------------------------------------------------------------------- accept 4.05 52.1 0 expect 4.05 52.1 10 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_hydroid_height.tif +multiplier=1 ------------------------------------------------------------------------------- accept 2 49 0 expect 2 49 44.643493652 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_invalid_channel_type.tif +multiplier=1 ------------------------------------------------------------------------------- expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_vgrid_unsupported_byte.tif +multiplier=1 ------------------------------------------------------------------------------- expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/us_noaa_geoid06_ak_subset_at_antimeridian.tif +multiplier=1 ------------------------------------------------------------------------------- tolerance 1 mm accept 179.99 54.5 0 expect 179.99 54.5 -2.2226 accept -179.99 54.5 0 expect -179.99 54.5 -2.3488 accept 179.999999 54.5 0 expect 179.999999 54.5 -2.2872 accept -179.999999 54.5 0 expect -179.999999 54.5 -2.2872 accept 179.8 54.5 0 expect 179.8 54.5 -0.7011 accept 179.799 54.5 0 expect failure errno coord_transfm_outside_grid accept 180.1833333 54.5 0 expect -179.8166667 54.5 -3.1933 accept -179.8166667 54.5 0 expect -179.8166667 54.5 -3.1933 accept 180.184 54.5 0 expect failure errno coord_transfm_outside_grid accept -179.816 54.5 0 expect failure errno coord_transfm_outside_grid ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_separate.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_strip.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_tiled.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_tiled_separate.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_positive_west.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_lon_shift_first.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_radian.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_degree.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- # The overview should be ignored ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_with_overview.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_extra_ifd_with_other_info.tif ------------------------------------------------------------------------------- tolerance 2 mm accept 4.5 52.5 0 expect 5.875 55.375 0 ------------------------------------------------------------------------------- # Subset of NTv2_0.gsb ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_with_subgrid.tif ------------------------------------------------------------------------------- # In subgrid ALbanff, of parent CAwest accept -115.5416667 51.1666667 0 expect -115.5427092888 51.1666899972 0 # In subgrid ONtronto, of parent CAeast accept -80.5041667 44.5458333 0 expect -80.50401615833 44.5458827236 0 ------------------------------------------------------------------------------- # Subset of NTv2_0.gsb ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_with_subgrid_no_grid_name.tif ------------------------------------------------------------------------------- # In subgrid ALbanff, of parent CAwest accept -115.5416667 51.1666667 0 expect -115.5427092888 51.1666899972 0 # In subgrid ONtronto, of parent CAeast accept -80.5041667 44.5458333 0 expect -80.50401615833 44.5458827236 0 ------------------------------------------------------------------------------- # Check a nested grid of a nested grid only based on spatial extent analysis ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif ------------------------------------------------------------------------------- accept -45.0 22.5 accept -44.9983333334 22.5013888889 # Check a nested grid of a nested grid only based on spatial extent analysis ------------------------------------------------------------------------------- operation +proj=vgridshift +grids=tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif +multiplier=1 ------------------------------------------------------------------------------- accept -45.0 22.5 0 accept -45.0 22.5 5 ------------------------------------------------------------------------------- operation +proj=hgridshift +grids=tests/test_vgrid.tif ------------------------------------------------------------------------------- expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- # IGNF:LAMBE to IGNF:LAMB93 using xyzgridshift operation ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +inv +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 \ +k_0=0.99987742 +x_0=600000 +y_0=2200000 +ellps=clrk80ign +pm=paris \ +step +proj=push +v_3 \ +step +proj=cart +ellps=clrk80ign \ +step +proj=xyzgridshift +grids=tests/subset_of_gr3df97a.tif +grid_ref=output_crs +ellps=GRS80 \ +step +proj=cart +ellps=GRS80 +inv \ +step +proj=pop +v_3 \ +step +proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 \ +x_0=700000 +y_0=6600000 +ellps=GRS80 ------------------------------------------------------------------------------- tolerance 1 mm accept 814149.529 1887019.768 0 expect 860690.804 6319036.849 0 # If using ntf_r93.gsb, one gets: 860690.805 6319036.850 roundtrip 1 ------------------------------------------------------------------------------- proj-9.8.1/test/gie/epsg_no_grid.gie000664 001750 001750 00000003675 15166171715 017336 0ustar00eveneven000000 000000 ### This file must contain only (crs_src, crs_dst) tuples where the best transformation ### does NOT involve the use a grid/json/etc. resource file ### Otherwise use epsg_grid.gie ################################### #### Generic / worldwide scope #### ################################### # ETRS89 -> ETRS89/UTM32 crs_src EPSG:4258 crs_dst EPSG:25832 tolerance 0.1 mm accept 55.0 12.0 expect 691875.6321 6098907.8250 # WGS 84 geographic 3D -> WGS 84 geocentric crs_src EPSG:4979 crs_dst EPSG:4978 tolerance 0.1 mm accept 55.0 12.0 10.0 expect 3586475.2672 762328.8513 5201391.7147 # WGS 84 geocentric -> WGS 84 geographic 3D crs_src EPSG:4979 crs_dst EPSG:4978 tolerance 0.1 mm expect 3586475.2672 762328.8513 5201391.7147 accept 55.0 12.0 10.0 ################### #### AUSTRALIA #### ################### # GDA2020 -> ITRF2014, using EPSG:8049 "ITRF2014 to GDA2020 (1)" 15-parameter Helmert with 2020.0 central epoch crs_src EPSG:7843 # GDA2020 crs_dst EPSG:7912 # ITRF2014 tolerance 0.1 mm accept -33.8623 151.2077 0.0 2020.0 expect -33.8623 151.2077 0.0 2020.0 crs_src EPSG:7843 # GDA2020 crs_dst EPSG:7912 # ITRF2014 tolerance 0.1 mm accept -33.8623 151.2077 0.0 2026.0 expect -33.862297056 151.207701181 -0.00101 2026.0 ################### ###### FINLAND #### ################### # Test northing, easting output (actually more a gie test itself, than a EPSG one) crs_src EPSG:4123 # Finland KKJ crs_dst EPSG:2393 # Finland YKJ Northing, Easting tolerance 0.1 mm accept 60.1699 24.9384 expect 6674944.7742 3385559.8151 proj-9.8.1/test/gie/spilhaus.gie000664 001750 001750 00000024153 15166171715 016521 0ustar00eveneven000000 000000 ------------------------------------------------------------ # Roundtrips over the whole globe ------------------------------------------------------------ operation +proj=spilhaus tolerance 1.5 mm accept -20.1 74.1 roundtrip 10 tolerance 10 mm accept -170.0000 -80.0000 roundtrip 10 accept -170.0000 -50.0000 roundtrip 10 accept -170.0000 -20.0000 roundtrip 10 accept -170.0000 10.0000 roundtrip 10 accept -170.0000 40.0000 roundtrip 10 accept -170.0000 70.0000 roundtrip 10 accept -121.0000 -80.0000 roundtrip 10 accept -121.0000 -50.0000 roundtrip 10 accept -121.0000 -20.0000 roundtrip 10 accept -121.0000 10.0000 roundtrip 10 accept -121.0000 40.0000 roundtrip 10 accept -121.0000 70.0000 roundtrip 10 accept -72.0000 -80.0000 roundtrip 10 accept -72.0000 -50.0000 roundtrip 10 accept -72.0000 -20.0000 roundtrip 10 accept -72.0000 10.0000 roundtrip 10 accept -72.0000 40.0000 roundtrip 10 accept -72.0000 70.0000 roundtrip 10 accept -23.0000 -80.0000 roundtrip 10 accept -23.0000 -50.0000 roundtrip 10 accept -23.0000 -20.0000 roundtrip 10 accept -23.0000 10.0000 roundtrip 10 accept -23.0000 40.0000 roundtrip 10 accept -23.0000 70.0000 roundtrip 10 accept 26.0000 -80.0000 roundtrip 10 accept 26.0000 -50.0000 roundtrip 10 accept 26.0000 -20.0000 roundtrip 10 accept 26.0000 10.0000 roundtrip 10 accept 26.0000 40.0000 roundtrip 10 accept 26.0000 70.0000 roundtrip 10 accept 75.0000 -80.0000 roundtrip 10 accept 75.0000 -50.0000 roundtrip 10 accept 75.0000 -20.0000 roundtrip 10 accept 75.0000 10.0000 roundtrip 10 accept 75.0000 40.0000 roundtrip 10 accept 75.0000 70.0000 roundtrip 10 accept 124.0000 -80.0000 roundtrip 10 accept 124.0000 -50.0000 roundtrip 10 accept 124.0000 -20.0000 roundtrip 10 accept 124.0000 10.0000 roundtrip 10 accept 124.0000 40.0000 roundtrip 10 accept 124.0000 70.0000 roundtrip 10 accept 173.0000 -80.0000 roundtrip 10 accept 173.0000 -50.0000 roundtrip 10 accept 173.0000 -20.0000 roundtrip 10 accept 173.0000 10.0000 roundtrip 10 accept 173.0000 40.0000 roundtrip 10 accept 173.0000 70.0000 roundtrip 10 ------------------------------------------------------------ # This gie part was initially generated with a python library # provided in the issue #1851, correcting the applying a factor # due to the conformal latitude (explained in the issue). # It can be edited. ------------------------------------------------------------ operation +proj=spilhaus tolerance 1 mm ------------------------------------------------------------ accept -170.0000 -80.0000 expect 437478.9752 -2678050.3019 accept -170.0000 -50.0000 expect 2186914.6725 -3372185.4149 accept -170.0000 -20.0000 expect 4059707.8321 -3830282.4180 accept -170.0000 10.0000 expect 6210065.9010 -4208321.1110 accept -170.0000 40.0000 expect 8929858.8196 -4592610.8117 accept -170.0000 70.0000 expect -4757306.7162 11243170.7712 accept -121.0000 -80.0000 expect 9363.8168 -3012575.4761 accept -121.0000 -50.0000 expect 861573.2313 -5086159.8537 accept -121.0000 -20.0000 expect 2601135.1803 -6940740.0711 accept -121.0000 10.0000 expect 5016978.6482 -8304150.6170 accept -121.0000 40.0000 expect 8632125.8964 -9423801.4294 accept -121.0000 70.0000 expect -6555705.6060 10246990.6251 accept -72.0000 -80.0000 expect -551816.0501 -2880353.7707 accept -72.0000 -50.0000 expect -2313351.2791 -5115437.4974 accept -72.0000 -20.0000 expect -1486391.8298 -11562191.5568 accept -72.0000 10.0000 expect -10414594.4936 2712423.6966 accept -72.0000 40.0000 expect -9084246.6998 5766099.6436 accept -72.0000 70.0000 expect -6598697.0383 8193941.7590 accept -23.0000 -80.0000 expect -780311.9008 -2349988.7659 accept -23.0000 -50.0000 expect -3008092.3663 -1795023.1904 accept -23.0000 -20.0000 expect -4925434.4371 14366.0251 accept -23.0000 10.0000 expect -5706896.4172 2337607.2418 accept -23.0000 40.0000 expect -5776514.0258 4647657.5555 accept -23.0000 70.0000 expect -5300606.7236 7143600.3698 accept 26.0000 -80.0000 expect -524250.2015 -1871449.2525 accept 26.0000 -50.0000 expect -1461547.3198 -290174.5921 accept 26.0000 -20.0000 expect -2077751.7222 1349250.8096 accept 26.0000 10.0000 expect -2553361.5158 3054667.1747 accept 26.0000 40.0000 expect -3067668.6963 4947317.2297 accept 26.0000 70.0000 expect -3829081.3051 7195256.6513 accept 75.0000 -80.0000 expect -30320.7348 -1747777.5703 accept 75.0000 -50.0000 expect 283794.2879 -63915.9519 accept 75.0000 -20.0000 expect 570564.7027 1640770.3960 accept 75.0000 10.0000 expect 613441.1718 3718159.4411 accept 75.0000 40.0000 expect -339352.8544 6281442.7842 accept 75.0000 70.0000 expect -2680263.1124 8214231.7220 accept 124.0000 -80.0000 expect 391373.4223 -2010028.8376 accept 124.0000 -50.0000 expect 1778858.7692 -974740.5396 accept 124.0000 -20.0000 expect 3251942.8279 194105.3840 accept 124.0000 10.0000 expect 5502810.9250 1869559.2284 accept 124.0000 40.0000 expect 11560747.4458 1351438.2908 accept 124.0000 70.0000 expect -2631686.3455 9979887.6971 accept 173.0000 -80.0000 expect 497943.3567 -2503256.4284 accept 173.0000 -50.0000 expect 2305199.5165 -2711772.3441 accept 173.0000 -20.0000 expect 4178078.2257 -2771571.4407 accept 173.0000 10.0000 expect 6367897.0128 -2860121.0611 accept 173.0000 40.0000 expect 9145287.0568 -3248602.9252 accept 173.0000 70.0000 expect -4081581.4885 11169069.7425 ------------------------------------------------------------ # This gie part was got from ESRI computations # provided in the issue #1851 # It can be edited. ------------------------------------------------------------ operation +proj=spilhaus +k_0=1.4142135623730951 tolerance 0.9 m ------------------------------------- accept 14.47226253 -84.71287749 expect -546875 -3046875 accept 84.55256518 -37.93882855 expect 1171875 703125 accept -66.58783346 27.86168989 expect -13046875 6171875 accept 12.77715082 51.22645041 expect -5546875 7890625 accept 114.35091069 28.44647901 expect 10703125 10234375 accept -58.76182587 -12.11844904 expect -13046875 -703125 accept 141.57916998 16.05031911 expect 9765625 78125 accept -64.1956924 -30.60226899 expect -11796875 -10859375 accept -83.61985956 -31.09509756 expect -1796875 -11796875 accept -118.96768373 14.44661994 expect 7578125 -12265625 accept -145.850344 50.26449114 expect 14296875 -9296875 accept -116.22716741 44.95066182 expect 13515625 -14609375 accept -112.96622187 49.30990506 expect 15859375 -16484375 accept 114.98472216 29.97596772 expect 14453125 14453125 accept -112.21827126 50.16610427 expect -15546875 15703125 accept -64.99929833 -30.00238885 expect -15390625 -15546875 ------------------------------------------------------------ # Stable for default parameters ------------------------------------------------------------ operation +proj=spilhaus +rot=45 +k_0=1 +lat_0=-49.56371678 +lon_0=66.94970198 +azi=40.17823482 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3733410.0118 -9320.8573 roundtrip 1 ------------------------------------------------------------ # Sentitive to input parameters ------------------------------------------------------------ operation +proj=spilhaus tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3733410.0118 -9320.8573 roundtrip 1 ------------------------------------------------------------ operation +proj=spilhaus +lon_0=10.1 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 4343770.7991 -3701935.6242 roundtrip 1 ------------------------------------------------------------ operation +proj=spilhaus +lat_0=30.1 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3637341.2895 -2571368.8666 roundtrip 1 ------------------------------------------------------------ operation +proj=spilhaus +azi=9.1 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3061806.4542 -1678791.7428 roundtrip 1 ------------------------------------------------------------ operation +proj=spilhaus +rot=40.1 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3720561.6630 309609.603620 roundtrip 1 ------------------------------------------------------------ operation +proj=spilhaus +k_0=0.9 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3360069.0106 -8388.7716 roundtrip 1 ------------------------------------------------------------ # Sphere ------------------------------------------------------------ operation +proj=spilhaus +R=6378137 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 3737644.5177 -7049.7883 roundtrip 1 ------------------------------------------------------------ # vs Adams WS2 ------------------------------------------------------------ operation +proj=adams_ws2 +R=6378137 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 8199312.0391 -1392652.9172 roundtrip 1 ------------------------------------------------------------ operation +proj=spilhaus +R=6378137 +lon_0=0 +lat_0=0 +azi=0 +rot=0 tolerance 1 mm ------------------------------------------------------------ accept 130.4 -16.2 expect 8199312.0391 -1392652.9172 roundtrip 1 proj-9.8.1/test/gie/ellipsoid.gie000664 001750 001750 00000015245 15166171715 016657 0ustar00eveneven000000 000000 =============================================================================== Test pj_ellipsoid, the reimplementation of pj_ell_set =============================================================================== ------------------------------------------------------------------------------- # First a spherical example ------------------------------------------------------------------------------- operation proj=merc R=6400000 ------------------------------------------------------------------------------- tolerance 10 nm accept 1 2 expect 111701.0721276371 223447.5262032605 accept 12 55 expect 1340412.8655316452 7387101.1430967357 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Then an explicitly defined ellipsoidal example ------------------------------------------------------------------------------- operation proj=merc a=6400000 rf=297 ------------------------------------------------------------------------------- tolerance 10 nm accept 1 2 expect 111701.0721276371 221945.9681832088 accept 12 55 expect 1340412.8655316452 7351803.9151705895 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Then try using a built in ellipsoid ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 ------------------------------------------------------------------------------- tolerance 10 nm accept 1 2 expect 111319.4907932736 221194.0771604237 accept 12 55 expect 1335833.8895192828 7326837.7148738774 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Then try to fail deliberately ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80000000000 expect failure errno invalid_op_illegal_arg_value operation proj=merc +a=-1 expect failure errno invalid_op_illegal_arg_value operation proj=merc accept 0 0 expect 0 0 operation proj=merc +a=1 +es=-1 expect failure errno invalid_op_illegal_arg_value operation proj=merc +R=0 expect failure errno invalid_op_illegal_arg_value operation +proj=merc +R_a +a=2 +f=2 expect failure errno invalid_op_illegal_arg_value operation expect failure operation cobra expect failure ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Finally test the spherification functionality ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_A tolerance 10 nm accept 12 55 expect 1334340.6237297705 7353636.6296552019 ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_V tolerance 10 nm accept 12 55 expect 1334339.2852675652 7353629.2533042720 ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_a tolerance 10 nm accept 12 55 expect 1333594.4904527504 7349524.6413825499 ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_g tolerance 10 nm accept 12 55 expect 1333592.6102291327 7349514.2793497816 ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_h tolerance 10 nm accept 12 55 expect 1333590.7300081658 7349503.9173316229 ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_lat_a=60 tolerance 10 nm accept 12 55 expect 1338073.7436268919 7374210.0924803326 ------------------------------------------------------------------------------- operation proj=merc ellps=GRS80 R_lat_g=60 tolerance 10 nm accept 12 55 expect 1338073.2696101593 7374207.4801437631 ------------------------------------------------------------------------------- operation proj=merc a=1E77 R_lat_a=90 b=1 expect failure ------------------------------------------------------------------------------- # This one from testvarious failed at first version of the pull request ------------------------------------------------------------------------------- operation proj=healpix a=1 lon_0=0 ellps=WGS84 ------------------------------------------------------------------------------- accept 0 41.937853904844985 expect 0 0.78452 accept -90 0 expect -1.56904 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Shape parameters ------------------------------------------------------------------------------- operation proj=utm zone=32 ellps=GRS80 rf=0 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 ellps=GRS80 e=-0.5 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 ellps=GRS80 e=1 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 ellps=GRS80 es=1 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 a=1 es=1.1 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 ellps=GRS80 b=0 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 ellps=GRS80 f=1 expect failure errno invalid_op_illegal_arg_value operation proj=utm zone=32 ellps=GRS80 b=6000000 accept 12 55 expect 699293.0880 5674591.5295 operation proj=utm zone=32 ellps=GRS80 rf=300 accept 12 55 expect 691873.1212 6099054.9661 operation proj=utm zone=32 ellps=GRS80 f=0.00333333333333 accept 12 55 expect 691873.1212 6099054.9661 operation proj=utm zone=32 ellps=GRS80 b=6000000 accept 12 55 expect 699293.0880 5674591.5295 operation proj=utm zone=32 a=6400000 b=6000000 accept 12 55 expect 700416.5900 5669475.8884 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test that flattening can be set to zero ------------------------------------------------------------------------------- operation proj=merc +a=1.0 +f=0.0 ------------------------------------------------------------------------------- accept 12 56 expect 0.20944 1.18505 ------------------------------------------------------------------------------- proj-9.8.1/test/gie/gridshift.gie000664 001750 001750 00000027255 15166171715 016662 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- =============================================================================== Test generalized shift grid method =============================================================================== ----------------------------- # Classic lat-lon shift grids ----------------------------- # Subset of NTv2_0.gsb ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/test_hgrid_with_subgrid.tif ------------------------------------------------------------------------------- accept 179.799 54.5 0 expect failure errno coord_transfm_outside_grid # In subgrid ALbanff, of parent CAwest accept -115.5416667 51.1666667 0 expect -115.5427092888 51.1666899972 0 roundtrip 1 # In subgrid ONtronto, of parent CAeast accept -80.5041667 44.5458333 0 expect -80.50401615833 44.5458827236 0 roundtrip 1 ------------------------------------------------------------------------------- # Subset of NTv2_0.gsb ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/test_hgrid_with_subgrid_no_grid_name.tif ------------------------------------------------------------------------------- # In subgrid ALbanff, of parent CAwest accept -115.5416667 51.1666667 0 expect -115.5427092888 51.1666899972 0 roundtrip 1 # In subgrid ONtronto, of parent CAeast accept -80.5041667 44.5458333 0 expect -80.50401615833 44.5458827236 0 roundtrip 1 ------------------------------------------------------------------------------- -------------------- # Classic geoidgrids -------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_geoid06_ak_subset_at_antimeridian.tif ------------------------------------------------------------------------------- tolerance 1 mm accept 179.99 54.5 0 expect 179.99 54.5 -2.2226 roundtrip 1 accept -179.99 54.5 0 expect -179.99 54.5 -2.3488 roundtrip 1 accept 179.999999 54.5 0 expect 179.999999 54.5 -2.2872 roundtrip 1 accept -179.999999 54.5 0 expect -179.999999 54.5 -2.2872 roundtrip 1 accept 179.8 54.5 0 expect 179.8 54.5 -0.7011 roundtrip 1 accept 179.799 54.5 0 expect failure errno coord_transfm_outside_grid accept 180.1833333 54.5 0 expect -179.8166667 54.5 -3.1933 roundtrip 1 accept -179.8166667 54.5 0 expect -179.8166667 54.5 -3.1933 roundtrip 1 accept 180.184 54.5 0 expect failure errno coord_transfm_outside_grid accept -179.816 54.5 0 expect failure errno coord_transfm_outside_grid ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/test_hydroid_height.tif ------------------------------------------------------------------------------- accept 2 49 0 expect 2 49 44.643493652 ------------------------------------------------------------------------------- ---------------------------------------------------------------------- # Geographic 3D offsets with quadratic interpolation (defined in file) ---------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_conus_extract.tif ------------------------------------------------------------------------------- tolerance 1 mm # Test point from https://www.ngs.noaa.gov/NCAT, exactly at one grid node accept -95.5000000000 37.0000000000 10.000 expect -95.4999998219 37.0000000147 9.984 roundtrip 1 # Test point from https://www.ngs.noaa.gov/NCAT # specifically selected to be close to the middle of a pixel accept -95.4916666666 37.0083333333 10.000 expect -95.4916664889 37.0083333484 9.984 roundtrip 1 # Test point from https://www.ngs.noaa.gov/NCAT # specifically selected to be close to the middle of a pixel (but # other side of previous test point) accept -95.4916666667 37.0083333334 10.000 expect -95.4916664890 37.0083333485 9.984 roundtrip 1 # Test point at north-east of truncated grid accept -95.416667 37.083333 0.000 expect -95.4166668251 37.0833330159 -0.0157 # Test point at south-west of truncated grid accept -95.58333 36.91667 0.000 expect -95.5833298166 36.9166700108 -0.0157 roundtrip 1 ---------------------------------------------------------------------- # Geographic 3D offsets, but split in one grid with horizontal offset and # another one with ellipsoidal height offset, with quadratic interpolation (defined in file) ---------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_alaska_extract.tif ------------------------------------------------------------------------------- tolerance 1 mm # Test point from https://www.ngs.noaa.gov/NCAT, exactly at one grid node accept -158.0 61.5 10.000 expect -157.9999996115 61.499999564 9.987 roundtrip 1 # Test point from https://www.ngs.noaa.gov/NCAT accept -158.1 61.51 10.000 expect -158.0999996011 61.5099995458 9.987 roundtrip 1 ---------------------------------------------------------------------- # Combine 2 above type of grids ---------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_conus_extract.tif,tests/us_noaa_nadcon5_nad83_2007_nad83_2011_alaska_extract.tif ------------------------------------------------------------------------------- tolerance 1 mm # Test point from https://www.ngs.noaa.gov/NCAT, exactly at one grid node accept -95.5000000000 37.0000000000 10.000 expect -95.4999998219 37.0000000147 9.984 roundtrip 1 # Test point from https://www.ngs.noaa.gov/NCAT, exactly at one grid node accept -158.0 61.5 10.000 expect -157.9999996115 61.499999564 9.987 roundtrip 1 ---------------------------------------------------------------------- # Test +no_z_transform ---------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_conus_extract.tif +no_z_transform ------------------------------------------------------------------------------- tolerance 1 mm accept -95.5000000000 37.0000000000 10.000 expect -95.4999998219 37.0000000147 10.000 roundtrip 1 ------------------------------ # Test bilinear vs biquadratic ------------------------------ ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_conus_extract.tif +interpolation=biquadratic ------------------------------------------------------------------------------- tolerance 0.005 mm accept -95.4916666666 37.0083333333 10.000 expect -95.49166648893 37.00833334837 9.984340 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_conus_extract.tif +interpolation=bilinear ------------------------------------------------------------------------------- tolerance 0.001 mm accept -95.4916666666 37.0083333333 10.000 expect -95.49166648893 37.00833334838 9.984341 roundtrip 1 ---------------------------------------------------------------------------------------------------------------------- # Test case with inverse biquadratic convergence where we are around a location where the interpolation window changes ---------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_1986_nad83_harn_conus_extract_sanfrancisco.tif +interpolation=biquadratic ------------------------------------------------------------------------------- direction inverse tolerance 0.005 mm accept -122.4250009683 37.8286740788 expect -122.4249999391 37.8286728006 ---------------------------------------------------------------------- # Test a grid referenced in a projected CRS, with an additional constant offset ---------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/test_gridshift_projected.tif ------------------------------------------------------------------------------- tolerance 0.5 mm accept -598000.000 -1160020.000 0.000 expect -5597999.885 -6160019.978 0.000 roundtrip 1 ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel \ +step +proj=gridshift +grids=tests/test_gridshift_projected.tif \ +step +inv +proj=mod_krovak +lat_0=49.5 +lon_0=24.8333333333333 +alpha=30.2881397222222 +k=0.9999 +x_0=5000000 +y_0=5000000 +ellps=bessel ------------------------------------------------------------------------------- tolerance 0.5 mm accept 16.610452439 49.202425040 0.000 expect 16.610455233 49.202425034 0.000 roundtrip 1 ------------- # Error cases ------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/us_noaa_nadcon5_nad83_2007_nad83_2011_conus_extract.tif +interpolation=invalid ------------------------------------------------------------------------------- expect failure errno invalid_op_illegal_arg_value ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift ------------------------------------------------------------------------------- expect failure errno invalid_op_missing_arg ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/test_vgrid_unsupported_byte.tif ------------------------------------------------------------------------------- expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation +proj=gridshift +grids=tests/i_do_not_exist.tif ------------------------------------------------------------------------------- expect failure errno invalid_op_file_not_found_or_invalid ------------------------------------------------------------------------------- proj-9.8.1/test/gie/4D-API_cs2cs-style.gie000664 001750 001750 00000052015 15166171715 020000 0ustar00eveneven000000 000000 ------------------------------------------------------------------------------- =============================================================================== Test the 4D API handling of cs2cs style transformation options. These tests are mostly based on the same material as those in more_builtins.gie, since we are testing the same kinds of things, but provided through a different interface. =============================================================================== ------------------------------------------------------------------------------- # Test the handling of the +towgs84 parameter. ------------------------------------------------------------------------------- # (additional tests of the towgs84 handling can be found in DHDN_ETRS89.gie) ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # This example is from Lotti Jivall: "Simplified transformations from # ITRF2008/IGS08 to ETRS89 for maritime applications" (see also more_builtins.gie) ------------------------------------------------------------------------------- operation proj=geocent \ towgs84 = 0.676780, 0.654950, -0.528270, \ -0.022742, 0.012667, 0.022704, \ -0.01070 ------------------------------------------------------------------------------- tolerance 1 um direction inverse # Broken test. FIXME #accept 3565285.00000000 855949.00000000 5201383.00000000 #expect 3565285.41342351 855948.67986759 5201382.72939791 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # This example is a random point, transformed from ED50 to ETRS89 using KMStrans2. ------------------------------------------------------------------------------- operation proj=latlong ellps=intl \ towgs84 = -081.07030, -089.36030, -115.75260, \ 000.48488, 000.02436, 000.41321, -0.540645 ------------------------------------------------------------------------------- tolerance 25 mm accept 16.82 55.17 61.0 expect 16.8210462130 55.1705688946 29.0317 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation proj=latlong nadgrids=ntf_r93.gsb ellps=GRS80 ------------------------------------------------------------------------------- # This functionality is also tested in more_builtins.gie ------------------------------------------------------------------------------- tolerance 1 mm accept 2.25 46.5 expect 2.250704350387 46.500051597273 direction inverse accept 2.250704350387 46.500051597273 expect 2.25 46.5 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- operation proj=latlong geoidgrids=egm96_15.gtx ellps=GRS80 ------------------------------------------------------------------------------- tolerance 15 cm # lax tolerance due to widespread bad egm96 file accept 12.5 55.5 0 expect 12.5 55.5 -36.3941 direction inverse accept 12.5 55.5 -36.3941 expect 12.5 55.5 0 ------------------------------------------------------------------------------- operation proj=merc geoidgrids=egm96_15.gtx ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 12.5 55.5 0 expect 1391493.63492 7424275.19462 -36.3941 direction inverse accept 1391493.63492 7424275.19462 -36.3941 expect 12.5 55.5 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Same as the two above, but also do axis swapping. ------------------------------------------------------------------------------- # NOTE: A number of the tests below are commented out. The actually do the # right thing, but the gie distance computation is not yet able to cope # with "unusual" axis orders ------------------------------------------------------------------------------- operation proj=latlong geoidgrids=egm96_15.gtx axis=neu ellps=GRS80 ------------------------------------------------------------------------------- tolerance 15 cm # lax tolerance due to widely distributed, bad egm96 file # Broken test. FIXME #accept 12.5 55.5 0 #expect 55.5 12.5 -36.3941 #direction inverse #accept 55.5 12.5 -36.3941 #expect 12.5 55.5 0 ------------------------------------------------------------------------------- operation proj=latlong geoidgrids=egm96_15.gtx axis=dne ellps=GRS80 ------------------------------------------------------------------------------- tolerance 15 cm # lax tolerance due to widely distributed, bad egm96 file # accept 12.5 55.5 0 # expect 36.3941 55.5 12.5 # direction inverse # accept 36.3941 55.5 12.5 # expect 12.5 55.5 0 ------------------------------------------------------------------------------- operation proj=merc geoidgrids=egm96_15.gtx ellps=GRS80 ------------------------------------------------------------------------------- tolerance 0.1 mm accept 12.5 55.5 0 expect 1391493.63492 7424275.19462 -36.3941 direction inverse accept 1391493.63492 7424275.19462 -36.3941 expect 12.5 55.5 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Some more complex axis swapping. ------------------------------------------------------------------------------- operation proj=latlong geoidgrids=egm96_15.gtx axis=nue ellps=GRS80 ------------------------------------------------------------------------------- tolerance 15 cm # lax tolerance due to widely distributed, bad egm96 file # Broken test. FIXME #accept 12.5 55.5 0 #expect 55.5 -36.3941 12.5 # direction inverse # accept 55.5 -36.3941 12.5 # expect 12.5 55.5 0 ------------------------------------------------------------------------------- operation proj=merc geoidgrids=egm96_15.gtx axis=sue ellps=GRS80 ------------------------------------------------------------------------------- tolerance 15 cm accept 12.5 55.5 0 expect -7424275.1946 -36.3941 1391493.6349 0.0000 # direction inverse # accept -7424275.1946 -36.3941 1391493.6349 0.0000 # expect 12.5 55.5 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # A test case from a comment by Github user c0nk ------------------------------------------------------------------------------- operation proj=somerc \ lat_0=46.95240555555556 lon_0=7.439583333333333 k_0=1 \ x_0=2600000 y_0=1200000 ellps=bessel \ towgs84=674.374,15.056,405.346 ------------------------------------------------------------------------------- tolerance 20 cm accept 7.438632495 46.951082877 expect 2600000.0 1200000.0 ------------------------------------------------------------------------------- # Same test, but now implemented as a pipeline. This is for testing a nasty bug, # where, at the end of pipeline creation, a false warning about missing ellps was # left behind from the creation of the Helmert step (now repaired in pj_init). ------------------------------------------------------------------------------- operation proj=pipeline \ step proj=cart ellps=WGS84 \ step proj=helmert x=674.37400 y=15.05600 z=405.34600 inv \ step proj=cart ellps=bessel inv \ step proj=somerc lat_0=46.95240555555556 lon_0=7.439583333333333 \ k_0=1 x_0=2600000 y_0=1200000 ellps=bessel units=m ------------------------------------------------------------------------------- tolerance 20 cm accept 7.438632495 46.951082877 0 expect 2600000.0 1200000.0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Make sure that transient errors are returned correctly. ------------------------------------------------------------------------------- operation +proj=geos +lon_0=0.00 +lat_0=0.00 +a=6378169.00 +b=6356583.80 +h=35785831.0 ------------------------------------------------------------------------------- accept 85.05493299 46.5261074 expect failure accept 85.05493299 46.5261074 0 expect failure accept 85.05493299 46.5261074 0 0 expect failure ------------------------------------------------------------------------------- # Test that Google's Web Mercator works as intended (see #834 for details). ------------------------------------------------------------------------------- use_proj4_init_rules true operation proj=pipeline step init=epsg:26915 inv step init=epsg:3857 ------------------------------------------------------------------------------- tolerance 20 cm accept 487147.594520173 4934316.46263998 expect -10370728.80 5552839.74 accept 487147.594520173 4934316.46263998 0 expect -10370728.80 5552839.74 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test Google's Web Mercator with +proj=webmerc +ellps=WGS84 ------------------------------------------------------------------------------- use_proj4_init_rules true operation proj=pipeline step init=epsg:26915 inv step proj=webmerc datum=WGS84 ------------------------------------------------------------------------------- tolerance 20 cm accept 487147.594520173 4934316.46263998 expect -10370728.80 5552839.74 accept 487147.594520173 4934316.46263998 0 expect -10370728.80 5552839.74 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Web Mercator test data from EPSG Guidance Note 7-2, p. 44. ------------------------------------------------------------------------------- operation proj=webmerc +ellps=WGS84 tolerance 1 cm accept -100.33333333 24.46358028 expect -11169055.58 2810000.00 accept -100.33333333 24.38178694 expect -11169055.58 2800000.00 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test that +datum parameters are handled correctly in pipelines. # See #872 for details. ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=longlat +datum=GGRS87 +inv \ +step +proj=longlat +datum=WGS84 ------------------------------------------------------------------------------- tolerance 20 cm accept 23.7275 37.9838 0 expect 23.729194873180 37.986398897578 31.289740102 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test that +towgs84=0,0,0 parameter is handled as still implying cart # transformation ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=utm +zone=11 +ellps=clrk66 +towgs84=0,0,0 +inv \ +step +proj=utm +zone=11 +datum=WGS84 ------------------------------------------------------------------------------- tolerance 20 cm accept 440720 3751320 0 expect 440719.958709357 3751294.2109841 -4.44340920541435 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test that pipelines with unit mismatch between steps can't be constructed. ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=merc \ +step +proj=merc expect failure pjd_err_malformed_pipeline operation +proj=pipeline \ +step +proj=latlong \ +step +proj=merc \ +step +proj=helmert +x=200 +y=100 expect failure pjd_err_malformed_pipeline operation +proj=pipeline \ +step +proj=merc +ellps=WGS84 \ +step +proj=unitconvert +xy_in=m +xy_out=km accept 12 56 expect 1335.8339 7522.963 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test invalid pipelines ------------------------------------------------------------------------------- # proj= before first step operation +proj=pipeline +proj=merc +step +inv +proj=merc expect failure pjd_err_malformed_pipeline # o_proj= before first step operation +proj=pipeline +o_proj=merc +step +proj=ob_tran expect failure pjd_err_malformed_pipeline # nested pipeline operation +proj=pipeline +step +proj=pipeline +step +proj=merc expect failure pjd_err_malformed_pipeline ------------------------------------------------------------------------------- # Test Pipeline Coordinate Stack ------------------------------------------------------------------------------- operation +proj=pipeline \ +step +proj=push +v_1 \ +step +proj=utm +zone=32 \ +step +proj=utm +zone=33 +inv \ +step +proj=pop +v_1 accept 12 56 0 2020 expect 12 56 0 2020 roundtrip 10 operation +proj=pipeline \ +step +proj=latlon \ # dummy step +step +proj=push +v_1 \ +step +proj=utm +zone=32 \ +step +proj=utm +zone=33 +inv \ +step +proj=pop +v_1 \ +step +proj=affine # dummy step accept 12 56 0 2020 expect 12 56 0 2020 roundtrip 10 # push value to stack without popping it again operation +proj=pipeline \ +step +proj=push +v_1 \ +step +proj=utm +zone=32 \ +step +proj=utm +zone=33 +inv accept 12 56 0 2020 expect 18 56 0 2020 # test that multiple pushes and pops works operation +proj=pipeline \ +step +proj=push +v_1 \ +step +proj=utm +zone=32 \ +step +proj=push +v_1 \ +step +proj=utm +zone=33 +inv \ +step +proj=utm +zone=34 \ +step +proj=pop +v_1 \ +step +proj=utm +zone=32 +inv \ +step +proj=pop +v_1 accept 12 56 0 2020 expect 12 56 0 2020 # pop from empty stack operation +proj=pipeline \ +step +proj=utm +zone=32 \ +step +proj=utm +zone=33 +inv \ +step +proj=pop +v_1 accept 12 56 0 2020 expect 18 56 0 2020 operation +proj=pipeline \ +step +proj=push +v_2 \ +step +inv +proj=eqearth \ +step +proj=laea \ +step +proj=pop +v_2 accept 900000 6000000 0 2020 expect 896633.0226 6000000 0 2020 # Datum shift in cartesian space but keeping the height # (simulates a datum-shift with affin since ISO19111 code # currently obfuscates proj-strings using cart/helmert/invcart) operation +proj=pipeline +ellps=GRS80 \ +step +proj=push +v_3 \ +step +proj=cart \ +step +proj=affine +xoff=1000 +yoff=2000 +xoff=3000 \ +step +proj=cart +inv \ +step +proj=pop +v_3 tolerance 50 cm accept 12 56 0 expect 12.0280112877 55.9896187413 0 roundtrip 1 operation +proj=push +v_3 accept 12 56 0 0 expect 12 56 0 0 operation +proj=pop +v_3 accept 12 56 0 0 expect 12 56 0 0 ------------------------------------------------------------------------------- # Test Pipeline +omit_inv ------------------------------------------------------------------------------- operation +proj=pipeline +step +proj=affine +xoff=1 +yoff=1 +omit_inv accept 2 49 0 0 expect 3 50 0 0 direction inverse accept 2 49 0 0 expect 2 49 0 0 operation +proj=pipeline +step +inv +proj=affine +xoff=1 +yoff=1 +omit_inv accept 2 49 0 0 expect 1 48 0 0 direction inverse accept 2 49 0 0 expect 2 49 0 0 ------------------------------------------------------------------------------- # Test Pipeline +omit_fwd ------------------------------------------------------------------------------- operation +proj=pipeline +step +proj=affine +xoff=1 +yoff=1 +omit_fwd accept 2 49 0 0 expect 2 49 0 0 direction inverse accept 2 49 0 0 expect 1 48 0 0 operation +proj=pipeline +step +inv +proj=affine +xoff=1 +yoff=1 +omit_fwd accept 2 49 0 0 expect 2 49 0 0 direction inverse accept 2 49 0 0 expect 3 50 0 0 ------------------------------------------------------------------------------- # Test bugfix of https://github.com/OSGeo/proj.4/issues/1002 # (do not interpolate nodata values) ------------------------------------------------------------------------------- operation +proj=latlong +ellps=WGS84 +geoidgrids=tests/test_nodata.gtx ------------------------------------------------------------------------------- accept 4.05 52.1 0 expect 4.05 52.1 -10 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test null grid with vgridshift ------------------------------------------------------------------------------- operation proj=vgridshift grids=tests/test_nodata.gtx,null ellps=GRS80 ------------------------------------------------------------------------------- accept 4.05 52.1 0 expect 4.05 52.1 -10 # Outside validity area of test_nodata.gtx. Fallback on null accept 4.05 -52.1 0 expect 4.05 -52.1 0 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- # Test bug fix of https://github.com/OSGeo/proj.4/issues/1025. # Using geocent in the new API with a custom ellipsoid should return coordinates # that correspond to that particular ellipsoid and not WGS84 as demonstrated in # the bug report. ------------------------------------------------------------------------------- operation +proj=pipeline +step \ +proj=longlat +a=3396190 +b=3376200 +inv +step \ +proj=geocent +a=3396190 +b=3376200 +lon_0=0 +units=m accept 0.0 0.0 0.0 expect 3396190.0 0.0 0.0 roundtrip 1 operation +proj=geocent +a=3396190 +b=3376200 +lon_0=0 +units=m accept 0.0 0.0 0.00 expect 3396190.0 0.0 0.0 roundtrip 1 ------------------------------------------------------------------------------- # Check that geocent and cart take into account to_meter (#1053) ------------------------------------------------------------------------------- operation +proj=geocent +a=1000 +b=1000 +to_meter=1000 accept 90 0 0 expect 0 1 0 roundtrip 1 operation +proj=cart +a=1000 +b=1000 +to_meter=1000 accept 90 0 0 expect 0 1 0 roundtrip 1 ------------------------------------------------------------------------------- # Check that vunits / vto_meter is honored ------------------------------------------------------------------------------- operation +proj=longlat +a=1 +b=1 +vto_meter=1000 accept 0 0 1000 expect 0 0 1 roundtrip 1 operation +proj=longlat +a=1 +b=1 +vto_meter=2000/2 accept 0 0 1000 expect 0 0 1 roundtrip 1 operation +proj=longlat +a=1 +b=1 +vto_meter=1/0 expect failure errno invalid_op_illegal_arg_value operation +proj=longlat +a=1 +b=1 +vto_meter=1000 +geoc accept 0 0 1000 expect 0 0 1 roundtrip 1 operation +proj=longlat +a=1 +b=1 +vunits=km accept 0 0 1000 expect 0 0 1 roundtrip 1 operation +proj=merc +a=1 +b=1 +vto_meter=1000 accept 0 0 1000 expect 0 0 1 roundtrip 1 operation +proj=merc +a=1 +b=1 +vunits=km accept 0 0 1000 expect 0 0 1 roundtrip 1 ------------------------------------------------------------------------------- # Check that proj_create() returns a syntax error when an exception is caught # in the creation of a PJ object. ------------------------------------------------------------------------------- operation this is a bogus CRS meant to trigger a syntax error in proj_create() expect failure errno invalid_op_wrong_syntax ------------------------------------------------------------------------------- # Test proj=set ------------------------------------------------------------------------------- operation +proj=set accept 1 2 3 4 expect 1 2 3 4 roundtrip 1 operation +proj=set +v_1=10 +v_2=20 +v_3=30 +v_4=40 accept 1 2 3 4 expect 10 20 30 40 operation +proj=set +v_1=10 +v_2=20 +v_3=30 +v_4=40 direction inverse accept 1 2 3 4 expect 10 20 30 40 proj-9.8.1/test/gie/guyou.gie000664 001750 001750 00000204203 15166171715 016035 0ustar00eveneven000000 000000 ------------------------------------------------------------ # This gie file was automatically generated using libproject # where the guyou code was adapted from ------------------------------------------------------------ ------------------------------------------------------------ operation +proj=guyou +R=6370997 tolerance 1 mm ------------------------------------------------------------ accept -179.2338274749 -90.7265739758 expect failure errno coord_transfm_invalid_coord accept -169.3015609686 -90.0683270041 expect failure errno coord_transfm_invalid_coord accept -159.4420546811 -89.5695551279 expect failure errno coord_transfm_outside_projection_domain accept -149.0045856345 -89.3536369188 expect failure errno coord_transfm_outside_projection_domain accept -139.7153960960 -88.6283945950 expect failure errno coord_transfm_outside_projection_domain accept -129.2286632319 -88.1551228787 expect failure errno coord_transfm_outside_projection_domain accept -119.6286953558 -87.5083524092 expect failure errno coord_transfm_outside_projection_domain accept -109.3328522812 -86.7510648640 expect failure errno coord_transfm_outside_projection_domain accept -99.1598524965 -86.5788079857 expect failure errno coord_transfm_outside_projection_domain accept -89.3858632536 -85.7390309668 expect -671252.534 -11805089.168 accept -79.5087279110 -85.6235707551 expect -677891.869 -11686402.401 accept -69.4424228202 -84.9021341881 expect -751854.643 -11529210.346 accept -59.8852518498 -84.8051735959 expect -707106.316 -11400468.436 accept -49.2060555595 -84.5394950727 expect -649513.005 -11249221.343 accept -39.5503275201 -84.2989454792 expect -569372.760 -11119412.618 accept -29.3360829537 -83.5760929301 expect -492270.211 -10930876.977 accept -19.3394041666 -83.2992154597 expect -346317.029 -10818825.415 accept -9.2056484989 -82.3824201970 expect -189497.900 -10632767.439 accept 0.1120336125 -81.7746272088 expect 2495.102 -10523224.283 accept 10.5665512094 -81.6687644685 expect 237086.343 -10527998.139 accept 20.1722250768 -81.3102866550 expect 465363.960 -10531084.361 accept 30.5265377635 -80.9100945160 expect 718603.642 -10578303.571 accept 40.4565955888 -79.9253199265 expect 1020024.277 -10597524.229 accept 50.1397967283 -79.1591500985 expect 1304571.493 -10703166.350 accept 60.7532284194 -78.3809992723 expect 1600003.385 -10897309.840 accept 70.7513325682 -77.7277296032 expect 1840823.214 -11154024.272 accept 80.2085188693 -77.6809008485 expect 1936586.409 -11469925.164 accept 90.8009658259 -76.9680414794 expect failure errno coord_transfm_outside_projection_domain accept 100.6742326194 -76.5942100817 expect failure errno coord_transfm_outside_projection_domain accept 110.5209403479 -75.7585741711 expect failure errno coord_transfm_outside_projection_domain accept 120.5896919383 -74.7649093703 expect failure errno coord_transfm_outside_projection_domain accept 130.7851397036 -73.8582467239 expect failure errno coord_transfm_outside_projection_domain accept 140.7197125638 -72.9106110624 expect failure errno coord_transfm_outside_projection_domain accept 150.4163065521 -72.4059990981 expect failure errno coord_transfm_outside_projection_domain accept 160.4383759680 -71.9757412863 expect failure errno coord_transfm_outside_projection_domain accept 170.5708534042 -71.9525642223 expect failure errno coord_transfm_outside_projection_domain accept 180.2751947006 -71.5923171899 expect failure errno coord_transfm_outside_projection_domain accept -179.7095201992 -79.9687467646 expect failure errno coord_transfm_outside_projection_domain accept -169.0436424710 -79.2424043855 expect failure errno coord_transfm_outside_projection_domain accept -159.2911675882 -78.8917009107 expect failure errno coord_transfm_outside_projection_domain accept -149.2815042820 -78.0224816760 expect failure errno coord_transfm_outside_projection_domain accept -139.5027225646 -77.9987560452 expect failure errno coord_transfm_outside_projection_domain accept -129.2026665027 -77.1138023157 expect failure errno coord_transfm_outside_projection_domain accept -119.6161746714 -76.2954327600 expect failure errno coord_transfm_outside_projection_domain accept -109.9384914753 -75.7674050764 expect failure errno coord_transfm_outside_projection_domain accept -99.2183181307 -75.3730011624 expect failure errno coord_transfm_outside_projection_domain accept -89.2190150125 -74.7701795551 expect -2454071.146 -11777557.324 accept -79.9068086129 -74.7070663318 expect -2420802.270 -11364639.564 accept -69.8146319687 -74.4419812746 expect -2332536.533 -10920591.181 accept -59.6461988138 -73.5860650374 expect -2238931.921 -10443654.878 accept -49.5451009633 -73.4954549764 expect -1957322.263 -10070951.521 accept -39.9329751143 -72.8013395667 expect -1694139.274 -9695298.489 accept -29.5097310678 -72.3497881968 expect -1313051.961 -9382518.879 accept -19.9921230083 -71.8366964443 expect -925602.320 -9142855.035 accept -9.2490763384 -71.0229026677 expect -448842.813 -8910142.058 accept 0.7720814716 -70.5972765500 expect 38276.499 -8815699.571 accept 10.0262635630 -70.0895817655 expect 507116.076 -8777330.134 accept 20.7479812506 -69.9032152988 expect 1049893.591 -8874298.589 accept 30.3631890384 -69.4998925877 expect 1545301.219 -9005805.601 accept 40.5162040333 -69.3056454098 expect 2041397.015 -9262605.098 accept 50.6243675940 -68.6167358219 expect 2562297.663 -9551642.297 accept 60.0486043403 -67.6600969757 expect 3069614.536 -9891241.853 accept 70.4774261179 -66.9833418981 expect 3532980.732 -10438619.729 accept 80.8997122704 -66.2858473379 expect 3898590.190 -11120572.688 accept 90.1834036836 -65.9440561722 expect failure errno coord_transfm_outside_projection_domain accept 100.1984006355 -65.8229326488 expect failure errno coord_transfm_outside_projection_domain accept 110.0486236854 -65.7960345782 expect failure errno coord_transfm_outside_projection_domain accept 120.0510835725 -65.5190096678 expect failure errno coord_transfm_outside_projection_domain accept 130.0896340570 -64.7428784330 expect failure errno coord_transfm_outside_projection_domain accept 140.3678752942 -64.2229928140 expect failure errno coord_transfm_outside_projection_domain accept 150.9150615805 -63.6990548356 expect failure errno coord_transfm_outside_projection_domain accept 160.7280406636 -63.2384418688 expect failure errno coord_transfm_outside_projection_domain accept 170.9183869916 -62.3774990742 expect failure errno coord_transfm_outside_projection_domain accept 180.5619551786 -61.8063907044 expect failure errno coord_transfm_outside_projection_domain accept -179.5943789042 -69.9992085183 expect failure errno coord_transfm_outside_projection_domain accept -169.2134281484 -69.9776667645 expect failure errno coord_transfm_outside_projection_domain accept -159.4518981501 -69.3934026425 expect failure errno coord_transfm_outside_projection_domain accept -149.3420413989 -68.4537518490 expect failure errno coord_transfm_outside_projection_domain accept -139.1131932532 -67.9826718761 expect failure errno coord_transfm_outside_projection_domain accept -129.7785737569 -67.0804523760 expect failure errno coord_transfm_outside_projection_domain accept -119.8160590971 -66.1963102135 expect failure errno coord_transfm_outside_projection_domain accept -109.4603476745 -65.8550220266 expect failure errno coord_transfm_outside_projection_domain accept -99.0748524068 -64.9756976432 expect failure errno coord_transfm_outside_projection_domain accept -89.9206313411 -64.7601702734 expect -4268828.098 -11805632.912 accept -79.4513980949 -64.0272106872 expect -4293419.464 -10904353.202 accept -69.0843325805 -63.4870607362 expect -4057816.546 -10051354.989 accept -59.7650575505 -63.4512934361 expect -3634811.426 -9423524.250 accept -49.8561841911 -63.1756677100 expect -3130112.256 -8850654.578 accept -39.7345386469 -62.7259798066 expect -2564843.692 -8361549.352 accept -29.4900121781 -62.6544841838 expect -1922148.138 -8030239.835 accept -19.6711096687 -62.5940745250 expect -1289744.456 -7810760.172 accept -9.2778801891 -62.3661044165 expect -613541.964 -7649920.107 accept 0.6845208231 -61.4547664483 expect 46410.846 -7484124.735 accept 10.3009385171 -60.8552091241 expect 708981.245 -7444183.153 accept 20.9408599577 -60.7500016629 expect 1443253.248 -7573067.407 accept 30.2033715333 -60.0297971335 expect 2116701.190 -7683450.314 accept 40.1756048990 -59.0918222414 expect 2877171.027 -7881749.372 accept 50.6849086093 -58.5125230628 expect 3670248.004 -8282000.517 accept 60.3342813246 -57.7415828952 expect 4433950.014 -8770184.848 accept 70.3874078634 -56.9198966570 expect 5230934.598 -9486837.736 accept 80.0904535261 -56.6354279372 expect 5822685.735 -10504356.087 accept 90.6634211024 -56.5251359813 expect failure errno coord_transfm_outside_projection_domain accept 100.1452342443 -55.8401169432 expect failure errno coord_transfm_outside_projection_domain accept 110.6864799856 -55.5116401637 expect failure errno coord_transfm_outside_projection_domain accept 120.3347823427 -55.2601958124 expect failure errno coord_transfm_outside_projection_domain accept 130.5134926951 -55.0238926067 expect failure errno coord_transfm_outside_projection_domain accept 140.3265242174 -54.4313834318 expect failure errno coord_transfm_outside_projection_domain accept 150.3685976599 -54.1552793246 expect failure errno coord_transfm_outside_projection_domain accept 160.9436711470 -53.1842316256 expect failure errno coord_transfm_outside_projection_domain accept 170.4034003030 -52.8621841373 expect failure errno coord_transfm_outside_projection_domain accept 180.0812683568 -52.6818511960 expect failure errno coord_transfm_outside_projection_domain accept -179.2224188803 -59.7295306861 expect failure errno coord_transfm_outside_projection_domain accept -169.4132895133 -59.2803345438 expect failure errno coord_transfm_outside_projection_domain accept -159.2336212919 -58.3674875157 expect failure errno coord_transfm_outside_projection_domain accept -149.6086565801 -57.6885943847 expect failure errno coord_transfm_outside_projection_domain accept -139.4537382278 -57.2767531329 expect failure errno coord_transfm_outside_projection_domain accept -129.8015642819 -57.0799116685 expect failure errno coord_transfm_outside_projection_domain accept -119.7728590554 -56.2047588377 expect failure errno coord_transfm_outside_projection_domain accept -109.7735055519 -55.4019628150 expect failure errno coord_transfm_outside_projection_domain accept -99.4371631073 -55.2867551270 expect failure errno coord_transfm_outside_projection_domain accept -89.8854290226 -55.2220077227 expect -6418775.726 -11794916.199 accept -79.1497539952 -54.3143207583 expect -6295512.226 -10173525.949 accept -69.3905433066 -53.3718201615 expect -5756144.839 -8936634.008 accept -59.2835867353 -53.2932693342 expect -4907385.858 -8087491.004 accept -49.4130759901 -52.3805314789 expect -4141087.186 -7360481.030 accept -39.8824125488 -52.1928991404 expect -3328366.078 -6910061.242 accept -29.4030406405 -51.2671252739 expect -2475693.404 -6448925.355 accept -19.4382561500 -50.9645531478 expect -1635897.800 -6198331.601 accept -9.1883916217 -50.6933815720 expect -773969.285 -6040410.862 accept 0.9421744209 -50.4553454248 expect 79572.416 -5974811.404 accept 10.2598469192 -50.3729595701 expect 868653.957 -6006249.038 accept 20.5264309044 -49.7736213847 expect 1760454.588 -6056357.985 accept 30.1563304273 -49.0546876336 expect 2631434.958 -6165824.779 accept 40.6791957468 -48.4680700697 expect 3620466.862 -6416267.044 accept 50.4781583451 -48.0216578802 expect 4589763.311 -6780810.836 accept 60.0996876001 -47.2258755796 expect 5649013.583 -7224720.556 accept 70.5609958631 -47.1401905695 expect 6849278.900 -8064195.707 accept 80.4369996020 -46.9794332723 expect 8182405.160 -9269149.720 accept 90.9151039880 -46.9029866463 expect failure errno coord_transfm_outside_projection_domain accept 100.4373534616 -46.3943619602 expect failure errno coord_transfm_outside_projection_domain accept 110.2842129880 -46.0308507793 expect failure errno coord_transfm_outside_projection_domain accept 120.6486866778 -45.8054277747 expect failure errno coord_transfm_outside_projection_domain accept 130.9364857762 -44.8554259969 expect failure errno coord_transfm_outside_projection_domain accept 140.6165699073 -43.9676693909 expect failure errno coord_transfm_outside_projection_domain accept 150.9528142413 -43.4405627423 expect failure errno coord_transfm_outside_projection_domain accept 160.5641245537 -42.8137396224 expect failure errno coord_transfm_outside_projection_domain accept 170.0938980656 -42.4864250646 expect failure errno coord_transfm_outside_projection_domain accept 180.3769971687 -41.5989802375 expect failure errno coord_transfm_outside_projection_domain accept -179.6808058361 -49.7903016746 expect failure errno coord_transfm_outside_projection_domain accept -169.7237623059 -49.1268921477 expect failure errno coord_transfm_outside_projection_domain accept -159.4102418582 -48.8849591656 expect failure errno coord_transfm_outside_projection_domain accept -149.2254953136 -48.1844242863 expect failure errno coord_transfm_outside_projection_domain accept -139.6041758915 -48.1179744801 expect failure errno coord_transfm_outside_projection_domain accept -129.3754143228 -47.1336832541 expect failure errno coord_transfm_outside_projection_domain accept -119.9746970079 -46.6736994965 expect failure errno coord_transfm_outside_projection_domain accept -109.7581919902 -46.0366124472 expect failure errno coord_transfm_outside_projection_domain accept -99.3823846098 -45.2381613350 expect failure errno coord_transfm_outside_projection_domain accept -89.0277852820 -44.5174266340 expect -11094288.600 -10444365.724 accept -79.1875075496 -44.2312628392 expect -8556831.915 -8448188.432 accept -69.3884076627 -43.7260948924 expect -7143035.210 -7330162.787 accept -59.0994244861 -42.9591919136 expect -5919312.756 -6474855.414 accept -49.2684633342 -42.4441379846 expect -4837843.073 -5897052.170 accept -39.8960036873 -42.3148188142 expect -3850941.078 -5525083.810 accept -29.6345258809 -41.8794443052 expect -2831674.368 -5185997.953 accept -19.0064372737 -41.6217897025 expect -1802198.153 -4961332.000 accept -9.6098419837 -41.1640449046 expect -910539.796 -4807542.914 accept 0.3859851257 -40.6879294032 expect 36674.106 -4715715.978 accept 10.6732390311 -40.2577682910 expect 1020571.561 -4700734.253 accept 20.4749360982 -39.4261884171 expect 1985510.706 -4700304.672 accept 30.7185540788 -39.1851869067 expect 3019577.777 -4853020.985 accept 40.6403024621 -38.8583130891 expect 4072800.273 -5067567.533 accept 50.3930386020 -38.6899841653 expect 5171620.590 -5386102.158 accept 60.7920764042 -38.4864124906 expect 6459723.244 -5832754.996 accept 70.9818120585 -38.2419029781 expect 7920365.597 -6384561.316 accept 80.3791490145 -38.1923095026 expect 9574134.022 -7007709.059 accept 90.5297378114 -37.5687159826 expect failure errno coord_transfm_outside_projection_domain accept 100.5373276614 -37.4573903187 expect failure errno coord_transfm_outside_projection_domain accept 110.0551604465 -36.8785291472 expect failure errno coord_transfm_outside_projection_domain accept 120.5920897044 -36.3504262236 expect failure errno coord_transfm_outside_projection_domain accept 130.1692367892 -36.3071095311 expect failure errno coord_transfm_outside_projection_domain accept 140.0034604349 -35.8053875550 expect failure errno coord_transfm_outside_projection_domain accept 150.9162515055 -35.0369229256 expect failure errno coord_transfm_outside_projection_domain accept 160.4006102901 -34.7824559736 expect failure errno coord_transfm_outside_projection_domain accept 170.8020614665 -34.6367632672 expect failure errno coord_transfm_outside_projection_domain accept 180.1238278697 -34.5735242626 expect failure errno coord_transfm_outside_projection_domain accept -179.3392011550 -39.0089519711 expect failure errno coord_transfm_outside_projection_domain accept -169.9896135260 -38.7758352491 expect failure errno coord_transfm_outside_projection_domain accept -159.9464206150 -38.3643075290 expect failure errno coord_transfm_outside_projection_domain accept -149.8580885141 -38.2565849818 expect failure errno coord_transfm_outside_projection_domain accept -139.0005407033 -37.8175552179 expect failure errno coord_transfm_outside_projection_domain accept -129.9744744916 -36.9749101428 expect failure errno coord_transfm_outside_projection_domain accept -119.4953757022 -36.8251466679 expect failure errno coord_transfm_outside_projection_domain accept -109.8732055905 -36.2644266473 expect failure errno coord_transfm_outside_projection_domain accept -99.2563126423 -35.6283177384 expect failure errno coord_transfm_outside_projection_domain accept -89.2720021301 -34.8337636531 expect -11652944.582 -6432012.406 accept -79.9152588062 -34.3347175023 expect -9740630.763 -6049660.979 accept -69.0059405699 -34.0306344677 expect -7905352.539 -5468386.167 accept -59.9476909228 -33.2328890516 expect -6646979.903 -4929914.448 accept -49.4912957918 -32.6555467192 expect -5330421.040 -4465548.182 accept -39.4661117786 -32.0831702107 expect -4167577.289 -4109295.788 accept -29.8207115548 -31.3399185713 expect -3111531.284 -3817143.482 accept -19.8496271346 -31.2738366265 expect -2047137.004 -3670990.375 accept -9.6527246528 -30.5326610745 expect -992826.938 -3500250.586 accept 0.5995552142 -29.7920841190 expect 61796.178 -3387838.259 accept 10.8411608718 -29.0671095836 expect 1125114.712 -3331112.646 accept 20.3367215658 -28.5199148717 expect 2132236.809 -3339194.703 accept 30.2505986215 -28.1864696949 expect 3217328.415 -3423954.421 accept 40.5431469837 -27.9403197244 expect 4399349.482 -3578155.060 accept 50.7509056230 -27.8467488939 expect 5649446.347 -3807241.841 accept 60.6370271381 -27.3001909011 expect 6983544.451 -4006532.475 accept 70.8220482203 -26.5371650435 expect 8516309.152 -4185254.482 accept 80.2959404087 -25.8737294352 expect 10093881.725 -4297066.952 accept 90.1903848581 -25.5035070096 expect failure errno coord_transfm_outside_projection_domain accept 100.5725497895 -24.6749860350 expect failure errno coord_transfm_outside_projection_domain accept 110.0757522922 -23.8621044004 expect failure errno coord_transfm_outside_projection_domain accept 120.4910038636 -23.3154304481 expect failure errno coord_transfm_outside_projection_domain accept 130.4609905705 -22.3944602016 expect failure errno coord_transfm_outside_projection_domain accept 140.6308884892 -22.1941484220 expect failure errno coord_transfm_outside_projection_domain accept 150.0358167607 -21.7789499055 expect failure errno coord_transfm_outside_projection_domain accept 160.5878069076 -21.7481812197 expect failure errno coord_transfm_outside_projection_domain accept 170.4050593367 -20.8432353205 expect failure errno coord_transfm_outside_projection_domain accept 180.6001699300 -20.8118177919 expect failure errno coord_transfm_outside_projection_domain accept -179.3361000782 -29.8463222037 expect failure errno coord_transfm_outside_projection_domain accept -169.8999068245 -28.9429338477 expect failure errno coord_transfm_outside_projection_domain accept -159.7592377898 -28.1615925042 expect failure errno coord_transfm_outside_projection_domain accept -149.9913638701 -28.0372332017 expect failure errno coord_transfm_outside_projection_domain accept -139.2301202473 -27.0565876723 expect failure errno coord_transfm_outside_projection_domain accept -129.1268966632 -26.9865340393 expect failure errno coord_transfm_outside_projection_domain accept -119.2460222852 -26.3197565889 expect failure errno coord_transfm_outside_projection_domain accept -109.2062914741 -26.1682287226 expect failure errno coord_transfm_outside_projection_domain accept -99.9045914125 -25.7799438486 expect failure errno coord_transfm_outside_projection_domain accept -89.4037956256 -25.6991927518 expect -11705355.483 -4359448.179 accept -79.8043265456 -25.1818797146 expect -10024094.636 -4156724.889 accept -69.7140423873 -24.8449696747 expect -8395540.010 -3863379.945 accept -59.0016643714 -23.9868520946 expect -6856108.329 -3450250.183 accept -49.1644096637 -23.9073140300 expect -5551692.748 -3211628.519 accept -39.4087456167 -23.3838764285 expect -4359255.995 -2956380.419 accept -29.5780322984 -22.5310742645 expect -3226303.975 -2709281.660 accept -19.4970507012 -21.7271569228 expect -2106709.007 -2516125.488 accept -9.2309552615 -21.1686043260 expect -992105.870 -2396128.627 accept 0.8151388636 -20.3765117338 expect 87667.538 -2289827.020 accept 10.7150561883 -20.1052727537 expect 1156777.413 -2278355.080 accept 20.3899871763 -20.0634670217 expect 2218350.011 -2325602.798 accept 30.0598555833 -19.6984569649 expect 3315266.258 -2366374.830 accept 40.9334924529 -18.8831525011 expect 4616943.129 -2395016.592 accept 50.3705152580 -17.9548619999 expect 5824857.949 -2409796.249 accept 60.7629076148 -17.7461797647 expect 7240990.735 -2550212.531 accept 70.5049492221 -17.5449804169 expect 8675560.230 -2678367.876 accept 80.3956836685 -17.4271102390 expect 10234009.562 -2784279.451 accept 90.6808556357 -16.9481935094 expect failure errno coord_transfm_outside_projection_domain accept 100.3669156655 -16.2433923188 expect failure errno coord_transfm_outside_projection_domain accept 110.3790328662 -15.6932515137 expect failure errno coord_transfm_outside_projection_domain accept 120.0882522480 -15.6441029970 expect failure errno coord_transfm_outside_projection_domain accept 130.9326190128 -15.1072978015 expect failure errno coord_transfm_outside_projection_domain accept 140.4297399110 -14.7647959439 expect failure errno coord_transfm_outside_projection_domain accept 150.6670621082 -14.1800360544 expect failure errno coord_transfm_outside_projection_domain accept 160.3083279810 -13.5438931052 expect failure errno coord_transfm_outside_projection_domain accept 170.8801051896 -12.6909069291 expect failure errno coord_transfm_outside_projection_domain accept 180.9127621133 -11.7358777998 expect failure errno coord_transfm_outside_projection_domain accept -179.9634704329 -19.2242789077 expect failure errno coord_transfm_outside_projection_domain accept -169.1616383718 -18.5021923570 expect failure errno coord_transfm_outside_projection_domain accept -159.5989242161 -17.7569846767 expect failure errno coord_transfm_outside_projection_domain accept -149.7658699571 -17.6838689314 expect failure errno coord_transfm_outside_projection_domain accept -139.2283007229 -17.1203530837 expect failure errno coord_transfm_outside_projection_domain accept -129.9856069274 -16.5121558117 expect failure errno coord_transfm_outside_projection_domain accept -119.7066160149 -15.8271303941 expect failure errno coord_transfm_outside_projection_domain accept -109.0808307002 -15.7236132624 expect failure errno coord_transfm_outside_projection_domain accept -99.7051569128 -15.2351555878 expect failure errno coord_transfm_outside_projection_domain accept -89.3984343202 -14.2830109769 expect -11714485.527 -2294583.435 accept -79.3161212354 -13.6551887362 expect -10093909.091 -2148649.820 accept -69.3369852179 -13.5338940098 expect -8554065.078 -2033627.111 accept -59.8017669014 -12.9486645585 expect -7176185.386 -1835041.574 accept -49.8326118713 -12.2344952465 expect -5830239.587 -1625964.636 accept -39.8183008451 -11.3212564171 expect -4563020.124 -1417370.862 accept -29.4511324259 -11.1046029410 expect -3315770.257 -1321269.119 accept -19.3421368019 -10.5209818176 expect -2152759.066 -1206758.050 accept -9.7794195765 -10.4434857815 expect -1080920.071 -1172971.439 accept 0.8324676722 -10.2027272199 expect 91827.144 -1137551.112 accept 10.3172409138 -9.5930872101 expect 1142198.103 -1077875.142 accept 20.7373198297 -8.6767624883 expect 2317698.619 -998481.282 accept 30.8106039881 -8.1660651538 expect 3491268.014 -975920.697 accept 40.2140519607 -7.4699626803 expect 4637259.093 -935187.118 accept 50.8253614228 -6.9240926089 expect 6003942.796 -922291.065 accept 60.3499171589 -6.7875741453 expect 7307879.342 -959056.886 accept 70.7538618658 -6.4717900189 expect 8823316.944 -969586.196 accept 80.1896127919 -6.1835665729 expect 10268415.013 -962009.778 accept 90.2901075635 -5.6342271039 expect failure errno coord_transfm_outside_projection_domain accept 100.1017071129 -4.7732910406 expect failure errno coord_transfm_outside_projection_domain accept 110.6453720127 -4.6247740358 expect failure errno coord_transfm_outside_projection_domain accept 120.7065181706 -4.2131506322 expect failure errno coord_transfm_outside_projection_domain accept 130.0549243427 -3.6680122287 expect failure errno coord_transfm_outside_projection_domain accept 140.9588650575 -3.4693258990 expect failure errno coord_transfm_outside_projection_domain accept 150.3128054514 -2.8622073994 expect failure errno coord_transfm_outside_projection_domain accept 160.6250791828 -2.1132082532 expect failure errno coord_transfm_outside_projection_domain accept 170.1249865639 -1.7779699685 expect failure errno coord_transfm_outside_projection_domain accept 180.4636820369 -1.0447468723 expect failure errno coord_transfm_outside_projection_domain accept -179.0903238876 -9.3809572676 expect failure errno coord_transfm_outside_projection_domain accept -169.8021514554 -8.8200596604 expect failure errno coord_transfm_outside_projection_domain accept -159.6897632216 -8.8151211116 expect failure errno coord_transfm_outside_projection_domain accept -149.3642571872 -7.9578518334 expect failure errno coord_transfm_outside_projection_domain accept -139.1869613409 -7.1376473645 expect failure errno coord_transfm_outside_projection_domain accept -129.8103143949 -6.6533639455 expect failure errno coord_transfm_outside_projection_domain accept -119.0042762894 -6.5424897944 expect failure errno coord_transfm_outside_projection_domain accept -109.3222469320 -6.2626405858 expect failure errno coord_transfm_outside_projection_domain accept -99.2447484897 -5.4322064514 expect failure errno coord_transfm_outside_projection_domain accept -89.6583934020 -4.8520491055 expect -11758391.270 -764820.107 accept -79.0822097483 -4.4980133305 expect -10100660.047 -696260.990 accept -69.2589747329 -4.0562562622 expect -8610130.179 -601975.105 accept -59.2607881882 -3.2433764780 expect -7170184.717 -454392.257 accept -49.3268655448 -2.9707739252 expect -5821746.865 -391509.219 accept -39.9159421567 -2.4973268139 expect -4617029.524 -311670.569 accept -29.1976699326 -1.6650273067 expect -3316547.033 -197263.637 accept -19.0027434133 -1.1195085022 expect -2132221.527 -127924.555 accept -9.1234117067 -0.6240596549 expect -1016591.323 -69833.247 accept 0.4677460038 0.2151624258 expect 52011.067 23925.387 accept 10.7004848538 0.3165167866 expect 1193291.206 35502.455 accept 20.1052099885 0.6126391439 expect 2258534.300 70229.157 accept 30.3541137581 0.7990141229 expect 3454411.853 95128.412 accept 40.7710878998 0.9567675855 expect 4725772.464 119944.290 accept 50.7725952624 1.4294037508 expect 6015746.643 189992.540 accept 60.5997615005 1.6613265138 expect 7361750.248 234549.877 accept 70.4466727438 1.9824185118 expect 8791561.281 295710.236 accept 80.4040420231 2.1516254056 expect 10309233.052 333889.458 accept 90.0745563464 2.5008083079 expect failure errno coord_transfm_outside_projection_domain accept 100.1323994743 3.4798778581 expect failure errno coord_transfm_outside_projection_domain accept 110.7633392793 3.6524418056 expect failure errno coord_transfm_outside_projection_domain accept 120.4935453458 3.8138602324 expect failure errno coord_transfm_outside_projection_domain accept 130.1832453461 4.1827817395 expect failure errno coord_transfm_outside_projection_domain accept 140.6879708211 4.3228601350 expect failure errno coord_transfm_outside_projection_domain accept 150.2872780471 5.0096390922 expect failure errno coord_transfm_outside_projection_domain accept 160.4567844437 5.3289862640 expect failure errno coord_transfm_outside_projection_domain accept 170.1798195714 5.7690110660 expect failure errno coord_transfm_outside_projection_domain accept 180.6237066153 6.6873727446 expect failure errno coord_transfm_outside_projection_domain accept -179.1879408995 0.2925675717 expect failure errno coord_transfm_outside_projection_domain accept -169.8193242429 0.5456299185 expect failure errno coord_transfm_outside_projection_domain accept -159.6143094365 0.8491614798 expect failure errno coord_transfm_outside_projection_domain accept -149.1327424918 1.5360082778 expect failure errno coord_transfm_outside_projection_domain accept -139.9118046279 2.2304783506 expect failure errno coord_transfm_outside_projection_domain accept -129.0707848713 3.1774300866 expect failure errno coord_transfm_outside_projection_domain accept -119.0155367869 3.7108516861 expect failure errno coord_transfm_outside_projection_domain accept -109.7138958240 4.2367325538 expect failure errno coord_transfm_outside_projection_domain accept -99.7626123411 4.9489248449 expect failure errno coord_transfm_outside_projection_domain accept -89.0617378460 5.0261998535 expect -11664192.229 792317.110 accept -79.6065498494 5.4925196315 expect -10179589.971 852372.934 accept -69.7109936494 5.4974120433 expect -8672018.782 818565.715 accept -59.3987361622 6.4168117979 expect -7176296.816 901167.992 accept -49.6550438823 6.6122077057 expect -5850898.508 874299.311 accept -39.5175223106 6.6442757043 expect -4554622.398 828384.475 accept -29.9263368298 7.1910087068 expect -3390376.767 855728.094 accept -19.6951353173 7.2342593616 expect -2202778.650 829386.822 accept -9.3597241491 7.7249502728 expect -1038303.808 866022.563 accept 0.9926766371 7.7430181007 expect 109876.645 862359.585 accept 10.4808252097 7.8032784791 expect 1163217.791 876306.606 accept 20.2149078300 8.7141462778 expect 2257974.529 1001202.962 accept 30.6115985045 8.7806411209 expect 3464879.027 1048739.004 accept 40.9406024668 9.0709137304 expect 4718612.849 1141091.327 accept 50.2246468449 9.2715613620 expect 5908642.815 1232215.612 accept 60.6212081762 10.1366134196 expect 7321174.335 1439008.711 accept 70.0896353531 11.0850079977 expect 8692101.993 1665083.891 accept 80.6785108917 11.6138802990 expect 10322728.590 1826238.813 accept 90.9684446145 11.7088334392 expect failure errno coord_transfm_outside_projection_domain accept 100.8216260873 12.6522648050 expect failure errno coord_transfm_outside_projection_domain accept 110.9341322494 12.8091489452 expect failure errno coord_transfm_outside_projection_domain accept 120.9598573820 12.9840580805 expect failure errno coord_transfm_outside_projection_domain accept 130.1838852888 13.9674113487 expect failure errno coord_transfm_outside_projection_domain accept 140.0163435591 14.2583869011 expect failure errno coord_transfm_outside_projection_domain accept 150.3440941987 15.1977215509 expect failure errno coord_transfm_outside_projection_domain accept 160.6373902313 15.9779535702 expect failure errno coord_transfm_outside_projection_domain accept 170.7489315029 16.0224553269 expect failure errno coord_transfm_outside_projection_domain accept 180.4315909707 16.2341190017 expect failure errno coord_transfm_outside_projection_domain accept -179.9613846400 10.9181102314 expect failure errno coord_transfm_outside_projection_domain accept -169.2464750707 11.2011582425 expect failure errno coord_transfm_outside_projection_domain accept -159.3961353214 12.0814824633 expect failure errno coord_transfm_outside_projection_domain accept -149.9176675016 12.1332225594 expect failure errno coord_transfm_outside_projection_domain accept -139.0300120770 12.7448426145 expect failure errno coord_transfm_outside_projection_domain accept -129.1479377942 13.2115900349 expect failure errno coord_transfm_outside_projection_domain accept -119.6509465671 13.5121287506 expect failure errno coord_transfm_outside_projection_domain accept -109.3485953585 13.9838806488 expect failure errno coord_transfm_outside_projection_domain accept -99.8025484668 14.3000196702 expect failure errno coord_transfm_outside_projection_domain accept -89.6183447418 14.3419323455 expect -11750225.007 2304558.947 accept -79.3692412058 14.8824779140 expect -10092517.831 2350135.174 accept -69.8245239316 15.4247668791 expect -8603644.646 2333402.982 accept -59.9411333586 16.1857250660 expect -7151141.941 2307064.350 accept -49.5439751158 16.6663945754 expect -5737011.937 2221425.397 accept -39.8565220229 16.7491842544 expect -4513398.389 2106838.116 accept -29.0387587694 16.7636024414 expect -3226356.467 1999372.842 accept -19.3072208645 17.5503784835 expect -2114944.499 2022939.979 accept -9.7495596439 18.3236674818 expect -1058075.992 2069782.832 accept 0.3372939259 19.1939757716 expect 36418.336 2154300.403 accept 10.4541089513 19.4557925775 expect 1130896.269 2202407.957 accept 20.3450540441 19.6776140317 expect 2216235.240 2279684.922 accept 30.5262355523 20.1057317982 expect 3364502.237 2421313.756 accept 40.6436957307 20.6888876489 expect 4554114.195 2625171.436 accept 50.8390922621 21.5842676384 expect 5818691.558 2921121.736 accept 60.4025168492 21.7910320981 expect 7107138.574 3149887.076 accept 70.7863531388 22.4913165287 expect 8622256.722 3492000.228 accept 80.2433369822 23.0164275848 expect 10137749.549 3763259.969 accept 90.9377687408 23.1511733038 expect failure errno coord_transfm_outside_projection_domain accept 100.5407405945 23.7761284488 expect failure errno coord_transfm_outside_projection_domain accept 110.0059145808 24.0087396721 expect failure errno coord_transfm_outside_projection_domain accept 120.0839708022 24.8505061022 expect failure errno coord_transfm_outside_projection_domain accept 130.5684827033 25.5457759639 expect failure errno coord_transfm_outside_projection_domain accept 140.2735017629 25.7841586919 expect failure errno coord_transfm_outside_projection_domain accept 150.4960718541 26.4511385776 expect failure errno coord_transfm_outside_projection_domain accept 160.5086557650 26.5248940712 expect failure errno coord_transfm_outside_projection_domain accept 170.1010951379 26.6437577891 expect failure errno coord_transfm_outside_projection_domain accept 180.4667032708 27.5171288037 expect failure errno coord_transfm_outside_projection_domain accept -179.2332673731 20.5208357556 expect failure errno coord_transfm_outside_projection_domain accept -169.9852102849 20.6884242583 expect failure errno coord_transfm_outside_projection_domain accept -159.0918834805 20.7807287727 expect failure errno coord_transfm_outside_projection_domain accept -149.3554284700 21.5195488802 expect failure errno coord_transfm_outside_projection_domain accept -139.0517203599 22.3228377599 expect failure errno coord_transfm_outside_projection_domain accept -129.7889045882 22.9579802676 expect failure errno coord_transfm_outside_projection_domain accept -119.2864956877 23.1541372478 expect failure errno coord_transfm_outside_projection_domain accept -109.1249371549 23.9554652672 expect failure errno coord_transfm_outside_projection_domain accept -99.7886236408 24.2156608317 expect failure errno coord_transfm_outside_projection_domain accept -89.8158531726 24.4254617574 expect -11779801.464 4109615.321 accept -79.1765040759 24.4730100405 expect -9932163.575 4013012.232 accept -69.4905958240 25.3275320515 expect -8347069.306 3939510.329 accept -59.3321746682 25.8062815391 expect -6849836.851 3736718.987 accept -49.4529549960 26.7200008826 expect -5517196.147 3613456.872 accept -39.1868721751 27.3024639669 expect -4254482.013 3466079.063 accept -29.6354019373 27.4373224654 expect -3161470.540 3320471.268 accept -19.1695661946 28.3567542308 expect -2009357.470 3307900.622 accept -9.9833263319 28.6603499308 expect -1037944.407 3278172.915 accept 0.5787958629 28.7930766257 expect 59988.867 3269432.632 accept 10.1908900667 29.4473999043 expect 1055041.336 3373090.240 accept 20.4711672585 29.9558795250 expect 2129353.829 3515969.801 accept 30.8531334707 30.4060992834 expect 3242994.151 3715442.228 accept 40.4755923878 31.2284502498 expect 4307211.258 4017976.832 accept 50.0364462297 31.8692054690 expect 5426487.569 4368373.509 accept 60.5819735012 32.1294434320 expect 6785271.674 4776262.501 accept 70.8279928055 32.4558417273 expect 8275156.713 5258027.307 accept 80.8754092571 33.4499285913 expect 9965465.329 5886025.407 accept 90.9417476435 34.2241428842 expect failure errno coord_transfm_outside_projection_domain accept 100.8763555867 34.9730810876 expect failure errno coord_transfm_outside_projection_domain accept 110.3824336404 35.8924849218 expect failure errno coord_transfm_outside_projection_domain accept 120.3907138338 36.0772654252 expect failure errno coord_transfm_outside_projection_domain accept 130.2213236652 36.3411661168 expect failure errno coord_transfm_outside_projection_domain accept 140.6643127929 37.0777875387 expect failure errno coord_transfm_outside_projection_domain accept 150.5696707234 38.0028546554 expect failure errno coord_transfm_outside_projection_domain accept 160.7330142926 38.8399381962 expect failure errno coord_transfm_outside_projection_domain accept 170.7575452122 39.6058463190 expect failure errno coord_transfm_outside_projection_domain accept 180.9074447347 39.7506383769 expect failure errno coord_transfm_outside_projection_domain accept -179.8967233384 30.5573004598 expect failure errno coord_transfm_outside_projection_domain accept -169.4186924041 30.7925389719 expect failure errno coord_transfm_outside_projection_domain accept -159.3550554816 31.7173298465 expect failure errno coord_transfm_outside_projection_domain accept -149.3340973772 32.5661389704 expect failure errno coord_transfm_outside_projection_domain accept -139.8793305042 33.1451975000 expect failure errno coord_transfm_outside_projection_domain accept -129.0718020283 34.1077930148 expect failure errno coord_transfm_outside_projection_domain accept -119.3765659597 34.6652275838 expect failure errno coord_transfm_outside_projection_domain accept -109.7130644703 34.6829381126 expect failure errno coord_transfm_outside_projection_domain accept -99.5574458139 35.6024191831 expect failure errno coord_transfm_outside_projection_domain accept -89.7509673047 36.5904991465 expect -11753853.356 6923417.921 accept -79.5569260283 36.6832637281 expect -9529017.479 6583316.301 accept -69.4216766587 36.7592109391 expect -7793705.647 6002236.199 accept -59.8993383836 36.9575776083 expect -6439637.617 5535272.788 accept -49.2671447978 37.9494837339 expect -5077618.232 5232434.984 accept -39.6598693615 38.9483055378 expect -3963735.765 5050631.148 accept -29.7093809090 39.1622288957 expect -2917074.123 4828275.833 accept -19.9357360497 39.2854809247 expect -1934827.091 4674852.040 accept -9.5411208555 39.4087263276 expect -919053.532 4586404.063 accept 0.8211876318 39.4440823975 expect 78929.176 4560557.981 accept 10.4970331716 40.3224517995 expect 1003036.028 4707596.187 accept 20.6917357286 40.5466917960 expect 1985755.247 4847003.414 accept 30.0757101328 40.6533776922 expect 2912090.233 5033105.330 accept 40.4799159627 40.9888819952 expect 3968966.207 5358999.089 accept 50.8203716780 41.3416456225 expect 5074769.526 5800574.001 accept 60.6789776888 42.2933132797 expect 6163689.909 6459786.753 accept 70.4598227375 42.9662236851 expect 7378970.615 7275378.446 accept 80.5212319721 43.4298814980 expect 8945156.756 8415826.102 accept 90.6029060873 44.3367368198 expect failure errno coord_transfm_outside_projection_domain accept 100.2968163089 44.7347899226 expect failure errno coord_transfm_outside_projection_domain accept 110.5453757907 45.2325884903 expect failure errno coord_transfm_outside_projection_domain accept 120.7132187645 46.1810408682 expect failure errno coord_transfm_outside_projection_domain accept 130.6036848279 46.5953112427 expect failure errno coord_transfm_outside_projection_domain accept 140.8567165951 47.1100600825 expect failure errno coord_transfm_outside_projection_domain accept 150.2452206431 47.5590007593 expect failure errno coord_transfm_outside_projection_domain accept 160.4264735249 47.6243197261 expect failure errno coord_transfm_outside_projection_domain accept 170.1158223427 47.9911994181 expect failure errno coord_transfm_outside_projection_domain accept 180.2100320112 48.9423016681 expect failure errno coord_transfm_outside_projection_domain accept -179.5621188253 40.3822966969 expect failure errno coord_transfm_outside_projection_domain accept -169.0785389235 40.7397870392 expect failure errno coord_transfm_outside_projection_domain accept -159.9453288766 41.0637989828 expect failure errno coord_transfm_outside_projection_domain accept -149.0072837113 41.2595851413 expect failure errno coord_transfm_outside_projection_domain accept -139.3677527689 41.6743754043 expect failure errno coord_transfm_outside_projection_domain accept -129.1116359654 41.9054883189 expect failure errno coord_transfm_outside_projection_domain accept -119.2367048985 42.7084205060 expect failure errno coord_transfm_outside_projection_domain accept -109.9662473469 42.7707453425 expect failure errno coord_transfm_outside_projection_domain accept -99.5437449083 43.0239640060 expect failure errno coord_transfm_outside_projection_domain accept -89.4757325990 43.2756199739 expect -11568961.170 9589089.555 accept -79.0930156514 43.3605739941 expect -8689821.361 8221563.160 accept -69.6623710654 43.7575713592 expect -7176557.127 7359145.048 accept -59.8551383188 44.0946182610 expect -5913531.749 6705033.126 accept -49.6085838009 44.4913127126 expect -4748242.269 6215794.411 accept -39.1320462959 45.2007809057 expect -3641766.743 5902607.826 accept -29.9547583046 46.1598354389 expect -2721962.231 5767450.222 accept -19.6901625335 47.0694221597 expect -1753442.745 5682225.919 accept -9.5530275863 47.7859862485 expect -838921.885 5658994.019 accept 0.0222239630 47.9106126700 expect 1945.836 5639486.547 accept 10.3786030568 48.2669975286 expect 905735.036 5728642.671 accept 20.3163155558 49.0365488340 expect 1761298.591 5954059.585 accept 30.0523609099 49.2604161517 expect 2613977.300 6191207.840 accept 40.2733019916 49.6471765112 expect 3515610.607 6567126.763 accept 50.6235974778 49.9195971337 expect 4454811.565 7067214.001 accept 60.0640817595 50.7570760574 expect 5269948.907 7769377.867 accept 70.2894928952 51.6926294743 expect 6110607.979 8783958.836 accept 80.7661690288 51.9879378395 expect 6957594.499 10145750.592 accept 90.2760527960 52.6279152662 expect failure errno coord_transfm_outside_projection_domain accept 100.4470132582 53.2519801847 expect failure errno coord_transfm_outside_projection_domain accept 110.1282829759 53.4607562483 expect failure errno coord_transfm_outside_projection_domain accept 120.5033443580 53.9989335755 expect failure errno coord_transfm_outside_projection_domain accept 130.6561885293 54.6823982531 expect failure errno coord_transfm_outside_projection_domain accept 140.8127091472 54.7255186711 expect failure errno coord_transfm_outside_projection_domain accept 150.0054753699 55.4459165788 expect failure errno coord_transfm_outside_projection_domain accept 160.8312787949 56.2470115579 expect failure errno coord_transfm_outside_projection_domain accept 170.4614439985 57.0241073917 expect failure errno coord_transfm_outside_projection_domain accept 180.7047485406 57.6022388310 expect failure errno coord_transfm_outside_projection_domain accept -179.1586219943 50.1365490236 expect failure errno coord_transfm_outside_projection_domain accept -169.1490260788 50.5023613434 expect failure errno coord_transfm_outside_projection_domain accept -159.9535088204 50.9035428509 expect failure errno coord_transfm_outside_projection_domain accept -149.0803324654 51.7753015653 expect failure errno coord_transfm_outside_projection_domain accept -139.7669273536 52.0799612604 expect failure errno coord_transfm_outside_projection_domain accept -129.3599142098 52.6974765371 expect failure errno coord_transfm_outside_projection_domain accept -119.6940989810 52.7976100393 expect failure errno coord_transfm_outside_projection_domain accept -109.7805542206 53.6394720461 expect failure errno coord_transfm_outside_projection_domain accept -99.9951721199 54.3490528856 expect failure errno coord_transfm_outside_projection_domain accept -89.4965032257 55.2735286995 expect -6404341.484 11736182.738 accept -79.2351509797 56.0182014529 expect -5919230.308 10347709.180 accept -69.2279142296 57.0024522025 expect -5142475.359 9389304.843 accept -59.7270769476 57.4201565369 expect -4433291.933 8686026.154 accept -49.4089463064 57.7914752628 expect -3650409.699 8117631.819 accept -39.6433533072 58.1804486015 expect -2906321.369 7735544.810 accept -29.8165100218 58.5388210940 expect -2168952.954 7465348.952 accept -19.0157349252 58.9403079367 expect -1370812.070 7285860.519 accept -9.1047501907 59.4124031484 expect -649264.745 7231297.403 accept 0.8510548344 60.1631637832 expect 59614.314 7301680.904 accept 10.7118819188 60.6049338062 expect 741931.784 7412559.974 accept 20.4398525342 61.3600300433 expect 1386377.568 7649843.890 accept 30.3017880976 62.2930069600 expect 1995284.860 8001431.858 accept 40.2742910491 62.4260882546 expect 2622736.428 8340981.454 accept 50.1679836632 63.3416128275 expect 3130090.680 8886842.807 accept 60.1315518295 64.0854342654 expect 3566294.596 9516574.314 accept 70.2671672611 64.9885817623 expect 3854824.970 10266355.614 accept 80.0566574740 65.2466871256 expect 4076741.126 11011949.460 accept 90.0598623933 65.7380193724 expect failure errno coord_transfm_outside_projection_domain accept 100.2499385302 65.8274446207 expect failure errno coord_transfm_outside_projection_domain accept 110.0091445253 66.5841928232 expect failure errno coord_transfm_outside_projection_domain accept 120.1938557094 67.3045114589 expect failure errno coord_transfm_outside_projection_domain accept 130.1756983494 67.6825233842 expect failure errno coord_transfm_outside_projection_domain accept 140.0774508004 68.5260112188 expect failure errno coord_transfm_outside_projection_domain accept 150.3642244888 69.0087889372 expect failure errno coord_transfm_outside_projection_domain accept 160.0390197585 69.5064813197 expect failure errno coord_transfm_outside_projection_domain accept 170.9882511230 69.9663419458 expect failure errno coord_transfm_outside_projection_domain accept 180.7855662108 70.7558001565 expect failure errno coord_transfm_outside_projection_domain accept -179.8630282894 60.2684905661 expect failure errno coord_transfm_outside_projection_domain accept -169.8774561096 60.7063957678 expect failure errno coord_transfm_outside_projection_domain accept -159.1639611249 61.1269337981 expect failure errno coord_transfm_outside_projection_domain accept -149.0814548820 61.9063878006 expect failure errno coord_transfm_outside_projection_domain accept -139.0476545160 62.7911475081 expect failure errno coord_transfm_outside_projection_domain accept -129.8276916999 62.8515546512 expect failure errno coord_transfm_outside_projection_domain accept -119.9464541970 63.7277849393 expect failure errno coord_transfm_outside_projection_domain accept -109.6864148265 63.9317823343 expect failure errno coord_transfm_outside_projection_domain accept -99.4184495170 64.2249537529 expect failure errno coord_transfm_outside_projection_domain accept -89.6146334566 64.3967878406 expect -4340586.273 11779220.401 accept -79.8288741622 64.8985190713 expect -4137365.737 10977840.319 accept -69.3484116496 65.6222831765 expect -3718954.598 10255930.505 accept -59.0699348251 66.3652733965 expect -3206977.816 9703730.126 accept -49.7020758778 66.6389497206 expect -2744067.651 9275193.520 accept -39.0292679218 66.7793834162 expect -2190174.896 8881394.899 accept -29.9077443640 67.3270538543 expect -1664513.475 8692728.991 accept -19.0042693882 67.9048158312 expect -1045199.569 8559031.056 accept -9.5338704651 68.3771424633 expect -517496.904 8521368.334 accept 0.2238591227 69.2477228904 expect 11757.034 8614778.137 accept 10.2978890619 69.7426835928 expect 528603.064 8728166.019 accept 20.2917095900 70.1793788385 expect 1014867.798 8907115.689 accept 30.9719421826 70.5868181503 expect 1499411.306 9170515.903 accept 40.5524514587 71.5715697583 expect 1833434.912 9555789.645 accept 50.3484489213 71.8505547121 expect 2175943.593 9913399.521 accept 60.1086300862 72.0684099625 expect 2460956.915 10322590.056 accept 70.6228216997 73.0662960367 expect 2561247.727 10868246.628 accept 80.5645741906 73.7542661119 expect 2585249.402 11363593.076 accept 90.6177784757 74.6865930919 expect failure errno coord_transfm_outside_projection_domain accept 100.2695381559 74.9397012116 expect failure errno coord_transfm_outside_projection_domain accept 110.3724185381 75.3340827987 expect failure errno coord_transfm_outside_projection_domain accept 120.0440856622 75.6509550283 expect failure errno coord_transfm_outside_projection_domain accept 130.4765915697 75.8794035471 expect failure errno coord_transfm_outside_projection_domain accept 140.8273607225 76.0863350217 expect failure errno coord_transfm_outside_projection_domain accept 150.7446122566 76.4130503173 expect failure errno coord_transfm_outside_projection_domain accept 160.9066279973 77.2046671411 expect failure errno coord_transfm_outside_projection_domain accept 170.7039755420 77.5254695021 expect failure errno coord_transfm_outside_projection_domain accept 180.7301487821 78.3643690894 expect failure errno coord_transfm_outside_projection_domain accept -179.8424303971 70.7640164273 expect failure errno coord_transfm_outside_projection_domain accept -169.3071682617 71.3534426809 expect failure errno coord_transfm_outside_projection_domain accept -159.9256766432 72.0788134529 expect failure errno coord_transfm_outside_projection_domain accept -149.4389257633 72.9048071827 expect failure errno coord_transfm_outside_projection_domain accept -139.4466193836 73.7144086150 expect failure errno coord_transfm_outside_projection_domain accept -129.4240513471 74.3140345936 expect failure errno coord_transfm_outside_projection_domain accept -119.2324314226 75.1789245938 expect failure errno coord_transfm_outside_projection_domain accept -109.4080456874 75.9740320316 expect failure errno coord_transfm_outside_projection_domain accept -99.9624349535 76.7254677659 expect failure errno coord_transfm_outside_projection_domain accept -89.8454838458 77.0914030368 expect -2065538.883 11806583.302 accept -79.3200173559 77.5923156814 expect -1944930.535 11436411.248 accept -69.2214782512 77.6455638940 expect -1833901.665 11099482.695 accept -59.3345608489 77.6726989114 expect -1672461.604 10796813.087 accept -49.4571426800 77.9149250146 expect -1437767.183 10554465.582 accept -39.0382734207 78.6675565544 expect -1110318.758 10416013.135 accept -29.1097584433 78.7699850319 expect -845013.962 10265588.005 accept -19.0735922396 78.8979558943 expect -558786.152 10166121.532 accept -9.0280312502 79.8097469605 expect -246483.187 10236709.513 accept 0.5580111982 79.8802703269 expect 15185.199 10229124.254 accept 10.8112184284 80.0989872186 expect 286673.574 10288907.138 accept 20.1411665329 80.3961610028 expect 512145.694 10396401.075 accept 30.8632607000 81.0343489380 expect 716121.219 10599328.302 accept 40.5566028935 81.1124451300 expect 903295.715 10744008.638 accept 50.8831926047 81.3748819436 expect 1050654.228 10948141.605 accept 60.0527293155 81.6666268419 expect 1137426.454 11149953.262 accept 70.4854280716 82.6250504715 expect 1097137.468 11420195.697 accept 80.6482540792 83.6156947131 expect 994412.198 11647508.801 accept 90.5789951029 84.1124942551 expect failure errno coord_transfm_outside_projection_domain accept 100.9443622402 84.2131541284 expect failure errno coord_transfm_outside_projection_domain accept 110.5781783058 84.7127066970 expect failure errno coord_transfm_outside_projection_domain accept 120.6025664533 85.5661224284 expect failure errno coord_transfm_outside_projection_domain accept 130.0976840056 86.3318417998 expect failure errno coord_transfm_outside_projection_domain accept 140.6428111911 86.3329325553 expect failure errno coord_transfm_outside_projection_domain accept 150.5251209004 86.5841388479 expect failure errno coord_transfm_outside_projection_domain accept 160.4225603060 86.8828039057 expect failure errno coord_transfm_outside_projection_domain accept 170.2581319411 87.4171568183 expect failure errno coord_transfm_outside_projection_domain accept 180.2641439484 88.1608036446 expect failure errno coord_transfm_outside_projection_domain accept -179.8401984185 80.4164439367 expect failure errno coord_transfm_outside_projection_domain accept -169.6918872432 81.0641431423 expect failure errno coord_transfm_outside_projection_domain accept -159.9211815920 81.7949103274 expect failure errno coord_transfm_outside_projection_domain accept -149.6075138921 82.1028453149 expect failure errno coord_transfm_outside_projection_domain accept -139.7372920424 82.6385276754 expect failure errno coord_transfm_outside_projection_domain accept -129.7998353504 83.1090136759 expect failure errno coord_transfm_outside_projection_domain accept -119.2572393635 83.4744235117 expect failure errno coord_transfm_outside_projection_domain accept -109.3246360912 84.2608342272 expect failure errno coord_transfm_outside_projection_domain accept -99.9601976700 84.4677069243 expect failure errno coord_transfm_outside_projection_domain accept -89.7505869053 85.0897168462 expect -774050.983 11808922.435 accept -79.8910338348 85.9672408001 expect -625258.031 11700550.054 accept -69.1554779909 86.8865771916 expect -457830.975 11637725.026 accept -59.6158025559 87.0075919956 expect -406020.652 11573918.187 accept -49.5091296711 87.2889393354 expect -324158.396 11535225.643 accept -39.6799799687 87.3973978464 expect -261177.241 11497165.712 accept -29.0273299961 87.9089486985 expect -159462.940 11524757.163 accept -19.1380418786 88.8203526189 expect -60802.011 11637057.505 accept -9.0084093906 88.8893310433 expect -27340.998 11639811.223 accept 0.0213854656 89.1326873925 expect 50.899 11675921.557 accept 10.6007672189 89.4603893825 expect 15609.535 11728897.894 accept 20.8972133561 90.3008662748 expect failure errno coord_transfm_invalid_coord accept 30.3236847010 90.9360697392 expect failure errno coord_transfm_invalid_coord accept 40.5211596030 91.7425546179 expect failure errno coord_transfm_invalid_coord accept 50.9530657025 92.7167872262 expect failure errno coord_transfm_invalid_coord accept 60.2966089164 93.5518747322 expect failure errno coord_transfm_invalid_coord accept 70.7058277145 93.9478756274 expect failure errno coord_transfm_invalid_coord accept 80.2842419408 94.5029189079 expect failure errno coord_transfm_invalid_coord accept 90.7046514943 94.5870657217 expect failure errno coord_transfm_invalid_coord accept 100.5757828394 94.7544968060 expect failure errno coord_transfm_invalid_coord accept 110.0784957493 95.6326996288 expect failure errno coord_transfm_invalid_coord accept 120.4420696450 95.7426558832 expect failure errno coord_transfm_invalid_coord accept 130.0586467430 96.7026620164 expect failure errno coord_transfm_invalid_coord accept 140.4982918026 97.2453640881 expect failure errno coord_transfm_invalid_coord accept 150.5137836610 97.2825819689 expect failure errno coord_transfm_invalid_coord accept 160.8117951323 97.8473653755 expect failure errno coord_transfm_invalid_coord accept 170.3402740510 98.3587698302 expect failure errno coord_transfm_invalid_coord accept 180.9378053935 98.8743871260 expect failure errno coord_transfm_invalid_coord accept -179.1153600127 89.6136396944 expect failure errno coord_transfm_outside_projection_domain accept -169.2261536485 89.9072554901 expect failure errno coord_transfm_outside_projection_domain accept -159.1164133392 89.9075872145 expect failure errno coord_transfm_outside_projection_domain accept -149.3960866375 90.6652890542 expect failure errno coord_transfm_invalid_coord accept -139.1720109722 91.2726639403 expect failure errno coord_transfm_invalid_coord accept -129.0729383513 91.6124307897 expect failure errno coord_transfm_invalid_coord accept -119.9896731679 92.3534538297 expect failure errno coord_transfm_invalid_coord accept -109.8793121665 92.7446796075 expect failure errno coord_transfm_invalid_coord accept -99.4084806305 92.8846710223 expect failure errno coord_transfm_invalid_coord accept -89.0674703286 93.0281709121 expect failure errno coord_transfm_invalid_coord accept -79.1149414160 93.4632295476 expect failure errno coord_transfm_invalid_coord accept -69.5261517902 94.2811379653 expect failure errno coord_transfm_invalid_coord accept -59.0417407292 95.2403005497 expect failure errno coord_transfm_invalid_coord accept -49.3488539326 95.3977809221 expect failure errno coord_transfm_invalid_coord accept -39.5548627592 96.1026759897 expect failure errno coord_transfm_invalid_coord accept -29.0476034815 96.4820442045 expect failure errno coord_transfm_invalid_coord accept -19.8278954645 96.7461121139 expect failure errno coord_transfm_invalid_coord accept -9.5488366916 97.4860281314 expect failure errno coord_transfm_invalid_coord accept 0.2307496667 97.5054708483 expect failure errno coord_transfm_invalid_coord accept 10.6032378382 97.7247157965 expect failure errno coord_transfm_invalid_coord accept 20.8959156966 98.0573474237 expect failure errno coord_transfm_invalid_coord accept 30.7104889319 98.5114024172 expect failure errno coord_transfm_invalid_coord accept 40.1762654017 98.8114138429 expect failure errno coord_transfm_invalid_coord accept 50.8420835837 99.5737438963 expect failure errno coord_transfm_invalid_coord accept 60.4391516649 99.6023781174 expect failure errno coord_transfm_invalid_coord accept 70.3833096116 99.8129052911 expect failure errno coord_transfm_invalid_coord accept 80.6796681432 100.7784977140 expect failure errno coord_transfm_invalid_coord accept 90.8893235311 100.9639616716 expect failure errno coord_transfm_invalid_coord accept 100.8499388037 101.2529844461 expect failure errno coord_transfm_invalid_coord accept 110.5488903609 101.9706798836 expect failure errno coord_transfm_invalid_coord accept 120.1208651509 102.2817021553 expect failure errno coord_transfm_invalid_coord accept 130.1003249085 102.6010355936 expect failure errno coord_transfm_invalid_coord accept 140.8624898562 102.6311753718 expect failure errno coord_transfm_invalid_coord accept 150.3852952852 103.0749738073 expect failure errno coord_transfm_invalid_coord accept 160.7214099599 103.8391207142 expect failure errno coord_transfm_invalid_coord accept 170.6439119665 104.6357199780 expect failure errno coord_transfm_invalid_coord accept 180.2173889904 105.2754168153 expect failure errno coord_transfm_invalid_coord ------------------------------------------------------------ ------------------------------------------------------------ operation +proj=guyou +R=1 tolerance 1 mm ------------------------------------------------------------ accept 0 90 expect 0 1.85407 accept 0 -90 expect 0 -1.85407 ------------------------------------------------------------ proj-9.8.1/test/gie/GDA.gie000664 001750 001750 00000007151 15166171715 015263 0ustar00eveneven000000 000000 ----------------------------------------------------------------------------------- Australian datum transformations ----------------------------------------------------------------------------------- Based on material from: Intergovernmental Committee on Surveying and Mapping (ICSM) Permanent Committee on Geodesy (PCG): Geocentric Datum of Australia 2020 Technical Manual Version 1.0, 25 July 2017 Which is distributed under Creative Commons CC-BY 4.0 These tests will probably be useful as a template for an AU setup file, defining transformations for Australian systems, but I'm reluctant to provide such a file myself - it probably should come from official AU sources. Thomas Knudsen, thokn@sdfe.dk, 2017-11-27 ----------------------------------------------------------------------------------- ----------------------------------------------------------------------------------- # GDA94 to GDA2020 ----------------------------------------------------------------------------------- # Just the Helmert transformation, to verify that we are within 100 um ----------------------------------------------------------------------------------- operation proj=helmert \ convention=coordinate_frame \ x = 0.06155 rx = -0.0394924 \ y = -0.01087 ry = -0.0327221 \ z = -0.04019 rz = -0.0328979 s = -0.009994 ----------------------------------------------------------------------------------- tolerance 75 um accept -4052051.7643 4212836.2017 -2545106.0245 expect -4052052.7379 4212835.9897 -2545104.5898 ------------------------------------------------------------------------------- ----------------------------------------------------------------------------------- # GDA94 to GDA2020 ----------------------------------------------------------------------------------- # All the way from geographic-to-cartesian-and-back-to-geographic ----------------------------------------------------------------------------------- operation proj = pipeline ellps=GRS80; \ step proj = cart; \ step proj = helmert \ convention=coordinate_frame \ x = 0.06155; rx = -0.0394924; \ y = -0.01087; ry = -0.0327221; \ z = -0.04019; rz = -0.0328979; s = -0.009994; \ step proj = cart inv; ----------------------------------------------------------------------------------- tolerance 2 mm accept 133.88551329 -23.67012389 603.3466 0 # Alice Springs GDA94 expect 133.8855216 -23.67011014 603.2489 0 # Alice Springs GDA2020 ------------------------------------------------------------------------------- ----------------------------------------------------------------------------------- # ITRF2014@2018 to GDA2020 - Test point ALIC (Alice Springs) ----------------------------------------------------------------------------------- # Just the Helmert transformation, to verify that we are within 100 um ----------------------------------------------------------------------------------- operation proj = helmert exact convention=coordinate_frame \ x = 0 rx = 0 dx = 0 drx = 0.00150379 \ y = 0 ry = 0 dy = 0 dry = 0.00118346 \ z = 0 rz = 0 dz = 0 drz = 0.00120716 \ s = 0 ds = 0 t_epoch=2020.0 ----------------------------------------------------------------------------------- tolerance 40 um accept -4052052.6588 4212835.9938 -2545104.6946 2018.0 # ITRF2014@2018.0 expect -4052052.7373 4212835.9835 -2545104.5867 # GDA2020 ----------------------------------------------------------------------------------- proj-9.8.1/test/unit/000775 001750 001750 00000000000 15166171735 014413 5ustar00eveneven000000 000000 proj-9.8.1/test/unit/test_operationfactory.cpp000664 001750 001750 00002172431 15166171715 021556 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include "test_primitives.hpp" // to be able to use internal::replaceAll #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj_constants.h" #include #include using namespace osgeo::proj::common; using namespace osgeo::proj::coordinates; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::internal; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS) { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat " "+ellps=clrk80ign +pm=paris +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_default) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::NEVER); // Directly found in database { auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4179"), // Pulkovo 42 authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 ctxt); ASSERT_EQ(list.size(), 3U); // Romania has a larger area than Poland (given our approx formula) EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m EXPECT_EQ(list[1]->getEPSGCode(), 1644); // Poland - 1m EXPECT_EQ(list[2]->nameStr(), "Ballpark geographic offset from Pulkovo 1942(58) to ETRS89"); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=krass +step +proj=helmert +x=2.3287 " "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 " "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step " "+inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // Reverse case { auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4258"), authFactory->createCoordinateReferenceSystem("4179"), ctxt); ASSERT_EQ(list.size(), 3U); // Romania has a larger area than Poland (given our approx formula) EXPECT_EQ(list[0]->nameStr(), "Inverse of Pulkovo 1942(58) to ETRS89 (4)"); // Romania - 3m EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=2.3287 " "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 " "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame +step " "+inv +proj=cart +ellps=krass +step +proj=pop +v_3 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_match_by_name) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::NEVER); auto NAD27 = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, GeographicCRS::EPSG_4267->nameStr()), GeographicCRS::EPSG_4267->datum(), GeographicCRS::EPSG_4267->datumEnsemble(), GeographicCRS::EPSG_4267->coordinateSystem()); auto list = CoordinateOperationFactory::create()->createOperations( NAD27, GeographicCRS::EPSG_4326, ctxt); auto listInv = CoordinateOperationFactory::create()->createOperations( GeographicCRS::EPSG_4326, NAD27, ctxt); auto listRef = CoordinateOperationFactory::create()->createOperations( GeographicCRS::EPSG_4267, GeographicCRS::EPSG_4326, ctxt); EXPECT_EQ(list.size(), listRef.size()); EXPECT_EQ(listInv.size(), listRef.size()); EXPECT_GE(listRef.size(), 2U); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_filter_accuracy) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 1.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4179"), authFactory->createCoordinateReferenceSystem("4258"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->getEPSGCode(), 1644); // Poland - 1m } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.9); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4179"), authFactory->createCoordinateReferenceSystem("4258"), ctxt); ASSERT_EQ(list.size(), 0U); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_filter_bbox) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // INSERT INTO "area" VALUES('EPSG','1197','Romania','Romania - onshore and // offshore.',43.44,48.27,20.26,31.41,0); { auto ctxt = CoordinateOperationContext::create( authFactory, Extent::createFromBBOX(20.26, 43.44, 31.41, 48.27), 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4179"), authFactory->createCoordinateReferenceSystem("4258"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m } { auto ctxt = CoordinateOperationContext::create( authFactory, Extent::createFromBBOX(20.26 + .1, 43.44 + .1, 31.41 - .1, 48.27 - .1), 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4179"), authFactory->createCoordinateReferenceSystem("4258"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m } { auto ctxt = CoordinateOperationContext::create( authFactory, Extent::createFromBBOX(20.26 - .1, 43.44 - .1, 31.41 + .1, 48.27 + .1), 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4179"), authFactory->createCoordinateReferenceSystem("4258"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_incompatible_area) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4267"), // NAD27 authFactory->createCoordinateReferenceSystem("4258"), // ETRS 89 ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_inverse_needed) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setUsePROJAlternativeGridNames(false); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4275"), // NTF authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=-168 " "+y=-60 +z=320 +step +inv +proj=cart +ellps=GRS80 +step +proj=pop " "+v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step " "+proj=axisswap +order=2,1"); EXPECT_EQ(list[1]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step " "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step " "+proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4275"), // NTF authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step " "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step " "+proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 authFactory->createCoordinateReferenceSystem("4275"), // NTF ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv " "+proj=hgridshift +grids=fr_ign_ntf_r93.tif +step " "+proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_ntv1_ntv2_ctable2) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4267"), // NAD27 authFactory->createCoordinateReferenceSystem("4269"), // NAD83 ctxt); ASSERT_EQ(list.size(), 10U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift " "+grids=ca_nrc_ntv2_0.tif +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift " "+grids=ca_nrc_ntv1_can.tif +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(list[2]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift " "+grids=us_noaa_conus.tif +step +proj=unitconvert +xy_in=rad " "+xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4267"), // NAD27 authFactory->createCoordinateReferenceSystem("4326"), // WGS84 ctxt); ASSERT_EQ(list.size(), 79U); EXPECT_EQ(list[0]->nameStr(), "NAD27 to WGS 84 (33)"); // 1.0 m, Canada - NAD27 EXPECT_EQ(list[1]->nameStr(), "NAD27 to WGS 84 (3)"); // 20.0 m, Canada - NAD27 EXPECT_EQ(list[2]->nameStr(), "NAD27 to WGS 84 (79)"); // 5.0 m, USA - CONUS including EEZ EXPECT_EQ(list[3]->nameStr(), "NAD27 to WGS 84 (4)"); // 10.0 m, USA - CONUS - onshore } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84_G1762) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto list = CoordinateOperationFactory::create()->createOperations( // NAD27 authFactoryEPSG->createCoordinateReferenceSystem("4267"), // WGS84 (G1762) authFactoryEPSG->createCoordinateReferenceSystem("9057"), ctxt); ASSERT_GE(list.size(), 78U); EXPECT_EQ(list[0]->nameStr(), "NAD27 to WGS 84 (33) + WGS 84 to WGS 84 (G1762)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=ca_nrc_ntv2_0.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[1]->nameStr(), "NAD27 to WGS 84 (3) + WGS 84 to WGS 84 (G1762)"); EXPECT_EQ(list[2]->nameStr(), "NAD27 to WGS 84 (79) + WGS 84 to WGS 84 (G1762)"); EXPECT_EQ(list[3]->nameStr(), "NAD27 to WGS 84 (4) + WGS 84 to WGS 84 (G1762)"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_WGS84_G1674_to_WGS84_G1762) { // Check that particular behavior with WGS 84 (Gxxx) related to // 'geodetic_datum_preferred_hub' table and custom no-op transformations // between WGS 84 and WGS 84 (Gxxx) doesn't affect direct transformations // to those realizations. auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto list = CoordinateOperationFactory::create()->createOperations( // WGS84 (G1674) authFactoryEPSG->createCoordinateReferenceSystem("9056"), // WGS84 (G1762) authFactoryEPSG->createCoordinateReferenceSystem("9057"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=-0.004 +y=0.003 +z=0.004 +rx=0.00027 " "+ry=-0.00027 +rz=0.00038 +s=-0.0069 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_EPSG_4240_Indian1975_to_EPSG_4326) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4240"), // Indian 1975 authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 3U); // Indian 1975 to WGS 84 (4), 3.0 m, Thailand - onshore EXPECT_EQ(list[0]->getEPSGCode(), 1812); // The following is the one we want to see. It has a lesser accuracy than // the above one and the same bbox, but the name of its area of use is // slightly different // Indian 1975 to WGS 84 (2), 5.0 m, Thailand - onshore and Gulf of Thailand EXPECT_EQ(list[1]->getEPSGCode(), 1304); // Indian 1975 to WGS 84 (3), 1.0 m, Thailand - Bongkot field EXPECT_EQ(list[2]->getEPSGCode(), 1537); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_crs) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4939"), // GDA94 3D authFactory->createCoordinateReferenceSystem("7843"), // GDA2020 3D ctxt); ASSERT_EQ(list.size(), 1U); // Check there is no push / pop of v_3 EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 " "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_helmert_geocentric_3D) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); auto list = CoordinateOperationFactory::create()->createOperations( // GDA94 geocentric authFactory->createCoordinateReferenceSystem("4348"), // GDA2020 geocentric authFactory->createCoordinateReferenceSystem("7842"), ctxt); ASSERT_EQ(list.size(), 1U); // Check there is no push / pop of v_3 EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 " "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 " "+convention=coordinate_frame"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 " "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 " "+convention=coordinate_frame"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_helmert_geog3D_to_geocentirc) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); auto list = CoordinateOperationFactory::create()->createOperations( // GDA94 3D authFactory->createCoordinateReferenceSystem("4939"), // GDA2020 geocentric authFactory->createCoordinateReferenceSystem("7842"), ctxt); ASSERT_GE(list.size(), 1U); // Check there is no push / pop of v_3 EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 " "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 " "+convention=coordinate_frame"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_invalid_EPSG_ID) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); // EPSG:4656 is incorrect. Should be EPSG:8997 auto obj = WKTParser().createFromWKT( "GEOGCS[\"ITRF2000\"," "DATUM[\"International_Terrestrial_Reference_Frame_2000\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101," "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6656\"]]," "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199433]," "AUTHORITY[\"EPSG\",\"4656\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(crs), GeographicCRS::EPSG_4326, ctxt); ASSERT_EQ(list.size(), 1U); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_datum_ensemble) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); auto dst_wkt = "GEOGCRS[\"unknown\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " MEMBER[\"World Geodetic System 1984 (Transit)\"," " ID[\"EPSG\",1166]]," " MEMBER[\"World Geodetic System 1984 (G730)\"," " ID[\"EPSG\",1152]]," " MEMBER[\"World Geodetic System 1984 (G873)\"," " ID[\"EPSG\",1153]]," " MEMBER[\"World Geodetic System 1984 (G1150)\"," " ID[\"EPSG\",1154]]," " MEMBER[\"World Geodetic System 1984 (G1674)\"," " ID[\"EPSG\",1155]]," " MEMBER[\"World Geodetic System 1984 (G1762)\"," " ID[\"EPSG\",1156]]," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]," " ID[\"EPSG\",7030]]," " ENSEMBLEACCURACY[2]]," " PRIMEM[\"Greenwich\",0," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]," " ID[\"EPSG\",8901]]," " CS[ellipsoidal,2," " ID[\"EPSG\",6422]]," " AXIS[\"Geodetic latitude (Lat)\",north," " ORDER[1]]," " AXIS[\"Geodetic longitude (Lon)\",east," " ORDER[2]]," " ANGLEUNIT[\"degree (supplier to define representation)\"," "0.0174532925199433,ID[\"EPSG\",9122]]]"; auto dstObj = WKTParser().createFromWKT(dst_wkt); auto dstCRS = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dstCRS != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 NN_NO_CHECK(dstCRS), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "ETRS89 to WGS 84 (1)"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_incompatible_celestial_body) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto authFactoryIAU_2015 = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); EXPECT_THROW( CoordinateOperationFactory::create()->createOperations( authFactoryEPSG->createCoordinateReferenceSystem("4326"), // WGS 84 authFactoryIAU_2015->createCoordinateReferenceSystem( "51200"), // Ananke ctxt), UnsupportedOperationException); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_incompatible_celestial_body_but_same_radius) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto authFactoryESRI = AuthorityFactory::create(DatabaseContext::create(), "ESRI"); auto authFactoryIAU_2015 = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); EXPECT_THROW(CoordinateOperationFactory::create()->createOperations( authFactoryESRI->createCoordinateReferenceSystem( "104936"), // GCS_Pan_2000 authFactoryIAU_2015->createCoordinateReferenceSystem( "51200"), // Ananke ctxt), UnsupportedOperationException); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_compatible_unknown_celestial_body) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto objSrc = createFromUserInput("+proj=longlat +R=10000 +type=crs", dbContext); auto srcCRS = nn_dynamic_pointer_cast(objSrc); auto objDst = createFromUserInput("+proj=longlat +R=10001 +type=crs", dbContext); const auto queryCounterBefore = dbContext->getQueryCounter(); auto dstCRS = nn_dynamic_pointer_cast(objDst); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt); EXPECT_EQ(dbContext->getQueryCounter(), queryCounterBefore); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_incompatible_unknown_celestial_body) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto objSrc = createFromUserInput("+proj=longlat +R=10000 +type=crs", dbContext); auto srcCRS = nn_dynamic_pointer_cast(objSrc); auto objDst = createFromUserInput("+proj=longlat +R=99999 +type=crs", dbContext); auto dstCRS = nn_dynamic_pointer_cast(objDst); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); EXPECT_THROW(CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt), UnsupportedOperationException); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_compatible_celestial_body_through_semi_major_axis) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto authFactoryIAU_2015 = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto dst_wkt = "GEOGCRS[\"unknown\",\n" " DATUM[\"unknown\",\n" " ELLIPSOID[\"unknown\",9999,0,\n" // Ananke is 10000 " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference_Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto dstObj = WKTParser().createFromWKT(dst_wkt); auto dstCRS = nn_dynamic_pointer_cast(dstObj); auto list = CoordinateOperationFactory::create()->createOperations( authFactoryIAU_2015->createCoordinateReferenceSystem("51200"), // Ananke NN_NO_CHECK(dstCRS), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_derived_geogCRS_3D) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto dst_wkt = "GEOGCRS[\"CH1903+ with 10m offset on ellipsoidal height\",\n" " BASEGEOGCRS[\"CH1903+\",\n" " DATUM[\"CH1903+\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6150]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " DERIVINGCONVERSION[\"Offset on ellipsoidal height\",\n" " METHOD[\"Geographic3D offsets\",\n" " ID[\"EPSG\",9660]],\n" " PARAMETER[\"Latitude offset\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8601]],\n" " PARAMETER[\"Longitude offset\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8602]],\n" " PARAMETER[\"Vertical Offset\",10,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8603]]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto dstObj = WKTParser().createFromWKT(dst_wkt); auto dstCRS = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dstCRS != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 3D NN_NO_CHECK(dstCRS), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ( list[0]->nameStr(), "Inverse of CH1903+ to WGS 84 (1) + Offset on ellipsoidal height"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 " "+step +inv +proj=cart +ellps=bessel " "+step +proj=geogoffset +dlat=0 +dlon=0 +dh=10 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, vertCRS_to_geogCRS_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setUsePROJAlternativeGridNames(false); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "3855"), // EGM2008 height authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 ctxt); ASSERT_EQ(list.size(), 3U); EXPECT_EQ( list[1]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "3855"), // EGM2008 height authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 ctxt); ASSERT_EQ(list.size(), 3U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 authFactory->createCoordinateReferenceSystem( "3855"), // EGM2008 height ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // NGVD29 depth (ftUS) authFactory->createCoordinateReferenceSystem("6359"), authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +s33=-0.304800609601219"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // NZVD2016 height authFactory->createCoordinateReferenceSystem("7839"), // NZGD2000 authFactory->createCoordinateReferenceSystem("4959"), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=nz_linz_nzgeoid2016.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } { // Test actually the database where we derive records using the more // classic 'Geographic3D to GravityRelatedHeight' method from // records using EPSG:1088 //'Geog3D to Geog2D+GravityRelatedHeight (gtx)' method auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(DatabaseContext::create(), std::string()), nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // Baltic 1957 height authFactory->createCoordinateReferenceSystem("8357"), // ETRS89 authFactory->createCoordinateReferenceSystem("4937"), ctxt); ASSERT_EQ(list.size(), 3U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift " "+grids=cz_cuzk_CR-2005.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift " "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } } // --------------------------------------------------------------------------- TEST(operation, geog3DCRS_to_geog2DCRS_plus_vertCRS_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // ETRS89 (3D) authFactory->createCoordinateReferenceSystem("4937"), // ETRS89 + Baltic 1957 height authFactory->createCoordinateReferenceSystem("8360"), ctxt); ASSERT_GE(list.size(), 2U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift " "+grids=cz_cuzk_CR-2005.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[0]->inverse()->nameStr(), "Inverse of ETRS89 to Baltic 1957 height (2)"); EXPECT_EQ( list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift " "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[1]->inverse()->nameStr(), "Inverse of 'ETRS89 to ETRS89 + Baltic 1957 height (1)'"); } } // --------------------------------------------------------------------------- TEST(operation, geog3DCRS_to_vertCRS_depth_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4937"), // ETRS89 authFactory->createCoordinateReferenceSystem("9672"), // CD Norway deph ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift " "+grids=no_kv_CD_above_Ell_ETRS89_v2023b.tif +multiplier=1 " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("9672"), // CD Norway deph authFactory->createCoordinateReferenceSystem("4937"), // ETRS89 ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=vgridshift " "+grids=no_kv_CD_above_Ell_ETRS89_v2023b.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } } // --------------------------------------------------------------------------- TEST(operation, geog3DCRS_to_geog2DCRS_plus_vertCRS_depth_context) { const auto test = [](bool allAuthorities, const char *srcCode) { auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto authFactoryOp = allAuthorities ? AuthorityFactory::create(DatabaseContext::create(), std::string()) : authFactoryEPSG; { auto ctxt = CoordinateOperationContext::create(authFactoryOp, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactoryEPSG->createCoordinateReferenceSystem(srcCode), // ETRS89-NOR [EUREF89] + CD Norway deph authFactoryEPSG->createCoordinateReferenceSystem("9883"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactoryEPSG->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift " "+grids=no_kv_CD_above_Ell_ETRS89_v2023b.tif +multiplier=1 " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } { auto ctxt = CoordinateOperationContext::create(authFactoryOp, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // ETRS89-NOR [EUREF89] + CD Norway deph authFactoryEPSG->createCoordinateReferenceSystem("9883"), authFactoryEPSG->createCoordinateReferenceSystem(srcCode), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactoryEPSG->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=vgridshift " "+grids=no_kv_CD_above_Ell_ETRS89_v2023b.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } }; test(false, "4937"); // ETRS89 3D test(true, "4937"); // ETRS89 3D test(false, "10874"); // ETRS89-NOR [EUREF89] 3D } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_noop) { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->nameStr(), "Null geographic offset from WGS 84 to WGS 84"); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_EQ(op->inverse()->nameStr(), op->nameStr()); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_longitude_rotation) { auto src = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "A"), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto dest = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "B"), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::PARIS), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto op = CoordinateOperationFactory::create()->createOperation(src, dest); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=longlat " "+ellps=WGS84 +pm=paris +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(op->inverse()->exportToWKT(WKTFormatter::create().get()), CoordinateOperationFactory::create() ->createOperation(dest, src) ->exportToWKT(WKTFormatter::create().get())); EXPECT_TRUE( op->inverse()->isEquivalentTo(CoordinateOperationFactory::create() ->createOperation(dest, src) .get())); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_longitude_rotation_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris) authFactory->createCoordinateReferenceSystem("4275"), // NTF ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to NTF (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=paris +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to NTF (2)"); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=2.33720833333333 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_lonlat_vs_latlon_crs) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4749"), // RGNC91-93 authFactory->createCoordinateReferenceSystem("10310"), // RGNC15 ctxt); ASSERT_EQ(list.size(), 5U); EXPECT_EQ(list[0]->nameStr(), "RGNC91-93 to WGS 84 (1) + Inverse of RGNC15 to WGS 84 (1)"); // Check that we get direct transformation, and not only through WGS 84 // The difficulty here is that the transformation is registered between // "RGNC91-93 (lon-lat)" et "RGNC15 (lon-lat)" EXPECT_EQ(list[1]->nameStr(), "axis order change (2D) + RGNC91-93 to " "RGNC15 (2) + axis order change (2D)"); EXPECT_EQ(list[2]->nameStr(), "axis order change (2D) + RGNC91-93 to " "RGNC15 (1) + axis order change (2D)"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_concatenated_operation) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::ALWAYS); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris) authFactory->createCoordinateReferenceSystem("4171"), // RGF93 ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to RGF93 v1 (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=grad +xy_out=rad " "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris " "+step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign " "+step +proj=xyzgridshift +grids=fr_ign_gr3df97a.tif " "+grid_ref=output_crs +ellps=GRS80 " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to RGF93 v1 (2)"); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift " "+grids=fr_ign_ntf_r93.tif +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_TRUE(nn_dynamic_pointer_cast(list[0]) != nullptr); auto grids = list[0]->gridsNeeded(DatabaseContext::create(), false); EXPECT_EQ(grids.size(), 1U); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_concatenated_operation_Egypt1907_to_WGS84) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4229"), // Egypt 1907 authFactory->createCoordinateReferenceSystem("4326"), // WGS84 ctxt); ASSERT_EQ(list.size(), 3U); // Concatenated operation EXPECT_EQ(list[1]->nameStr(), "Egypt 1907 to WGS 84 (2)"); ASSERT_EQ(list[1]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[1]->coordinateOperationAccuracies()[0]->value(), "6.0"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_ED50_to_WGS72_no_NTF_intermediate) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4230"), // ED50 authFactory->createCoordinateReferenceSystem("4322"), // WGS 72 ctxt); ASSERT_GE(list.size(), 2U); // We should not use the ancient NTF as an intermediate when looking for // ED50 -> WGS 72 operations. for (const auto &op : list) { EXPECT_TRUE(op->nameStr().find("NTF") == std::string::npos) << op->nameStr(); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_same_grid_name) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4314"), // DHDN authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 ctxt); ASSERT_TRUE(!list.empty()); EXPECT_EQ(list[0]->nameStr(), "DHDN to ETRS89 (8)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift " "+grids=de_adv_BETA2007.tif +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_geographic_offset_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4120"), // NTF(Paris) authFactory->createCoordinateReferenceSystem("4121"), // NTF ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Greek to GGRS87 (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=-5.86 +dlon=0.28 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_CH1903_to_CH1903plus_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::ALWAYS); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4149"), // CH1903 authFactory->createCoordinateReferenceSystem("4150"), // CH1903+ ctxt); ASSERT_TRUE(list.size() == 1U); EXPECT_EQ(list[0]->nameStr(), "CH1903 to CH1903+ (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=ch_swisstopo_CHENyx06a.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_init_IGNF_to_init_IGNF_context) { auto dbContext = DatabaseContext::create(); auto sourceCRS_obj = PROJStringParser() .attachDatabaseContext(dbContext) .setUsePROJ4InitRules(true) .createFromPROJString("+init=IGNF:NTFG"); auto sourceCRS = nn_dynamic_pointer_cast(sourceCRS_obj); ASSERT_TRUE(sourceCRS != nullptr); auto targetCRS_obj = PROJStringParser() .attachDatabaseContext(dbContext) .setUsePROJ4InitRules(true) .createFromPROJString("+init=IGNF:RGF93G"); auto targetCRS = nn_dynamic_pointer_cast(targetCRS_obj); ASSERT_TRUE(targetCRS != nullptr); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(sourceCRS), NN_CHECK_ASSERT(targetCRS), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=fr_ign_ntf_r93.tif +step " "+proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_context_deprecated) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "4226"), // Cote d'Ivoire (deprecated) authFactory->createCoordinateReferenceSystem("4258"), // ETRS89 ctxt); ASSERT_TRUE(!list.empty()); EXPECT_EQ(list[0]->nameStr(), "Ballpark geographic offset from Cote d'Ivoire to ETRS89"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_3D) { auto geogcrs_m_obj = PROJStringParser().createFromPROJString( "+proj=longlat +vunits=m +type=crs"); auto geogcrs_m = nn_dynamic_pointer_cast(geogcrs_m_obj); ASSERT_TRUE(geogcrs_m != nullptr); auto geogcrs_ft_obj = PROJStringParser().createFromPROJString( "+proj=longlat +vunits=ft +type=crs"); auto geogcrs_ft = nn_dynamic_pointer_cast(geogcrs_ft_obj); ASSERT_TRUE(geogcrs_ft != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(geogcrs_ft)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=m +z_out=ft"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(geogcrs_ft), NN_CHECK_ASSERT(geogcrs_m)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=ft +z_out=m"); } auto geogcrs_m_with_pm_obj = PROJStringParser().createFromPROJString( "+proj=longlat +pm=paris +vunits=m +type=crs"); auto geogcrs_m_with_pm = nn_dynamic_pointer_cast(geogcrs_m_with_pm_obj); ASSERT_TRUE(geogcrs_m_with_pm != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(geogcrs_m_with_pm), NN_CHECK_ASSERT(geogcrs_ft)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m " "+xy_out=rad +z_out=m +step +inv +proj=longlat +ellps=WGS84 " "+pm=paris +step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=ft"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_3D_lat_long_non_metre_to_geogCRS_longlat) { auto wkt = "GEOGCRS[\"my CRS\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563],\n" " ID[\"EPSG\",6326]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " LENGTHUNIT[\"my_vunit\",0.3]]]"; auto srcCRS_obj = WKTParser().createFromWKT(wkt); auto srcCRS = nn_dynamic_pointer_cast(srcCRS_obj); ASSERT_TRUE(srcCRS != nullptr); auto dstCRS_obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +type=crs"); auto dstCRS = nn_dynamic_pointer_cast(dstCRS_obj); ASSERT_TRUE(dstCRS != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(srcCRS), NN_CHECK_ASSERT(dstCRS)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +z_in=0.3 +z_out=m"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_without_id_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto src = authFactory->createCoordinateReferenceSystem("4289"); // Amersfoort auto dst = authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_GE(list.size(), 1U); auto wkt2 = "GEOGCRS[\"unnamed\",\n" " DATUM[\"Amersfoort\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]," " USAGE[\n" " SCOPE[\"unknown\"],\n" " AREA[\"Netherlands - onshore\"],\n" " BBOX[50.75,3.2,53.7,7.22]]]\n"; auto obj = WKTParser().createFromWKT(wkt2); auto src_from_wkt2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(src_from_wkt2 != nullptr); auto list2 = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src_from_wkt2), dst, ctxt); ASSERT_GE(list.size(), list2.size()); for (size_t i = 0; i < list.size(); i++) { const auto &op = list[i]; const auto &op2 = list2[i]; EXPECT_TRUE( op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT)) << op->nameStr() << " " << op2->nameStr(); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_promoted_to_3D_to_geogCRS_3D_context) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( authFactoryEPSG->createCoordinateReferenceSystem("4269") ->promoteTo3D(std::string(), dbContext), // NAD83 (86) promoted to 3D authFactoryEPSG->createCoordinateReferenceSystem( "6319"), // NAD83 (2011) 3D ctxt); ASSERT_GE(list.size(), 1U); // Check we don't get an ESRI operation EXPECT_EQ(list[0]->nameStr(), "NAD83 to NAD83(HARN) (47) + " "NAD83(HARN) to NAD83(FBN) (1) + " "NAD83(FBN) to NAD83(NSRS2007) (1) + " "NAD83(NSRS2007) to NAD83(2011) (1)"); } { auto list = CoordinateOperationFactory::create()->createOperations( authFactoryEPSG->createCoordinateReferenceSystem( "6319"), // NAD83 (2011) 3D authFactoryEPSG->createCoordinateReferenceSystem("4269") ->promoteTo3D(std::string(), dbContext), // NAD83 (86) promoted to 3D ctxt); ASSERT_GE(list.size(), 1U); // Check we don't get an ESRI operation EXPECT_EQ(list[0]->nameStr(), "Inverse of NAD83(NSRS2007) to NAD83(2011) (1) + " "Inverse of NAD83(FBN) to NAD83(NSRS2007) (1) + " "Inverse of NAD83(HARN) to NAD83(FBN) (1) + " "Inverse of NAD83 to NAD83(HARN) (47)"); } } // --------------------------------------------------------------------------- static GeodeticCRSNNPtr createGeocentricDatumWGS84() { PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 4328) .set(IdentifiedObject::NAME_KEY, "WGS 84"); return GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- static GeodeticCRSNNPtr createGeocentricKM() { PropertyMap propertiesCRS; propertiesCRS.set(IdentifiedObject::NAME_KEY, "Based on WGS 84"); return GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric( UnitOfMeasure("kilometre", 1000.0, UnitOfMeasure::Type::LINEAR))); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geogCRS_same_datum) { auto op = CoordinateOperationFactory::create()->createOperation( createGeocentricDatumWGS84(), GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geogCRS_different_datum) { auto op = CoordinateOperationFactory::create()->createOperation( createGeocentricDatumWGS84(), GeographicCRS::EPSG_4269); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->nameStr(), "Conversion from WGS 84 to WGS 84 (geographic) + " "Ballpark geographic offset from WGS 84 (geographic) to NAD83"); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geocentricCRS_different_datum) { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4269, createGeocentricDatumWGS84()); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->nameStr(), "Ballpark geographic offset from NAD83 to WGS 84 (geographic) + " "Conversion from WGS 84 (geographic) to WGS 84"); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geocentricCRS_same_noop) { auto op = CoordinateOperationFactory::create()->createOperation( createGeocentricDatumWGS84(), createGeocentricDatumWGS84()); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->nameStr(), "Null geocentric translation from WGS 84 to WGS 84"); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_EQ(op->inverse()->nameStr(), op->nameStr()); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geocentricCRS_different_ballpark) { PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 4328) .set(IdentifiedObject::NAME_KEY, "unknown"); auto otherGeocentricCRS = GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6269, CartesianCS::createGeocentric(UnitOfMeasure::METRE)); auto op = CoordinateOperationFactory::create()->createOperation( createGeocentricKM(), otherGeocentricCRS); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->nameStr(), "Ballpark geocentric translation from Based on WGS 84 to unknown"); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +xy_in=km +z_in=km +xy_out=m +z_out=m"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geogCRS_same_datum_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4326"), // WGS84 geocentric authFactory->createCoordinateReferenceSystem("4978"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart " "+ellps=WGS84"); EXPECT_EQ(list[0]->inverse()->nameStr(), "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geogCRS_same_datum_context_all_auth) { // This is to check we don't use OGC:CRS84 as a pivot auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto authFactoryAll = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactoryAll, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactoryEPSG->createCoordinateReferenceSystem("4326"), // WGS84 geocentric authFactoryEPSG->createCoordinateReferenceSystem("4978"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geocentricCRS_different_datum_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // ITRF2000 (geocentric) authFactory->createCoordinateReferenceSystem("4919"), // ITRF2005 (geocentric) authFactory->createCoordinateReferenceSystem("4896"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "ITRF2000 to ITRF2005 (1)"); EXPECT_PRED_FORMAT2( ComparePROJString, list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=helmert +x=-0.0001 " "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 " "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 " "+t_epoch=2000 +convention=position_vector"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_geocentricCRS_same_datum_to_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // WGS84 geocentric authFactory->createCoordinateReferenceSystem("4978"), authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geog2D_to_geog3D_same_datum_but_with_potential_other_pivot_context) { // Check that when going from geog2D to geog3D of same datum, we don't // try to go through a WGS84 pivot... auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("5365"), // CR 05 2D authFactory->createCoordinateReferenceSystem("5364"), // CR 05 3D ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_different_datum_though_geocentric_transform_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // ITRF2000 (geog3D) authFactory->createCoordinateReferenceSystem("7909"), // ITRF2005 (geog3D) authFactory->createCoordinateReferenceSystem("7910"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + " "ITRF2000 to ITRF2005 (1) + " "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)"); EXPECT_PRED_FORMAT2( ComparePROJString, list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 " "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 " "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 " "+t_epoch=2000 +convention=position_vector +step +inv " "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad " "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1"); EXPECT_TRUE(list[0]->requiresPerCoordinateInputTime()); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geocentricCRS_different_datum_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // ITRF2000 (geog3D) authFactory->createCoordinateReferenceSystem("7909"), // ITRF2005 (geocentric) authFactory->createCoordinateReferenceSystem("4896"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + " "ITRF2000 to ITRF2005 (1)"); EXPECT_PRED_FORMAT2( ComparePROJString, list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 " "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 " "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 " "+t_epoch=2000 +convention=position_vector"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_geogCRS_different_datum_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // ITRF2000 (geocentric) authFactory->createCoordinateReferenceSystem("4919"), // ITRF2005 (geog3D) authFactory->createCoordinateReferenceSystem("7910"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "ITRF2000 to ITRF2005 (1) + " "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)"); EXPECT_PRED_FORMAT2( ComparePROJString, list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=helmert +x=-0.0001 " "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 " "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 " "+t_epoch=2000 +convention=position_vector +step +inv " "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad " "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_3D_to_geogCRS_3D_different_datum_context) { // Test for https://github.com/OSGeo/PROJ/issues/2541 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // RGF93 (3D) authFactory->createCoordinateReferenceSystem("4965"), // CH1903+ promoted to 3D authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D( std::string(), dbContext), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "RGF93 v1 to ETRS89 (1) + Inverse of CH1903+ to ETRS89 (1)"); // Check that there is no +push +v_3 EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 " "+step +inv +proj=cart +ellps=bessel " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.374 +y=15.056 +z=405.346 " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geocentric_to_geogCRS_3D_different_datum_context) { // Test variant of https://github.com/OSGeo/PROJ/issues/2541 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // RGF93 (geocentric) authFactory->createCoordinateReferenceSystem("4964"), // CH1903+ promoted to 3D authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D( std::string(), dbContext), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Conversion from RGF93 v1 (geocentric) to RGF93 v1 (geog3D) + " "RGF93 v1 to ETRS89 (1) + " "Inverse of CH1903+ to ETRS89 (1)"); // Check that there is no +push +v_3 EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 " "+step +inv +proj=cart +ellps=bessel " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.374 +y=15.056 +z=405.346"); } // --------------------------------------------------------------------------- TEST(operation, createBetweenGeodeticCRSWithDatumBasedIntermediates) { auto dbContext = DatabaseContext::create(); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactoryEPSG, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // IG05/12 Intermediate CRS authFactoryEPSG->createCoordinateReferenceSystem("6990"), // ITRF2014 authFactoryEPSG->createCoordinateReferenceSystem("9000"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of ITRF2008 to IG05/12 Intermediate CRS + " "Conversion from ITRF2008 (geog2D) to ITRF2008 (geocentric) + " "ITRF2008 to ITRF2014 (1) + " "Conversion from ITRF2014 (geocentric) to ITRF2014 (geog2D)"); auto listInv = CoordinateOperationFactory::create()->createOperations( // ITRF2014 authFactoryEPSG->createCoordinateReferenceSystem("9000"), // IG05/12 Intermediate CRS authFactoryEPSG->createCoordinateReferenceSystem("6990"), ctxt); ASSERT_EQ(listInv.size(), 1U); EXPECT_EQ(listInv[0]->nameStr(), "Conversion from ITRF2014 (geog2D) to ITRF2014 (geocentric) + " "Inverse of ITRF2008 to ITRF2014 (1) + " "Conversion from ITRF2008 (geocentric) to ITRF2008 (geog2D) + " "ITRF2008 to IG05/12 Intermediate CRS"); } // --------------------------------------------------------------------------- TEST(operation, esri_projectedCRS_to_geogCRS_with_ITRF_intermediate_context) { auto dbContext = DatabaseContext::create(); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto authFactoryESRI = AuthorityFactory::create(dbContext, "ESRI"); auto ctxt = CoordinateOperationContext::create(authFactoryEPSG, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US (projected) authFactoryESRI->createCoordinateReferenceSystem("103501"), // ITRF2005 (geog3D) authFactoryEPSG->createCoordinateReferenceSystem("7910"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of NAD_1983_CORS96_StatePlane_North_Carolina_" "FIPS_3200_Ft_US + " "Conversion from NAD83(CORS96) (geog2D) to NAD83(CORS96) " "(geocentric) + Inverse of ITRF2000 to NAD83(CORS96) (1) + " "ITRF2000 to ITRF2005 (1) + " "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)"); EXPECT_PRED_FORMAT2( ComparePROJString, list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft " "+xy_out=m +step +inv +proj=lcc +lat_0=33.75 +lon_0=-79 " "+lat_1=34.3333333333333 +lat_2=36.1666666666667 " "+x_0=609601.219202438 +y_0=0 +ellps=GRS80 +step +proj=cart " "+ellps=GRS80 +step +inv +proj=helmert +x=0.9956 +y=-1.9013 " "+z=-0.5215 +rx=0.025915 +ry=0.009426 +rz=0.011599 +s=0.00062 " "+dx=0.0007 +dy=-0.0007 +dz=0.0005 +drx=6.7e-05 +dry=-0.000757 " "+drz=-5.1e-05 +ds=-0.00018 +t_epoch=1997 " "+convention=coordinate_frame +step +proj=helmert +x=-0.0001 " "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 " "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 " "+t_epoch=2000 +convention=position_vector +step +inv +proj=cart " "+ellps=GRS80 +step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=m +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_WGS84_to_GDA2020) { // 2D reduction of use case of https://github.com/OSGeo/PROJ/issues/2348 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( // GDA2020 authFactory->createCoordinateReferenceSystem("7844"), // WGS 84 authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "GDA2020 to WGS 84 (2)"); } // Inverse { auto list = CoordinateOperationFactory::create()->createOperations( // WGS 84 authFactory->createCoordinateReferenceSystem("4326"), // GDA2020 authFactory->createCoordinateReferenceSystem("7844"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of GDA2020 to WGS 84 (2)"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_geogCRS_with_intermediate_no_ids) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto objSrc = WKTParser().createFromWKT( "GEOGCRS[\"input\",\n" " DATUM[\"International Terrestrial Reference Frame 2014\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",1165]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = WKTParser().createFromWKT( "GEOGCRS[\"output\",\n" " DATUM[\"Estonia 1997\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6180]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]]]"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dest), ctxt); ASSERT_GE(list.size(), 1U); // Test that a non-noop operation is returned EXPECT_EQ( list[0]->nameStr(), "Conversion from input to ITRF2014 + ITRF2014 to ETRF2014 (2) + " "ETRF2014 to NKG_ETRF14 (1) + NKG_ETRF14 to EST97 (1) + " "Conversion from EST97 to output"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_3D_source_datum_name_is_alias_to_geogCRS) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto objSrc = WKTParser().createFromWKT( "GEOGCRS[\"something\",\n" " DATUM[\"WGS84\",\n" " ELLIPSOID[\"WGS84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"Meter\",1,\n" " ID[\"EPSG\",9001]]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_EQ(list[0]->nameStr(), "Null geographic offset from something to WGS 84"); } // --------------------------------------------------------------------------- static ProjectedCRSNNPtr createUTM31_WGS84() { return ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- static ProjectedCRSNNPtr createUTM32_WGS84() { return ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 32, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_projCRS) { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, createUTM31_WGS84()); ASSERT_TRUE(op != nullptr); EXPECT_TRUE(std::dynamic_pointer_cast(op) != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm " "+zone=31 +ellps=WGS84"); PJ_CONTEXT *ctx = proj_context_create(); auto transformer = op->coordinateTransformer(ctx); PJ_COORD c; c.v[0] = 49; c.v[1] = 2; c.v[2] = 0; c.v[3] = HUGE_VAL; c = transformer->transform(c); EXPECT_NEAR(c.v[0], 426857.98771728, 1e-8); EXPECT_NEAR(c.v[1], 5427937.52346492, 1e-8); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_longlat_to_geogCS_latlong) { auto sourceCRS = GeographicCRS::OGC_CRS84; auto targetCRS = GeographicCRS::EPSG_4326; auto op = CoordinateOperationFactory::create()->createOperation(sourceCRS, targetCRS); ASSERT_TRUE(op != nullptr); auto conv = std::dynamic_pointer_cast(op); ASSERT_TRUE(conv != nullptr); EXPECT_TRUE(op->sourceCRS() && op->sourceCRS()->isEquivalentTo(sourceCRS.get())); EXPECT_TRUE(op->targetCRS() && op->targetCRS()->isEquivalentTo(targetCRS.get())); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); auto convInverse = nn_dynamic_pointer_cast(conv->inverse()); ASSERT_TRUE(convInverse != nullptr); EXPECT_TRUE(convInverse->sourceCRS() && convInverse->sourceCRS()->isEquivalentTo(targetCRS.get())); EXPECT_TRUE(convInverse->targetCRS() && convInverse->targetCRS()->isEquivalentTo(sourceCRS.get())); EXPECT_EQ(conv->method()->exportToWKT(WKTFormatter::create().get()), convInverse->method()->exportToWKT(WKTFormatter::create().get())); EXPECT_TRUE(conv->method()->isEquivalentTo(convInverse->method().get())); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_longlat_to_geogCS_latlong_database) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( AuthorityFactory::create(DatabaseContext::create(), "OGC") ->createCoordinateReferenceSystem("CRS84"), AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_longlat_to_projCRS) { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::OGC_CRS84, createUTM31_WGS84()); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=31 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_different_from_baseCRS_to_projCRS) { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4807, createUTM31_WGS84()); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat " "+ellps=clrk80ign +pm=paris +step +proj=utm +zone=31 " "+ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_with_towgs84_to_geocentric) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto objSrc = WKTParser().createFromWKT( "GEOGCS[\"WGS84 Coordinate System\",DATUM[\"WGS_1984\"," "SPHEROID[\"WGS 1984\",6378137,298.257223563]," "TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]," "AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST]," "AUTHORITY[\"EPSG\",\"4326\"]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto src3D = src->promoteTo3D(std::string(), dbContext); auto objDst = WKTParser().createFromWKT( "GEOCCS[\"WGS 84\",DATUM[\"WGS_1984\"," "SPHEROID[\"WGS 84\",6378137,298.257223563," "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"Geocentric X\",OTHER],AXIS[\"Geocentric Y\",OTHER]," "AXIS[\"Geocentric Z\",NORTH],AUTHORITY[\"EPSG\",\"4978\"]]"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); { auto list = CoordinateOperationFactory::create()->createOperations( src3D, NN_NO_CHECK(dst), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m " "+xy_out=rad +z_out=m " "+step +proj=cart +ellps=WGS84"); } { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(dst), src3D, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } } // --------------------------------------------------------------------------- TEST(operation, geogCRS_different_from_baseCRS_to_projCRS_context_compatible_area) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::ALWAYS); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris) authFactory->createCoordinateReferenceSystem("32631"), // UTM31 WGS84 ctxt); ASSERT_EQ(list.size(), 4U); EXPECT_EQ( list[0]->nameStr(), "NTF (Paris) to NTF (1) + Inverse of WGS 84 to NTF (3) + UTM zone 31N"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1"); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift " "+grids=fr_ign_ntf_r93.tif +step +proj=utm +zone=31 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, geocentricCRS_to_projCRS) { auto op = CoordinateOperationFactory::create()->createOperation( createGeocentricDatumWGS84(), createUTM31_WGS84()); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step " "+proj=utm +zone=31 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_geogCRS) { auto op = CoordinateOperationFactory::create()->createOperation( createUTM31_WGS84(), GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_no_id_to_geogCRS_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto src = authFactory->createCoordinateReferenceSystem( "28992"); // Amersfoort / RD New auto dst = authFactory->createCoordinateReferenceSystem("4258"); // ETRS89 2D auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_GE(list.size(), 1U); auto wkt2 = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"Amersfoort\",\n" " DATUM[\"Amersfoort\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Oblique Stereographic\"],\n" " PARAMETER[\"Latitude of natural origin\",52.1561605555556],\n" " PARAMETER[\"Longitude of natural origin\",5.38763888888889],\n" " PARAMETER[\"Scale factor at natural origin\",0.9999079],\n" " PARAMETER[\"False easting\",155000],\n" " PARAMETER[\"False northing\",463000]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",28992]]"; auto obj = WKTParser().createFromWKT(wkt2); auto src_from_wkt2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(src_from_wkt2 != nullptr); auto list2 = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src_from_wkt2), dst, ctxt); ASSERT_GE(list.size(), list2.size() - 1); for (size_t i = 0; i < list.size(); i++) { const auto &op = list[i]; const auto &op2 = list2[i]; EXPECT_TRUE( op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT)); } } // --------------------------------------------------------------------------- TEST(operation, projCRS_3D_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto wkt = "PROJCRS[\"NAD83(HARN) / Oregon GIC Lambert (ft)\",\n" " BASEGEOGCRS[\"NAD83(HARN)\",\n" " DATUM[\"NAD83 (High Accuracy Reference Network)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4957]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Lambert Conic Conformal (2SP)\",\n" " ID[\"EPSG\",9802]],\n" " PARAMETER[\"Latitude of false origin\",41.75,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",-120.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",43,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",45.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",1312335.958,\n" " LENGTHUNIT[\"foot\",0.3048],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",0,\n" " LENGTHUNIT[\"foot\",0.3048],\n" " ID[\"EPSG\",8827]]],\n" " CS[Cartesian,3],\n" " AXIS[\"easting\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"foot\",0.3048]],\n" " AXIS[\"northing\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"foot\",0.3048]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"foot\",0.3048]]]"; auto obj = WKTParser().createFromWKT(wkt); auto src = NN_CHECK_ASSERT(nn_dynamic_pointer_cast(obj)); auto dst = authFactory->createCoordinateReferenceSystem( "4957"); // NAD83(HARN) (3D) auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " // Check that z ft->m conversion is done (and just once) "+step +proj=unitconvert +xy_in=ft +z_in=ft +xy_out=m +z_out=m " "+step +inv +proj=lcc +lat_0=41.75 +lon_0=-120.5 +lat_1=43 " "+lat_2=45.5 +x_0=399999.9999984 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_3D_to_projCRS_2D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto wkt = "PROJCRS[\"Projected 3d CRS\",\n" " BASEGEOGCRS[\"JGD2000\",\n" " DATUM[\"Japanese Geodetic Datum 2000\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4947]],\n" // the code is what triggered the bug " CONVERSION[\"Japan Plane Rectangular CS zone VII\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",36,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",137.166666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",17807]],\n" " CS[Cartesian,3],\n" " AXIS[\"northing (X)\",north,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"easting (Y)\",east,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto src = NN_CHECK_ASSERT(nn_dynamic_pointer_cast(obj)); auto dst = authFactory->createCoordinateReferenceSystem("32653"); // WGS 84 UTM 53 // We just want to check that we don't get inconsistent chaining exception auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_GE(list.size(), 1U); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_3D_to_projCRS_with_2D_geocentric_translation) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto src = authFactory->createCoordinateReferenceSystem("4979"); // WGS 84 3D // Azores Central 1948 / UTM zone 26N auto dst = authFactory->createCoordinateReferenceSystem("2189"); auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=push +v_3 " // this is what we check. Due to the // target system being 2D only "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=104 +y=-167 +z=38 " "+step +inv +proj=cart +ellps=intl " "+step +proj=pop +v_3 " // this is what we check "+step +proj=utm +zone=26 +ellps=intl"); auto listReverse = CoordinateOperationFactory::create()->createOperations(dst, src, ctxt); ASSERT_GE(listReverse.size(), 1U); EXPECT_EQ( listReverse[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=26 +ellps=intl " "+step +proj=push +v_3 " // this is what we check "+step +proj=cart +ellps=intl " "+step +proj=helmert +x=-104 +y=167 +z=-38 " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " // this is what we check "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS) { auto op = CoordinateOperationFactory::create()->createOperation( createUTM31_WGS84(), createUTM32_WGS84()); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step " "+proj=utm +zone=32 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_different_baseCRS) { auto utm32 = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4807, Conversion::createUTM(PropertyMap(), 32, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto op = CoordinateOperationFactory::create()->createOperation( createUTM31_WGS84(), utm32); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step " "+proj=utm +zone=32 +ellps=clrk80ign +pm=paris"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_compatible_area) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("32634"), // UTM 34 authFactory->createCoordinateReferenceSystem( "2171"), // Pulkovo 42 Poland I ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "Inverse of UTM zone 34N + Inverse of Pulkovo 1942(58) to WGS 84 " "(1) + Poland zone I"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_compatible_area_bis) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "3844"), // Pulkovo 42 Stereo 70 (Romania) authFactory->createCoordinateReferenceSystem("32634"), // UTM 34 ctxt); ASSERT_EQ(list.size(), 3U); EXPECT_EQ(list[0]->nameStr(), "Inverse of Stereo 70 + " "Pulkovo 1942(58) to WGS 84 " "(19) + UTM zone 34N"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "3"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_one_incompatible_area) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("32631"), // UTM 31 authFactory->createCoordinateReferenceSystem( "2171"), // Pulkovo 42 Poland I ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "Inverse of UTM zone 31N + Inverse of Pulkovo 1942(58) to WGS 84 " "(1) + Poland zone I"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_incompatible_areas) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("32631"), // UTM 31 authFactory->createCoordinateReferenceSystem("32633"), // UTM 33 ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of UTM zone 31N + UTM zone 33N"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "0"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_incompatible_areas_ballpark) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("26711"), // UTM 11 NAD27 authFactory->createCoordinateReferenceSystem( "3034"), // ETRS89 / LCC Europe ctxt); ASSERT_GE(list.size(), 1U); EXPECT_TRUE(list[0]->hasBallparkTransformation()); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_grid_offsets) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); { auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "3392"), // Karbala 1979 / UTM zone 38N authFactory->createCoordinateReferenceSystem( "3891"), // IGRS / UTM zone 38N ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[1]->nameStr(), "Karbala 1979 / UTM zone 38N to IGRS / UTM zone 38N (1)"); EXPECT_EQ( list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +xoff=-287.54 +yoff=278.25"); } { auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "3891"), // IGRS / UTM zone 38N authFactory->createCoordinateReferenceSystem( "3392"), // Karbala 1979 / UTM zone 38N ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[1]->nameStr(), "Inverse of Karbala 1979 / UTM zone 38N " "to IGRS / UTM zone 38N (1)"); EXPECT_EQ( list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +xoff=287.54 +yoff=-278.25"); } } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_context_grid_offsets_non_metre_unit_noop) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "10516"), // NAD83(2011) / Adjusted Jackson (ftUS) authFactory->createCoordinateReferenceSystem( "8162"), // NAD83(HARN) / WISCRS Jackson (ftUS) ctxt); ASSERT_GE(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NAD83(2011) / Adjusted Jackson (ftUS) to " "NAD83(HARN) / WISCRS Jackson (ftUS) (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST( operation, projCRS_to_projCRS_context_incompatible_areas_crs_extent_use_intersection) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("26711"), // UTM 11 NAD27 authFactory->createCoordinateReferenceSystem( "3034"), // ETRS89 / LCC Europe ctxt); ASSERT_GE(list.size(), 0U); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_geogCRS_crs_extent_use_none) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("23031"), // ED50 UTM31 authFactory->createCoordinateReferenceSystem("4326"), ctxt); bool found_EPSG_15964 = false; for (const auto &op : list) { if (op->nameStr().find("ED50 to WGS 84 (42)") != std::string::npos) { found_EPSG_15964 = true; } } // not expected since doesn't intersect EPSG:23031 area of use EXPECT_FALSE(found_EPSG_15964); } { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); // Ignore source and target CRS extent ctxt->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse::NONE); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("23031"), // ED50 UTM31 authFactory->createCoordinateReferenceSystem("4326"), ctxt); bool found_EPSG_15964 = false; for (const auto &op : list) { if (op->nameStr().find("ED50 to WGS 84 (42)") != std::string::npos) { found_EPSG_15964 = true; } } EXPECT_TRUE(found_EPSG_15964); } } // --------------------------------------------------------------------------- // Test that ConcatenatedOperations with non-overlapping sub-operation extents // are returned when CRS_EXTENT_USE=NONE (fixes issue with EPSG:8047) TEST(operation, projCRS_to_geogCRS_concatenated_operation_extent_none) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // EPSG:8047 "ED50 to WGS 84 (15)" is a ConcatenatedOperation: // - EPSG:1147 (ED50 to ED87) domain = Norway offshore north of 65°N // - EPSG:1146 (ED87 to WGS 84) domain = North Sea // These domains don't overlap, but the operation should still be // available when extent filtering is disabled. // Without NONE, the ED87 pathway may not appear from projected CRS { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setDiscardSuperseded(false); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("23031"), // ED50 UTM31 authFactory->createCoordinateReferenceSystem("4326"), ctxt); bool found_ED87_pathway = false; for (const auto &op : list) { if (op->nameStr().find("ED87") != std::string::npos) { found_ED87_pathway = true; } } // With default extent settings, ED87 pathway typically not found // from projected CRS due to extent intersection checks EXPECT_FALSE(found_ED87_pathway); } // With NONE, the ED87 pathway should appear { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setDiscardSuperseded(false); ctxt->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse::NONE); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("23031"), // ED50 UTM31 authFactory->createCoordinateReferenceSystem("4326"), ctxt); bool found_ED87_pathway = false; for (const auto &op : list) { if (op->nameStr().find("ED87") != std::string::npos) { found_ED87_pathway = true; break; } } EXPECT_TRUE(found_ED87_pathway); } } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_north_pole_inverted_axis) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("32661"), AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("5041"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_south_pole_inverted_axis) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("32761"), AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("5042"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_to_projCRS_through_geog3D) { // Check that when going from projCRS to projCRS, using // geog2D-->geog3D-->geog3D-->geog2D we do not have issues with // inconsistent CRS chaining, due to how we 'hack' a bit some intermediate // steps auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("5367"), // CR05 / CRTM05 authFactory->createCoordinateReferenceSystem( "8908"), // CR-SIRGAS / CRTM05 ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +inv +proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 " "+x_0=500000 +y_0=0 +ellps=WGS84 " "+step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=-0.16959 +y=0.35312 +z=0.51846 " "+rx=-0.03385 +ry=0.16325 +rz=-0.03446 +s=0.03693 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 +x_0=500000 " "+y_0=0 +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST(operation, transform_from_amersfoort_rd_new_to_epsg_4326) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("28992"), authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 2U); // The order matters: "Amersfoort to WGS 84 (4)" replaces "Amersfoort to WGS // 84 (3)" EXPECT_EQ(list[0]->nameStr(), "Inverse of RD New + Amersfoort to WGS 84 (4)"); EXPECT_EQ(list[1]->nameStr(), "Inverse of RD New + Amersfoort to WGS 84 (3)"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_geogCRS) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto op = CoordinateOperationFactory::create()->createOperation( boundCRS, GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 " "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step " "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_geodCRS) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto op = CoordinateOperationFactory::create()->createOperation( boundCRS, GeodeticCRS::EPSG_4978); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step " "+proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=grad +xy_out=rad " "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris " "+step +proj=cart +ellps=clrk80ign " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_geodCRS_not_related_to_hub) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( boundCRS, // ETRS89 geocentric authFactory->createCoordinateReferenceSystem("4936"), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step " "+proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=grad +xy_out=rad " "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris " "+step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=cart +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_geogCRS_with_area) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4267, std::vector{1, 2, 3, 4, 5, 6, 7}); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = CoordinateOperationFactory::create()->createOperation( boundCRS, authFactory->createCoordinateReferenceSystem("4326")); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=clrk66 +step +proj=helmert +x=1 +y=2 " "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step " "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto op = CoordinateOperationFactory::create()->createOperation( boundCRS, GeographicCRS::EPSG_4269); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), CoordinateOperationFactory::create() ->createOperation(GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4269) ->exportToPROJString(PROJStringFormatter::create().get())); } // --------------------------------------------------------------------------- TEST(operation, createOperation_boundCRS_identified_by_datum) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=utm +zone=32 +a=6378249.2 +b=6356515 " "+towgs84=-263.0,6.0,431.0 +no_defs +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 +step +proj=cart +ellps=WGS84 +step " "+proj=helmert +x=263 +y=-6 +z=-431 +step +inv +proj=cart " "+ellps=clrk80ign +step +proj=pop +v_3 +step +proj=utm +zone=32 " "+ellps=clrk80ign"); auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); const auto queryCounterBefore = dbContext->getQueryCounter(); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest), ctxt); EXPECT_EQ(dbContext->getQueryCounter(), queryCounterBefore); ASSERT_EQ(list.size(), 1U); EXPECT_TRUE(list[0]->isEquivalentTo(op.get())); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_clrk_66_geogCRS_to_nad83_geogCRS) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=latlong +ellps=clrk66 +nadgrids=ntv1_can.dat,conus +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=latlong +datum=NAD83 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=ntv1_can.dat,conus " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); const auto queryCounterBefore = dbContext->getQueryCounter(); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest), ctxt); // Two extra queries related to the conus grid EXPECT_EQ(dbContext->getQueryCounter(), queryCounterBefore + 2); ASSERT_EQ(list.size(), 1U); EXPECT_TRUE(list[0]->isEquivalentTo(op.get())); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_clrk_66_projCRS_to_nad83_geogCRS) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=17 +ellps=clrk66 +nadgrids=ntv1_can.dat,conus " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=latlong +datum=NAD83 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=17 +ellps=clrk66 " "+step +proj=hgridshift +grids=ntv1_can.dat,conus " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_projCRS_to_geogCRS) { auto utm31 = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4807, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto boundCRS = BoundCRS::createFromTOWGS84( utm31, std::vector{1, 2, 3, 4, 5, 6, 7}); auto op = CoordinateOperationFactory::create()->createOperation( boundCRS, GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign " "+pm=paris +step +proj=push +v_3 +step +proj=cart " "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 " "+rz=6 +s=7 +convention=position_vector +step +inv +proj=cart " "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_projCRS) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto utm31 = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto op = CoordinateOperationFactory::create()->createOperation(boundCRS, utm31); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 " "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step " "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step " "+proj=utm +zone=31 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS_context) { auto src = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // ETRS89 auto dst = authFactory->createCoordinateReferenceSystem("4258"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_EQ(list.size(), 1U); // Check with it is a concatenated operation, since it doesn't particularly // show up in the PROJ string EXPECT_TRUE(dynamic_cast(list[0].get()) != nullptr); EXPECT_EQ(list[0]->nameStr(), "Transformation from NTF (Paris) to WGS84 + " "Inverse of ETRS89 to WGS 84 (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=grad +xy_out=rad " "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris " "+step +proj=push +v_3 +step +proj=cart +ellps=clrk80ign " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_boundCRS_of_geogCRS) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, boundCRS); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=1 " "+y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector " "+step +inv +proj=cart +ellps=clrk80ign +step +proj=pop +v_3 " "+step +proj=longlat +ellps=clrk80ign +pm=paris +step " "+proj=unitconvert +xy_in=rad +xy_out=grad +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_geogCRS_same_datum_context) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4269, std::vector{1, 2, 3, 4, 5, 6, 7}); auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( boundCRS, GeographicCRS::EPSG_4269, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_geogCRS_hubCRS_and_targetCRS_same_but_baseCRS_not) { const char *wkt = "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"Ellipsoid (US Feet)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Up\",UP]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(boundCRS), GeographicCRS::EPSG_4979, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=deg +z_out=m"); } // --------------------------------------------------------------------------- TEST( operation, boundCRS_to_derived_geog_with_transformation_with_source_crs_being_base_crs_of_source_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"BoundCRS\",\n" " \"source_crs\": {\n" " \"type\": \"DerivedGeographicCRS\",\n" " \"name\": \"CH1903+ with height offset\",\n" " \"base_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"CH1903+\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"CH1903+\",\n" " \"ellipsoid\": {\n" " \"name\": \"Bessel 1841\",\n" " \"semi_major_axis\": 6377397.155,\n" " \"inverse_flattening\": 299.1528128\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"Ellipsoidal to gravity related height\",\n" " \"method\": {\n" " \"name\": \"Geographic3D offsets\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9660\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude offset\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8601\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude offset\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8602\n" " }\n" " },\n" " {\n" " \"name\": \"Vertical Offset\",\n" " \"value\": 10,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8603\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"World Geodetic System 1984 ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"World Geodetic System 1984 (Transit)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1166\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G730)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1152\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G873)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1153\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1150)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1154\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1674)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1155\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1762)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1156\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G2139)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1309\n" " }\n" " }\n" " ],\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2.0\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4979\n" " }\n" " },\n" " \"transformation\": {\n" " \"name\": \"CH1903+ to WGS 84 (1)\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"CH1903+\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"CH1903+\",\n" " \"ellipsoid\": {\n" " \"name\": \"Bessel 1841\",\n" " \"semi_major_axis\": 6377397.155,\n" " \"inverse_flattening\": 299.1528128\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"method\": {\n" " \"name\": \"Geocentric translations (geog2D domain)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9603\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"X-axis translation\",\n" " \"value\": 674.374,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8605\n" " }\n" " },\n" " {\n" " \"name\": \"Y-axis translation\",\n" " \"value\": 15.056,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8606\n" " }\n" " },\n" " {\n" " \"name\": \"Z-axis translation\",\n" " \"value\": 405.346,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8607\n" " }\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1676\n" " }\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(boundCRS), GeographicCRS::EPSG_4979, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +inv +proj=geogoffset +dlat=0 +dlon=0 +dh=10 " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.374 +y=15.056 +z=405.346 " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_boundCRS) { auto utm31 = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4807, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto utm32 = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4269, Conversion::createUTM(PropertyMap(), 32, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto boundCRS1 = BoundCRS::createFromTOWGS84( utm31, std::vector{1, 2, 3, 4, 5, 6, 7}); auto boundCRS2 = BoundCRS::createFromTOWGS84( utm32, std::vector{8, 9, 10, 11, 12, 13, 14}); auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1, boundCRS2); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign " "+pm=paris +step +proj=push +v_3 +step +proj=cart " "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 " "+rz=6 +s=7 +convention=position_vector +step +inv +proj=helmert " "+x=8 +y=9 +z=10 +rx=11 +ry=12 +rz=13 +s=14 " "+convention=position_vector +step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 +step +proj=utm +zone=32 +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_boundCRS_noop_for_TOWGS84) { auto boundCRS1 = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto boundCRS2 = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4269, std::vector{1, 2, 3, 4, 5, 6, 7}); auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1, boundCRS2); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv " "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign +step +inv +proj=cart " "+ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_boundCRS_unralated_hub) { auto boundCRS1 = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto boundCRS2 = BoundCRS::create( GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979, Transformation::createGeocentricTranslations( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979, 1.0, 2.0, 3.0, std::vector())); auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1, boundCRS2); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), CoordinateOperationFactory::create() ->createOperation(boundCRS1->baseCRS(), boundCRS2->baseCRS()) ->exportToPROJString(PROJStringFormatter::create().get())); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_projCRS_towgs84_to_boundCRS_of_projCRS_nadgrids) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=15 +datum=NAD83 +units=m +no_defs +ellps=GRS80 " "+towgs84=0,0,0 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=utm +zone=15 +datum=NAD27 +units=m +no_defs +ellps=clrk66 " "+nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=15 +ellps=GRS80 +step " "+inv +proj=hgridshift " "+grids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat +step +proj=utm " "+zone=15 +ellps=clrk66"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_of_projCRS_towgs84_non_metre_unit_to_geocentric) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=merc +ellps=GRS80 +towgs84=0,0,0 +units=ft +vunits=ft " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=geocent +datum=WGS84 +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=ft +z_in=ft +xy_out=m +z_out=m " "+step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=GRS80 " "+step +proj=cart +ellps=WGS84"); } // --------------------------------------------------------------------------- static CRSNNPtr buildCRSFromProjStrThroughWKT(const std::string &projStr) { auto crsFromProj = nn_dynamic_pointer_cast( PROJStringParser().createFromPROJString(projStr)); if (crsFromProj == nullptr) { throw "crsFromProj == nullptr"; } auto crsFromWkt = nn_dynamic_pointer_cast( WKTParser().createFromWKT(crsFromProj->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()))); if (crsFromWkt == nullptr) { throw "crsFromWkt == nullptr"; } return NN_NO_CHECK(crsFromWkt); } TEST(operation, boundCRS_to_boundCRS_with_base_geog_crs_different_from_source_of_transf) { auto src = buildCRSFromProjStrThroughWKT( "+proj=lcc +lat_1=49 +lat_0=49 +lon_0=0 +k_0=0.999877499 +x_0=600000 " "+y_0=200000 +ellps=clrk80ign +pm=paris +towgs84=-168,-60,320,0,0,0,0 " "+units=m +no_defs +type=crs"); auto dst = buildCRSFromProjStrThroughWKT( "+proj=longlat +ellps=clrk80ign +pm=paris " "+towgs84=-168,-60,320,0,0,0,0 +no_defs +type=crs"); auto op = CoordinateOperationFactory::create()->createOperation(src, dst); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=lcc +lat_1=49 +lat_0=49 +lon_0=0 " "+k_0=0.999877499 +x_0=600000 +y_0=200000 +ellps=clrk80ign " "+pm=paris " "+step +proj=longlat +ellps=clrk80ign +pm=paris " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_with_basecrs_with_extent_to_geogCRS) { auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"NAD83 / California zone 3 (ftUS)\",\n" " BASEGEODCRS[\"NAD83\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 California zone 3 (US Survey " "feet)\",\n" " METHOD[\"Lambert Conic Conformal (2SP)\",\n" " ID[\"EPSG\",9802]],\n" " PARAMETER[\"Latitude of false origin\",36.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",-120.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\"," " 38.4333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\"," " 37.0666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",6561666.667,\n" " LENGTHUNIT[\"US survey foot\"," " 0.304800609601219],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",1640416.667,\n" " LENGTHUNIT[\"US survey foot\"," " 0.304800609601219],\n" " ID[\"EPSG\",8827]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\"," " 0.304800609601219]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\"," " 0.304800609601219]],\n" " SCOPE[\"unknown\"],\n" " AREA[\"USA - California - SPCS - 3\"],\n" " BBOX[36.73,-123.02,38.71,-117.83],\n" " ID[\"EPSG\",2227]]],\n" " TARGETCRS[\n" " GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " ABRIDGEDTRANSFORMATION[\"NAD83 to WGS 84 (1)\",\n" " METHOD[\"Geocentric translations (geog2D domain)\",\n" " ID[\"EPSG\",9603]],\n" " PARAMETER[\"X-axis translation\",0,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",0,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",0,\n" " ID[\"EPSG\",8607]],\n" " SCOPE[\"unknown\"],\n" " AREA[\"North America - Canada and USA (CONUS, Alaska " "mainland)\"],\n" " BBOX[23.81,-172.54,86.46,-47.74],\n" " ID[\"EPSG\",1188]]]"; auto obj = WKTParser().createFromWKT(wkt); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(boundCRS), GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->nameStr(), "Inverse of SPCS83 California zone 3 (US Survey " "feet) + NAD83 to WGS 84 (1)"); } // --------------------------------------------------------------------------- TEST(operation, ETRS89_3D_to_proj_string_with_geoidgrids_nadgrids) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // ETRS89 3D auto src = authFactory->createCoordinateReferenceSystem("4937"); auto objDst = PROJStringParser().createFromPROJString( "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 " "+k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel " "+nadgrids=rdtrans2008.gsb +geoidgrids=naptrans2008.gtx " "+geoid_crs=horizontal_crs +units=m " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( src, NN_NO_CHECK(dst), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +inv +proj=hgridshift +grids=rdtrans2008.gsb " "+step +inv +proj=vgridshift +grids=naptrans2008.gtx " "+multiplier=1 " "+step +proj=sterea +lat_0=52.1561605555556 " "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 " "+y_0=463000 +ellps=bessel"); } // --------------------------------------------------------------------------- TEST(operation, nadgrids_with_pm) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto objSrc = PROJStringParser().createFromPROJString( "+proj=tmerc +lat_0=39.66666666666666 +lon_0=1 +k=1 +x_0=200000 " "+y_0=300000 +ellps=intl +nadgrids=foo.gsb +pm=lisbon " "+units=m +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("4326"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 " "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon " // Check that there is no extra +step +proj=longlat +pm=lisbon "+step +proj=hgridshift +grids=foo.gsb " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); // ETRS89 dst = authFactory->createCoordinateReferenceSystem("4258"); list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 " "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon " // Check that there is no extra +step +proj=longlat +pm=lisbon "+step +proj=hgridshift +grids=foo.gsb " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); // From WKT BOUNDCRS auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); auto src_wkt = src->exportToWKT(formatter.get()); auto objFromWkt = WKTParser().createFromWKT(src_wkt); auto crsFromWkt = nn_dynamic_pointer_cast(objFromWkt); ASSERT_TRUE(crsFromWkt); list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(crsFromWkt), dst, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=39.6666666666667 +lon_0=1 " "+k=1 +x_0=200000 +y_0=300000 +ellps=intl +pm=lisbon " // Check that there is no extra +step +proj=longlat +pm=lisbon "+step +proj=hgridshift +grids=foo.gsb " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, towgs84_pm_3d) { // Test fix for https://github.com/OSGeo/gdal/issues/5408 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto objSrc = PROJStringParser().createFromPROJString( "+proj=tmerc +lat_0=0 +lon_0=34 +k=1 +x_0=0 +y_0=-5000000 " "+ellps=bessel +pm=ferro " "+towgs84=1,2,3,4,5,6,7 " "+units=m +no_defs +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto src3D = src->promoteTo3D(std::string(), dbContext); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto dst3D = dst->promoteTo3D(std::string(), dbContext); // Import thing to check is that there's no push/pop v_3 const std::string expected_pipeline = "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=0 +lon_0=34 +k=1 +x_0=0 +y_0=-5000000 " "+ellps=bessel +pm=ferro " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 " "+ry=5 +rz=6 +s=7 +convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m"; auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); { auto list = CoordinateOperationFactory::create()->createOperations( src3D, dst3D, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expected_pipeline); } // Retry when creating objects from WKT { auto objSrcFromWkt = WKTParser().createFromWKT(src3D->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get())); auto srcFromWkt = nn_dynamic_pointer_cast(objSrcFromWkt); ASSERT_TRUE(srcFromWkt != nullptr); auto objDstFromWkt = WKTParser().createFromWKT(dst3D->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get())); auto dstFromWkt = nn_dynamic_pointer_cast(objDstFromWkt); ASSERT_TRUE(dstFromWkt != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcFromWkt), NN_NO_CHECK(dstFromWkt), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expected_pipeline); } } // --------------------------------------------------------------------------- TEST(operation, WGS84_G1762_to_compoundCRS_with_bound_vertCRS) { auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // WGS 84 (G1762) 3D auto src = authFactoryEPSG->createCoordinateReferenceSystem("7665"); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=NAD83 +geoidgrids=@foo.gtx +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( src, NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 53U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- static VerticalCRSNNPtr createVerticalCRS() { PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create(propertiesVDatum); PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5701) .set(IdentifiedObject::NAME_KEY, "ODN height"); return VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS) { auto compound = CompoundCRS::create( PropertyMap(), std::vector{GeographicCRS::EPSG_4326, createVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation( compound, GeographicCRS::EPSG_4807); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), CoordinateOperationFactory::create() ->createOperation(GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807) ->exportToPROJString(PROJStringFormatter::create().get())); } // --------------------------------------------------------------------------- static BoundCRSNNPtr createBoundVerticalCRS() { auto vertCRS = createVerticalCRS(); auto transformation = Transformation::createGravityRelatedHeightToGeographic3D( PropertyMap(), vertCRS, GeographicCRS::EPSG_4979, nullptr, "us_nga_egm08_25.tif", std::vector()); return BoundCRS::create(vertCRS, GeographicCRS::EPSG_4979, transformation); } // --------------------------------------------------------------------------- TEST(operation, transformation_height_to_PROJ_string) { auto transf = createBoundVerticalCRS()->transformation(); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); auto grids = transf->gridsNeeded(DatabaseContext::create(), false); ASSERT_EQ(grids.size(), 1U); auto gridDesc = *(grids.begin()); EXPECT_EQ(gridDesc.shortName, "us_nga_egm08_25.tif"); EXPECT_TRUE(gridDesc.packageName.empty()); EXPECT_EQ(gridDesc.url, "https://cdn.proj.org/us_nga_egm08_25.tif"); if (gridDesc.available) { EXPECT_TRUE(!gridDesc.fullName.empty()) << gridDesc.fullName; EXPECT_TRUE(gridDesc.fullName.find(gridDesc.shortName) != std::string::npos) << gridDesc.fullName; } else { EXPECT_TRUE(gridDesc.fullName.empty()) << gridDesc.fullName; } EXPECT_EQ(gridDesc.directDownload, true); EXPECT_EQ(gridDesc.openLicense, true); } // --------------------------------------------------------------------------- TEST(operation, transformation_Geographic3D_to_GravityRelatedHeight_gtx) { auto wkt = "COORDINATEOPERATION[\"ETRS89 to NAP height (1)\",\n" " VERSION[\"RDNAP-Nld 2008\"],\n" " SOURCECRS[\n" " GEOGCRS[\"ETRS89\",\n" " DATUM[\"European Terrestrial Reference System 1989\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4937]]],\n" " TARGETCRS[\n" " VERTCRS[\"NAP height\",\n" " VDATUM[\"Normaal Amsterdams Peil\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5709]]],\n" " METHOD[\"Geographic3D to GravityRelatedHeight (US .gtx)\",\n" " ID[\"EPSG\",9665]],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"naptrans2008.gtx\"],\n" " OPERATIONACCURACY[0.01],\n" " USAGE[\n" " SCOPE[\"unknown\"],\n" " AREA[\"Netherlands - onshore\"],\n" " BBOX[50.75,3.2,53.7,7.22]],\n" " ID[\"EPSG\",7001]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); // Check that we correctly inverse files in the case of // "Geographic3D to GravityRelatedHeight (US .gtx)" EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift " "+grids=naptrans2008.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_ntv2_to_PROJ_string) { auto transformation = Transformation::createNTv2( PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326, "foo.gsb", std::vector()); EXPECT_EQ( transformation->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step " "+proj=hgridshift +grids=foo.gsb +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_VERTCON_to_PROJ_string) { auto verticalCRS1 = createVerticalCRS(); auto verticalCRS2 = VerticalCRS::create( PropertyMap(), VerticalReferenceFrame::create(PropertyMap()), VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); // Use of this type of transformation is a bit of nonsense here // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS, // and NAD27/NAD83 as horizontal CRS... auto vtransformation = Transformation::createVERTCON( PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx", std::vector()); EXPECT_EQ(vtransformation->exportToPROJString( PROJStringFormatter::create().get()), "+proj=vgridshift +grids=bla.gtx +multiplier=0.001"); } // --------------------------------------------------------------------------- TEST(operation, transformation_NZLVD_to_PROJ_string) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto op = factory->createCoordinateOperation("7860", false); EXPECT_EQ(op->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=vgridshift +grids=nz_linz_auckht1946-nzvd2016.tif " "+multiplier=1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_BEV_AT_to_PROJ_string) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto op = factory->createCoordinateOperation("9275", false); EXPECT_EQ(op->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=vgridshift +grids=at_bev_GV_Hoehengrid_V1.tif " "+multiplier=1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_longitude_rotation_to_PROJ_string) { auto src = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto dest = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::PARIS), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto transformation = Transformation::createLongitudeRotation( PropertyMap(), src, dest, Angle(10)); EXPECT_TRUE(transformation->validateParameters().empty()); EXPECT_EQ( transformation->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv " "+proj=longlat +ellps=WGS84 +pm=10 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(transformation->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv " "+proj=longlat +ellps=WGS84 +pm=-10 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_Geographic2D_offsets_to_PROJ_string) { auto transformation = Transformation::createGeographic2DOffsets( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326, Angle(0.5), Angle(-1), {}); EXPECT_TRUE(transformation->validateParameters().empty()); EXPECT_EQ( transformation->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=1800 +dlon=-3600 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(transformation->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=-1800 +dlon=3600 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_Geographic3D_offsets_to_PROJ_string) { auto transformation = Transformation::createGeographic3DOffsets( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326, Angle(0.5), Angle(-1), Length(2), {}); EXPECT_TRUE(transformation->validateParameters().empty()); EXPECT_EQ( transformation->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(transformation->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_Geographic2D_with_height_offsets_to_PROJ_string) { auto transformation = Transformation::createGeographic2DWithHeightOffsets( PropertyMap(), CompoundCRS::create(PropertyMap(), {GeographicCRS::EPSG_4326, createVerticalCRS()}), GeographicCRS::EPSG_4326, Angle(0.5), Angle(-1), Length(2), {}); EXPECT_TRUE(transformation->validateParameters().empty()); EXPECT_EQ( transformation->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_EQ(transformation->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset " "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_vertical_offset_to_PROJ_string) { auto transformation = Transformation::createVerticalOffset( PropertyMap(), createVerticalCRS(), createVerticalCRS(), Length(1), {}); EXPECT_TRUE(transformation->validateParameters().empty()); EXPECT_EQ( transformation->exportToPROJString(PROJStringFormatter::create().get()), "+proj=geogoffset +dh=1"); EXPECT_EQ(transformation->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=geogoffset +dh=-1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundVerticalCRS_to_geogCRS) { auto compound = CompoundCRS::create( PropertyMap(), std::vector{GeographicCRS::EPSG_4326, createBoundVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation( compound, GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift " "+grids=us_nga_egm08_25.tif +multiplier=1 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundGeogCRS_to_geogCRS) { auto geogCRS = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto horizBoundCRS = BoundCRS::createFromTOWGS84( geogCRS, std::vector{1, 2, 3, 4, 5, 6, 7}); auto compound = CompoundCRS::create( PropertyMap(), std::vector{horizBoundCRS, createVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation( compound, GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundGeogCRS_and_boundVerticalCRS_to_geogCRS) { auto horizBoundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4807, std::vector{1, 2, 3, 4, 5, 6, 7}); auto compound = CompoundCRS::create( PropertyMap(), std::vector{horizBoundCRS, createBoundVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation( compound, GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); // Not completely sure the order of horizontal and vertical operations // makes sense EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=grad +xy_out=rad " "+step +inv +proj=longlat +ellps=clrk80ign +pm=paris " "+step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); auto grids = op->gridsNeeded(DatabaseContext::create(), false); EXPECT_EQ(grids.size(), 1U); auto opInverse = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4979, compound); ASSERT_TRUE(opInverse != nullptr); EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get())); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundProjCRS_and_boundVerticalCRS_to_geogCRS) { auto horizBoundCRS = BoundCRS::createFromTOWGS84( ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4807, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)), std::vector{1, 2, 3, 4, 5, 6, 7}); auto compound = CompoundCRS::create( PropertyMap(), std::vector{horizBoundCRS, createBoundVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation( compound, GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); // Not completely sure the order of horizontal and vertical operations // makes sense EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=31 +ellps=clrk80ign +pm=paris " "+step +proj=push +v_3 " "+step +proj=cart +ellps=clrk80ign " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); auto opInverse = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4979, compound); ASSERT_TRUE(opInverse != nullptr); EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get())); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundVerticalCRS_from_geoidgrids_with_m_to_geogCRS) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_NO_CHECK(src), GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundVerticalCRS_from_geoidgrids_with_ftus_to_geogCRS) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +vunits=us-ft " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_NO_CHECK(src), GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundProjCRS_with_ftus_and_boundVerticalCRS_to_geogCRS) { auto wkt = "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + " "NAVD88 height - Geoid12B (US Feet)\",\n" " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"Degree\",0.0174532925199433]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",30],\n" " PARAMETER[\"central_meridian\",-87.5],\n" " PARAMETER[\"scale_factor\",0.999933333333333],\n" " PARAMETER[\"false_easting\",1968500],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"ESRI\",\"102630\"]],\n" " VERT_CS[\"NAVD88 height (ftUS)\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"6360\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_NO_CHECK(crs), GeographicCRS::EPSG_4979); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m " "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 " "+k=0.999933333333333 +x_0=600000 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=vgridshift +grids=foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundVerticalCRS_from_grids_to_geogCRS_with_ftus_ctxt) { auto dbContext = DatabaseContext::create(); const char *wktSrc = "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"metre\",1.0,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5703\"]]]"; auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); const char *wktDst = "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"Ellipsoid (US Feet)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Up\",UP]]]"; auto objDst = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst); auto dstCRS = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dstCRS != nullptr); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=us-ft " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_with_boundGeogCRS_boundVerticalCRS_from_grids_to_boundGeogCRS_with_ftus_ctxt) { // Variant of above but with TOWGS84 in source & target CRS auto dbContext = DatabaseContext::create(); const char *wktSrc = "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"metre\",1.0,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5703\"]]]"; auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); const char *wktDst = "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"Ellipsoid (US Feet)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Up\",UP]]]"; auto objDst = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst); auto dstCRS = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dstCRS != nullptr); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=us-ft " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_with_boundVerticalCRS_from_grids_to_boundGeogCRS_with_ftus_ctxt) { // Variant of above but with TOWGS84 in target CRS only auto dbContext = DatabaseContext::create(); const char *wktSrc = "COMPD_CS[\"NAD83 + NAVD88 height - Geoid12B (Meters)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"metre\",1.0,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5703\"]]]"; auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); const char *wktDst = "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"Ellipsoid (US Feet)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Up\",UP]]]"; auto objDst = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst); auto dstCRS = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dstCRS != nullptr); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=us-ft " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_bound_of_projected_and_bound_of_vertical_to_geog3D) { auto dbContext = DatabaseContext::create(); const char *wktSrc = "COMPD_CS[\"TempTM + CGVD28 height - HT2_0\",\n" " PROJCS[\"Custom\",\n" " GEOGCS[\"NAD83(CSRS)\",\n" " DATUM[\"NAD83_Canadian_Spatial_Reference_System\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6140\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4617\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",49.351346659616],\n" " PARAMETER[\"central_meridian\",-123.20266499149],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",15307.188],\n" " PARAMETER[\"false_northing\",6540.975],\n" " UNIT[\"Meters\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]],\n" " VERT_CS[\"CGVD28 height - HT2_0\",\n" " VERT_DATUM[\"Canadian Geodetic Vertical Datum of " "1928\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"HT2_0.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5114\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5713\"]]]"; auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); // NAD83(CSRS) 3D auto dstCRS = authFactoryEPSG->createCoordinateReferenceSystem("4955"); auto ctxt = CoordinateOperationContext::create(authFactoryEPSG, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), dstCRS, ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=49.351346659616 " "+lon_0=-123.20266499149 +k=1 " "+x_0=15307.188 +y_0=6540.975 +ellps=GRS80 " "+step +proj=vgridshift +grids=HT2_0.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_boundGeogCRS_and_geoid_to_geodCRS_NAD2011_ctxt) { auto dbContext = DatabaseContext::create(); const char *wktSrc = "COMPD_CS[\"NAD83 / California zone 5 (ftUS) + " "NAVD88 height - Geoid12B (ftUS)\"," " PROJCS[\"NAD83 / California zone 5 (ftUS)\"," " GEOGCS[\"NAD83\"," " DATUM[\"North_American_Datum_1983\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " TOWGS84[0,0,0,0,0,0,0]," " AUTHORITY[\"EPSG\",\"6269\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4269\"]]," " PROJECTION[\"Lambert_Conformal_Conic_2SP\"]," " PARAMETER[\"standard_parallel_1\",35.46666666666667]," " PARAMETER[\"standard_parallel_2\",34.03333333333333]," " PARAMETER[\"latitude_of_origin\",33.5]," " PARAMETER[\"central_meridian\",-118]," " PARAMETER[\"false_easting\",6561666.667]," " PARAMETER[\"false_northing\",1640416.667]," " UNIT[\"US survey foot\",0.3048006096012192," " AUTHORITY[\"EPSG\",\"9003\"]]," " AXIS[\"X\",EAST]," " AXIS[\"Y\",NORTH]," " AUTHORITY[\"EPSG\",\"2229\"]]," "VERT_CS[\"NAVD88 height - Geoid12B (ftUS)\"," " VERT_DATUM[\"North American Vertical Datum 1988\",2005," " AUTHORITY[\"EPSG\",\"5103\"]]," " UNIT[\"US survey foot\",0.3048006096012192," " AUTHORITY[\"EPSG\",\"9003\"]]," " AXIS[\"Gravity-related height\",UP]," " AUTHORITY[\"EPSG\",\"6360\"]]]"; auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); // NAD83(2011) geocentric auto dstCRS = authFactoryEPSG->createCoordinateReferenceSystem("6317"); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), dstCRS, ctxt); bool found = false; for (const auto &op : list) { if (op->nameStr() == "Inverse of unnamed + " "Transformation from NAD83 to WGS84 + " "Inverse of NAD83(2011) to WGS 84 (1) + " "Conversion from NAVD88 height (ftUS) to NAVD88 height + " "Inverse of NAD83(2011) to NAVD88 height (1) + " "Conversion from NAD83(2011) (geog3D) to NAD83(2011) " "(geocentric)") { found = true; EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m " "+step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 " "+lat_1=35.4666666666667 +lat_2=34.0333333333333 " "+x_0=2000000.0001016 +y_0=500000.0001016 +ellps=GRS80 " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif " "+multiplier=1 " "+step +proj=cart +ellps=GRS80"); } } EXPECT_TRUE(found); if (!found) { for (const auto &op : list) { std::cerr << op->nameStr() << std::endl; } } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_vert_bound_to_bound_geog3D) { // Test case of https://github.com/OSGeo/PROJ/issues/3927 auto dbContext = DatabaseContext::create(); const char *wktSrc = "COMPOUNDCRS[\"ENU (-77.410692720411:39.4145340892321) + EGM96 geoid " "height\",\n" " PROJCRS[\"ENU (-77.410692720411:39.4145340892321)\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" " ELLIPSOID[\"WGS84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Orthographic\",\n" " ID[\"EPSG\",9840]],\n" " PARAMETER[\"Latitude of natural " "origin\",39.4145340892321,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural " "origin\",-77.410692720411,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"northing\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"EGM96 geoid height\",\n" " VDATUM[\"EGM96 geoid\"],\n" " CS[vertical,1],\n" " AXIS[\"up\",up,\n" " LENGTHUNIT[\"m\",1]],\n" " ID[\"EPSG\",5773]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"EGM96 geoid height to WGS 84 " "ellipsoidal height\",\n" " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"egm96_15.gtx\",\n" " ID[\"EPSG\",8666]]]]]"; auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktSrc); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); const char *wktDst = "BOUNDCRS[\n" " SOURCECRS[\n" " GEOGCRS[\"WGS84 Coordinate System\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 1984\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " REMARK[\"Promoted to 3D from EPSG:4326\"]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Geodesy. Navigation and positioning using GPS " "satellite system.\"],\n" " AREA[\"World.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"Transformation from WGS84 Coordinate " "System to WGS84\",\n" " METHOD[\"Position Vector transformation (geog2D domain)\",\n" " ID[\"EPSG\",9606]],\n" " PARAMETER[\"X-axis translation\",0,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",0,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",0,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",0,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",0,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",0,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",1,\n" " ID[\"EPSG\",8611]]]]"; auto objDst = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wktDst); auto dstCRS = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dstCRS != nullptr); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=ortho +lat_0=39.4145340892321 " "+lon_0=-77.410692720411 +x_0=0 +y_0=0 +ellps=WGS84 " "+step +proj=vgridshift +grids=egm96_15.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_derivedVerticalCRS_ellipsoidal_height_to_geog) { // Test scenario of https://github.com/OSGeo/PROJ/issues/4175 // Note that the CompoundCRS with a DerivedVerticalCRS with a "Ellipsoid" // datum is an extension, not OGC Topic 2 compliant. auto wkt = "COMPOUNDCRS[\"UTM30 with vertical offset\",\n" " PROJCRS[\"WGS 84 / UTM zone 30N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " CONVERSION[\"UTM zone 30N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32630]],\n" " VERTCRS[\"Derived verticalCRS\",\n" " BASEVERTCRS[\"Ellipsoid (metre)\",\n" " VDATUM[\"Ellipsoid\"]],\n" " DERIVINGCONVERSION[\"Conv Vertical Offset\",\n" " METHOD[\"Vertical Offset\",\n" " ID[\"EPSG\",9616]],\n" " PARAMETER[\"Vertical Offset\",42.3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8603]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]]"; auto objDst = WKTParser().createFromWKT(wkt); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4979, NN_NO_CHECK(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=geogoffset +dh=42.3 " "+step +proj=utm +zone=30 +ellps=WGS84"); EXPECT_STREQ(op->nameStr().c_str(), "Conv Vertical Offset + UTM zone 30N"); EXPECT_FALSE(op->hasBallparkTransformation()); ASSERT_EQ(op->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(op->coordinateOperationAccuracies()[0]->value(), "0"); } // --------------------------------------------------------------------------- TEST(operation, geocent_to_compoundCRS) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=geocent +datum=WGS84 +units=m +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=cart +ellps=WGS84 " "+step +inv +proj=hgridshift +grids=@foo.gsb " "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, geocent_to_compoundCRS_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // WGS84 geocentric auto src = authFactory->createCoordinateReferenceSystem("4978"); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( src, NN_CHECK_ASSERT(dst), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=cart +ellps=WGS84 " "+step +inv +proj=hgridshift +grids=@foo.gsb " "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS) { auto compound1 = CompoundCRS::create( PropertyMap(), std::vector{createUTM31_WGS84(), createVerticalCRS()}); auto compound2 = CompoundCRS::create( PropertyMap(), std::vector{createUTM32_WGS84(), createVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation(compound1, compound2); ASSERT_TRUE(op != nullptr); auto opRef = CoordinateOperationFactory::create()->createOperation( createUTM31_WGS84(), createUTM32_WGS84()); ASSERT_TRUE(opRef != nullptr); EXPECT_TRUE(op->isEquivalentTo(opRef.get())); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) { auto verticalCRS1 = createVerticalCRS(); auto verticalCRS2 = VerticalCRS::create( PropertyMap(), VerticalReferenceFrame::create(PropertyMap()), VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); // Use of this type of transformation is a bit of nonsense here // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS, // and NAD27/NAD83 as horizontal CRS... auto vtransformation = Transformation::createVERTCON( PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx", std::vector()); auto compound1 = CompoundCRS::create( PropertyMap(), std::vector{ ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createTransverseMercator(PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)), BoundCRS::create(verticalCRS1, verticalCRS2, vtransformation)}); auto compound2 = CompoundCRS::create( PropertyMap(), std::vector{createUTM32_WGS84(), verticalCRS2}); auto op = CoordinateOperationFactory::create()->createOperation(compound1, compound2); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k=3 " "+x_0=4 +y_0=5 +ellps=WGS84 +step " "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step " "+proj=utm +zone=32 " "+ellps=WGS84"); { auto formatter = PROJStringFormatter::create(); formatter->setUseApproxTMerc(true); EXPECT_EQ( op->exportToPROJString(formatter.get()), "+proj=pipeline +step +inv +proj=tmerc +approx +lat_0=1 +lon_0=2 " "+k=3 +x_0=4 +y_0=5 +ellps=WGS84 +step " "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step " "+proj=utm +approx +zone=32 " "+ellps=WGS84"); } { auto formatter = PROJStringFormatter::create(); formatter->setUseApproxTMerc(true); EXPECT_EQ( op->inverse()->exportToPROJString(formatter.get()), "+proj=pipeline +step +inv +proj=utm +approx +zone=32 +ellps=WGS84 " "+step +inv +proj=vgridshift +grids=bla.gtx " "+multiplier=0.001 +step +proj=tmerc +approx +lat_0=1 +lon_0=2 " "+k=3 +x_0=4 +y_0=5 +ellps=WGS84"); } auto opInverse = CoordinateOperationFactory::create()->createOperation( compound2, compound1); ASSERT_TRUE(opInverse != nullptr); { auto formatter = PROJStringFormatter::create(); auto formatter2 = PROJStringFormatter::create(); EXPECT_EQ(opInverse->inverse()->exportToPROJString(formatter.get()), op->exportToPROJString(formatter2.get())); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=hgridshift +grids=@foo.gsb " "+step +inv +proj=hgridshift +grids=@bar.gsb " "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=hgridshift +grids=@foo.gsb " "+step +inv +proj=hgridshift +grids=@bar.gsb " "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids_different_vunits) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+vunits=us-ft +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=hgridshift +grids=@foo.gsb " "+step +inv +proj=hgridshift +grids=@bar.gsb " "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=us-ft"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_nadgrids_same_geoidgrids) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_towgs84_same_geoidgrids) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +towgs84=0,0,0 +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=0,0,0 +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=cart +ellps=GRS67 " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_ellsp_but_different_towgs84_different_geoidgrids) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=1,2,3 +geoidgrids=@foo.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=4,5,6 +geoidgrids=@bar.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto srcGeog = src->extractGeographicCRS(); ASSERT_TRUE(srcGeog != nullptr); ASSERT_TRUE(srcGeog->datum() != nullptr); auto dstGeog = dst->extractGeographicCRS(); ASSERT_TRUE(dstGeog != nullptr); ASSERT_TRUE(dstGeog->datum() != nullptr); EXPECT_FALSE(srcGeog->datum()->isEquivalentTo( dstGeog->datum().get(), IComparable::Criterion::EQUIVALENT)); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); // Check there's no proj=push +v_1 +v_2 EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=-3 +y=-3 +z=-3 " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_WKT1_same_geoidgrids_context) { auto objSrc = WKTParser().createFromWKT( "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B " "(Meters)\",\n" " PROJCS[\"NAD83 / Alabama West\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",30],\n" " PARAMETER[\"central_meridian\",-87.5],\n" " PARAMETER[\"scale_factor\",0.999933333],\n" " PARAMETER[\"false_easting\",600000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"X\",EAST],\n" " AXIS[\"Y\",NORTH],\n" " AUTHORITY[\"EPSG\",\"26930\"]],\n" " VERT_CS[\"NAVD88 height\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " " "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx," "g2012a_conus.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5703\"]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = WKTParser().createFromWKT( "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + NAVD88 " "height - Geoid12B (US Feet)\",\n" " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"Degree\",0.0174532925199433]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",30],\n" " PARAMETER[\"central_meridian\",-87.5],\n" " PARAMETER[\"scale_factor\",0.999933333333333],\n" " PARAMETER[\"false_easting\",1968500],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"ESRI\",\"102630\"]],\n" " VERT_CS[\"NAVD88 height (ftUS)\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " " "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx," "g2012a_conus.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"6360\"]]]"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 " "+x_0=600000 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +z_in=m +z_out=us-ft " "+step +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333333333 " "+x_0=600000 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +xy_in=m +xy_out=us-ft"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_issue_2232) { auto objSrc = WKTParser().createFromWKT( "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B " "(Meters)\",\n" " PROJCS[\"NAD83 / Alabama West\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",30],\n" " PARAMETER[\"central_meridian\",-87.5],\n" " PARAMETER[\"scale_factor\",0.999933333],\n" " PARAMETER[\"false_easting\",600000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"X\",EAST],\n" " AXIS[\"Y\",NORTH],\n" " AUTHORITY[\"EPSG\",\"26930\"]],\n" " VERT_CS[\"NAVD88 height - Geoid12B (Meters)\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"metre\",1.0,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5703\"]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = WKTParser().createFromWKT( "COMPD_CS[\"NAD83 + some CRS (US Feet)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"some CRS (US Feet)\",\n" " VERT_DATUM[\"some datum\",2005],\n" " UNIT[\"US survey foot\",0.3048006096012192,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Up\",UP]]]"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt); EXPECT_GE(list.size(), 1U); auto list2 = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src), ctxt); EXPECT_EQ(list2.size(), list.size()); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // NAD27 + NGVD29 height (ftUS) authFactory->createCoordinateReferenceSystem("7406"), // NAD83(NSRS2007) + NAVD88 height authFactory->createCoordinateReferenceSystem("5500"), ctxt); // 152 or 155 depending if the VERTCON grids are there ASSERT_GE(list.size(), 152U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->nameStr(), "NGVD29 height (ftUS) to NAVD88 height (3) + " "NAD27 to WGS 84 (79) + Inverse of " "NAD83(NSRS2007) to WGS 84 (1)"); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m " "+step +proj=vgridshift +grids=us_noaa_vertcone.tif +multiplier=1 " "+step +proj=hgridshift +grids=us_noaa_conus.tif +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); { // Test that we can round-trip this through WKT and still get the same // PROJ string. auto wkt = list[0]->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); auto obj = WKTParser().createFromWKT(wkt); auto co = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(co != nullptr); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), co->exportToPROJString(PROJStringFormatter::create().get())); } bool foundApprox = false; for (size_t i = 0; i < list.size(); i++) { auto projString = list[i]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_TRUE( projString.find("+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=us-ft " "+xy_out=rad +z_out=m") == 0) << list[i]->nameStr(); if (list[i]->nameStr().find("Transformation from NGVD29 height (ftUS) " "to NAVD88 height (ballpark vertical " "transformation)") == 0) { EXPECT_TRUE(list[i]->hasBallparkTransformation()); EXPECT_EQ(list[i]->nameStr(), "Transformation from NGVD29 height (ftUS) to NAVD88 " "height (ballpark vertical transformation) + NAD27 to " "WGS 84 (79) + Inverse of NAD83(NSRS2007) to WGS 84 (1)"); EXPECT_EQ( projString, "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad " "+z_out=m +step +proj=hgridshift +grids=us_noaa_conus.tif " "+step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); foundApprox = true; break; } } EXPECT_TRUE(foundApprox); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_context_helmert_noop) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); // WGS84 + EGM96 auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext); auto srcCrs = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCrs != nullptr); // ETRS89 + EGM96 auto objDest = createFromUserInput("EPSG:4258+5773", dbContext); auto destCrs = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(destCrs != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- // EGM96 has a geoid model referenced to WGS84, and Belfast height has a // geoid model referenced to ETRS89 TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_ETRS89_Belfast) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); // WGS84 + EGM96 auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext); auto srcCrs = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCrs != nullptr); // ETRS89 + Belfast height auto objDest = createFromUserInput("EPSG:4258+5732", dbContext); auto destCrs = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(destCrs != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + " "Inverse of ETRS89 to WGS 84 (1) + " "ETRS89 to Belfast height (2)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif " "+multiplier=1 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- // Variant of above where source intermediate geog3D CRS == target intermediate // geog3D CRS TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM96_to_WGS84_Belfast) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); // WGS84 + EGM96 auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext); auto srcCrs = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCrs != nullptr); // WGS84 + Belfast height auto objDest = createFromUserInput("EPSG:4326+5732", dbContext); auto destCrs = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(destCrs != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + " "ETRS89 to Belfast height (2) using ETRS89 to WGS 84 (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_Belfast.tif " "+multiplier=1 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_OSGB36_BNG_ODN_height_to_WGS84_EGM96) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // "OSGB36 / British National Grid + ODN height auto srcObj = createFromUserInput("EPSG:27700+5701", dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto authFactoryEPSG = AuthorityFactory::create(dbContext, std::string("EPSG")); auto dst = authFactoryEPSG->createCoordinateReferenceSystem( "9707"); // "WGS 84 + EGM96 height" { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of British National Grid + " "OSGB36 to ETRS89 (2) + " "Inverse of ETRS89 to ODN height (2) + " "ETRS89 to WGS 84 (1) + " "WGS 84 to EGM96 height (1)"); const char *expected_proj = "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 " "+x_0=400000 +y_0=-100000 +ellps=airy " "+step +proj=hgridshift +grids=uk_os_OSTN15_NTv2_OSGBtoETRS.tif " "+step +proj=vgridshift +grids=uk_os_OSGM15_GB.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=us_nga_egm96_15.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expected_proj); } { auto list = CoordinateOperationFactory::create()->createOperations( dst, NN_NO_CHECK(src), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + " "Inverse of ETRS89 to WGS 84 (1) + " "ETRS89 to ODN height (2) + " "Inverse of OSGB36 to ETRS89 (2) + " "British National Grid"); const char *expected_proj = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=uk_os_OSGM15_GB.tif " "+multiplier=1 " "+step +inv +proj=hgridshift " "+grids=uk_os_OSTN15_NTv2_OSGBtoETRS.tif " "+step +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 " "+x_0=400000 +y_0=-100000 +ellps=airy"; EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expected_proj); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_context_helmert) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); // GDA94 + AHD height auto objSrc = createFromUserInput("EPSG:9464", dbContext); auto srcCrs = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCrs != nullptr); // GDA2020 + AHD height auto objDest = createFromUserInput("EPSG:9463", dbContext); auto destCrs = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(destCrs != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCrs), NN_NO_CHECK(destCrs), ctxt); ASSERT_GE(list.size(), 1U); // Check presence of push/pop v_3 EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 " "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_GDA94_AHD_to_GDA2020_AVWS) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // GDA94 + AHD height authFactory->createCoordinateReferenceSystem("9464"), // GDA2020 + AVWS height authFactory->createCoordinateReferenceSystem("9462"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of 'GDA94 to GDA94 + AHD height (1)' + " "GDA94 to GDA2020 (1) + " "GDA2020 to AVWS height (2)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=au_ga_AUSGeoid09_V1.01.tif " "+multiplier=1 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 " "+rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=au_ga_AGQG_20201120.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_horizontal_boundCRS_to_compoundCRS_and_same_vertical) { auto dbContext = DatabaseContext::create(); auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "COMPOUNDCRS[\"AGD66 / AMG zone 55 + AHD height\",\n" " BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"AGD66 / AMG zone 55 + AHD height\",\n" " BASEGEOGCRS[\"AGD66\",\n" " DATUM[\"User datum (no grid)\",\n" " ELLIPSOID[\"Australian National " "Spheroid\",6378160,298.25,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"Australian Map Grid zone 55\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of natural origin\",147,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9996,\n" " SCALEUNIT[\"unity\",1]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1]],\n" " PARAMETER[\"False northing\",10000000,\n" " LENGTHUNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Engineering survey, topographic " "mapping.\"],\n" " AREA[\"Australia - onshore and offshore between " "144°E and 150°E. Papua New Guinea (PNG) - onshore between 144°E and " "150°E.\"],\n" " BBOX[-47.2,144,-1.3,150.01]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"GDA2020\",\n" " DATUM[\"Geocentric Datum of Australia 2020\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " ABRIDGEDTRANSFORMATION[\"User-defined Bursa-Wolf " "Transform\",\n" " METHOD[\"Position Vector transformation (geog2D " "domain)\"],\n" " PARAMETER[\"X-axis translation\",-153.501992013804,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",91.8979603422544,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",-76.7203604254192,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",-11.9539464931016,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",13.9315768664084,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",-3.45993996519333,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",0.999965937122937,\n" " ID[\"EPSG\",8611]]]],\n" " VERTCRS[\"AHD height\",\n" " VDATUM[\"Australian Height Datum\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Cadastre, engineering surveying applications over " "distances up to 10km.\"],\n" " AREA[\"Australia - Australian Capital Territory, New " "South Wales, Northern Territory, Queensland, South Australia, " "Tasmania, Western Australia and Victoria - onshore. Christmas Island " "- onshore. Cocos and Keeling Islands - onshore.\"],\n" " BBOX[-43.7,96.76,-9.86,153.69]],\n" " ID[\"EPSG\",5711]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); // GDA2020 / MGA zone 55 + AHD height auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto dstObj = createFromUserInput("EPSG:7855+5711", dbContext, false); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=55 +south +ellps=aust_SA " "+step +proj=push +v_3 " "+step +proj=cart +ellps=aust_SA " "+step +proj=helmert +x=-153.501992013804 +y=91.8979603422544 " "+z=-76.7203604254192 +rx=-11.9539464931016 +ry=13.9315768664084 " "+rz=-3.45993996519333 +s=-34.0628770629792 " "+convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=utm +zone=55 +south +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_with_horizontal_boundCRS_to_compoundCRS_with_derived_vertical) { auto dbContext = DatabaseContext::create(); auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "COMPOUNDCRS[\"AGD84 / AMG zone 55 + AHD height + local offset\",\n" " BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"AGD84 / AMG zone 55\",\n" " BASEGEOGCRS[\"AGD84\",\n" " DATUM[\"User datum (no grid)\",\n" " ELLIPSOID[\"Australian National Spheroid\"," "6378160,298.25,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"Australian Map Grid zone 55\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of natural origin\",147,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Scale factor at natural origin\"," "0.9996,\n" " SCALEUNIT[\"unity\",1]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1]],\n" " PARAMETER[\"False northing\",10000000,\n" " LENGTHUNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"GDA2020\",\n" " DATUM[\"Geocentric Datum of Australia 2020\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " ABRIDGEDTRANSFORMATION[\"User-defined Bursa-Wolf " "Transform\",\n" " METHOD[\"Position Vector transformation (geog2D " "domain)\"],\n" " PARAMETER[\"X-axis translation\",-153.306840928477,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",92.4132660160741,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",-76.481435572882,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",-11.9644937558611,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",13.9284887447329,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",-3.44390727325759,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",0.999965772441661,\n" " ID[\"EPSG\",8611]]]],\n" " VERTCRS[\"AHD height + local offset\",\n" " BASEVERTCRS[\"AHD height\",\n" " VDATUM[\"Australian Height Datum\"]],\n" " DERIVINGCONVERSION[\"vertical offset\",\n" " METHOD[\"PROJ affine\"],\n" " PARAMETER[\"zoff\",1.055,\n" " LENGTHUNIT[\"metre\",1]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); // GDA2020 / MGA zone 55 + AHD height auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto dstObj = createFromUserInput("EPSG:7855+5711", dbContext, false); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=55 +south +ellps=aust_SA " "+step +proj=push +v_3 " "+step +proj=cart +ellps=aust_SA " "+step +proj=helmert +x=-153.306840928477 " "+y=92.4132660160741 " "+z=-76.481435572882 +rx=-11.9644937558611 " "+ry=13.9284887447329 " "+rz=-3.44390727325759 +s=-34.2275583390395 " "+convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +inv +proj=affine +zoff=1.055 " "+step +proj=utm +zone=55 +south +ellps=GRS80"); } { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(dst), NN_NO_CHECK(src), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=55 +south +ellps=GRS80 " "+step +proj=affine +zoff=1.055 " "+step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 " "+step +inv +proj=helmert +x=-153.306840928477 " "+y=92.4132660160741 +z=-76.481435572882 " "+rx=-11.9644937558611 +ry=13.9284887447329 " "+rz=-3.44390727325759 +s=-34.2275583390395 " "+convention=position_vector " "+step +inv +proj=cart +ellps=aust_SA " "+step +proj=pop +v_3 " "+step +proj=utm +zone=55 +south +ellps=aust_SA"); } } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_concatenated_operation_with_two_vert_transformation) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // ETRS89 + Baltic 1957 height authFactory->createCoordinateReferenceSystem("8360"), // ETRS89 + EVRF2007 height authFactory->createCoordinateReferenceSystem("7423"), ctxt); ASSERT_GE(list.size(), 2U); // For Czechia EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vertoffset +lat_0=49.9166666666667 " "+lon_0=15.25 +dh=0.13 +slope_lat=0.026 +slope_lon=0 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[0]->nameStr(), "Baltic 1957 height to EVRF2007 height (1)"); // For Slovakia EXPECT_EQ( list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift " "+grids=sk_gku_Slovakia_ETRS89h_to_Baltic1957.tif +multiplier=1 " "+step +inv +proj=vgridshift " "+grids=sk_gku_Slovakia_ETRS89h_to_EVRF2007.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( list[1]->nameStr(), "ETRS89 + Baltic 1957 height to ETRS89 + EVRF2007 height (1)"); EXPECT_EQ(list[1]->inverse()->nameStr(), "Inverse of 'ETRS89 + Baltic " "1957 height to ETRS89 + " "EVRF2007 height (1)'"); } } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_concatenated_operation_with_two_vert_transformation_and_different_source_dest_interp) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // "BD72 + Ostend height" auto srcObj = createFromUserInput("EPSG:4313+5710", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); // "Amersfoort + NAP height" auto dstObj = createFromUserInput("EPSG:4289+5709", authFactory->databaseContext(), false); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "BD72 to ETRS89 (3) + " "Inverse of ETRS89 to Ostend height (1) + " "ETRS89 to NAP height (2) + " "Inverse of Amersfoort to ETRS89 (9)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=be_ign_bd72lb72_etrs89lb08.tif " "+step +proj=vgridshift +grids=be_ign_hBG18.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif " "+multiplier=1 " "+step +inv +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_issue_2720) { auto dbContext = DatabaseContext::create(); auto objSrc = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "COMPD_CS[\"Orthographic + EGM96 geoid height\"," "PROJCS[\"Orthographic\"," "GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_unknown\"," "SPHEROID[\"WGS84\",6378137,298.257223563]]," "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]]," "PROJECTION[\"Orthographic\"]," "PARAMETER[\"Latitude_Of_Center\",36.1754430555555000]," "PARAMETER[\"Longitude_Of_Center\",-86.7740944444444000]," "PARAMETER[\"false_easting\",0]," "PARAMETER[\"false_northing\",0]," "UNIT[\"Meter\",1]]," "VERT_CS[\"EGM96 geoid height\"," "VERT_DATUM[\"EGM96 geoid\",2005," "EXTENSION[\"PROJ4_GRIDS\",\"egm96_15.gtx\"]," "AUTHORITY[\"EPSG\",\"5171\"]]," "UNIT[\"metre\",1," "AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"Up\",UP]," "AUTHORITY[\"EPSG\",\"5773\"]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "COMPD_CS[\"WGS84 Coordinate System + EGM96 geoid height\"," "GEOGCS[\"WGS84 Coordinate System\"," "DATUM[\"WGS 1984\"," "SPHEROID[\"WGS 1984\",6378137,298.257223563]," "TOWGS84[0,0,0,0,0,0,0]," "AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0]," "UNIT[\"degree\",0.0174532925199433]," "AUTHORITY[\"EPSG\",\"4326\"]]," "VERT_CS[\"EGM96 geoid height\"," "VERT_DATUM[\"EGM96 geoid\",2005," "EXTENSION[\"PROJ4_GRIDS\",\"egm96_15.gtx\"]," "AUTHORITY[\"EPSG\",\"5171\"]]," "UNIT[\"metre\",1," "AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"Up\",UP]," "AUTHORITY[\"EPSG\",\"5773\"]]]"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt); EXPECT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=ortho +f=0 +lat_0=36.1754430555555 " "+lon_0=-86.7740944444444 +x_0=0 +y_0=0 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_issue_3328) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); // "WGS 84 + EGM96 height" auto srcObj = createFromUserInput("EPSG:4326+5773", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); // "WGS 84 + CGVD28 height" auto dstObj = createFromUserInput("EPSG:4326+5713", authFactory->databaseContext(), false); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + " "NAD83(CSRS) to CGVD28 height (1) " "using NAD83(CSRS) to WGS 84 (2)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=push +v_1 +v_2 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=cart +ellps=WGS84 " "+step +inv +proj=helmert +x=-0.991 +y=1.9072 +z=0.5129 " "+rx=-0.0257899075194932 " "+ry=-0.0096500989602704 +rz=-0.0116599432323421 +s=0 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=ca_nrc_HT2_1997.tif " "+multiplier=1 " "+step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=-0.991 +y=1.9072 +z=0.5129 " "+rx=-0.0257899075194932 " "+ry=-0.0096500989602704 +rz=-0.0116599432323421 +s=0 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1 " "+step +proj=pop +v_1 +v_2"); } // --------------------------------------------------------------------------- #ifdef requires_epsg_12_054 TEST(operation, compoundCRS_to_compoundCRS_WGS84_EGM2008_to_RD_new_NAP_height) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // WGS 84 + EGM2008 height authFactory->createCoordinateReferenceSystem("9518"), // Amersfoort / RD + NAP height authFactory->createCoordinateReferenceSystem("7415"), ctxt); ASSERT_EQ(list.size(), 4U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM2008 height (1) + " "Inverse of ETRS89-NLD [AGRS2010] to WGS 84 (1) + " "ETRS89-NLD [AGRS2010] to NAP height (2) + " "Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (9) + RD"); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif " "+multiplier=1 " "+step +inv +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif " "+step +proj=sterea +lat_0=52.1561605555556 +lon_0=5.38763888888889 " "+k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1.124"); EXPECT_FALSE(list[1]->hasBallparkTransformation()); EXPECT_EQ(list[1]->nameStr(), "Inverse of WGS 84 to EGM2008 height (1) + " "Inverse of ETRS89-NLD [AGRS2010] to WGS 84 (1) + " "ETRS89-NLD [AGRS2010] to NAP height (2) + " "Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (8) + RD"); ASSERT_EQ(list[1]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[1]->coordinateOperationAccuracies()[0]->value(), "1.373"); // Using not available "WGS 84 to EGM2008 height (2)" with 1' EGM2008 grid EXPECT_FALSE(list[2]->hasBallparkTransformation()); EXPECT_EQ(list[2]->nameStr(), "Inverse of WGS 84 to EGM2008 height (2) + " "Inverse of ETRS89-NLD [AGRS2010] to WGS 84 (1) + " "ETRS89-NLD [AGRS2010] to NAP height (2) + " "Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (9) + RD"); // Using not available "WGS 84 to EGM2008 height (2)" with 1' EGM2008 grid EXPECT_FALSE(list[3]->hasBallparkTransformation()); EXPECT_EQ(list[3]->nameStr(), "Inverse of WGS 84 to EGM2008 height (2) + " "Inverse of ETRS89-NLD [AGRS2010] to WGS 84 (1) + " "ETRS89-NLD [AGRS2010] to NAP height (2) + " "Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (8) + RD"); } #endif // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_compoundCRS_concatenated_operation_with_two_vert_transformation_and_ballpark_geog) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // "NAD83(CSRS) + CGVD28 height" auto srcObj = createFromUserInput("EPSG:4617+5713", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); // "NAD83(CSRS) + CGVD2013(CGG2013) height" auto dstObj = createFromUserInput("EPSG:4617+6647", authFactory->databaseContext(), false); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of NAD83(CSRS)v6 to CGVD28 height (1) + " "NAD83(CSRS)v6 to CGVD2013(CGG2013) height (1) " "using Ballpark geographic offset " "from NAD83(CSRS) to NAD83(CSRS)v6"); } #if 0 // Note: below situation is no longer triggered since EPSG v10.066 update // Not obvious to find an equivalent one. // That transformation involves doing CGVD28 height to CGVD2013(CGG2013) // height by doing: // - CGVD28 height to NAD83(CSRS): EPSG registered operation // - NAD83(CSRS) to CGVD2013(CGG2013) height by doing: // * NAD83(CSRS) to NAD83(CSRS)v6: ballpark // * NAD83(CSRS)v6 to CGVD2013(CGG2013): EPSG registered operation auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); // Check that we have the transformation using NAD83(CSRS)v6 first // (as well as the one between NAD83(CSRS) to CGVD28 height) EXPECT_EQ(list[0]->nameStr(), "Inverse of NAD83(CSRS) to CGVD28 height (1) + " "Inverse of Ballpark geographic offset from NAD83(CSRS)v6 to " "NAD83(CSRS) + " "NAD83(CSRS)v6 to CGVD2013(CGG2013) height (1) + " "Inverse of Ballpark geographic offset from NAD83(CSRS) to " "NAD83(CSRS)v6"); } { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(dst), NN_NO_CHECK(src), ctxt); ASSERT_GE(list.size(), 1U); // Check that we have the transformation using NAD83(CSRS)v6 first // (as well as the one between NAD83(CSRS) to CGVD28 height) EXPECT_EQ( list[0]->nameStr(), "Ballpark geographic offset from NAD83(CSRS) to NAD83(CSRS)v6 + " "Inverse of NAD83(CSRS)v6 to CGVD2013(CGG2013) height (1) + " "Ballpark geographic offset from NAD83(CSRS)v6 to NAD83(CSRS) + " "NAD83(CSRS) to CGVD28 height (1)"); } #endif } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_issue_3152_ch1903lv03_ln02_bound) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"CH1903 / LV03 + LN02 height\",\n" " BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"CH1903 / LV03\",\n" " BASEGEOGCRS[\"CH1903\",\n" " DATUM[\"CH1903\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4149]],\n" " CONVERSION[\"Map projection of CH1903 / LV03\",\n" " METHOD[\"Hotine Oblique Mercator (variant B)\",\n" " ID[\"EPSG\",9815]],\n" " PARAMETER[\"Latitude of projection " "centre\",46.9524055555556,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection " "centre\",7.43958333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth of initial line\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Angle from Rectified to Skew " "Grid\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8814]],\n" " PARAMETER[\"Scale factor on initial line\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"Easting at projection " "centre\",600000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8816]],\n" " PARAMETER[\"Northing at projection " "centre\",200000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8817]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",21781]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " ABRIDGEDTRANSFORMATION[\"MyTransformation from CH1903 to " "WGS84\",\n" " METHOD[\"Position Vector transformation (geog2D " "domain)\",\n" " ID[\"EPSG\",9606]],\n" " PARAMETER[\"X-axis translation\",674.374,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",15.056,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",405.346,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",0,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",0,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",0,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",1,\n" " ID[\"EPSG\",8611]]]],\n" " VERTCRS[\"LN02 height\",\n" " VDATUM[\"Landesnivellement 1902\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5728]]]"; auto srcObj = createFromUserInput(wkt, dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto authFactoryEPSG = AuthorityFactory::create(dbContext, std::string("EPSG")); auto dst = authFactoryEPSG->createCoordinateReferenceSystem( "9518"); // "WGS 84 + EGM2008 height" auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_GE(list.size(), 1U); // Check that BoundCRS helmert transformation is used EXPECT_EQ(list[0]->nameStr(), "Inverse of Map projection of CH1903 / LV03 + " "MyTransformation from CH1903 to WGS84 + " "Inverse of ETRS89 to WGS 84 (1) + " "Inverse of ETRS89 to LN02 height + " "ETRS89 to WGS 84 (1) + " "WGS 84 to EGM2008 height (1)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=somerc +lat_0=46.9524055555556 " "+lon_0=7.43958333333333 +k_0=1 " "+x_0=600000 +y_0=200000 +ellps=bessel " "+step +proj=push +v_3 " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.374 +y=15.056 +z=405.346 " "+rx=0 +ry=0 +rz=0 +s=0 +convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=vgridshift " "+grids=ch_swisstopo_chgeo2004_ETRS89_LN02.tif " "+multiplier=1 " "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); auto listInv = CoordinateOperationFactory::create()->createOperations( dst, NN_NO_CHECK(src), ctxt); ASSERT_GE(listInv.size(), 1U); EXPECT_EQ(listInv[0]->nameStr(), "Inverse of WGS 84 to EGM2008 height (1) + " "Inverse of ETRS89 to WGS 84 (1) + " "ETRS89 to LN02 height + " "ETRS89 to WGS 84 (1) + " "Inverse of MyTransformation from CH1903 to WGS84 + " "Map projection of CH1903 / LV03"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_issue_3191_BD72_Ostend_height_to_WGS84_EGM96) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // BD72 + Ostend height auto srcObj = createFromUserInput("EPSG:4313+5710", dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto authFactoryEPSG = AuthorityFactory::create(dbContext, std::string("EPSG")); auto dst = authFactoryEPSG->createCoordinateReferenceSystem( "9707"); // "WGS 84 + EGM96 height" { auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "BD72 to ETRS89 (3) + " "Inverse of ETRS89 to Ostend height (1) + " "ETRS89 to WGS 84 (1) + " "WGS 84 to EGM96 height (1)"); const char *expected_proj = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=be_ign_bd72lb72_etrs89lb08.tif " "+step +proj=vgridshift +grids=be_ign_hBG18.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=us_nga_egm96_15.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expected_proj); } { auto list = CoordinateOperationFactory::create()->createOperations( dst, NN_NO_CHECK(src), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + " "Inverse of ETRS89 to WGS 84 (1) + " "ETRS89 to Ostend height (1) + " "Inverse of BD72 to ETRS89 (3)"); const char *expected_proj = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=be_ign_hBG18.tif +multiplier=1 " "+step +inv +proj=hgridshift +grids=be_ign_bd72lb72_etrs89lb08.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expected_proj); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_issue_4550) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPD_CS[" "\"NAD83(HARN) / Washington South (ftUS) + " "NAVD88 height (ftUS) - Geoid18 (ftUS)\"," "PROJCS[\"NAD83(HARN) / Washington South (ftUS)\"," "GEOGCS[\"NAD83(HARN)\"," "DATUM[\"NAD83 (High Accuracy Reference Network)\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101," "AUTHORITY[\"EPSG\",\"7019\"]],TOWGS84[0,0,0,0,0,0,0]," "AUTHORITY[\"EPSG\",\"6152\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," "AUTHORITY[\"EPSG\",\"4152\"]]," "PROJECTION[\"Lambert_Conformal_Conic_2SP\"]," "PARAMETER[\"standard_parallel_1\",47.33333333333334]," "PARAMETER[\"standard_parallel_2\",45.83333333333334]," "PARAMETER[\"latitude_of_origin\",45.33333333333334]," "PARAMETER[\"central_meridian\",-120.5]," "PARAMETER[\"false_easting\",1640416.667]," "PARAMETER[\"false_northing\",0]," "UNIT[\"US survey foot\",0.3048006096012192," "AUTHORITY[\"EPSG\",\"9003\"]]," "AXIS[\"X\",EAST],AXIS[\"Y\",NORTH]," "AUTHORITY[\"EPSG\",\"2927\"]]," "VERT_CS[\"NAVD88 height (ftUS) - Geoid18 (ftUS)\"," "VERT_DATUM[\"North American Vertical Datum 1988\",2005," "AUTHORITY[\"EPSG\",\"5103\"]]," "UNIT[\"US survey foot\",0.3048006096012192," "AUTHORITY[\"EPSG\",\"9003\"]],AXIS[\"Up\",UP]," "AUTHORITY[\"EPSG\",\"6360\"]]]"; auto srcObj = createFromUserInput(wkt, dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); // "UTM Zone 10N / WGS 84 + NAVD88 height" auto dstObj = createFromUserInput("EPSG:32610+5703", dbContext, false); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m " "+step +inv +proj=lcc +lat_0=45.3333333333333 +lon_0=-120.5 " "+lat_1=47.3333333333333 +lat_2=45.8333333333333 " "+x_0=500000.0001016 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=utm +zone=10 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_with_derivedVerticalCRS_ellipsoidal_height) { // Test scenario of https://github.com/OSGeo/PROJ/issues/4175 // Note that the CompoundCRS with a DerivedVerticalCRS with a "Ellipsoid" // datum is an extension, not OGC Topic 2 compliant. auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // WGS84 + EGM96 auto objSrc = createFromUserInput("EPSG:4326+5773", dbContext); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); // auto wkt = "COMPOUNDCRS[\"UTM30 with vertical offset\",\n" " PROJCRS[\"WGS 84 / UTM zone 30N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " CONVERSION[\"UTM zone 30N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32630]],\n" " VERTCRS[\"Derived verticalCRS\",\n" " BASEVERTCRS[\"Ellipsoid (metre)\",\n" " VDATUM[\"Ellipsoid\"]],\n" " DERIVINGCONVERSION[\"Conv Vertical Offset\",\n" " METHOD[\"Vertical Offset\",\n" " ID[\"EPSG\",9616]],\n" " PARAMETER[\"Vertical Offset\",42.3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8603]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]]"; auto objDst = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=geogoffset +dh=42.3 " "+step +proj=utm +zone=30 +ellps=WGS84"); EXPECT_STREQ(list[0]->nameStr().c_str(), "Inverse of WGS 84 to EGM96 height (1) + Conv Vertical Offset " "+ UTM zone 30N"); EXPECT_FALSE(list[0]->hasBallparkTransformation()); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_compoundCRS_using_intermediate_of_horizontal_transform) { // Related to scenario of https://github.com/OSGeo/PROJ/issues/4618 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse::KNOWN_AVAILABLE); // "PNG94 / PNGMG94 zone 54 + Kumul 34 height" auto objSrc = createFromUserInput("EPSG:5550+7651", dbContext); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); // "WGS 84 (G2139) + EGM2008 height auto objDst = createFromUserInput("EPSG:9755+3855", dbContext); auto dstCRS = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dstCRS != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), NN_NO_CHECK(dstCRS), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ( list[0]->nameStr(), "Inverse of Papua New Guinea Map Grid 1994 zone 54 + " "PNG94 to WGS 84 (1) + " "Inverse of EGM96 height to Kumul 34 height (1) + " "Inverse of WGS 84 to EGM96 height (1) + " "WGS 84 to WGS 84 (G2139) + " "WGS 84 (G2139) to EGM2008 height (from WGS 84 to EGM2008 height (1))"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=54 +south +ellps=GRS80 " "+step +proj=geogoffset +dh=0.87 " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, vertCRS_to_vertCRS) { auto vertcrs_m_obj = PROJStringParser().createFromPROJString("+vunits=m"); auto vertcrs_m = nn_dynamic_pointer_cast(vertcrs_m_obj); ASSERT_TRUE(vertcrs_m != nullptr); auto vertcrs_ft_obj = PROJStringParser().createFromPROJString("+vunits=ft"); auto vertcrs_ft = nn_dynamic_pointer_cast(vertcrs_ft_obj); ASSERT_TRUE(vertcrs_ft != nullptr); auto vertcrs_us_ft_obj = PROJStringParser().createFromPROJString("+vunits=us-ft"); auto vertcrs_us_ft = nn_dynamic_pointer_cast(vertcrs_us_ft_obj); ASSERT_TRUE(vertcrs_us_ft != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=m +z_out=ft"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=ft +z_out=m"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_m)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=ft +z_out=m"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_us_ft)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=ft +z_out=us-ft"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertcrs_us_ft), NN_CHECK_ASSERT(vertcrs_ft)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=unitconvert +z_in=us-ft +z_out=ft"); } auto vertCRSMetreUp = nn_dynamic_pointer_cast(WKTParser().createFromWKT( "VERTCRS[\"my height\",VDATUM[\"my datum\"],CS[vertical,1]," "AXIS[\"gravity-related height (H)\",up," "LENGTHUNIT[\"metre\",1]]]")); ASSERT_TRUE(vertCRSMetreUp != nullptr); auto vertCRSMetreDown = nn_dynamic_pointer_cast(WKTParser().createFromWKT( "VERTCRS[\"my depth\",VDATUM[\"my datum\"],CS[vertical,1]," "AXIS[\"depth (D)\",down,LENGTHUNIT[\"metre\",1]]]")); ASSERT_TRUE(vertCRSMetreDown != nullptr); auto vertCRSMetreDownFtUS = nn_dynamic_pointer_cast(WKTParser().createFromWKT( "VERTCRS[\"my depth (ftUS)\",VDATUM[\"my datum\"],CS[vertical,1]," "AXIS[\"depth (D)\",down,LENGTHUNIT[\"US survey " "foot\",0.304800609601219]]]")); ASSERT_TRUE(vertCRSMetreDownFtUS != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertCRSMetreUp), NN_CHECK_ASSERT(vertCRSMetreDown)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=1,2,-3"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(vertCRSMetreUp), NN_CHECK_ASSERT(vertCRSMetreDownFtUS)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +s33=-3.28083333333333"); } } // --------------------------------------------------------------------------- TEST(operation, vertCRS_to_vertCRS_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // NGVD29 height (m) authFactory->createCoordinateReferenceSystem("7968"), // NAVD88 height (1) authFactory->createCoordinateReferenceSystem("5703"), ctxt); ASSERT_EQ(list.size(), 4U); EXPECT_EQ(list[0]->nameStr(), "NGVD29 height (m) to NAVD88 height (3)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=vgridshift +grids=us_noaa_vertcone.tif +multiplier=1"); } // --------------------------------------------------------------------------- TEST(operation, vertCRS_to_vertCRS_New_Zealand_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( // NZVD2016 height authFactory->createCoordinateReferenceSystem("7839"), // Auckland 1946 height authFactory->createCoordinateReferenceSystem("5759"), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=vgridshift +grids=nz_linz_auckht1946-nzvd2016.tif " "+multiplier=1"); } // --------------------------------------------------------------------------- TEST(operation, vertCRS_to_vertCRS_pivot_context) { // Test that PROJ can chain a registered vertical CT with a // height-to-depth axis conversion when the target CRS differs from // the CT's registered target only by axis direction. auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto checkPipeline = [&](const std::string &srcCode, const std::string &tgtCode, const std::string &expectedProj) { auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem(srcCode), authFactory->createCoordinateReferenceSystem(tgtCode), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), expectedProj); }; // Using Strategy 1 of createOperationsVertToVertWithIntermediateVert() // Caspian: EPSG:5705 (Baltic 1977 height) -> EPSG:5706 (Caspian depth) // via EPSG:5438 (dh=28) + height-to-depth axisswap checkPipeline("5705", "5706", "+proj=pipeline +step +proj=geogoffset +dh=28 " "+step +proj=axisswap +order=1,2,-3"); // Caspian: EPSG:5706 (Caspian depth) -> EPSG:5705 (Baltic 1977 height) // axisswap + inverse of EPSG:5438 (dh=-28) // Note: yes that pipeline is identical to the above one, since it is its // own inverse. checkPipeline("5706", "5705", "+proj=pipeline +step +proj=geogoffset +dh=28 " "+step +proj=axisswap +order=1,2,-3"); // KOC ft: EPSG:5790 -> EPSG:5614 (KOC WD depth ft) // via EPSG:7987 (dh=-4.74) + axisswap + unit conversion m->ft checkPipeline("5790", "5614", "+proj=pipeline " "+step +proj=geogoffset +dh=-4.74 " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=unitconvert +z_in=m +z_out=ft"); // KOC ft: EPSG:5614 (KOC WD depth ft) -> EPSG:5790 (KOC CD height) // unit conversion ft->m + axisswap + inverse of EPSG:7987 (dh=4.74) checkPipeline("5614", "5790", "+proj=pipeline " "+step +proj=unitconvert +z_in=ft +z_out=m " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=geogoffset +dh=4.74"); // EPSG:5705 (Baltic 1977 height) to EPSG:5336 (Black Sea depth) // Strategy 1 composes: EPSG:5447 (5705 to 5735, geogoffset +dh=0.4) // + height-to-depth (axisswap order=1,2,-3) checkPipeline("5705", "5336", "+proj=pipeline " "+step +proj=geogoffset +dh=0.4 " "+step +proj=axisswap +order=1,2,-3"); // Using Strategy 2 of createOperationsVertToVertWithIntermediateVert() // EPSG:5336 (Black Sea depth) to EPSG:5705 (Baltic 1977 height) // Strategy 2 composes: depth-to-height (5336 to 5735, axisswap) // + inverse of EPSG:5447 (5735 to 5705, dh=-0.4) checkPipeline("5336", "5705", "+proj=pipeline " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=geogoffset +dh=-0.4"); } // --------------------------------------------------------------------------- TEST(operation, projCRS_3D_to_geogCRS_3D) { auto compoundcrs_ft_obj = PROJStringParser().createFromPROJString( "+proj=merc +vunits=ft +type=crs"); auto proj3DCRS_ft = nn_dynamic_pointer_cast(compoundcrs_ft_obj); ASSERT_TRUE(proj3DCRS_ft != nullptr); auto geogcrs_m_obj = PROJStringParser().createFromPROJString( "+proj=longlat +vunits=m +type=crs"); auto geogcrs_m = nn_dynamic_pointer_cast(geogcrs_m_obj); ASSERT_TRUE(geogcrs_m != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(proj3DCRS_ft), NN_CHECK_ASSERT(geogcrs_m)); ASSERT_TRUE(op != nullptr); EXPECT_FALSE(op->hasBallparkTransformation()); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=m +z_in=ft " "+xy_out=m +z_out=m " "+step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 " "+ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +z_in=m " "+xy_out=deg +z_out=m"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(proj3DCRS_ft)); ASSERT_TRUE(op != nullptr); EXPECT_FALSE(op->hasBallparkTransformation()); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=m +z_in=m " "+xy_out=m +z_out=ft"); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D) { auto compoundcrs_ft_obj = WKTParser().createFromWKT( "COMPOUNDCRS[\"unknown\",\n" " PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Mercator (variant A)\",\n" " ID[\"EPSG\",9804]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " VERTCRS[\"unknown\",\n" " VDATUM[\"unknown\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"foot\",0.3048,\n" " ID[\"EPSG\",9002]]]]]"); auto compoundcrs_ft = nn_dynamic_pointer_cast(compoundcrs_ft_obj); ASSERT_TRUE(compoundcrs_ft != nullptr); auto geogcrs_m_obj = PROJStringParser().createFromPROJString( "+proj=longlat +vunits=m +type=crs"); auto geogcrs_m = nn_dynamic_pointer_cast(geogcrs_m_obj); ASSERT_TRUE(geogcrs_m != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(compoundcrs_ft), NN_CHECK_ASSERT(geogcrs_m)); ASSERT_TRUE(op != nullptr); EXPECT_TRUE(op->hasBallparkTransformation()); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 " "+y_0=0 +ellps=WGS84 +step +proj=unitconvert +xy_in=rad " "+z_in=ft +xy_out=deg +z_out=m"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(compoundcrs_ft)); ASSERT_TRUE(op != nullptr); EXPECT_TRUE(op->hasBallparkTransformation()); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m " "+xy_out=rad +z_out=ft +step +proj=merc +lon_0=0 +k=1 +x_0=0 " "+y_0=0 +ellps=WGS84"); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // CompoundCRS to Geog3DCRS, with vertical unit change, but without // ellipsoid height <--> vertical height correction { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "7406"), // NAD27 + NGVD29 height (ftUS) authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 ctxt); ASSERT_GE(list.size(), 1U); EXPECT_TRUE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->nameStr(), "NAD27 to WGS 84 (79) + Transformation from NGVD29 height " "(ftUS) to WGS 84 (ballpark vertical transformation, without " "ellipsoid height to vertical height correction)"); EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step " "+proj=hgridshift +grids=us_noaa_conus.tif " "+step +proj=unitconvert " "+xy_in=rad +z_in=us-ft +xy_out=deg +z_out=m +step " "+proj=axisswap +order=2,1"); } // CompoundCRS to Geog3DCRS, with same vertical unit, and with // direct ellipsoid height <--> vertical height correction and // direct horizontal transform (no-op here) { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "5500"), // NAD83(NSRS2007) + NAVD88 height authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 ctxt); ASSERT_GE(list.size(), 2U); EXPECT_EQ(list[1]->nameStr(), "Inverse of NAD83(NSRS2007) to NAVD88 height (1) + " "NAD83(NSRS2007) to WGS 84 (1)"); EXPECT_EQ(list[1]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_noaa_geoid09_conus.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[1]->remarks(), "For NAD83(NSRS2007) to NAVD88 height (1) (EPSG:9173): Uses " "Geoid09 hybrid model. Replaced by 2012 model (CT code 6326)." "\n" "For NAD83(NSRS2007) to WGS 84 (1) (EPSG:15931): " "Approximation assuming that NAD83(NSRS2007) is equivalent " "to WGS 84 within the accuracy of the transformation."); } // NAD83 + NAVD88 height --> WGS 84 { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83 + NAVD88 height auto srcObj = createFromUserInput( "EPSG:4269+5703", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto list = CoordinateOperationFactory::create()->createOperations( nnSrc, authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 ctxt); ASSERT_GE(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NAD83 to NAD83(HARN) (47) + " "NAD83(HARN) to NAD83(FBN) (1) + " "Inverse of NAD83(FBN) to NAVD88 height (1) + " "Inverse of NAD83(HARN) to NAD83(FBN) (1) + " "NAD83(HARN) to WGS 84 (3)"); EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=gridshift " "+grids=us_noaa_nadcon5_nad83_1986_nad83_harn_conus.tif " "+step +proj=gridshift +no_z_transform " "+grids=us_noaa_nadcon5_nad83_harn_nad83_fbn_conus.tif " "+step +proj=vgridshift +grids=us_noaa_geoid03_conus.tif " "+multiplier=1 " "+step +inv +proj=gridshift " "+grids=us_noaa_nadcon5_nad83_harn_nad83_fbn_conus.tif " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=-0.991 +y=1.9072 +z=0.5129 " "+rx=-0.0257899075194932 " "+ry=-0.0096500989602704 +rz=-0.0116599432323421 +s=0 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // Another variation, but post horizontal adjustment is in two steps { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height auto srcObj = createFromUserInput( "EPSG:6318+5703", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto list = CoordinateOperationFactory::create()->createOperations( nnSrc, authFactory->createCoordinateReferenceSystem("4985"), // WGS 72 3D ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of NAD83(2011) to NAVD88 height (3) + " "NAD83(2011) to WGS 84 (1) + " "Inverse of WGS 72 to WGS 84 (2)"); EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif " "+multiplier=1 " "+step +proj=cart +ellps=WGS84 " "+step +inv +proj=helmert +x=0 +y=0 +z=4.5 +rx=0 +ry=0 " "+rz=0.554 +s=0.219 +convention=position_vector " "+step +inv +proj=cart +ellps=WGS72 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // Check that we can handle vertical transformations where there is a // mix of available ones in the PROJ namespace (mx_inegi_ggm10) and in // in the EPSG namespace (us_noaa_g2018u0) // This test might no longer test this scenario if mx_inegi_ggm10 is // referenced one day by EPSG, but at least this tests a common use case. { auto authFactoryAll = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactoryAll, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height auto srcObj = createFromUserInput( "EPSG:6318+5703", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto list = CoordinateOperationFactory::create()->createOperations( nnSrc, authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 3D ctxt); bool foundGeoid2018 = false; bool foundGGM10 = false; for (const auto &op : list) { try { const auto projString = op->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()); if (projString.find("us_noaa_g2018u0.tif") != std::string::npos) foundGeoid2018 = true; else if (projString.find("mx_inegi_ggm10.tif") != std::string::npos) foundGGM10 = true; } catch (const std::exception &) { } } EXPECT_TRUE(foundGeoid2018); EXPECT_TRUE(foundGGM10); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_with_3D_helmert_context) { // Use case of https://github.com/OSGeo/PROJ/issues/2225 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // WGS84 + EGM96 height auto srcObj = createFromUserInput("EPSG:4326+5773", dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), // CH1903+ authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D( std::string(), dbContext), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + " "Inverse of CH1903+ to WGS 84 (1)"); // Check that there is no push v_3 / pop v_3 const char *expected_proj = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 " "+step +inv +proj=cart +ellps=bessel " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), expected_proj); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_with_3D_helmert_same_geog_src_target_context) { // Use case of https://github.com/OSGeo/PROJ/pull/2584 // From EPSG:XXXX+YYYY to EPSG:XXXX (3D), with a vertical shift grid // operation in another datum ZZZZ, and the XXXX<--->ZZZZ being an Helmert auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // CH1903+ + EGM96 height auto srcObj = createFromUserInput("EPSG:4150+5773", dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), // CH1903+ authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D( std::string(), dbContext), ctxt); ASSERT_GE(list.size(), 1U); // Check that there is push v_3 / pop v_3 in the step before vgridshift // Check that there is *no* push v_3 / pop v_3 after vgridshift const char *expected_proj = "+proj=pipeline " "+step +proj=push +v_1 +v_2 " // avoid any horizontal change "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.374 +y=15.056 +z=405.346 " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 " "+step +inv +proj=cart +ellps=bessel " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1 " "+step +proj=pop +v_1 +v_2" // avoid any horizontal change ; EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), expected_proj); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_with_null_helmert_same_geog_src_target_context) { // Variation of previous case // From EPSG:XXXX+YYYY to EPSG:XXXX (3D), with a vertical shift grid // operation in another datum ZZZZ, and the XXXX<--->ZZZZ being a // null Helmert auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // ETRS89 + EGM96 height auto srcObj = createFromUserInput("EPSG:4258+5773", dbContext, false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), // ETRS89 3D authFactory->createCoordinateReferenceSystem("4937"), ctxt); ASSERT_GE(list.size(), 1U); // No push/pop needed const char *expected_proj = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), expected_proj); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_with_same_geog_src_target_interp_context) { auto dbContext = DatabaseContext::create(); // Tests a mix of Datum and DatumEnsemble regarding WGS 84 when we compare // the datums used in the source -> interpolation_crs and // interpolation_crs -> target transformations. auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto dstObj = WKTParser().createFromWKT( "COMPOUNDCRS[\"WGS 84 + my_height\",\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"my_height\",\n" " VDATUM[\"my_height\"],\n" " CS[vertical,1],\n" " AXIS[\"up\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[" "\"my_height to WGS84 ellipsoidal height\",\n" " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n" " PARAMETERFILE[\"Geoid (height correction) model file\"," "\"fake.gtx\",\n" " ID[\"EPSG\",8666]]]]]"); auto dst = nn_dynamic_pointer_cast(dstObj); ASSERT_TRUE(dst != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 3D NN_NO_CHECK(dst), ctxt); ASSERT_EQ(list.size(), 1U); const char *expected_proj = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift +grids=fake.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ(list[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), expected_proj); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_WGS84_to_GDA2020_AHD_Height) { // Use case of https://github.com/OSGeo/PROJ/issues/2348 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( // GDA2020 + AHD height authFactory->createCoordinateReferenceSystem("9463"), // WGS 84 3D authFactory->createCoordinateReferenceSystem("4979"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of GDA2020 to AHD height (1) + " "GDA2020 to WGS 84 (2)"); } // Inverse { auto list = CoordinateOperationFactory::create()->createOperations( // WGS 84 3D authFactory->createCoordinateReferenceSystem("4979"), // GDA2020 + AHD height authFactory->createCoordinateReferenceSystem("9463"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of GDA2020 to WGS 84 (2) + " "GDA2020 to AHD height (1)"); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83 + NAVD88 height auto srcObj = createFromUserInput("EPSG:4269+5703", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("4269")->promoteTo3D( std::string(), authFactory->databaseContext()); // NAD83 auto listCompoundToGeog2D = CoordinateOperationFactory::create()->createOperations(nnSrc, dst, ctxt); // The checked value is not that important, but in case this changes, // likely due to a EPSG upgrade, worth checking EXPECT_EQ(listCompoundToGeog2D.size(), 122U); auto listGeog2DToCompound = CoordinateOperationFactory::create()->createOperations(dst, nnSrc, ctxt); EXPECT_EQ(listGeog2DToCompound.size(), listCompoundToGeog2D.size()); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_projCRS_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // SPCS83 California zone 1 (US Survey feet) + NAVD88 height (ftUS) auto srcObj = createFromUserInput("EPSG:2225+6360", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("4269")->promoteTo3D( std::string(), authFactory->databaseContext()); // NAD83 auto list = CoordinateOperationFactory::create()->createOperations( nnSrc, dst, ctxt); // The checked value is not that important, but in case this changes, // likely due to a EPSG upgrade, worth checking // We want to make sure that the horizontal adjustments before and after // the vertical transformation are the reverse of each other, and there are // not mixes with different alternative operations (like California grid // forward and Nevada grid reverse) EXPECT_EQ(list.size(), 16U); ASSERT_GE(list.size(), 1U); // Check that unit conversion is OK auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ(op_proj, "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m " "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-122 " "+lat_1=41.6666666666667 +lat_2=40 +x_0=2000000.0001016 " "+y_0=500000.0001016 +ellps=GRS80 " "+step +proj=hgridshift +grids=us_noaa_cnhpgn.tif " "+step +proj=gridshift +no_z_transform " "+grids=us_noaa_nadcon5_nad83_harn_nad83_fbn_conus.tif " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=vgridshift +grids=us_noaa_geoid03_conus.tif " "+multiplier=1 " "+step +inv +proj=gridshift " "+grids=us_noaa_nadcon5_nad83_harn_nad83_fbn_conus.tif " "+step +inv +proj=hgridshift +grids=us_noaa_cnhpgn.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_KNOWN_AVAILABLE_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse::KNOWN_AVAILABLE); auto list = CoordinateOperationFactory::create()->createOperations( authFactory->createCoordinateReferenceSystem( "9537"), // RGAF09 + Martinique 1987 height authFactory->createCoordinateReferenceSystem("4557"), // RRAF 1991 ctxt); ASSERT_GE(list.size(), 2U); // Make sure that "RGAF09 to Martinique 1987 height (2)" (using RAMART2016) // is listed first EXPECT_EQ(list[0]->nameStr(), "Inverse of RGAF09 to Martinique 1987 height (2) + " "Inverse of RRAF 1991 to RGAF09 (1)"); EXPECT_EQ(list[1]->nameStr(), "Inverse of RRAF 1991 to RGAF09 (1) + " "Inverse of RRAF 1991 to Martinique 1987 height (1)"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_from_wkt_without_id_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"NAD83(2011) + NAVD88 height\",\n" " GEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System 2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " VERTCRS[\"NAVD88 height\",\n" " VDATUM[\"North American Vertical Datum 1988\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); // NAD83(2011) + NAVD88 height auto srcRefObj = createFromUserInput("EPSG:6318+5703", authFactory->databaseContext(), false); auto srcRef = nn_dynamic_pointer_cast(srcRefObj); ASSERT_TRUE(srcRef != nullptr); ASSERT_TRUE( src->isEquivalentTo(srcRef.get(), IComparable::Criterion::EQUIVALENT)); auto listRef = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcRef), dst, ctxt); EXPECT_EQ(list.size(), listRef.size()); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_projCRS_from_wkt_without_id_or_extent_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"NAD83 / Pennsylvania South + NAVD88 height\",\n" " PROJCRS[\"NAD83 / Pennsylvania South\",\n" " BASEGEOGCRS[\"NAD83\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n" " METHOD[\"Lambert Conic Conformal (2SP)\",\n" " ID[\"EPSG\",9802]],\n" " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",-77.75,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard " "parallel\",40.9666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard " "parallel\",39.9333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",600000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"NAVD88 height\",\n" " VDATUM[\"North American Vertical Datum 1988\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("4269"); // NAD83 auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); // NAD83 / Pennsylvania South + NAVD88 height auto srcRefObj = createFromUserInput("EPSG:32129+5703", authFactory->databaseContext(), false); auto srcRef = nn_dynamic_pointer_cast(srcRefObj); ASSERT_TRUE(srcRef != nullptr); ASSERT_TRUE( src->isEquivalentTo(srcRef.get(), IComparable::Criterion::EQUIVALENT)); auto listRef = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcRef), dst, ctxt); EXPECT_EQ(list.size(), listRef.size()); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_with_vertical_unit_change) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height (ftUS) auto srcObj = createFromUserInput("EPSG:6318+6360", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) 3D auto listCompoundToGeog = CoordinateOperationFactory::create()->createOperations(nnSrc, dst, ctxt); ASSERT_TRUE(!listCompoundToGeog.empty()); // NAD83(2011) + NAVD88 height auto srcObjCompoundVMetre = createFromUserInput( "EPSG:6318+5703", authFactory->databaseContext(), false); auto srcCompoundVMetre = nn_dynamic_pointer_cast(srcObjCompoundVMetre); ASSERT_TRUE(srcCompoundVMetre != nullptr); auto listCompoundMetreToGeog = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCompoundVMetre), dst, ctxt); // Check that we get the same and similar results whether we start from // regular NAVD88 height or its ftUs variant ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size()); EXPECT_EQ(listCompoundToGeog[0]->nameStr(), "Conversion from NAVD88 height (ftUS) to NAVD88 height + " + listCompoundMetreToGeog[0]->nameStr()); EXPECT_EQ( listCompoundToGeog[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), replaceAll(listCompoundMetreToGeog[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+step +proj=unitconvert +xy_in=deg +xy_out=rad", "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad " "+z_out=m")); // Check reverse path auto listGeogToCompound = CoordinateOperationFactory::create()->createOperations(dst, nnSrc, ctxt); EXPECT_EQ(listGeogToCompound.size(), listCompoundToGeog.size()); } // --------------------------------------------------------------------------- // Use case of https://github.com/OSGeo/PROJ/issues/3938 TEST(operation, compoundCRS_ftUS_to_geogCRS_ft) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height (ftUS) auto srcObj = createFromUserInput("EPSG:6318+6360", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("6319")->alterCSLinearUnit( UnitOfMeasure::FOOT); // NAD83(2011) with foot auto res = CoordinateOperationFactory::create()->createOperations( nnSrc, dst, ctxt); ASSERT_TRUE(!res.empty()); EXPECT_EQ( res[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad +z_out=m " "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=ft " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( res.back()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=deg +z_out=ft"); auto resInv = CoordinateOperationFactory::create()->createOperations( dst, nnSrc, ctxt); ASSERT_TRUE(!resInv.empty()); EXPECT_EQ( resInv[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=m " "+step +inv +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=us-ft " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( resInv.back()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=unitconvert +xy_in=deg +z_in=ft +xy_out=deg +z_out=us-ft"); } // --------------------------------------------------------------------------- // Use case of https://github.com/OSGeo/PROJ/issues/3938 TEST(operation, compoundCRS_ft_to_geogCRS_ft) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height (ft) auto srcObj = createFromUserInput("EPSG:6318+8228", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("6319")->alterCSLinearUnit( UnitOfMeasure::FOOT); // NAD83(2011) with foot auto res = CoordinateOperationFactory::create()->createOperations( nnSrc, dst, ctxt); ASSERT_TRUE(!res.empty()); EXPECT_EQ( res[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=m " "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=ft " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( res.back()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=noop"); auto resInv = CoordinateOperationFactory::create()->createOperations( dst, nnSrc, ctxt); ASSERT_TRUE(!resInv.empty()); EXPECT_EQ( resInv[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=m " "+step +inv +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=ft " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( resInv.back()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=noop"); } // --------------------------------------------------------------------------- // Use case of https://github.com/OSGeo/PROJ/issues/3938 TEST(operation, compoundCRS_m_to_geogCRS_ft) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height auto srcObj = createFromUserInput("EPSG:6318+5703", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("6319")->alterCSLinearUnit( UnitOfMeasure::FOOT); // NAD83(2011) with foot auto res = CoordinateOperationFactory::create()->createOperations( nnSrc, dst, ctxt); ASSERT_TRUE(!res.empty()); EXPECT_EQ( res[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=ft " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( res.back()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=unitconvert +z_in=m +z_out=ft"); auto resInv = CoordinateOperationFactory::create()->createOperations( dst, nnSrc, ctxt); ASSERT_TRUE(!resInv.empty()); EXPECT_EQ( resInv[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=m " "+step +inv +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( resInv.back()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=unitconvert +z_in=ft +z_out=m"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_geogCRS_with_vertical_unit_change_and_complex_horizontal_change) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 height (ftUS) auto srcObj = createFromUserInput("EPSG:6318+6360", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("7665"); // WGS84(G1762) 3D auto listCompoundToGeog = CoordinateOperationFactory::create()->createOperations(nnSrc, dst, ctxt); // NAD83(2011) + NAVD88 height auto srcObjCompoundVMetre = createFromUserInput( "EPSG:6318+5703", authFactory->databaseContext(), false); auto srcCompoundVMetre = nn_dynamic_pointer_cast(srcObjCompoundVMetre); ASSERT_TRUE(srcCompoundVMetre != nullptr); auto listCompoundMetreToGeog = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCompoundVMetre), dst, ctxt); // Check that we get the same and similar results whether we start from // regular NAVD88 height or its ftUs variant ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size()); ASSERT_GE(listCompoundToGeog.size(), 1U); EXPECT_EQ(listCompoundToGeog[0]->nameStr(), "Conversion from NAVD88 height (ftUS) to NAVD88 height + " + listCompoundMetreToGeog[0]->nameStr()); EXPECT_EQ( listCompoundToGeog[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), replaceAll(listCompoundMetreToGeog[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+step +proj=unitconvert +xy_in=deg +xy_out=rad", "+step +proj=unitconvert +xy_in=deg +z_in=us-ft +xy_out=rad " "+z_out=m")); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_with_height_depth_reversal) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 depth auto srcObj = createFromUserInput("EPSG:6318+6357", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) 3D auto listCompoundToGeog = CoordinateOperationFactory::create()->createOperations(nnSrc, dst, ctxt); ASSERT_TRUE(!listCompoundToGeog.empty()); // NAD83(2011) + NAVD88 height auto srcObjCompoundVMetre = createFromUserInput( "EPSG:6318+5703", authFactory->databaseContext(), false); auto srcCompoundVMetre = nn_dynamic_pointer_cast(srcObjCompoundVMetre); ASSERT_TRUE(srcCompoundVMetre != nullptr); auto listCompoundMetreToGeog = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCompoundVMetre), dst, ctxt); // Check that we get the same and similar results whether we start from // regular NAVD88 height or its depth variant ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size()); EXPECT_EQ(listCompoundToGeog[0]->nameStr(), "Conversion from NAVD88 depth to NAVD88 height + " + listCompoundMetreToGeog[0]->nameStr()); EXPECT_EQ( listCompoundToGeog[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), replaceAll(listCompoundMetreToGeog[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+step +proj=unitconvert +xy_in=deg +xy_out=rad", "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=axisswap +order=1,2,-3")); // Check reverse path auto listGeogToCompound = CoordinateOperationFactory::create()->createOperations(dst, nnSrc, ctxt); EXPECT_EQ(listGeogToCompound.size(), listCompoundToGeog.size()); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_to_geogCRS_with_vertical_unit_change_and_height_depth_reversal) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); // NAD83(2011) + NAVD88 depth (ftUS) auto srcObj = createFromUserInput("EPSG:6318+6358", authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto nnSrc = NN_NO_CHECK(src); auto dst = authFactory->createCoordinateReferenceSystem("6319"); // NAD83(2011) 3D auto listCompoundToGeog = CoordinateOperationFactory::create()->createOperations(nnSrc, dst, ctxt); ASSERT_TRUE(!listCompoundToGeog.empty()); // NAD83(2011) + NAVD88 height auto srcObjCompoundVMetre = createFromUserInput( "EPSG:6318+5703", authFactory->databaseContext(), false); auto srcCompoundVMetre = nn_dynamic_pointer_cast(srcObjCompoundVMetre); ASSERT_TRUE(srcCompoundVMetre != nullptr); auto listCompoundMetreToGeog = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCompoundVMetre), dst, ctxt); // Check that we get the same and similar results whether we start from // regular NAVD88 height or its depth (ftUS) variant ASSERT_EQ(listCompoundToGeog.size(), listCompoundMetreToGeog.size()); EXPECT_EQ(listCompoundToGeog[0]->nameStr(), "Conversion from NAVD88 depth (ftUS) to NAVD88 height + " + listCompoundMetreToGeog[0]->nameStr()); EXPECT_EQ( listCompoundToGeog[0]->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), replaceAll(listCompoundMetreToGeog[0]->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+step +proj=unitconvert +xy_in=deg +xy_out=rad", "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=affine +s33=-0.304800609601219")); // Check reverse path auto listGeogToCompound = CoordinateOperationFactory::create()->createOperations(dst, nnSrc, ctxt); EXPECT_EQ(listGeogToCompound.size(), listCompoundToGeog.size()); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_vertCRS_with_geoid_model_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"NAD83 / Pennsylvania South + NAVD88 height\",\n" " PROJCRS[\"NAD83 / Pennsylvania South\",\n" " BASEGEOGCRS[\"NAD83\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n" " METHOD[\"Lambert Conic Conformal (2SP)\",\n" " ID[\"EPSG\",9802]],\n" " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",-77.75,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard " "parallel\",40.9666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard " "parallel\",39.9333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",600000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"NAVD88 height\",\n" " VDATUM[\"North American Vertical Datum 1988\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " GEOIDMODEL[\"GEOID12B\"]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("4269")->promoteTo3D( std::string(), authFactory->databaseContext()); // NAD83 auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_TRUE(!list.empty()); EXPECT_EQ(list[0]->nameStr(), "Inverse of SPCS83 Pennsylvania South zone (meters) + " "Ballpark geographic offset from NAD83 to NAD83(2011) + " "Inverse of NAD83(2011) to NAVD88 height (1) + " "Ballpark geographic offset from NAD83(2011) to NAD83"); auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ( op_proj, "+proj=pipeline " "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-77.75 " "+lat_1=40.9666666666667 +lat_2=39.9333333333333 +x_0=600000 " "+y_0=0 +ellps=GRS80 " "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_horizCRS_with_TOWGS84_vertCRS_with_geoid_model_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPD_CS[\"NAD83(CSRS) + CGVD28 height - HT2_0\",\n" " GEOGCS[\"NAD83(CSRS)\",\n" " DATUM[\"NAD83_Canadian_Spatial_Reference_System\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6140\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4617\"]],\n" " VERT_CS[\"CGVD28 height - HT2_0\",\n" " VERT_DATUM[\"Canadian Geodetic Vertical Datum of " "1928\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"HT2_0.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5114\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5713\"]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); // NAD83(CSRS) 3D auto dst = authFactory->createCoordinateReferenceSystem("4955"); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_EQ(list.size(), 1U); auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ(op_proj, "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=HT2_0.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_vertCRS_with_geoid_model_by_id_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"NAD83(CSRS) / MTM zone 7 + CGVD28 height\",\n" " PROJCRS[\"NAD83(CSRS) / MTM zone 7\",\n" " BASEGEOGCRS[\"NAD83(CSRS)\",\n" " DATUM[\"North American Datum of 1983 (CSRS)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"MTM zone 7\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-70.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",304800,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (E(X))\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (N(Y))\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"CGVD28 height\",\n" " VDATUM[\"Canadian Geodetic Vertical Datum of 1928\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " GEOIDMODEL[\"HT2_2002v70\",\n" " ID[\"EPSG\",9985]],\n" " ID[\"EPSG\",5713]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("4955")->promoteTo3D( std::string(), authFactory->databaseContext()); // NAD83(CSRS) 3d auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_TRUE(!list.empty()); EXPECT_EQ(list[0]->nameStr(), "Inverse of MTM zone 7 + " "Ballpark geographic offset from NAD83(CSRS) to NAD83(CSRS)v4 + " "Inverse of NAD83(CSRS)v4 to CGVD28 height (1) + " "Ballpark geographic offset from NAD83(CSRS)v4 to NAD83(CSRS)"); } // --------------------------------------------------------------------------- TEST( operation, compoundCRS_of_vertCRS_with_geoid_model_by_name_and_several_records_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"Compound CRS NAD83(2011) / Puerto Rico and Virgin Is. + " "VIVD09 height + PROJ us_noaa_g2012bp0.tif\",\n" " PROJCRS[\"NAD83(2011) / Puerto Rico and Virgin Is.\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",6318]],\n" " CONVERSION[\"SPCS83Puerto Rico & Virgin Islands zone " "(meter)\",\n" " METHOD[\"Lambert Conic Conformal (2SP)\",\n" " ID[\"EPSG\",9802]],\n" " PARAMETER[\"Latitude of false origin\",17.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false " "origin\",-66.4333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard " "parallel\",18.4333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard " "parallel\",18.0333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",200000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",200000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]],\n" " ID[\"EPSG\",15230]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " VERTCRS[\"VIVD09 height + PROJ us_noaa_g2012bp0.tif\",\n" " VDATUM[\"Virgin Islands Vertical Datum of 2009\",\n" " ID[\"EPSG\",1124]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " GEOIDMODEL[\"PROJ us_noaa_g2012bp0.tif\"]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("6319")->promoteTo3D( std::string(), authFactory->databaseContext()); // NAD83(2011) 3d auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_TRUE(!list.empty()); EXPECT_STREQ(list[0]->nameStr().c_str(), "Inverse of SPCS83Puerto Rico & Virgin Islands zone (meter) + " "Transformation from VIVD09 height + " "PROJ us_noaa_g2012bp0.tif to NAD83(2011)"); auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_STREQ(op_proj.c_str(), "+proj=pipeline " "+step +inv +proj=lcc +lat_0=17.8333333333333 " "+lon_0=-66.4333333333333 +lat_1=18.4333333333333 " "+lat_2=18.0333333333333 +x_0=200000 +y_0=200000 +ellps=GRS80 " "+step +proj=vgridshift +grids=us_noaa_g2012bp0.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); ASSERT_EQ(list[0]->domains().size(), 1U); auto domain = list[0]->domains()[0]; ASSERT_TRUE(domain->domainOfValidity() != nullptr); EXPECT_TRUE(domain->domainOfValidity()->description().has_value()); // This is the thing we actually want to check, that the area of use // is the one of Virgin Islands, and not Puerto Rico EXPECT_STREQ( domain->domainOfValidity()->description()->c_str(), "US Virgin Islands - onshore - St Croix, St John, and St Thomas."); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_vertCRS_with_geoid_model_by_name_and_datum_ensemble) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"Compound CRS OSGB36 / British National Grid + ODN " "height + PROJ uk_os_OSGM15_GB.tif\",\n" " PROJCRS[\"OSGB36 / British National Grid\",\n" " BASEGEOGCRS[\"OSGB36\",\n" " DATUM[\"Ordnance Survey of Great Britain 1936\",\n" " ELLIPSOID[\"Airy 1830\",6377563.396,299.3249646,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4277]],\n" " CONVERSION[\"British National Grid\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",49,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9996012717,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",400000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",-100000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Engineering survey, topographic mapping.\"],\n" " AREA[\"United Kingdom (UK) - offshore to boundary of UKCS " "within 49°45'N to 61°N and 9°W to 2°E; onshore Great Britain " "(England, Wales and Scotland). Isle of Man onshore.\"],\n" " BBOX[49.75,-9.01,61.01,2.01]],\n" " ID[\"EPSG\",27700]],\n" " VERTCRS[\"ODN height + PROJ uk_os_OSGM15_GB.tif\",\n" " VDATUM[\"Ordnance Datum Newlyn\",\n" " ID[\"EPSG\",5101]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " GEOIDMODEL[\"PROJ uk_os_OSGM15_GB.tif\"]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem("4936")->promoteTo3D( std::string(), authFactory->databaseContext()); // ETRS89 geocentric auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_TRUE(!list.empty()); auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_STREQ(op_proj.c_str(), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 " "+x_0=400000 +y_0=-100000 +ellps=airy " "+step +proj=hgridshift " "+grids=uk_os_OSTN15_NTv2_OSGBtoETRS.tif " "+step +proj=vgridshift " "+grids=uk_os_OSGM15_GB.tif +multiplier=1 " "+step +proj=cart +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_of_bound_horizCRS_and_bound_vertCRS_to_geogCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "COMPOUNDCRS[\"CH1903 / LV03 + EGM96 height\",\n" " BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"CH1903 / LV03\",\n" " BASEGEOGCRS[\"CH1903\",\n" " DATUM[\"CH1903\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4149]],\n" " CONVERSION[\"Swiss Oblique Mercator 1903M\",\n" " METHOD[\"Hotine Oblique Mercator (variant B)\",\n" " ID[\"EPSG\",9815]],\n" " PARAMETER[\"Latitude of projection " "centre\",46.9524055555556,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection " "centre\",7.43958333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth of initial line\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Angle from Rectified to Skew " "Grid\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8814]],\n" " PARAMETER[\"Scale factor on initial line\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"Easting at projection " "centre\",600000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8816]],\n" " PARAMETER[\"Northing at projection " "centre\",200000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8817]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (Y)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (X)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " ABRIDGEDTRANSFORMATION[\"CH1903 to WGS 84 (2)\",\n" " VERSION[\"BfL-CH 2\"],\n" " METHOD[\"Geocentric translations (geog2D domain)\",\n" " ID[\"EPSG\",9603]],\n" " PARAMETER[\"X-axis translation\",674.374,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",15.056,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",405.346,\n" " ID[\"EPSG\",8607]]]],\n" " BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"EGM96 height\",\n" " VDATUM[\"EGM96 geoid\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Geodesy.\"],\n" " AREA[\"World.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",5773]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"WGS 84 to EGM96 height (1)\",\n" " METHOD[\"Geographic3D to GravityRelatedHeight (EGM)\",\n" " ID[\"EPSG\",9661]],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"us_nga_egm96_15.tif\"]]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); // WGS 84 3D auto dst = authFactory->createCoordinateReferenceSystem("4979"); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_EQ(list.size(), 1U); auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ(op_proj, "+proj=pipeline " "+step +inv +proj=somerc +lat_0=46.9524055555556 " "+lon_0=7.43958333333333 +k_0=1 " "+x_0=600000 +y_0=200000 +ellps=bessel " "+step +proj=push +v_3 " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.374 +y=15.056 +z=405.346 " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_from_WKT2_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto src = authFactory->createCoordinateReferenceSystem( "7415"); // Amersfoort / RD New + NAP height auto dst = authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_GE(list.size(), 1U); auto wkt2 = src->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); auto obj = WKTParser().createFromWKT(wkt2); auto src_from_wkt2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(src_from_wkt2 != nullptr); auto list2 = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src_from_wkt2), dst, ctxt); ASSERT_GE(list.size(), list2.size()); for (size_t i = 0; i < list.size(); i++) { const auto &op = list[i]; const auto &op2 = list2[i]; EXPECT_TRUE( op->isEquivalentTo(op2.get(), IComparable::Criterion::EQUIVALENT)); } } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_from_WKT2_no_id_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto src = authFactory->createCoordinateReferenceSystem( "7415"); // Amersfoort / RD New + NAP height auto dst = authFactory->createCoordinateReferenceSystem("4937"); // ETRS89 3D auto list = CoordinateOperationFactory::create()->createOperations(src, dst, ctxt); ASSERT_GE(list.size(), 1U); { auto op_proj = list[0]->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ( op_proj, "+proj=pipeline +step +inv +proj=sterea +lat_0=52.1561605555556 " "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 " "+ellps=bessel " "+step +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif " "+step +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } auto wkt2 = "COMPOUNDCRS[\"unknown\",\n" " PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"Amersfoort\",\n" " DATUM[\"Amersfoort\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Oblique Stereographic\"],\n" " PARAMETER[\"Latitude of natural origin\",52.1561605555556],\n" " PARAMETER[\"Longitude of natural origin\",5.38763888888889],\n" " PARAMETER[\"Scale factor at natural origin\",0.9999079],\n" " PARAMETER[\"False easting\",155000],\n" " PARAMETER[\"False northing\",463000]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " LENGTHUNIT[\"metre\",1]],\n" " VERTCRS[\"NAP height\",\n" " VDATUM[\"Normaal Amsterdams Peil\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]],\n" " USAGE[\n" " SCOPE[\"unknown\"],\n" " AREA[\"Netherlands - onshore\"],\n" " BBOX[50.75,3.2,53.7,7.22]]]"; auto obj = WKTParser().createFromWKT(wkt2); auto src_from_wkt2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(src_from_wkt2 != nullptr); auto list2 = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src_from_wkt2), dst, ctxt); ASSERT_EQ(list.size(), list2.size()); for (size_t i = 0; i < list.size(); i++) { const auto &op = list[i]; const auto &op2 = list2[i]; auto op_proj = op->exportToPROJString(PROJStringFormatter::create().get()); auto op2_proj = op2->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ(op_proj, op2_proj) << "op=" << op->nameStr() << " op2=" << op2->nameStr(); } } // --------------------------------------------------------------------------- #ifdef requires_epsg_12_054 TEST(operation, compoundCRS_to_geogCRS_3D_Amersfoort_NAP_height_to_Amersfoort) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setAllowBallparkTransformations(false); auto list = CoordinateOperationFactory::create()->createOperations( // Amersfoort / RD + NAP height authFactory->createCoordinateReferenceSystem("7415"), // Amersfoort promoted to 3D authFactory->createCoordinateReferenceSystem("4289")->promoteTo3D( std::string(), dbContext), ctxt); ASSERT_EQ(list.size(), 1U); // I'm not sure this is absolutely correct, but we do not certainly // a purely 2D grid-based horizontal transformation to be used when // going back from ETRS89 to Amersfoort. EXPECT_EQ(list[0]->nameStr(), "Inverse of RD + " "Inverse of ETRS89-NLD [AGRS2010] to NAP height (2) " "using Amersfoort to ETRS89-NLD [AGRS2010] (8)"); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=sterea +lat_0=52.1561605555556 " "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 " "+ellps=bessel " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1 " "+step +proj=push +v_1 +v_2 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=565.7381 +y=50.4018 +z=465.2904 " "+rx=0.395025981036064 +ry=-0.330772431242031 +rz=1.87607329462821 " "+s=4.07244 +convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 " "+step +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif +multiplier=1 " "+step +proj=cart +ellps=GRS80 " "+step +inv +proj=helmert +x=565.7381 +y=50.4018 +z=465.2904 " "+rx=0.395025981036064 +ry=-0.330772431242031 +rz=1.87607329462821 " "+s=4.07244 +convention=coordinate_frame " "+step +inv +proj=cart +ellps=bessel " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1 " "+step +proj=pop +v_1 +v_2"); } #endif // --------------------------------------------------------------------------- TEST(operation, proj3DCRS_with_non_meter_horiz_and_vertical_to_geog) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=31 +datum=WGS84 +units=us-ft +vunits=us-ft +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 1U); // Check that vertical unit conversion is done just once EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +z_in=us-ft " "+xy_out=m +z_out=m " "+step +inv +proj=utm +zone=31 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_non_meter_horiz_and_vertical_to_geog) { auto objSrc = WKTParser().createFromWKT( "COMPOUNDCRS[\"unknown\",\n" " PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16031]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n" " ID[\"EPSG\",9003]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n" " ID[\"EPSG\",9003]]]],\n" " VERTCRS[\"unknown\",\n" " VDATUM[\"unknown\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219,\n" " ID[\"EPSG\",9003]]]]]" ); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), authFactory->createCoordinateReferenceSystem("4979"), ctxt); ASSERT_EQ(list.size(), 1U); // Check that vertical unit conversion is done just once EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m " "+step +inv +proj=utm +zone=31 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +z_in=us-ft " "+xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_geogCRS_3D_using_intermediate_of_horizontal_transform) { // Scenario of https://github.com/OSGeo/PROJ/issues/4618 auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse::KNOWN_AVAILABLE); // "PNG94 / PNGMG94 zone 54 + Kumul 34 height" auto objSrc = createFromUserInput("EPSG:5550+7651", dbContext); auto srcCRS = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(srcCRS != nullptr); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(srcCRS), // WGS 84 (G2139) (3D) authFactoryEPSG->createCoordinateReferenceSystem("9754"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "Inverse of Papua New Guinea Map Grid 1994 zone 54 + " "PNG94 to WGS 84 (1) + " "Inverse of EGM96 height to Kumul 34 height (1) + " "Inverse of WGS 84 to EGM96 height (1) + " "WGS 84 to WGS 84 (G2139)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=54 +south +ellps=GRS80 " "+step +proj=geogoffset +dh=0.87 " "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_compoundCRS) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx " "+geoid_crs=horizontal_crs " "+type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=@foo.gsb " "+step +inv +proj=hgridshift +grids=@bar.gsb " "+step +inv +proj=vgridshift +grids=@bar.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); auto opInverse = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src)); ASSERT_TRUE(opInverse != nullptr); EXPECT_TRUE(opInverse->inverse()->_isEquivalentTo(op.get())); } // --------------------------------------------------------------------------- TEST(operation, boundCRS_to_compoundCRS_with_hubCRS_same_as_compound_geographicCRS) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"CH1903 / LV03\",\n" " BASEGEOGCRS[\"CH1903\",\n" " DATUM[\"CH1903\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6149]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Hotine Oblique Mercator (variant B)\",\n" " ID[\"EPSG\",9815]],\n" " PARAMETER[\"Latitude of projection " "centre\",46.9524055555556,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection " "centre\",7.43958333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth of initial line\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Angle from Rectified to Skew Grid\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8814]],\n" " PARAMETER[\"Scale factor on initial line\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"Easting at projection centre\",600000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8816]],\n" " PARAMETER[\"Northing at projection centre\",200000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8817]]],\n" " CS[Cartesian,3],\n" " AXIS[\"y\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"x\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"Transformation from CH1903 to WGS84\",\n" " METHOD[\"Position Vector transformation (geog2D domain)\",\n" " ID[\"EPSG\",9606]],\n" " PARAMETER[\"X-axis translation\",674.4,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",15.1,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",405.3,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",0,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",0,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",0,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",1,\n" " ID[\"EPSG\",8611]]]]"; auto srcObj = createFromUserInput(wkt, authFactory->databaseContext(), false); auto src = nn_dynamic_pointer_cast(srcObj); ASSERT_TRUE(src != nullptr); auto dst = authFactory->createCoordinateReferenceSystem( "9518"); // "WGS 84 + EGM2008 height" auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), dst, ctxt); ASSERT_GE(list.size(), 1U); // Check that BoundCRS helmert transformation is used EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=somerc +lat_0=46.9524055555556 " "+lon_0=7.43958333333333 +k_0=1 " "+x_0=600000 +y_0=200000 +ellps=bessel " "+step +proj=cart +ellps=bessel " "+step +proj=helmert +x=674.4 +y=15.1 +z=405.3 +rx=0 +ry=0 +rz=0 " "+s=0 +convention=position_vector " "+step +inv +proj=cart +ellps=WGS84 " "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, IGNF_LAMB1_TO_EPSG_4326) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::ALWAYS); auto list = CoordinateOperationFactory::create()->createOperations( AuthorityFactory::create(DatabaseContext::create(), "IGNF") ->createCoordinateReferenceSystem("LAMB1"), AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 " "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 " "+ellps=clrk80ign +pm=paris +step +proj=hgridshift " "+grids=fr_ign_ntf_r93.tif +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); EXPECT_FALSE(list[1]->hasBallparkTransformation()); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 " "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 " "+ellps=clrk80ign +pm=paris +step +proj=push +v_3 +step " "+proj=cart +ellps=clrk80ign +step +proj=helmert +x=-168 +y=-60 " "+z=320 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step " "+proj=axisswap +order=2,1"); auto list2 = CoordinateOperationFactory::create()->createOperations( AuthorityFactory::create(DatabaseContext::create(), "EPSG") // NTF (Paris) / Lambert Nord France equivalent to IGNF:LAMB1 ->createCoordinateReferenceSystem("27561"), AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_GE(list2.size(), 3U); EXPECT_EQ(replaceAll(list2[0]->exportToPROJString( PROJStringFormatter::create().get()), "0.999877341", "0.99987734"), list[0]->exportToPROJString(PROJStringFormatter::create().get())); // The second entry in list2 (list2[1]) uses the // weird +pm=2.33720833333333 from "NTF (Paris) to NTF (2)" // so skip to the 3th method EXPECT_EQ(replaceAll(list2[2]->exportToPROJString( PROJStringFormatter::create().get()), "0.999877341", "0.99987734"), list[1]->exportToPROJString(PROJStringFormatter::create().get())); } // --------------------------------------------------------------------------- TEST(operation, NAD83_to_projected_CRS_based_on_NAD83_2011) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( // NAD83 authFactoryEPSG->createCoordinateReferenceSystem("4269"), // NAD83(2011) / California Albers authFactoryEPSG->createCoordinateReferenceSystem("6414"), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "NAD83 to NAD83(HARN) (47) + " "NAD83(HARN) to NAD83(FBN) (1) + " "NAD83(FBN) to NAD83(NSRS2007) (1) + " "NAD83(NSRS2007) to NAD83(2011) (1) + " "California Albers"); EXPECT_EQ(list[1]->nameStr(), "Ballpark geographic offset from NAD83 to " "NAD83(2011) + California Albers"); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=aea +lat_0=0 +lon_0=-120 +lat_1=34 " "+lat_2=40.5 +x_0=0 +y_0=-4000000 +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST(operation, isPROJInstantiable) { { auto transformation = Transformation::createGeocentricTranslations( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, {}); EXPECT_TRUE(transformation->isPROJInstantiable( DatabaseContext::create(), false)); } // Missing grid { auto transformation = Transformation::createNTv2( PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326, "foo.gsb", std::vector()); EXPECT_FALSE(transformation->isPROJInstantiable( DatabaseContext::create(), false)); } // Unsupported method { auto transformation = Transformation::create( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, nullptr, OperationMethod::create(PropertyMap(), std::vector{}), std::vector{}, std::vector{}); EXPECT_FALSE(transformation->isPROJInstantiable( DatabaseContext::create(), false)); } } // --------------------------------------------------------------------------- TEST(operation, createOperation_on_crs_with_canonical_bound_crs) { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4267, std::vector{1, 2, 3, 4, 5, 6, 7}); auto crs = boundCRS->baseCRSWithCanonicalBoundCRS(); { auto op = CoordinateOperationFactory::create()->createOperation( crs, GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_TRUE(op->isEquivalentTo(boundCRS->transformation().get())); { auto wkt1 = op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019) .get()); auto wkt2 = boundCRS->transformation()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019) .get()); EXPECT_EQ(wkt1, wkt2); } } { auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, crs); ASSERT_TRUE(op != nullptr); EXPECT_TRUE( op->isEquivalentTo(boundCRS->transformation()->inverse().get())); { auto wkt1 = op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019) .get()); auto wkt2 = boundCRS->transformation()->inverse()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019) .get()); EXPECT_EQ(wkt1, wkt2); } } } // --------------------------------------------------------------------------- TEST(operation, createOperation_fallback_to_proj4_strings) { auto objDest = PROJStringParser().createFromPROJString( "+proj=longlat +over +datum=WGS84 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=longlat +over +datum=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_fallback_to_proj4_strings_bound_of_geog) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +over +ellps=GRS80 +towgs84=0,0,0 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=longlat +over +ellps=clrk66 +towgs84=0,0,0 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=longlat +over +ellps=GRS80 +towgs84=0,0,0 " "+step +proj=longlat +over +ellps=clrk66 +towgs84=0,0,0 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_fallback_to_proj4_strings_regular_with_datum_to_projliteral) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=11 +datum=NAD27 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +over +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=11 +datum=NAD27 " "+step +proj=longlat +datum=WGS84 +over " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_fallback_to_proj4_strings_proj_NAD83_to_projliteral) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=11 +datum=NAD83 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +over +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=11 +ellps=GRS80 " "+step +proj=longlat +datum=WGS84 +over " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_fallback_to_proj4_strings_geog_NAD83_to_projliteral) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +datum=NAD83 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +over +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=longlat +datum=WGS84 +over " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_fallback_to_proj4_strings_regular_with_nadgrids_to_projliteral) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=11 +ellps=clrk66 +nadgrids=@conus +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +over +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=11 +ellps=clrk66 +nadgrids=@conus " "+step +proj=longlat +datum=WGS84 +over " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_fallback_to_proj4_strings_projliteral_to_projliteral) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=11 +datum=NAD27 +over +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +over +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=11 +datum=NAD27 +over " "+step +proj=longlat +datum=WGS84 +over " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_fallback_to_proj4_strings_regular_to_projliteral_with_towgs84) { auto objSrc = createFromUserInput("EPSG:4326", DatabaseContext::create(), false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=utm +zone=31 +ellps=GRS80 +towgs84=1,2,3 +over +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=31 +ellps=GRS80 +towgs84=1,2,3 +over"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_on_crs_with_bound_crs_and_wktext) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 " "+units=m +no_defs +nadgrids=@GDA94_GDA2020_conformal.gsb +ignored1 " "+ignored2=val +wktext +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 " "+units=m +no_defs +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=55 +south " "+ellps=GRS80 +step +proj=hgridshift " "+grids=@GDA94_GDA2020_conformal.gsb +step +proj=utm +zone=55 " "+south +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_fallback_to_proj4_strings_with_axis_inverted_projCRS) { auto objSrc = createFromUserInput("EPSG:2193", DatabaseContext::create(), false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=WGS84 +lon_wrap=180 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +inv +proj=tmerc +lat_0=0 +lon_0=173 +k=0.9996 " "+x_0=1600000 +y_0=10000000 +ellps=GRS80 " "+step +proj=longlat +ellps=WGS84 +lon_wrap=180 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_proj_string_with_non_metre_height) { auto objSrc = createFromUserInput("EPSG:6318+5703", DatabaseContext::create(), false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +vunits=us-ft +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GT(list.size(), 1U); // What is important to check here is the vertical unit conversion EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=us-ft"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_with_derived_vertical_CRS) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, "EPSG"); // ETRS89 + EGM2008 height auto objSrc = createFromUserInput("EPSG:4258+3855", dbContext, false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto wkt = "COMPOUNDCRS[\"WGS 84 + Custom Vertical\",\n" " GEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " VERTCRS[\"Custom Vertical\",\n" " BASEVERTCRS[\"EGM2008 height\",\n" " VDATUM[\"EGM2008 geoid\"]],\n" " DERIVINGCONVERSION[\"vertical offs. and slope\",\n" " METHOD[\"Vertical Offset and Slope\",\n" " ID[\"EPSG\",1046]],\n" " PARAMETER[\"Ordinate 1 of evaluation point\",47,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8617]],\n" " PARAMETER[\"Ordinate 2 of evaluation point\",8,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8618]],\n" " PARAMETER[\"Vertical Offset\",-0.245,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8603]],\n" " PARAMETER[\"Inclination in latitude\",-0.21,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8730]],\n" " PARAMETER[\"Inclination in longitude\",-0.032,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8731]],\n" " PARAMETER[\"EPSG code for Horizontal CRS\",4326,\n" " ID[\"EPSG\",1037]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"unknown\"],\n" " AREA[\"World\"],\n" " BBOX[-90,-180,90,180]]]]"; auto objDst = createFromUserInput(wkt, dbContext, false); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_EQ(list.size(), 1U); EXPECT_EQ(list[0]->nameStr(), "ETRS89 to WGS 84 (1) + vertical offs. and slope"); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vertoffset +lat_0=47 +lon_0=8 +dh=-0.245 +slope_lat=-0.21 " "+slope_lon=-0.032 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, compoundCRS_to_PROJJSON_with_non_metre_height) { auto srcPROJJSON = "{\n" " \"$schema\": " "\"https://proj.org/schemas/v0.2/projjson.schema.json\",\n" " \"type\": \"CompoundCRS\",\n" " \"name\": \"Compound CRS NAD83(2011) / Nebraska (ftUS) + North " "American Vertical Datum 1988 + PROJ us_noaa_g2012bu0.tif\",\n" " \"components\": [\n" " {\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"NAD83(2011) / Nebraska (ftUS)\",\n" " \"base_crs\": {\n" " \"name\": \"NAD83(2011)\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"NAD83 (National Spatial Reference System " "2011)\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6318\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"SPCS83 Nebraska zone (US Survey feet)\",\n" " \"method\": {\n" " \"name\": \"Lambert Conic Conformal (2SP)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9802\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of false origin\",\n" " \"value\": 39.8333333333333,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8821\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of false origin\",\n" " \"value\": -100,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8822\n" " }\n" " },\n" " {\n" " \"name\": \"Latitude of 1st standard parallel\",\n" " \"value\": 43,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8823\n" " }\n" " },\n" " {\n" " \"name\": \"Latitude of 2nd standard parallel\",\n" " \"value\": 40,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8824\n" " }\n" " },\n" " {\n" " \"name\": \"Easting at false origin\",\n" " \"value\": 1640416.6667,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8826\n" " }\n" " },\n" " {\n" " \"name\": \"Northing at false origin\",\n" " \"value\": 0,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8827\n" " }\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 15396\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"X\",\n" " \"direction\": \"east\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"Y\",\n" " \"direction\": \"north\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " }\n" " ]\n" " }\n" " },\n" " {\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"North American Vertical Datum 1988 + PROJ " "us_noaa_g2012bu0.tif\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"North American Vertical Datum 1988\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 5103\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " }\n" " ]\n" " },\n" " \"geoid_model\": {\n" " \"name\": \"PROJ us_noaa_g2012bu0.tif\",\n" " \"interpolation_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"NAD83(2011)\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"NAD83 (National Spatial Reference System " "2011)\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6319\n" " }\n" " }\n" " }\n" " }\n" " ]\n" "}"; auto objSrc = createFromUserInput(srcPROJJSON, DatabaseContext::create(), false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); // The untypical potentially a bit buggy thing (and what caused a bug) // is the US-ft unit for the vertical axis of the base CRS ... // When outputting that to WKT, and // re-exporting to PROJJSON, one gets metre, which conforms more to the // official definition of NAD83(2011) 3D. // The vertical unit of the base CRS shouldn't matter much anyway, so this // is valid. auto dstPROJJSON = "{\n" " \"$schema\": " "\"https://proj.org/schemas/v0.2/projjson.schema.json\",\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"Projected CRS NAD83(2011) / UTM zone 14N with " "ellipsoidal NAD83(2011) height\",\n" " \"base_crs\": {\n" " \"name\": \"NAD83(2011)\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"NAD83 (National Spatial Reference System 2011)\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1116\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"UTM zone 14N\",\n" " \"method\": {\n" " \"name\": \"Transverse Mercator\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9807\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of natural origin\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8801\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of natural origin\",\n" " \"value\": -99,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8802\n" " }\n" " },\n" " {\n" " \"name\": \"Scale factor at natural origin\",\n" " \"value\": 0.9996,\n" " \"unit\": \"unity\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8805\n" " }\n" " },\n" " {\n" " \"name\": \"False easting\",\n" " \"value\": 500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8806\n" " }\n" " },\n" " {\n" " \"name\": \"False northing\",\n" " \"value\": 0,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8807\n" " }\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 16014\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"US survey foot\",\n" " \"conversion_factor\": 0.304800609601219\n" " }\n" " }\n" " ]\n" " }\n" "}"; auto objDst = createFromUserInput(dstPROJJSON, DatabaseContext::create(), false); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GT(list.size(), 1U); // What is important to check here is the vertical unit conversion EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m " "+step +inv +proj=lcc +lat_0=39.8333333333333 +lon_0=-100 +lat_1=43 " "+lat_2=40 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=vgridshift +grids=us_noaa_g2012bu0.tif +multiplier=1 " "+step +proj=utm +zone=14 +ellps=GRS80 " "+step +proj=unitconvert +xy_in=m +z_in=m +xy_out=us-ft +z_out=us-ft"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_ossfuzz_18587) { auto objSrc = createFromUserInput("EPSG:4326", DatabaseContext::create(), false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); // Extremely weird string ! We should likely reject it auto objDst = PROJStringParser().createFromPROJString( "type=crs proj=pipeline step proj=merc vunits=m nadgrids=@x " "proj=\"\nproj=pipeline step\n\""); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); // Just check that we don't go into an infinite recursion try { CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); } catch (const std::exception &) { } } // --------------------------------------------------------------------------- class derivedGeographicCRS_with_to_wgs84_to_geographicCRS : public ::testing::Test { public: ~derivedGeographicCRS_with_to_wgs84_to_geographicCRS() override; protected: void run(const CRSNNPtr &src) { auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( src, NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); std::string pipeline( "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=ob_tran +o_proj=latlon +over +lat_0=0 " "+lon_0=180 " "+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 " "+step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=1 +y=2 +z=3 " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), pipeline); auto op2 = CoordinateOperationFactory::create()->createOperation( src, nn_static_pointer_cast(GeographicCRS::EPSG_4326)); ASSERT_TRUE(op2 != nullptr); EXPECT_EQ( op2->exportToPROJString(PROJStringFormatter::create().get()), pipeline + " +step +proj=axisswap +order=2,1"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(dst), src); ASSERT_TRUE(op != nullptr); std::string pipeline( "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 " "+step +proj=helmert +x=-1 +y=-2 +z=-3 " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=ob_tran +o_proj=latlon +over +lat_0=0 +lon_0=180 " "+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " + pipeline); auto op2 = CoordinateOperationFactory::create()->createOperation( nn_static_pointer_cast(GeographicCRS::EPSG_4326), src); ASSERT_TRUE(op2 != nullptr); EXPECT_EQ( op2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " + pipeline); } } }; derivedGeographicCRS_with_to_wgs84_to_geographicCRS:: ~derivedGeographicCRS_with_to_wgs84_to_geographicCRS() = default; // --------------------------------------------------------------------------- TEST_F(derivedGeographicCRS_with_to_wgs84_to_geographicCRS, src_from_proj) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 +o_lat_p=18.0 " "+o_lon_p=-200.0 +ellps=WGS84 +towgs84=1,2,3 +over +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); run(NN_CHECK_ASSERT(src)); } // --------------------------------------------------------------------------- TEST_F(derivedGeographicCRS_with_to_wgs84_to_geographicCRS, src_from_wkt2) { // Same as above, but testing with a WKT CRS source // The subtle difference is that the base CRS of the DerivedGeographicCRS // will have a lat, long axis order auto objSrcProj = PROJStringParser().createFromPROJString( "+proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 +o_lat_p=18.0 " "+o_lon_p=-200.0 +ellps=WGS84 +towgs84=1,2,3 +over +type=crs"); auto srcFromProj = nn_dynamic_pointer_cast(objSrcProj); ASSERT_TRUE(srcFromProj != nullptr); auto srcWkt = srcFromProj->exportToWKT(WKTFormatter::create().get()); auto objSrc = createFromUserInput(srcWkt, DatabaseContext::create(), false); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); run(NN_CHECK_ASSERT(src)); } // --------------------------------------------------------------------------- TEST(operation, createOperation_spherical_ocentric_to_geographic) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +datum=WGS84 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), GeographicCRS::EPSG_4326); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_geographic_to_spherical_ocentric) { auto objDest = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +datum=WGS84 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=geoc +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_spherical_ocentric_to_geocentric) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +datum=WGS84 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=geocent +datum=WGS84 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +ellps=WGS84 " "+step +proj=cart +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_bound_of_spherical_ocentric_to_same_type) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +ellps=GRS80 +towgs84=1,2,3 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +ellps=clrk66 +towgs84=4,5,6 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +ellps=GRS80 " "+step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=-3 +y=-3 +z=-3 " "+step +inv +proj=cart +ellps=clrk66 " "+step +proj=pop +v_3 " "+step +proj=geoc +ellps=clrk66 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_spherical_ocentric_to_projected) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +datum=WGS84 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=utm +zone=11 +datum=WGS84 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +ellps=WGS84 " "+step +proj=utm +zone=11 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_spherical_ocentric_to_projected_of_spherical_ocentric) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=longlat +geoc +datum=WGS84 +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = PROJStringParser().createFromPROJString( "+proj=utm +geoc +zone=11 +datum=WGS84 +type=crs"); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +ellps=WGS84 " "+step +proj=utm +zone=11 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_spherical_ocentric_spherical_to_ellipsoidal_north_west) { auto objSrc = WKTParser().createFromWKT( "GEODCRS[\"Mercury (2015) - Sphere / Ocentric\",\n" " DATUM[\"Mercury (2015) - Sphere\",\n" " ELLIPSOID[\"Mercury (2015) - Sphere\",2440530,0,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[spherical,2],\n" " AXIS[\"planetocentric latitude (U)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"planetocentric longitude (V)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = WKTParser().createFromWKT( "GEOGCRS[\"Mercury (2015) / Ographic\",\n" " DATUM[\"Mercury (2015)\",\n" " ELLIPSOID[\"Mercury (2015)\",2440530,1075.12334801762,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",west,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]" ); auto dest = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dest != nullptr); { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dest)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=1,-2"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(dest), NN_CHECK_ASSERT(src)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=1,-2"); } } // --------------------------------------------------------------------------- TEST( operation, createOperation_ellipsoidal_ographic_west_to_projected_of_ellipsoidal_ographic_west) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto op = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19901"), authFactory->createCoordinateReferenceSystem("19911")); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=-2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260 " "+step +proj=axisswap +order=-1,2"); // Inverse auto op2 = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19911"), authFactory->createCoordinateReferenceSystem("19901")); ASSERT_TRUE(op2 != nullptr); EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=axisswap +order=-1,2 " "+step +inv +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,-1"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_ellipsoidal_ographic_west_to_projected_of_ellipsoidal_ocentric) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto op = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19901"), authFactory->createCoordinateReferenceSystem("19912")); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=-2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260"); // Inverse auto op2 = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19912"), authFactory->createCoordinateReferenceSystem("19901")); ASSERT_TRUE(op2 != nullptr); EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,-1"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_ellipsoidal_ocentric_to_projected_of_ellipsoidal_ocentric) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto op = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19902"), authFactory->createCoordinateReferenceSystem("19912")); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +a=2440530 +b=2438260 " "+step +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260"); // Inverse auto op2 = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19912"), authFactory->createCoordinateReferenceSystem("19902")); ASSERT_TRUE(op2 != nullptr); EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260 " "+step +proj=geoc +a=2440530 +b=2438260 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_ellipsoidal_ocentric_to_projected_of_ellipsoidal_ographic_west) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); auto op = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19902"), authFactory->createCoordinateReferenceSystem("19911")); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=geoc +a=2440530 +b=2438260 " "+step +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260 " "+step +proj=axisswap +order=-1,2"); // Inverse auto op2 = CoordinateOperationFactory::create()->createOperation( authFactory->createCoordinateReferenceSystem("19911"), authFactory->createCoordinateReferenceSystem("19902")); ASSERT_TRUE(op2 != nullptr); EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=axisswap +order=-1,2 " "+step +inv +proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+a=2440530 +b=2438260 " "+step +proj=geoc +a=2440530 +b=2438260 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_ossfuzz_47873) { auto objSrc = PROJStringParser().createFromPROJString( "+proj=ob_tran +o_proj=longlat +o_lat_1=1 +o_lat_2=2 +datum=WGS84 " "+geoidgrids=@x +geoid_crs=horizontal_crs +type=crs"); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDst = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +geoidgrids=@y +type=crs"); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); // Just check that we don't go into an infinite recursion try { CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst)); } catch (const std::exception &) { } } // --------------------------------------------------------------------------- TEST(operation, createOperation_ossfuzz_47873_simplified_if_i_might_say) { auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"unknown\",\n" " VDATUM[\"unknown using geoidgrids=@x\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"unnamed\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " DERIVINGCONVERSION[\"unknown\",\n" " METHOD[\"PROJ ob_tran o_proj=longlat\"],\n" " PARAMETER[\"o_lat_1\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"o_lat_2\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " ABRIDGEDTRANSFORMATION[\"unknown to unnamed ellipsoidal " "height\",\n" " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"@x\",\n" " ID[\"EPSG\",8666]]]]"; auto objSrc = WKTParser().createFromWKT(wkt); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto dst = authFactory->createCoordinateReferenceSystem("4979"); // Just check that we don't go into an infinite recursion try { CoordinateOperationFactory::create()->createOperation( NN_CHECK_ASSERT(src), dst); } catch (const std::exception &) { } } // --------------------------------------------------------------------------- TEST(operation, createOperation_derived_projected_crs) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto src = authFactory->createCoordinateReferenceSystem("6507"); auto wkt = "DERIVEDPROJCRS[\"Custom Site Calibrated CRS\",\n" " BASEPROJCRS[\"NAD83(2011) / Mississippi East (ftUS)\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 Mississippi East zone (US Survey " "feet)\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",29.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural " "origin\",-88.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.99995,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",984250,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8807]]]],\n" " DERIVINGCONVERSION[\"Affine transformation as PROJ-based\",\n" " METHOD[\"PROJ-based operation method: " "+proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=us-ft " "+step +proj=affine +xoff=20 " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m\"]],\n" " CS[Cartesian,2],\n" " AXIS[\"northing (Y)\",north,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"easting (X)\",east,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " REMARK[\"EPSG:6507 with 20 feet offset and axis inversion\"]]"; auto objDst = WKTParser().createFromWKT(wkt); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( src, NN_NO_CHECK(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=affine +xoff=20 " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, geogCRS_to_compoundCRS_with_boundVerticalCRS_and_derivedProjected) { auto wkt = "DERIVEDPROJCRS[\"Custom Site Calibrated CRS\",\n" " BASEPROJCRS[\"NAD83(2011) / Mississippi East (ftUS)\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 Mississippi East zone (US Survey " "feet)\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",29.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural " "origin\",-88.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.99995,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",984250,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8807]]]],\n" " DERIVINGCONVERSION[\"Affine transformation as PROJ-based\",\n" " METHOD[\"PROJ-based operation method: " "+proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=us-ft " "+step +proj=affine +xoff=20 " "+step +proj=unitconvert +xy_in=us-ft +xy_out=m\"]],\n" " CS[Cartesian,2],\n" " AXIS[\"northing (Y)\",north,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"easting (X)\",east,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " REMARK[\"EPSG:6507 with 20 feet offset and axis inversion\"]]"; auto objDst = WKTParser().createFromWKT(wkt); auto dst = nn_dynamic_pointer_cast(objDst); ASSERT_TRUE(dst != nullptr); auto compound = CompoundCRS::create( PropertyMap(), std::vector{NN_NO_CHECK(dst), createBoundVerticalCRS()}); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4979, compound); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift +grids=us_nga_egm08_25.tif " "+multiplier=1 " "+step +proj=tmerc +lat_0=29.5 +lon_0=-88.8333333333333 " "+k=0.99995 +x_0=300000 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +xy_in=m +xy_out=us-ft " "+step +proj=affine +xoff=20 " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_geog2D) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "NAD83(CSRS)v7" auto crs = factory->createCoordinateReferenceSystem("8255"); auto crs_2002 = CoordinateMetadata::create(crs, 2002.0, dbContext); auto crs_2010 = CoordinateMetadata::create(crs, 2010.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2002, crs_2010, ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2002 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_fwd " "+step +proj=deformation +dt=8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2010 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1 " "+step +proj=set +v_4=2010"); EXPECT_TRUE(list[1]->hasBallparkTransformation()); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_geog3D) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "NAD83(CSRS)v7" auto crs = factory->createCoordinateReferenceSystem("8254"); auto crs_2002 = CoordinateMetadata::create(crs, 2002.0, dbContext); auto crs_2010 = CoordinateMetadata::create(crs, 2010.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2002, crs_2010, ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_fwd " "+step +proj=deformation +dt=8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2010 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); EXPECT_TRUE(list[1]->hasBallparkTransformation()); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_geocentric) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "NAD83(CSRS)v7" auto crs = factory->createCoordinateReferenceSystem("8253"); auto crs_2002 = CoordinateMetadata::create(crs, 2002.0, dbContext); auto crs_2010 = CoordinateMetadata::create(crs, 2010.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2002, crs_2010, ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2002 " "+step +proj=deformation +dt=8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2010"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2010 " "+step +proj=deformation +dt=-8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2002"); EXPECT_TRUE(list[1]->hasBallparkTransformation()); EXPECT_EQ( list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=set +v_4=2002 +step +proj=set +v_4=2010"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_geocentric_to_geog3D) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "NAD83(CSRS)v7" auto crs_geocentric = factory->createCoordinateReferenceSystem("8253"); auto crs_2002 = CoordinateMetadata::create(crs_geocentric, 2002.0, dbContext); auto crs_geog3d = factory->createCoordinateReferenceSystem("8254"); auto crs_2010 = CoordinateMetadata::create(crs_geog3d, 2010.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2002, crs_2010, ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2002 " "+step +proj=deformation +dt=8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2010 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1 " "+step +proj=set +v_4=2010"); EXPECT_TRUE(list[1]->hasBallparkTransformation()); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_NAD83_CSRS_v7_TO_ITRF2014) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "NAD83(CSRS)v7" auto sourceCRS = factory->createCoordinateReferenceSystem("8254"); auto crs_2002 = CoordinateMetadata::create(sourceCRS, 2002.0, dbContext); // ITRF2014 auto targetCRS = factory->createCoordinateReferenceSystem("7912"); auto crs_2005 = CoordinateMetadata::create(targetCRS, 2005.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2002, crs_2005, ctxt); ASSERT_GE(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2002 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_fwd " "+step +proj=deformation +dt=3 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2005 +omit_inv " "+step +inv +proj=helmert " "+x=1.0053 +y=-1.90921 +z=-0.54157 +rx=-0.02678138 " "+ry=0.00042027 +rz=-0.01093206 +s=0.00036891 +dx=0.00079 +dy=-0.0006 " "+dz=-0.00144 +drx=-6.667e-05 +dry=0.00075744 +drz=5.133e-05 " "+ds=-7.201e-05 +t_epoch=2010 +convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1 " "+step +proj=set +v_4=2005"); EXPECT_TRUE(list[1]->hasBallparkTransformation()); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_ITRF2014_to_NAD83_CSRS_v7) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // ITRF2014 auto sourceCRS = factory->createCoordinateReferenceSystem("7912"); auto crs_2005 = CoordinateMetadata::create(sourceCRS, 2005.0, dbContext); // "NAD83(CSRS)v7" auto targetCRS = factory->createCoordinateReferenceSystem("8254"); auto crs_2002 = CoordinateMetadata::create(targetCRS, 2002.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2005, crs_2002, ctxt); ASSERT_GE(list.size(), 2U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2005 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=1.0053 +y=-1.90921 +z=-0.54157 +rx=-0.02678138 " "+ry=0.00042027 +rz=-0.01093206 +s=0.00036891 +dx=0.00079 +dy=-0.0006 " "+dz=-0.00144 +drx=-6.667e-05 +dry=0.00075744 +drz=5.133e-05 " "+ds=-7.201e-05 +t_epoch=2010 +convention=position_vector " "+step +proj=set +v_4=2005 +omit_fwd " "+step +proj=deformation +dt=-3 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1 " "+step +proj=set +v_4=2002"); EXPECT_TRUE(list[1]->hasBallparkTransformation()); } // --------------------------------------------------------------------------- TEST(operation, createOperation_point_motion_operation_nkg) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto sourceCRS = factory->createCoordinateReferenceSystem("8403"); auto crs_2020 = CoordinateMetadata::create(sourceCRS, 2020.0, dbContext); auto crs_2025 = CoordinateMetadata::create(sourceCRS, 2025.0, dbContext); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( crs_2020, crs_2025, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=2020 +omit_fwd " "+step +proj=deformation +dt=5 +grids=eur_nkg_nkgrf17vel.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2025 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_compound_to_compound_with_point_motion_operation) { auto dbContext = DatabaseContext::create(); auto factoryNRCAN = AuthorityFactory::create(dbContext, "NRCAN"); auto sourceCM = factoryNRCAN->createCoordinateMetadata("NAD83_CSRS_1997_MTM7_HT2_1997"); auto targetCM = factoryNRCAN->createCoordinateMetadata( "NAD83_CSRS_2010_UTM19_CGVD2013_2010"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( sourceCM, targetCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=1997 " "+step +inv +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80 " "+step +proj=vgridshift +grids=ca_nrc_HT2_1997.tif +multiplier=1 " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=1997 +omit_fwd " "+step +proj=deformation +dt=13 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2010 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=ca_nrc_CGG2013an83.tif " "+multiplier=1 " "+step +proj=utm +zone=19 +ellps=GRS80 " "+step +proj=set +v_4=2010"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_compound_to_compound_with_epoch_HT2_1997_CGG2013a) { auto dbContext = DatabaseContext::create(); auto factoryNRCAN = AuthorityFactory::create(dbContext, "NRCAN"); auto sourceCM = factoryNRCAN->createCoordinateMetadata("NAD83_CSRS_1997_MTM7_HT2_1997"); auto targetCM = factoryNRCAN->createCoordinateMetadata( "NAD83_CSRS_1997_UTM19_CGVD2013_1997"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( sourceCM, targetCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80 " "+step +proj=vgridshift +grids=ca_nrc_HT2_1997_CGG2013a.tif " "+multiplier=-1 " "+step +proj=utm +zone=19 +ellps=GRS80"); } { auto list = CoordinateOperationFactory::create()->createOperations( targetCM, sourceCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=19 +ellps=GRS80 " "+step +inv +proj=vgridshift " "+grids=ca_nrc_HT2_1997_CGG2013a.tif " "+multiplier=-1 " "+step +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80"); } } // --------------------------------------------------------------------------- TEST(operation, createOperation_compound_to_compound_with_epoch_HT2_2002_CGG2013a) { auto dbContext = DatabaseContext::create(); auto factoryNRCAN = AuthorityFactory::create(dbContext, "NRCAN"); auto sourceCM = factoryNRCAN->createCoordinateMetadata("NAD83_CSRS_2002_MTM7_HT2_2002"); auto targetCM = factoryNRCAN->createCoordinateMetadata( "NAD83_CSRS_2002_UTM19_CGVD2013_2002"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( sourceCM, targetCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80 " "+step +proj=vgridshift +grids=ca_nrc_HT2_2002v70_CGG2013a.tif " "+multiplier=-1 " "+step +proj=utm +zone=19 +ellps=GRS80"); } { auto list = CoordinateOperationFactory::create()->createOperations( targetCM, sourceCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=19 +ellps=GRS80 " "+step +inv +proj=vgridshift " "+grids=ca_nrc_HT2_2002v70_CGG2013a.tif " "+multiplier=-1 " "+step +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80"); } } // --------------------------------------------------------------------------- TEST(operation, createOperation_compound_to_compound_with_epoch_HT2_2010_CGG2013a) { auto dbContext = DatabaseContext::create(); auto factoryNRCAN = AuthorityFactory::create(dbContext, "NRCAN"); auto sourceCM = factoryNRCAN->createCoordinateMetadata("NAD83_CSRS_2010_MTM7_HT2_2010"); auto targetCM = factoryNRCAN->createCoordinateMetadata( "NAD83_CSRS_2010_UTM19_CGVD2013_2010"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( sourceCM, targetCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80 " "+step +proj=vgridshift +grids=ca_nrc_HT2_2010v70_CGG2013a.tif " "+multiplier=-1 " "+step +proj=utm +zone=19 +ellps=GRS80"); } { auto list = CoordinateOperationFactory::create()->createOperations( targetCM, sourceCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ( list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=19 +ellps=GRS80 " "+step +inv +proj=vgridshift " "+grids=ca_nrc_HT2_2010v70_CGG2013a.tif " "+multiplier=-1 " "+step +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80"); } } // --------------------------------------------------------------------------- TEST( operation, createOperation_compound_to_compound_with_Geographic3D_Offset_by_velocity_grid) { auto dbContext = DatabaseContext::create(); auto wkt = "COMPOUNDCRS[\"NAD83(CSRS)v3 / MTM zone 7 + CGVD28 height\",\n" " PROJCRS[\"NAD83(CSRS)v3 / MTM zone 7\",\n" " BASEGEOGCRS[\"NAD83(CSRS)v3\",\n" " DATUM[\"North American Datum of 1983 (CSRS) version 3\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",8240]],\n" " CONVERSION[\"MTM zone 7\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-70.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",304800,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (E(X))\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"northing (N(Y))\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " VERTCRS[\"CGVD28 height\",\n" " VDATUM[\"Canadian Geodetic Vertical Datum of 1928\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " GEOIDMODEL[\"HT2_1997\",\n" " ID[\"EPSG\",9983]],\n" " ID[\"EPSG\",5713]]]"; auto objSrc = WKTParser().createFromWKT(wkt); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = createFromUserInput( "NAD83(CSRS)v7 / UTM zone 19 + CGVD2013a(2010) height", dbContext); auto dst = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); // Very similar output pipeline as // createOperation_compound_to_compound_with_point_motion_operation EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 " "+x_0=304800 +y_0=0 +ellps=GRS80 " "+step +proj=vgridshift +grids=ca_nrc_HT2_1997.tif +multiplier=1 " "+step +proj=cart +ellps=GRS80 " "+step +proj=deformation +dt=13 " "+grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=ca_nrc_CGG2013an83.tif " "+multiplier=1 " "+step +proj=utm +zone=19 +ellps=GRS80"); } // --------------------------------------------------------------------------- TEST( operation, createOperation_compound_to_compound_with_point_motion_operation_special_case_CGVD2013a) { auto dbContext = DatabaseContext::create(); auto factoryNRCAN = AuthorityFactory::create(dbContext, "NRCAN"); auto sourceCM = factoryNRCAN->createCoordinateMetadata( "NAD83_CSRS_1997_UTM17_CGVD2013_1997"); auto targetCM = factoryNRCAN->createCoordinateMetadata( "NAD83_CSRS_2002_UTM17_CGVD2013_2002"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( sourceCM, targetCM, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=1997 " "+step +inv +proj=utm +zone=17 +ellps=GRS80 " "+step +proj=vgridshift +grids=ca_nrc_CGG2013an83.tif " "+multiplier=1 " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=1997 +omit_fwd " "+step +proj=deformation +dt=5 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +inv +proj=vgridshift +grids=ca_nrc_CGG2013an83.tif " "+multiplier=1 " "+step +proj=utm +zone=17 +ellps=GRS80 " "+step +proj=set +v_4=2002"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_Geographic3D_Offset_by_velocity_grid) { auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto sourceCRS = factoryEPSG->createCoordinateReferenceSystem("8254"); // NAD83(CSRS)v7 auto targetCRS = factoryEPSG->createCoordinateReferenceSystem("8239"); // NAD83(CSRS)v3 auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( sourceCRS, targetCRS, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +inv +proj=deformation +dt=13 " "+grids=ca_nrc_NAD83v70VG.tif +ellps=GRS80 " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_test_createOperationsWithDatumPivot_iter_1) { // Test // CoordinateOperationFactory::Private::createOperationsWithDatumPivot() // iter=1, ie getType(candidateSrcGeod) == getType(candidateDstGeod) auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); // NAD83(CSRS)v2 (2D) auto sourceCRS = factoryEPSG->createCoordinateReferenceSystem("8237"); // NAD83(CSRS)v3 (2D) auto targetCRS = factoryEPSG->createCoordinateReferenceSystem("8240"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); // Do *NOT* set SpatialCriterion::PARTIAL_INTERSECTION, otherwise we'd // get the direct operations ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( sourceCRS, targetCRS, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_STREQ(list[0]->nameStr().c_str(), "Conversion from NAD83(CSRS)v2 (geog2D) to " "NAD83(CSRS)v2 (geocentric) + " "NAD83(CSRS)v2 to NAD83(CSRS)v3 (3) + " "Conversion from NAD83(CSRS)v3 (geocentric) to " "NAD83(CSRS)v3 (geog2D)"); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_Vrtical_Offset_by_velocity_grid) { auto dbContext = DatabaseContext::create(); auto objSrc = createFromUserInput("NAD83(CSRS)v7 + CGVD2013a(2002) height", dbContext); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = createFromUserInput("NAD83(CSRS)v7 + CGVD2013a(2010) height", dbContext); auto dst = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_1 +v_2 " "+step +proj=cart +ellps=GRS80 " "+step +inv +proj=deformation +dt=-8 " "+grids=ca_nrc_NAD83v70VG.tif +ellps=GRS80 " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_1 +v_2 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_epsg_10622_local_orthographic) { auto dbContext = DatabaseContext::create(); auto objSrc = createFromUserInput("EPSG:6318", dbContext); auto src = nn_dynamic_pointer_cast(objSrc); ASSERT_TRUE(src != nullptr); auto objDest = createFromUserInput("EPSG:10622", dbContext); auto dst = nn_dynamic_pointer_cast(objDest); ASSERT_TRUE(dst != nullptr); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( NN_NO_CHECK(src), NN_NO_CHECK(dst), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ortho " "+lat_0=37.6289686531 +lon_0=-122.3939412704 " "+alpha=27.7928209333 +k=0.9999968 +x_0=0 +y_0=0 +ellps=GRS80 " "+step +proj=unitconvert +xy_in=m +xy_out=us-ft"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_ITRF2000_to_ETRS89) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( // ITRF2000 authFactoryEPSG->createCoordinateReferenceSystem("9989"), // ETRS89 authFactoryEPSG->createCoordinateReferenceSystem("4937"), ctxt); // Check that we find a mix of grid-based (NKG ones, with very narrow area // of use, but easier to lookup) and non-grid based operations (wider area // of use, but require more effort to infer) bool foundNonGridBasedOp = false; bool foundGridBaseOp = false; for (const auto &op : list) { if (!op->hasBallparkTransformation()) { if (op->gridsNeeded(dbContext, false).empty()) foundNonGridBasedOp = true; else foundGridBaseOp = true; } } EXPECT_TRUE(foundNonGridBasedOp); EXPECT_TRUE(foundGridBaseOp); } // --------------------------------------------------------------------------- TEST(operation, createOperation_ITRF2014_to_ETRS89_DNK) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( // ITRF2014 authFactoryEPSG->createCoordinateReferenceSystem("7789"), // ETRS89-DNK authFactoryEPSG->createCoordinateReferenceSystem("10890"), ctxt); ASSERT_GE(list.size(), 1U); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=helmert +x=0 +y=0 +z=0 +rx=0.001785 " "+ry=0.011151 +rz=-0.01617 +s=0 +dx=0 +dy=0 +dz=0 +drx=8.5e-05 " "+dry=0.000531 +drz=-0.00077 +ds=0 +t_epoch=2010 " "+convention=position_vector " "+step +inv +proj=deformation +t_epoch=2000 " "+grids=eur_nkg_nkgrf17vel.tif +ellps=GRS80 " "+step +proj=helmert +x=0.66818 +y=0.04453 +z=-0.45049 " "+rx=0.00312883 +ry=-0.02373423 +rz=0.00442969 +s=-0.003136 " "+convention=position_vector " "+step +proj=deformation +dt=15.829 " "+grids=eur_nkg_nkgrf17vel.tif +ellps=GRS80"); } // --------------------------------------------------------------------------- #ifdef requires_epsg_12_054 TEST(operation, createOperation_ETRS89_XXX_to_ETRS89_YYY_using_ETRF2000) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( // ETRS89-PRT [1995] authFactoryEPSG->createCoordinateReferenceSystem("11108"), // ETRS89-ESP [ERGNSS] authFactoryEPSG->createCoordinateReferenceSystem("11128"), ctxt); ASSERT_GE(list.size(), 2U); EXPECT_STREQ(list[0]->nameStr().c_str(), "Conversion from ETRS89-PRT [1995] (geog2D) to " "ETRS89-PRT [1995] (geocentric) + " "ETRS89-PRT [1995] to ETRF2000 (1) + " "Inverse of ETRS89-ESP [ERGNSS] to ETRF2000 (1) + " "Conversion from ETRS89-ESP [ERGNSS] (geocentric) to " "ETRS89-ESP [ERGNSS] (geog2D)"); EXPECT_FALSE(list[0]->hasBallparkTransformation()); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=0.0063 +y=0.00294 +z=0.01726 " "+rx=-0.0007616 +ry=-6.4e-05 +rz=-0.0008768 +s=-0.001534 " "+dx=0 +dy=6e-05 +dz=0.0014 +drx=-0.000119 +dry=-1e-05 " "+drz=-0.000162 +ds=-1e-05 +t_epoch=1995.4 " "+convention=position_vector " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "0.1"); EXPECT_STREQ(list[1]->nameStr().c_str(), "Inverse of ETRS89 to ETRS89-PRT [1995] + " "ETRS89 to ETRS89-ESP [ERGNSS]"); EXPECT_FALSE(list[1]->hasBallparkTransformation()); EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); ASSERT_EQ(list[1]->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(list[1]->coordinateOperationAccuracies()[0]->value(), "0.1"); } #endif // --------------------------------------------------------------------------- TEST(operation, createOperation_time_dependent_helmert_directly_from_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto ITRF2014_geocentric = factory->createCoordinateReferenceSystem("7789"); auto ITRF2014_geocentric_at_2026 = CoordinateMetadata::create(ITRF2014_geocentric, 2026.0, dbContext); auto GDA2020_geocentric = factory->createCoordinateReferenceSystem("7842"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( ITRF2014_geocentric_at_2026, GDA2020_geocentric, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2026 " "+step +proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 " "+dx=0 +dy=0 +dz=0 " "+drx=0.00150379 +dry=0.00118346 +drz=0.00120716 +ds=0 " "+t_epoch=2020 +convention=coordinate_frame " "+step +proj=set +v_4=2026"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2026 " "+step +inv +proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 " "+dx=0 +dy=0 +dz=0 " "+drx=0.00150379 +dry=0.00118346 +drz=0.00120716 +ds=0 " "+t_epoch=2020 +convention=coordinate_frame " "+step +proj=set +v_4=2026"); auto list2 = CoordinateOperationFactory::create()->createOperations( GDA2020_geocentric, ITRF2014_geocentric_at_2026, ctxt); ASSERT_GE(list2.size(), 1U); EXPECT_EQ(list2[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2026 " "+step +inv +proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 " "+dx=0 +dy=0 +dz=0 " "+drx=0.00150379 +dry=0.00118346 +drz=0.00120716 +ds=0 " "+t_epoch=2020 +convention=coordinate_frame " "+step +proj=set +v_4=2026"); EXPECT_EQ(list2[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=set +v_4=2026 " "+step +proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 " "+dx=0 +dy=0 +dz=0 " "+drx=0.00150379 +dry=0.00118346 +drz=0.00120716 +ds=0 " "+t_epoch=2020 +convention=coordinate_frame " "+step +proj=set +v_4=2026"); } // --------------------------------------------------------------------------- TEST(operation, createOperation_defmodel_from_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto ITRF96 = factory->createCoordinateReferenceSystem("7907"); auto ITRF96_at_2026 = CoordinateMetadata::create(ITRF96, 2026.0, dbContext); auto NZGD2000 = factory->createCoordinateReferenceSystem("4959"); auto ctxt = CoordinateOperationContext::create( AuthorityFactory::create(dbContext, std::string()), nullptr, 0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); auto list = CoordinateOperationFactory::create()->createOperations( ITRF96_at_2026, NZGD2000, ctxt); ASSERT_GE(list.size(), 1U); EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=set +v_4=2026 " "+step +inv +proj=defmodel +model=nz_linz_nzgd2000-20180701.json " "+step +proj=set +v_4=2026 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=set +v_4=2026 " "+step +proj=defmodel +model=nz_linz_nzgd2000-20180701.json " "+step +proj=set +v_4=2026 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); auto list2 = CoordinateOperationFactory::create()->createOperations( NZGD2000, ITRF96_at_2026, ctxt); ASSERT_GE(list2.size(), 1U); EXPECT_EQ(list2[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=set +v_4=2026 " "+step +proj=defmodel +model=nz_linz_nzgd2000-20180701.json " "+step +proj=set +v_4=2026 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(list2[0]->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=set +v_4=2026 " "+step +inv +proj=defmodel +model=nz_linz_nzgd2000-20180701.json " "+step +proj=set +v_4=2026 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- #ifdef requires_epsg_12_054 TEST(operation, createOperation_ETRF2000_to_Amersfoort) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( // ETRF2000 authFactoryEPSG->createCoordinateReferenceSystem("9067"), // Amersfoort authFactoryEPSG->createCoordinateReferenceSystem("4289"), ctxt); ASSERT_GE(list.size(), 1U); // We check that we go through ETRS89-NLD [AGRS2010] to use the most // precise "Amersfoort to ETRS89-NLD [AGRS2010] (9)" operation. EXPECT_EQ( list[0]->nameStr(), "Conversion from ETRF2000 (geog2D) to ETRF2000 (geocentric) + " "Inverse of ETRS89-NLD [AGRS2010] to ETRF2000 (1) + " "Conversion from ETRS89-NLD [AGRS2010] (geocentric) to " "ETRS89-NLD [AGRS2010] (geog2D) + " "Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (9)"); } { auto list = CoordinateOperationFactory::create()->createOperations( // Amersfoort authFactoryEPSG->createCoordinateReferenceSystem("4289"), // ETRF2000 authFactoryEPSG->createCoordinateReferenceSystem("9067"), ctxt); ASSERT_GE(list.size(), 1U); // We check that we go through ETRS89-NLD [AGRS2010] to use the most // precise "Amersfoort to ETRS89-NLD [AGRS2010] (9)" operation. EXPECT_EQ(list[0]->nameStr(), "Amersfoort to ETRS89-NLD [AGRS2010] (9) + " "Conversion from ETRS89-NLD [AGRS2010] (geog2D) to " "ETRS89-NLD [AGRS2010] (geocentric) + " "ETRS89-NLD [AGRS2010] to ETRF2000 (1) + " "Conversion from ETRF2000 (geocentric) to ETRF2000 (geog2D)"); } } // --------------------------------------------------------------------------- TEST(operation, createOperation_ETRS89_to_Amersfoort) { auto dbContext = DatabaseContext::create(); auto authFactory = AuthorityFactory::create(dbContext, std::string()); auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); ctxt->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); { auto list = CoordinateOperationFactory::create()->createOperations( // ETRS89 authFactoryEPSG->createCoordinateReferenceSystem("4258"), // Amersfoort authFactoryEPSG->createCoordinateReferenceSystem("4289"), ctxt); ASSERT_GE(list.size(), 1U); // We check that we go through ETRS89-NLD [AGRS2010] to use the most // precise "Amersfoort to ETRS89-NLD [AGRS2010] (9)" operation. EXPECT_EQ(list[0]->nameStr(), "ETRS89 to ETRS89-NLD [AGRS2010] + " "Inverse of Amersfoort to ETRS89-NLD [AGRS2010] (9)"); } { auto list = CoordinateOperationFactory::create()->createOperations( // Amersfoort authFactoryEPSG->createCoordinateReferenceSystem("4289"), // ETRS89 authFactoryEPSG->createCoordinateReferenceSystem("4258"), ctxt); ASSERT_GE(list.size(), 1U); // We check that we go through ETRS89-NLD [AGRS2010] to use the most // precise "Amersfoort to ETRS89-NLD [AGRS2010] (9)" operation. EXPECT_EQ(list[0]->nameStr(), "Amersfoort to ETRS89-NLD [AGRS2010] (9) + " "Inverse of ETRS89 to ETRS89-NLD [AGRS2010]"); } } #endif proj-9.8.1/test/unit/CMakeLists.txt000664 001750 001750 00000017264 15166171715 017163 0ustar00eveneven000000 000000 # CMake configuration for PROJ unit tests # External GTest provided by (e.g.) libgtest-dev set(MIN_GTest_VERSION "1.8.1") if(NOT CMAKE_REQUIRED_QUIET) # CMake 3.17+ use CHECK_START/CHECK_PASS/CHECK_FAIL message(STATUS "Looking for GTest") endif() find_package(GTest QUIET) set(USE_EXTERNAL_GTEST_DEFAULT OFF) if(GTest_FOUND) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Looking for GTest - found (${GTest_VERSION})") endif() if(GTest_VERSION VERSION_LESS MIN_GTest_VERSION) message(WARNING "External GTest version is too old") else() set(USE_EXTERNAL_GTEST_DEFAULT ON) endif() else() if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Looking for GTest - not found") endif() endif() option(USE_EXTERNAL_GTEST "Compile against external GTest" ${USE_EXTERNAL_GTEST_DEFAULT} ) if(USE_EXTERNAL_GTEST) if(NOT GTest_FOUND) message(SEND_ERROR "External GTest >= ${MIN_GTest_VERSION} not found, \ skipping some tests") # exit the remainder of this file return() endif() message(STATUS "Using external GTest") # CMake < 3.20.0 uses GTest::GTest # CMake >= 3.20 uses GTest::gtest, and deprecates GTest::GTest # so for older CMake, create an alias from GTest::GTest to GTest::gtest if(NOT TARGET GTest::gtest) add_library(GTest::gtest INTERFACE IMPORTED) set_target_properties(GTest::gtest PROPERTIES INTERFACE_LINK_LIBRARIES "GTest::GTest") endif() elseif(HAS_NETWORK) message(STATUS "Fetching GTest from GitHub ...") # Add Google Test # # See https://github.com/google/googletest/blob/main/googletest/README.md if(POLICY CMP0135) cmake_policy(SET CMP0135 NEW) # for DOWNLOAD_EXTRACT_TIMESTAMP option endif() set(GTEST_VERSION "1.15.2") include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/refs/tags/v${GTEST_VERSION}.zip EXCLUDE_FROM_ALL # ignored before CMake 3.28 ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28.0") FetchContent_MakeAvailable(googletest) else() # Pre CMake 3.28 workaround to prevent installing files FetchContent_GetProperties(googletest) if(NOT googletest_POPULATED) FetchContent_Populate(googletest) add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) endif() endif() else() if(TESTING_USE_NETWORK) set(_msg_detail "install GTest dependency") else() set(_msg_detail "either install GTest dependency, or if possible, \ set TESTING_USE_NETWORK=ON to fetch content from GitHub" ) endif() message(WARNING "Tests that require GTest will be skipped; ${_msg_detail}") # exit the remainder of this file return() endif() # # Build PROJ unit tests # include_directories(${PROJ_SOURCE_DIR}/include) # Add the directory containing proj_config.h include_directories(${PROJ_BINARY_DIR}/src) # Apply to targets in the current directory and below add_compile_options("$<$:${PROJ_C_WARN_FLAGS}>") add_compile_options("$<$:${PROJ_CXX_WARN_FLAGS}>") set(PROJ_TEST_ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES" "PROJ_DATA=${PROJ_BINARY_DIR}/data/for_tests" "PROJ_SOURCE_DATA=${PROJ_SOURCE_DIR}/data" ) add_executable(proj_errno_string_test main.cpp proj_errno_string_test.cpp) target_link_libraries(proj_errno_string_test PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME proj_errno_string_test COMMAND proj_errno_string_test) set_property(TEST proj_errno_string_test PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) add_executable(proj_angular_io_test main.cpp proj_angular_io_test.cpp) target_link_libraries(proj_angular_io_test PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME proj_angular_io_test COMMAND proj_angular_io_test) set_property(TEST proj_angular_io_test PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) add_executable(proj_context_test main.cpp proj_context_test.cpp) target_link_libraries(proj_context_test PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME proj_context_test COMMAND proj_context_test) set_property(TEST proj_context_test PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) if(MSVC AND BUILD_SHARED_LIBS) # ph_phi2_test not compatible of a .dll build else() add_executable(pj_phi2_test main.cpp pj_phi2_test.cpp) target_link_libraries(pj_phi2_test PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME pj_phi2_test COMMAND pj_phi2_test) set_property(TEST pj_phi2_test PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) endif() set(PROJ_TEST_CPP_API_SOURCES main.cpp test_util.cpp test_common.cpp test_coordinates.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_operationfactory.cpp test_datum.cpp test_factory.cpp test_c_api.cpp test_grids.cpp test_projinfo_lib.cpp) add_executable(proj_test_cpp_api ${PROJ_TEST_CPP_API_SOURCES}) set_property(SOURCE ${PROJ_TEST_CPP_API_SOURCES} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) target_link_libraries(proj_test_cpp_api PRIVATE GTest::gtest PRIVATE SQLite3::SQLite3 PRIVATE ${PROJ_LIBRARIES}) add_test(NAME proj_test_cpp_api COMMAND proj_test_cpp_api) set_property(TEST proj_test_cpp_api PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) if(TIFF_ENABLED) target_compile_definitions(proj_test_cpp_api PRIVATE -DTIFF_ENABLED) endif() if(EMBED_RESOURCE_FILES) target_compile_definitions(proj_test_cpp_api PRIVATE EMBED_RESOURCE_FILES) endif() if (USE_ONLY_EMBEDDED_RESOURCE_FILES) target_compile_definitions(proj_test_cpp_api PRIVATE USE_ONLY_EMBEDDED_RESOURCE_FILES) endif() add_executable(gie_self_tests main.cpp gie_self_tests.cpp) target_link_libraries(gie_self_tests PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME gie_self_tests COMMAND gie_self_tests) set_property(TEST gie_self_tests PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) if(TIFF_ENABLED) target_compile_definitions(gie_self_tests PRIVATE -DTIFF_ENABLED) endif() add_executable(test_network main.cpp test_network.cpp) if(CURL_ENABLED) target_compile_definitions(test_network PRIVATE -DCURL_ENABLED) target_link_libraries(test_network PRIVATE CURL::libcurl) endif() target_link_libraries(test_network PRIVATE GTest::gtest PRIVATE SQLite3::SQLite3 PRIVATE ${PROJ_LIBRARIES}) if(TIFF_ENABLED) add_test(NAME test_network COMMAND test_network) set_property(TEST test_network PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) endif() add_executable(test_defmodel main.cpp test_defmodel.cpp) target_link_libraries(test_defmodel PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME test_defmodel COMMAND test_defmodel) set_property(TEST test_defmodel PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) add_executable(test_tinshift main.cpp test_tinshift.cpp) target_link_libraries(test_tinshift PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME test_tinshift COMMAND test_tinshift) set_property(TEST test_tinshift PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) add_executable(test_misc main.cpp test_misc.cpp) target_link_libraries(test_misc PRIVATE GTest::gtest PRIVATE ${PROJ_LIBRARIES}) add_test(NAME test_misc COMMAND test_misc) set_property(TEST test_misc PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) if (Threads_FOUND AND CMAKE_USE_PTHREADS_INIT AND NOT APPLE) add_definitions(-DPROJ_HAS_PTHREADS) add_executable(test_fork test_fork.c) target_link_libraries(test_fork PRIVATE ${PROJ_LIBRARIES} PRIVATE ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME test_fork COMMAND test_fork) set_property(TEST test_fork PROPERTY ENVIRONMENT ${PROJ_TEST_ENVIRONMENT}) endif() proj-9.8.1/test/unit/test_primitives.hpp000664 001750 001750 00000010172 15166171715 020355 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2018 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef TEST_PRIMITIVES_HPP_INCLUDED #define TEST_PRIMITIVES_HPP_INCLUDED #include "gtest_include.h" #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/internal/internal.hpp" #include #include static ::testing::AssertionResult ComparePROJString(const char *m_expr, const char *n_expr, const std::string &m, const std::string &n) { // if (m == n) return ::testing::AssertionSuccess(); auto mTokens = osgeo::proj::internal::split(m, ' '); auto nTokens = osgeo::proj::internal::split(n, ' '); if (mTokens.size() == nTokens.size()) { bool success = true; for (size_t i = 0; i < mTokens.size(); i++) { auto mSubTokens = osgeo::proj::internal::split(mTokens[i], '='); auto nSubTokens = osgeo::proj::internal::split(nTokens[i], '='); if (mSubTokens.size() != nSubTokens.size()) { success = false; break; } if (mSubTokens.size() == 2 && nSubTokens.size() == 2) { if (mSubTokens[0] != nSubTokens[0]) { success = false; break; } double mValue = 0.0; bool mIsDouble = false; try { mValue = osgeo::proj::internal::c_locale_stod(mSubTokens[1]); mIsDouble = true; } catch (const std::exception &) { } double nValue = 0.0; bool nIsDouble = false; try { nValue = osgeo::proj::internal::c_locale_stod(nSubTokens[1]); nIsDouble = true; } catch (const std::exception &) { } if (mIsDouble != nIsDouble) { success = false; break; } if (mIsDouble) { success = std::abs(mValue - nValue) <= 1e-14 * std::abs(mValue); } else { success = mSubTokens[1] == nSubTokens[1]; } if (!success) { break; } } } if (success) { return ::testing::AssertionSuccess(); } } return ::testing::AssertionFailure() << m_expr << " and " << n_expr << " (" << m << " and " << n << ") are different"; } #endif /* TEST_PRIMITIVES_HPP_INCLUDED */ proj-9.8.1/test/unit/test_io.cpp000664 001750 001750 00003000665 15166171715 016575 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" // to be able to use internal::toString #define FROM_PROJ_CPP #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj_constants.h" #include using namespace osgeo::proj::common; using namespace osgeo::proj::coordinates; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::internal; using namespace osgeo::proj::io; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; // --------------------------------------------------------------------------- TEST(io, wkt_parsing) { { auto n = WKTNode::createFrom("MYNODE[]"); EXPECT_EQ(n->value(), "MYNODE"); EXPECT_TRUE(n->children().empty()); EXPECT_EQ(n->toString(), "MYNODE"); } { auto n = WKTNode::createFrom(" MYNODE [ ] "); EXPECT_EQ(n->value(), "MYNODE"); EXPECT_TRUE(n->children().empty()); } EXPECT_THROW(WKTNode::createFrom(""), ParsingException); EXPECT_THROW(WKTNode::createFrom("x"), ParsingException); EXPECT_THROW(WKTNode::createFrom("x,"), ParsingException); EXPECT_THROW(WKTNode::createFrom("x["), ParsingException); EXPECT_THROW(WKTNode::createFrom("["), ParsingException); { auto n = WKTNode::createFrom("MYNODE[\"x\"]"); EXPECT_EQ(n->value(), "MYNODE"); ASSERT_EQ(n->children().size(), 1U); EXPECT_EQ(n->children()[0]->value(), "\"x\""); EXPECT_EQ(n->toString(), "MYNODE[\"x\"]"); } EXPECT_THROW(WKTNode::createFrom("MYNODE[\"x\""), ParsingException); { auto n = WKTNode::createFrom("MYNODE[ \"x\" ]"); EXPECT_EQ(n->value(), "MYNODE"); ASSERT_EQ(n->children().size(), 1U); EXPECT_EQ(n->children()[0]->value(), "\"x\""); } { auto n = WKTNode::createFrom("MYNODE[\"x[\",1]"); EXPECT_EQ(n->value(), "MYNODE"); ASSERT_EQ(n->children().size(), 2U); EXPECT_EQ(n->children()[0]->value(), "\"x[\""); EXPECT_EQ(n->children()[1]->value(), "1"); EXPECT_EQ(n->toString(), "MYNODE[\"x[\",1]"); } EXPECT_THROW(WKTNode::createFrom("MYNODE[\"x\","), ParsingException); { auto n = WKTNode::createFrom("A[B[y]]"); EXPECT_EQ(n->value(), "A"); ASSERT_EQ(n->children().size(), 1U); EXPECT_EQ(n->children()[0]->value(), "B"); ASSERT_EQ(n->children()[0]->children().size(), 1U); EXPECT_EQ(n->children()[0]->children()[0]->value(), "y"); EXPECT_EQ(n->toString(), "A[B[y]]"); } EXPECT_THROW(WKTNode::createFrom("A[B["), ParsingException); std::string str; for (int i = 0; i < 17; i++) { str = "A[" + str + "]"; } EXPECT_THROW(WKTNode::createFrom(str), ParsingException); { auto wkt = "A[\"a\",B[\"b\",C[\"c\"]],D[\"d\"]]"; EXPECT_EQ(WKTNode::createFrom(wkt)->toString(), wkt); } } // --------------------------------------------------------------------------- TEST(io, wkt_parsing_with_parenthesis) { auto n = WKTNode::createFrom("A(\"x\",B(\"y\"))"); EXPECT_EQ(n->toString(), "A[\"x\",B[\"y\"]]"); } // --------------------------------------------------------------------------- TEST(io, wkt_parsing_with_double_quotes_inside) { auto n = WKTNode::createFrom("A[\"xy\"\"z\"]"); EXPECT_EQ(n->children()[0]->value(), "\"xy\"z\""); EXPECT_EQ(n->toString(), "A[\"xy\"\"z\"]"); EXPECT_THROW(WKTNode::createFrom("A[\"x\""), ParsingException); } // --------------------------------------------------------------------------- TEST(io, wkt_parsing_with_printed_quotes) { static const std::string startPrintedQuote("\xE2\x80\x9C"); static const std::string endPrintedQuote("\xE2\x80\x9D"); auto n = WKTNode::createFrom("A[" + startPrintedQuote + "x" + endPrintedQuote + "]"); EXPECT_EQ(n->children()[0]->value(), "\"x\""); EXPECT_EQ(n->toString(), "A[\"x\"]"); } // --------------------------------------------------------------------------- TEST(wkt_parse, sphere) { auto obj = WKTParser().createFromWKT( "ELLIPSOID[\"Sphere\",6378137,0,LENGTHUNIT[\"metre\",1]]"); auto ellipsoid = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellipsoid != nullptr); EXPECT_TRUE(ellipsoid->isSphere()); } // --------------------------------------------------------------------------- TEST(wkt_parse, datum_with_ANCHOR) { auto obj = WKTParser().createFromWKT( "DATUM[\"WGS_1984 with anchor\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",7030]],\n" " ANCHOR[\"My anchor\"]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Earth"); EXPECT_EQ(datum->primeMeridian()->nameStr(), "Greenwich"); auto anchor = datum->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "My anchor"); EXPECT_FALSE(datum->anchorEpoch().has_value()); } // --------------------------------------------------------------------------- TEST(wkt_parse, datum_with_ANCHOREPOCH) { auto obj = WKTParser().createFromWKT( "DATUM[\"my_datum\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOREPOCH[2002.5]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); auto anchorEpoch = datum->anchorEpoch(); EXPECT_TRUE(anchorEpoch.has_value()); ASSERT_EQ(anchorEpoch->convertToUnit(UnitOfMeasure::YEAR), 2002.5); EXPECT_FALSE(datum->anchorDefinition().has_value()); } // --------------------------------------------------------------------------- TEST(wkt_parse, datum_with_invalid_ANCHOREPOCH) { auto wkt = "DATUM[\"my_datum\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOREPOCH[invalid]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, datum_with_invalid_ANCHOREPOCH_too_many_children) { auto wkt = "DATUM[\"my_datum\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOREPOCH[2002.5,invalid]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, datum_with_pm) { const char *wkt = "DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n" " ELLIPSOID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6807]],\n" "PRIMEM[\"Paris\",2.5969213,\n" " ANGLEUNIT[\"grad\",0.0157079632679489],\n" " ID[\"EPSG\",8903]]"; auto obj = WKTParser().createFromWKT(wkt); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->primeMeridian()->nameStr(), "Paris"); EXPECT_EQ( datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, datum_no_pm_not_earth) { auto obj = WKTParser().createFromWKT("DATUM[\"unnamed\",\n" " ELLIPSOID[\"unnamed\",1,0,\n" " LENGTHUNIT[\"metre\",1]]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Non-Earth body"); EXPECT_EQ(datum->primeMeridian()->nameStr(), "Reference meridian"); } // --------------------------------------------------------------------------- TEST(wkt_parse, guess_celestial_body_from_ellipsoid_name) { auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT("DATUM[\"unnamed\",\n" " ELLIPSOID[\"Ananke\",10000,0,\n" " LENGTHUNIT[\"metre\",1]]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Ananke"); } // --------------------------------------------------------------------------- TEST(wkt_parse, guess_celestial_body_from_ellipsoid_name_false_positive) { auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT("DATUM[\"unnamed\",\n" " ELLIPSOID[\"Ananke\",999999,0,\n" " LENGTHUNIT[\"metre\",1]]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Non-Earth body"); } // --------------------------------------------------------------------------- TEST(wkt_parse, dynamic_geodetic_reference_frame) { auto obj = WKTParser().createFromWKT( "GEOGCRS[\"WGS 84 (G1762)\"," "DYNAMIC[FRAMEEPOCH[2005.0]]," "TRF[\"World Geodetic System 1984 (G1762)\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563]," " ANCHOR[\"My anchor\"]]," "CS[ellipsoidal,3]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto dgrf = std::dynamic_pointer_cast(crs->datum()); ASSERT_TRUE(dgrf != nullptr); auto anchor = dgrf->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "My anchor"); EXPECT_TRUE(dgrf->frameReferenceEpoch() == Measure(2005.0, UnitOfMeasure::YEAR)); auto model = dgrf->deformationModelName(); EXPECT_TRUE(!model.has_value()); } // --------------------------------------------------------------------------- TEST(wkt_parse, dynamic_geodetic_reference_frame_with_model) { auto obj = WKTParser().createFromWKT( "GEOGCRS[\"WGS 84 (G1762)\"," "DYNAMIC[FRAMEEPOCH[2005.0],MODEL[\"my_model\"]]," "TRF[\"World Geodetic System 1984 (G1762)\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563]," " ANCHOR[\"My anchor\"]]," "CS[ellipsoidal,3]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto dgrf = std::dynamic_pointer_cast(crs->datum()); ASSERT_TRUE(dgrf != nullptr); auto anchor = dgrf->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "My anchor"); EXPECT_TRUE(dgrf->frameReferenceEpoch() == Measure(2005.0, UnitOfMeasure::YEAR)); auto model = dgrf->deformationModelName(); EXPECT_TRUE(model.has_value()); EXPECT_EQ(*model, "my_model"); } // --------------------------------------------------------------------------- TEST(wkt_parse, dynamic_geodetic_reference_frame_with_velocitygrid) { auto obj = WKTParser().createFromWKT( "GEOGCRS[\"WGS 84 (G1762)\"," "DYNAMIC[FRAMEEPOCH[2005.0],VELOCITYGRID[\"my_model\"]]," "TRF[\"World Geodetic System 1984 (G1762)\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563]," " ANCHOR[\"My anchor\"]]," "CS[ellipsoidal,3]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto dgrf = std::dynamic_pointer_cast(crs->datum()); ASSERT_TRUE(dgrf != nullptr); auto anchor = dgrf->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "My anchor"); EXPECT_TRUE(dgrf->frameReferenceEpoch() == Measure(2005.0, UnitOfMeasure::YEAR)); auto model = dgrf->deformationModelName(); EXPECT_TRUE(model.has_value()); EXPECT_EQ(*model, "my_model"); } // --------------------------------------------------------------------------- TEST(wkt_parse, geogcrs_with_ensemble) { auto obj = WKTParser().createFromWKT( "GEOGCRS[\"WGS 84\"," "ENSEMBLE[\"WGS 84 ensemble\"," " MEMBER[\"WGS 84 (TRANSIT)\"]," " MEMBER[\"WGS 84 (G730)\"]," " MEMBER[\"WGS 84 (G834)\"]," " MEMBER[\"WGS 84 (G1150)\"]," " MEMBER[\"WGS 84 (G1674)\"]," " MEMBER[\"WGS 84 (G1762)\"]," " ELLIPSOID[\"WGS " "84\",6378137,298.2572236,LENGTHUNIT[\"metre\",1.0]]," " ENSEMBLEACCURACY[2]" "]," "CS[ellipsoidal,3]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); ASSERT_TRUE(crs->datum() == nullptr); ASSERT_TRUE(crs->datumEnsemble() != nullptr); EXPECT_EQ(crs->datumEnsemble()->datums().size(), 6U); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_geogcrs_with_ensemble) { auto wkt = "GEOGCRS[\"WGS 84\"," "ENSEMBLE[\"WGS 84 ensemble\"," " MEMBER[\"WGS 84 (TRANSIT)\"]," " MEMBER[\"WGS 84 (G730)\"]," " MEMBER[\"WGS 84 (G834)\"]," " MEMBER[\"WGS 84 (G1150)\"]," " MEMBER[\"WGS 84 (G1674)\"]," " MEMBER[\"WGS 84 (G1762)\"]," " ENSEMBLEACCURACY[2]" "]," "CS[ellipsoidal,3]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]" "]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, ensemble_without_members) { auto wkt = "GEOGCRS[\"WGS 84\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1]]," " ENSEMBLEACCURACY[2]]," "CS[ellipsoidal,2]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]" "]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = dynamic_cast(obj.get()); ASSERT_TRUE(crs != nullptr); EXPECT_GE(crs->datumEnsemble()->datums().size(), 2U); } // --------------------------------------------------------------------------- TEST(wkt_parse, ensemble_without_members_no_db) { auto wkt = "GEOGCRS[\"WGS 84\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1]]," " ENSEMBLEACCURACY[2]]," "CS[ellipsoidal,2]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]" "]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, ensemble_without_members_unknown_name) { auto wkt = "GEOGCRS[\"WGS 84\"," " ENSEMBLE[\"i do not exist\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1]]," " ENSEMBLEACCURACY[2]]," "CS[ellipsoidal,2]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]" "]"; EXPECT_THROW(WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- static void checkEPSG_4326(GeographicCRSPtr crs, bool latLong = true, bool checkEPSGCodes = true) { if (checkEPSGCodes) { ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "4326"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); } EXPECT_EQ(crs->nameStr(), "WGS 84"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); if (latLong) { EXPECT_TRUE(cs->axisList()[0]->nameStr() == "Latitude" || cs->axisList()[0]->nameStr() == "Geodetic latitude") << cs->axisList()[0]->nameStr(); EXPECT_EQ(tolower(cs->axisList()[0]->abbreviation()), "lat"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH); EXPECT_TRUE(cs->axisList()[1]->nameStr() == "Longitude" || cs->axisList()[1]->nameStr() == "Geodetic longitude") << cs->axisList()[1]->nameStr(); EXPECT_EQ(tolower(cs->axisList()[1]->abbreviation()), "lon"); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST); } else { EXPECT_EQ(cs->axisList()[0]->nameStr(), "Longitude"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lon"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST); EXPECT_EQ(cs->axisList()[1]->nameStr(), "Latitude"); EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lat"); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH); } auto datum = crs->datum(); if (checkEPSGCodes) { ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "6326"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); } EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); auto ellipsoid = datum->ellipsoid(); EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0); EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE); EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563); if (checkEPSGCodes) { ASSERT_EQ(ellipsoid->identifiers().size(), 1U); EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030"); EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG"); } EXPECT_EQ(ellipsoid->nameStr(), "WGS 84"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_EPSG_4326) { auto obj = WKTParser().createFromWKT( "GEOGCS[\"WGS 84\"," " DATUM[\"WGS_1984\"," " SPHEROID[\"WGS 84\",6378137,298.257223563," " AUTHORITY[\"EPSG\",\"7030\"]]," " AUTHORITY[\"EPSG\",\"6326\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4326\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs, false /* longlat order */); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_EPSG_4267) { auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT( "GEOGCS[\"NAD27\"," " DATUM[\"North_American_Datum_1927\"," " SPHEROID[\"Clarke 1866\",6378206.4,294.978698213898," " AUTHORITY[\"EPSG\",\"7008\"]]," " AUTHORITY[\"EPSG\",\"6267\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4267\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "6267"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(datum->nameStr(), "North American Datum 1927"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_EPSG_4807_grad_mess) { auto obj = WKTParser().createFromWKT( "GEOGCS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n" " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n" " AUTHORITY[\"EPSG\",\"6807\"]],\n" " AUTHORITY[\"EPSG\",\"6807\"]],\n" /* WKT1_GDAL weirdness: PRIMEM is converted to degree */ " PRIMEM[\"Paris\",2.33722917,\n" " AUTHORITY[\"EPSG\",\"8903\"]],\n" " UNIT[\"grad\",0.015707963267949,\n" " AUTHORITY[\"EPSG\",\"9105\"]],\n" " AXIS[\"latitude\",NORTH],\n" " AXIS[\"longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4807\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); auto primem = datum->primeMeridian(); EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::GRAD); // Check that we have corrected the value that was in degree into grad. EXPECT_EQ(primem->longitude().value(), 2.5969213); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_EPSG_4901_grad) { auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT("GEOGCS[\"GCS_ATF_Paris\",DATUM[\"D_ATF\"," "SPHEROID[\"Plessis_1817\",6376523.0,308.64]]," "PRIMEM[\"Paris_RGS\",2.33720833333333]," "UNIT[\"Grad\",0.0157079632679489]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); auto primem = datum->primeMeridian(); EXPECT_EQ(primem->nameStr(), "Paris RGS"); // The PRIMEM is really in degree EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE); EXPECT_NEAR(primem->longitude().value(), 2.33720833333333, 1e-14); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_LINUNIT) { const auto wkt = "GEOGCS[\"WGS_1984_3D\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]," "LINUNIT[\"Meter\",1.0]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); const auto &axisList = crs->coordinateSystem()->axisList(); ASSERT_EQ(axisList.size(), 3U); EXPECT_NEAR(axisList[0]->unit().conversionToSI(), 0.0174532925199433, 1e-15); EXPECT_NEAR(axisList[1]->unit().conversionToSI(), 0.0174532925199433, 1e-15); EXPECT_EQ(axisList[2]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_epsg_org_EPSG_4901_PRIMEM_weird_sexagesimal_DMS) { // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS" // unit and a DD.MMSSsss value, but this will likely be changed to // use decimal degree. auto obj = WKTParser().createFromWKT( "GEOGCRS[\"ATF (Paris)\"," " DATUM[\"Ancienne Triangulation Francaise (Paris)\"," " ELLIPSOID[\"Plessis 1817\",6376523,308.64," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]," " ID[\"EPSG\",7027]]," " ID[\"EPSG\",6901]]," " PRIMEM[\"Paris RGS\",2.201395," " ANGLEUNIT[\"sexagesimal DMS\",1,ID[\"EPSG\",9110]]," " ID[\"EPSG\",8914]]," " CS[ellipsoidal,2," " ID[\"EPSG\",6403]]," " AXIS[\"Geodetic latitude (Lat)\",north," " ORDER[1]]," " AXIS[\"Geodetic longitude (Lon)\",east," " ORDER[2]]," " ANGLEUNIT[\"grad\",0.015707963267949,ID[\"EPSG\",9105]]," " USAGE[SCOPE[\"Geodesy.\"],AREA[\"France - mainland onshore.\"]," " BBOX[42.33,-4.87,51.14,8.23]]," "ID[\"EPSG\",4901]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); auto primem = datum->primeMeridian(); EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE); EXPECT_NEAR(primem->longitude().value(), 2.33720833333333, 1e-14); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_old_datum_name_from_EPSG_code) { auto wkt = "GEOGCS[\"S-JTSK (Ferro)\",\n" " " "DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni_Ferro\",\n" " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,\n" " AUTHORITY[\"EPSG\",\"7004\"]],\n" " AUTHORITY[\"EPSG\",\"6818\"]],\n" " PRIMEM[\"Ferro\",-17.66666666666667,\n" " AUTHORITY[\"EPSG\",\"8909\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4818\"]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); EXPECT_EQ( datum->nameStr(), "System of the Unified Trigonometrical Cadastral Network (Ferro)"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_old_datum_name_without_EPSG_code) { auto wkt = "GEOGCS[\"S-JTSK (Ferro)\",\n" " " "DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni_Ferro\",\n" " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128]],\n" " PRIMEM[\"Ferro\",-17.66666666666667],\n" " UNIT[\"degree\",0.0174532925199433]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); EXPECT_EQ( datum->nameStr(), "System of the Unified Trigonometrical Cadastral Network (Ferro)"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_deprecated) { auto wkt = "GEOGCS[\"SAD69 (deprecated)\",\n" " DATUM[\"South_American_Datum_1969\",\n" " SPHEROID[\"GRS 1967\",6378160,298.247167427,\n" " AUTHORITY[\"EPSG\",\"7036\"]],\n" " AUTHORITY[\"EPSG\",\"6291\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9108\"]],\n" " AUTHORITY[\"EPSG\",\"4291\"]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "SAD69"); EXPECT_TRUE(crs->isDeprecated()); } // --------------------------------------------------------------------------- static std::string contentWKT2_EPSG_4326( "[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",7030]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " ID[\"EPSG\",4326]]"); // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_with_PROJ4_extension) { auto wkt = "GEOGCS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"WGS84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433],\n" " EXTENSION[\"PROJ4\",\"+proj=longlat +foo=bar +wktext\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +foo=bar +wktext +type=crs"); EXPECT_TRUE( crs->exportToWKT(WKTFormatter::create().get()).find("EXTENSION") == std::string::npos); EXPECT_TRUE( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get()) .find("EXTENSION") == std::string::npos); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_epsg_org_api_4326) { // Output from // https://apps.epsg.org/api/v1/CoordRefSystem/4326/export/?format=wkt&formatVersion=1 // using a datum ensemble name auto wkt = "GEOGCS[\"WGS 84\",DATUM[\"World Geodetic System 1984 ensemble\"," "SPHEROID[\"WGS 84\",6378137,298.257223563," "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree (supplier to define representation)\"," "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," "AXIS[\"Lat\",north],AXIS[\"Lon\",east]," "AUTHORITY[\"EPSG\",\"4326\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_epsg_org_api_4258) { // Output from // https://apps.epsg.org/api/v1/CoordRefSystem/4258/export/?format=wkt&formatVersion=1 // using a datum ensemble name auto wkt = "GEOGCS[\"ETRS89\"," "DATUM[\"European Terrestrial Reference System 1989 ensemble\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101," "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6258\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree (supplier to define representation)\"," "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," "AXIS[\"Lat\",north],AXIS[\"Lon\",east]," "AUTHORITY[\"EPSG\",\"4258\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "European Terrestrial Reference System 1989"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geographic_missing_unit_and_axis) { auto wkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\"," "SPHEROID[\"WGS 84\",6378137,298.257223563]]]]"; // Missing UNIT[] is illegal in strict mode EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); auto obj = WKTParser().setStrict(false).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->unit(), UnitOfMeasure::DEGREE); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geocentric_with_PROJ4_extension) { auto wkt = "GEOCCS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"WGS84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"Meter\",1],\n" " AXIS[\"Geocentric X\",OTHER],\n" " AXIS[\"Geocentric Y\",OTHER],\n" " AXIS[\"Geocentric Z\",NORTH],\n" " EXTENSION[\"PROJ4\",\"+proj=geocent +foo=bar +wktext\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=geocent +foo=bar +wktext +type=crs"); EXPECT_TRUE( crs->exportToWKT(WKTFormatter::create().get()).find("EXTENSION") == std::string::npos); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geocentric_missing_unit_and_axis) { auto wkt = "GEOCCS[\"WGS 84\",DATUM[\"WGS_1984\"," "SPHEROID[\"WGS 84\",6378137,298.257223563]]]]"; // Missing UNIT[] is illegal in strict mode EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); auto obj = WKTParser().setStrict(false).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 3U); EXPECT_EQ(cs->axisList()[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_non_conformant_inf_inverse_flattening) { // Some WKT in the wild use "inf". Cf SPHEROID["unnamed",6370997,"inf"] // in https://zenodo.org/record/3878979#.Y_P4g4CZNH4, // https://zenodo.org/record/5831940#.Y_P4i4CZNH5 // or https://grasswiki.osgeo.org/wiki/Marine_Science auto obj = WKTParser().setStrict(false).createFromWKT( "GEOGCS[\"GCS_sphere\",DATUM[\"D_unknown\"," "SPHEROID[\"Spherical_Earth\",6370997,\"inf\"]]," "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->ellipsoid()->inverseFlattening()->value(), 0.0); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_GCS_unknown_D_unknown) { auto obj = WKTParser().createFromWKT( "GEOGCS[\"GCS_unknown\",DATUM[\"D_unknown\"," "SPHEROID[\"unknown\",6370997,0]]," "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "unknown"); EXPECT_EQ(crs->datum()->nameStr(), "unknown"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_GCS_unknown_D_Unknown_based_on_WGS_84_ellipsoid) { auto obj = WKTParser().createFromWKT( "GEOGCS[\"GCS_unknown\",DATUM[\"D_Unknown_based_on_WGS_84_ellipsoid\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "unknown"); EXPECT_EQ(crs->datum()->nameStr(), "Unknown based on WGS 84 ellipsoid"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_GEODCRS_EPSG_4326) { auto obj = WKTParser().createFromWKT("GEODCRS" + contentWKT2_EPSG_4326); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_long_GEODETICCRS_EPSG_4326) { auto obj = WKTParser().createFromWKT("GEODETICCRS" + contentWKT2_EPSG_4326); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_GEOGCRS_EPSG_4326) { auto obj = WKTParser().createFromWKT("GEOGCRS" + contentWKT2_EPSG_4326); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_long_GEOGRAPHICCRS_EPSG_4326) { auto obj = WKTParser().createFromWKT("GEOGRAPHICCRS" + contentWKT2_EPSG_4326); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_simplified_EPSG_4326) { auto obj = WKTParser().createFromWKT( "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude (lat)\",north],\n" // test "name // (abbreviation)" " AXIS[\"longitude (lon)\",east],\n" " UNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",4326]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_GEODETICDATUM) { auto obj = WKTParser().createFromWKT( "GEODCRS[\"WGS 84\",\n" " GEODETICDATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"(lat)\",north],\n" // test "(abbreviation)" " AXIS[\"(lon)\",east],\n" " UNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",4326]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_TRF) { auto obj = WKTParser().createFromWKT( "GEODCRS[\"WGS 84\",\n" " TRF[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " UNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",4326]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */); } // --------------------------------------------------------------------------- static void checkEPSG_4979(GeographicCRSPtr crs) { ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "4979"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(crs->nameStr(), "WGS 84"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 3U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Latitude"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lat"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(cs->axisList()[1]->nameStr(), "Longitude"); EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lon"); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST); EXPECT_EQ(cs->axisList()[2]->nameStr(), "Ellipsoidal height"); EXPECT_EQ(cs->axisList()[2]->abbreviation(), "h"); EXPECT_EQ(cs->axisList()[2]->direction(), AxisDirection::UP); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); auto ellipsoid = datum->ellipsoid(); EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0); EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE); EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563); EXPECT_EQ(ellipsoid->nameStr(), "WGS 84"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_EPSG_4979) { auto obj = WKTParser().createFromWKT( "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " UNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkEPSG_4979(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_spherical_planetocentric) { const auto wkt = "GEODCRS[\"Mercury (2015) / Ocentric\",\n" " DATUM[\"Mercury (2015)\",\n" " ELLIPSOID[\"Mercury (2015)\",2440530,1075.12334801762,\n" " LENGTHUNIT[\"metre\",1]],\n" " ANCHOR[\"Hun Kal: 20.0\"]],\n" " PRIMEM[\"Reference Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[spherical,2],\n" " AXIS[\"planetocentric latitude (U)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"planetocentric longitude (V)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"IAU\",19902,2015],\n" " REMARK[\"Source of IAU Coordinate systems: " "doi://10.1007/s10569-017-9805-5\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->isSphericalPlanetocentric()); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); } // --------------------------------------------------------------------------- static void checkGeocentric(GeodeticCRSPtr crs) { // Explicitly check this is NOT a GeographicCRS EXPECT_TRUE(!dynamic_cast(crs.get())); EXPECT_EQ(crs->nameStr(), "WGS 84 (geocentric)"); EXPECT_TRUE(crs->isGeocentric()); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 3U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Geocentric X"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), "X"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::GEOCENTRIC_X); EXPECT_EQ(cs->axisList()[1]->nameStr(), "Geocentric Y"); EXPECT_EQ(cs->axisList()[1]->abbreviation(), "Y"); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::GEOCENTRIC_Y); EXPECT_EQ(cs->axisList()[2]->nameStr(), "Geocentric Z"); EXPECT_EQ(cs->axisList()[2]->abbreviation(), "Z"); EXPECT_EQ(cs->axisList()[2]->direction(), AxisDirection::GEOCENTRIC_Z); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); auto ellipsoid = datum->ellipsoid(); EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0); EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE); EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563); EXPECT_EQ(ellipsoid->nameStr(), "WGS 84"); auto primem = datum->primeMeridian(); ASSERT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_geocentric) { auto wkt = "GEODCRS[\"WGS 84 (geocentric)\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",7030]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8901]],\n" " CS[Cartesian,3],\n" // nominal value is 'geocentricX' with g lower case. " AXIS[\"(X)\",GeocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " ID[\"EPSG\",4328]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkGeocentric(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_simplified_geocentric) { auto wkt = "GEODCRS[\"WGS 84 (geocentric)\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " AXIS[\"(Z)\",geocentricZ],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",4328]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkGeocentric(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geocentric) { auto wkt = "GEOCCS[\"WGS 84 (geocentric)\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Geocentric X\",OTHER],\n" " AXIS[\"Geocentric Y\",OTHER],\n" " AXIS[\"Geocentric Z\",NORTH],\n" " AUTHORITY[\"EPSG\",\"4328\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkGeocentric(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_geocentric_with_z_OTHER) { auto wkt = "GEOCCS[\"WGS 84 (geocentric)\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Geocentric X\",OTHER],\n" " AXIS[\"Geocentric Y\",OTHER],\n" " AXIS[\"Geocentric Z\",OTHER],\n" " AUTHORITY[\"EPSG\",\"4328\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkGeocentric(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_geocentric_DEFININGTRANSFORMATION) { auto obj = WKTParser().createFromWKT( "GEODCRS[\"ETRF2000\"," "DATUM[\"European Terrestrial Reference Frame 2000\"," "ELLIPSOID[\"GRS 1980\",6378137,298.257222101]]," "CS[Cartesian,3]," "AXIS[\"(X)\",geocentricX]," "AXIS[\"(Y)\",geocentricY]," "AXIS[\"(Z)\",geocentricZ]," "LENGTHUNIT[\"metre\",1.0]," "DEFININGTRANSFORMATION[\"ITRF2000 to ETRF2000 (EUREF)\"," "ID[\"EPSG\",7940]]," "ID[\"EPSG\",7930]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // For now we ignore the DEFININGTRANSFORMATION const char *expected_wkt = "GEODCRS[\"ETRF2000\",\n" " DATUM[\"European Terrestrial Reference Frame 2000\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",7930]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected_wkt); } // --------------------------------------------------------------------------- static void checkProjected(ProjectedCRSPtr crs, bool checkEPSGCodes = true) { EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "32631"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); auto geogCRS = nn_dynamic_pointer_cast(crs->baseCRS()); ASSERT_TRUE(geogCRS != nullptr); checkEPSG_4326(NN_CHECK_ASSERT(geogCRS), true, checkEPSGCodes); auto conversion = crs->derivingConversion(); EXPECT_EQ(conversion->nameStr(), "UTM zone 31N"); auto method = conversion->method(); EXPECT_EQ(method->nameStr(), "Transverse Mercator"); auto values = conversion->parameterValues(); ASSERT_EQ(values.size(), 5U); { const auto &opParamvalue = nn_dynamic_pointer_cast(values[0]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_EQ(paramName, "Latitude of natural origin"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(measure.value(), 0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[1]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_EQ(paramName, "Longitude of natural origin"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(measure.value(), 3); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[2]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_EQ(paramName, "Scale factor at natural origin"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::SCALE_UNITY); EXPECT_EQ(measure.value(), 0.9996); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[3]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_EQ(paramName, "False easting"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE); EXPECT_EQ(measure.value(), 500000); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[4]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_EQ(paramName, "False northing"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE); EXPECT_EQ(measure.value(), 0); } auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Easting"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), "E"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST); EXPECT_EQ(cs->axisList()[1]->nameStr(), "Northing"); EXPECT_EQ(cs->axisList()[1]->abbreviation(), "N"); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_projected) { auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"(E)\",East],\n" // should normally be uppercase " AXIS[\"(N)\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkProjected(crs); EXPECT_TRUE(!crs->baseCRS()->identifiers().empty()); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_projected_no_axis) { auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"latitude\",NORTH],\n" " AXIS[\"longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AUTHORITY[\"EPSG\",\"32631\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkProjected(crs); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_projected_wrong_axis_geogcs) { auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"longitude\",EAST],\n" " AXIS[\"latitude\",NORTH],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AUTHORITY[\"EPSG\",\"32631\"]]"; { WKTParser parser; parser.setStrict(false).attachDatabaseContext( DatabaseContext::create()); auto obj = parser.createFromWKT(wkt); EXPECT_TRUE(!parser.warningList().empty()); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->baseCRS()->identifiers().empty()); auto cs = crs->baseCRS()->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH); } { WKTParser parser; parser.setStrict(false) .setUnsetIdentifiersIfIncompatibleDef(false) .attachDatabaseContext(DatabaseContext::create()); auto obj = parser.createFromWKT(wkt); EXPECT_TRUE(parser.warningList().empty()); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(!crs->baseCRS()->identifiers().empty()); } } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_projected_wrong_angular_unit) { auto wkt = "PROJCS[\"Merchich / Nord Maroc\"," " GEOGCS[\"Merchich\"," " DATUM[\"Merchich\"," " SPHEROID[\"Clarke 1880 (IGN)\"," "6378249.2,293.466021293627]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"grad\",0.015707963267949," " AUTHORITY[\"EPSG\",\"9105\"]]," " AUTHORITY[\"EPSG\",\"4261\"]]," " PROJECTION[\"Lambert_Conformal_Conic_1SP\"]," " PARAMETER[\"latitude_of_origin\",37]," " PARAMETER[\"central_meridian\",-6]," " PARAMETER[\"scale_factor\",0.999625769]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",300000]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]]"; { WKTParser parser; parser.setStrict(false).attachDatabaseContext( DatabaseContext::create()); auto obj = parser.createFromWKT(wkt); EXPECT_TRUE(!parser.warningList().empty()); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // No base CRS identifiers EXPECT_TRUE(crs->baseCRS()->identifiers().empty()); auto cs = crs->baseCRS()->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_NEAR(cs->axisList()[0]->unit().conversionToSI(), UnitOfMeasure::GRAD.conversionToSI(), 1e-10); } { WKTParser parser; parser.setUnsetIdentifiersIfIncompatibleDef(false) .attachDatabaseContext(DatabaseContext::create()); auto obj = parser.createFromWKT(wkt); EXPECT_TRUE(parser.warningList().empty()); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // Base CRS identifier preserved EXPECT_TRUE(!crs->baseCRS()->identifiers().empty()); auto cs = crs->baseCRS()->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_NEAR(cs->axisList()[0]->unit().conversionToSI(), UnitOfMeasure::GRAD.conversionToSI(), 1e-10); } } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_projected_with_PROJ4_extension) { auto wkt = "PROJCS[\"unnamed\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"WGS84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"Meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " EXTENSION[\"PROJ4\",\"+proj=merc +wktext\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=merc +wktext +type=crs"); EXPECT_TRUE( crs->exportToWKT(WKTFormatter::create().get()).find("EXTENSION") == std::string::npos); EXPECT_TRUE( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get()) .find("EXTENSION") == std::string::npos); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_projected_missing_unit_and_axis) { auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",GEOGCS[\"WGS 84\"," "DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563," "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree\",0.0174532925199433," "AUTHORITY[\"EPSG\",\"9122\"]]," "AUTHORITY[\"EPSG\",\"4326\"]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0]," "PARAMETER[\"central_meridian\",3]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"false_easting\",500000]," "PARAMETER[\"false_northing\",0]]"; // Missing UNIT[] is illegal in strict mode EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); auto obj = WKTParser().setStrict(false).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_Mercator_1SP_with_latitude_origin_0) { auto wkt = "PROJCS[\"unnamed\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"WGS84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"Meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(got_wkt.find("Mercator_1SP") != std::string::npos) << got_wkt; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_Mercator_1SP_without_scale_factor) { // See https://github.com/OSGeo/PROJ/issues/1700 auto wkt = "PROJCS[\"unnamed\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"WGS84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"Meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; WKTParser parser; parser.setStrict(false).attachDatabaseContext(DatabaseContext::create()); auto obj = parser.createFromWKT(wkt); EXPECT_TRUE(!parser.warningList().empty()); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(got_wkt.find("PARAMETER[\"scale_factor\",1]") != std::string::npos) << got_wkt; EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +units=m " "+no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_krovak_south_west) { auto wkt = "PROJCS[\"S-JTSK / Krovak\"," " GEOGCS[\"S-JTSK\"," " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\"," " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128," " AUTHORITY[\"EPSG\",\"7004\"]]," " AUTHORITY[\"EPSG\",\"6156\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4156\"]]," " PROJECTION[\"Krovak\"]," " PARAMETER[\"latitude_of_center\",49.5]," " PARAMETER[\"longitude_of_center\",24.83333333333333]," " PARAMETER[\"azimuth\",30.2881397527778]," " PARAMETER[\"pseudo_standard_parallel_1\",78.5]," " PARAMETER[\"scale_factor\",0.9999]," " PARAMETER[\"false_easting\",0]," " PARAMETER[\"false_northing\",0]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"X\",SOUTH]," " AXIS[\"Y\",WEST]," " AUTHORITY[\"EPSG\",\"5513\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak"); auto expected_wkt2 = "PROJCRS[\"S-JTSK / Krovak\",\n" " BASEGEODCRS[\"S-JTSK\",\n" " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Krovak\",\n" " ID[\"EPSG\",9819]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"x\",south,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"y\",west,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5513]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2); auto projString = crs->exportToPROJString(PROJStringFormatter::create().get()); auto expectedPROJString = "+proj=krovak +axis=swu +lat_0=49.5 " "+lon_0=24.8333333333333 +alpha=30.2881397527778 " "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m " "+no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); obj = PROJStringParser().createFromPROJString(projString); auto crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); auto wkt2 = crs2->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt2.find("METHOD[\"Krovak\"") != std::string::npos) << wkt2; EXPECT_TRUE( wkt2.find("PARAMETER[\"Latitude of pseudo standard parallel\",78.5,") != std::string::npos) << wkt2; EXPECT_TRUE( wkt2.find("PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,") != std::string::npos) << wkt2; EXPECT_EQ(crs2->exportToPROJString(PROJStringFormatter::create().get()), expectedPROJString); obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=unitconvert +xy_in=deg " "+xy_out=rad " "+step +proj=krovak +lat_0=49.5 " "+lon_0=24.8333333333333 +alpha=30.2881397527778 " "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=axisswap +order=-2,-1"); crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); wkt2 = crs2->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt2.find("METHOD[\"Krovak\"") != std::string::npos) << wkt2; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_krovak_north_oriented) { auto wkt = "PROJCS[\"S-JTSK / Krovak East North\"," " GEOGCS[\"S-JTSK\"," " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\"," " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128," " AUTHORITY[\"EPSG\",\"7004\"]]," " AUTHORITY[\"EPSG\",\"6156\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4156\"]]," " PROJECTION[\"Krovak\"]," " PARAMETER[\"latitude_of_center\",49.5]," " PARAMETER[\"longitude_of_center\",24.83333333333333]," " PARAMETER[\"azimuth\",30.2881397527778]," " PARAMETER[\"pseudo_standard_parallel_1\",78.5]," " PARAMETER[\"scale_factor\",0.9999]," " PARAMETER[\"false_easting\",0]," " PARAMETER[\"false_northing\",0]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"X\",EAST]," " AXIS[\"Y\",NORTH]," " AUTHORITY[\"EPSG\",\"5514\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak (North Orientated)"); EXPECT_EQ( crs->exportToWKT(WKTFormatter::create().get()), "PROJCRS[\"S-JTSK / Krovak East North\",\n" " BASEGEODCRS[\"S-JTSK\",\n" " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Krovak (North Orientated)\",\n" " ID[\"EPSG\",1041]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"x\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"y\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5514]]"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_krovak_modified_south_west) { auto wkt = "PROJCRS[\"S-JTSK/05 / Modified Krovak\",\n" " BASEGEOGCRS[\"S-JTSK/05\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network/05\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",5228]],\n" " CONVERSION[\"Modified Krovak (Greenwich)\",\n" " METHOD[\"Krovak Modified\",\n" " ID[\"EPSG\",1042]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",5000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"southing (X)\",south,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"westing (Y)\",west,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Engineering survey, topographic mapping.\"],\n" " AREA[\"Czechia.\"],\n" " BBOX[48.58,12.09,51.06,18.86]],\n" " ID[\"EPSG\",5515]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak Modified"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); auto projString = crs->exportToPROJString(PROJStringFormatter::create().get()); auto expectedPROJString = "+proj=mod_krovak +axis=swu +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397222222 +k=0.9999 +x_0=5000000 +y_0=5000000 " "+ellps=bessel +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); obj = PROJStringParser().createFromPROJString(projString); auto crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); auto wkt2 = crs2->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt2.find("METHOD[\"Krovak Modified\"") != std::string::npos) << wkt2; EXPECT_TRUE( wkt2.find("PARAMETER[\"Latitude of pseudo standard parallel\",78.5,") != std::string::npos) << wkt2; EXPECT_TRUE( wkt2.find("PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,") != std::string::npos) << wkt2; EXPECT_EQ(crs2->exportToPROJString(PROJStringFormatter::create().get()), expectedPROJString); obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=unitconvert +xy_in=deg " "+xy_out=rad " "+step +proj=mod_krovak +lat_0=49.5 " "+lon_0=24.8333333333333 +alpha=30.2881397222222 " "+k=0.9999 +x_0=5000000 +y_0=5000000 +ellps=bessel " "+step +proj=axisswap +order=-2,-1"); crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); wkt2 = crs2->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt2.find("METHOD[\"Krovak Modified\"") != std::string::npos) << wkt2; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_krovak_modified_east_north) { auto wkt = "PROJCRS[\"S-JTSK/05 / Modified Krovak East North\",\n" " BASEGEOGCRS[\"S-JTSK/05\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network/05\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",5228]],\n" " CONVERSION[\"Modified Krovak East North (Greenwich)\",\n" " METHOD[\"Krovak Modified (North Orientated)\",\n" " ID[\"EPSG\",1043]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",5000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"GIS.\"],\n" " AREA[\"Czechia.\"],\n" " BBOX[48.58,12.09,51.06,18.86]],\n" " ID[\"EPSG\",5516]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak Modified (North Orientated)"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); auto projString = crs->exportToPROJString(PROJStringFormatter::create().get()); auto expectedPROJString = "+proj=mod_krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397222222 +k=0.9999 +x_0=5000000 +y_0=5000000 " "+ellps=bessel +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); obj = PROJStringParser().createFromPROJString(projString); auto crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); auto wkt2 = crs2->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt2.find("METHOD[\"Krovak Modified (North Orientated)\"") != std::string::npos) << wkt2; EXPECT_TRUE( wkt2.find("PARAMETER[\"Latitude of pseudo standard parallel\",78.5,") != std::string::npos) << wkt2; EXPECT_TRUE( wkt2.find("PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,") != std::string::npos) << wkt2; EXPECT_EQ(crs2->exportToPROJString(PROJStringFormatter::create().get()), expectedPROJString); obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=unitconvert +xy_in=deg " "+xy_out=rad " "+step +proj=mod_krovak +lat_0=49.5 " "+lon_0=24.8333333333333 +alpha=30.2881397222222 " "+k=0.9999 +x_0=5000000 +y_0=5000000 +ellps=bessel"); crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); wkt2 = crs2->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt2.find("METHOD[\"Krovak Modified (North Orientated)\"") != std::string::npos) << wkt2; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_70) { auto wkt = "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",70],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto projString = crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()); auto expectedPROJString = "+proj=stere +lat_0=90 +lat_ts=70 +lon_0=2 " "+x_0=3 +y_0=4 +datum=WGS84 +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->nameStr(), "Easting"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(), AxisDirection::SOUTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->nameStr(), "Northing"); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(), AxisDirection::SOUTH); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_minus_70) { auto wkt = "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",-70],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->nameStr(), "Easting"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->nameStr(), "Northing"); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(), AxisDirection::NORTH); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_90) { auto wkt = "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",90],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto projString = crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()); auto expectedPROJString = "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=2 " "+x_0=3 +y_0=4 +datum=WGS84 +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_polar_stereographic_latitude_of_origin_90_scale_factor_1) { auto wkt = "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",90],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto projString = crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()); auto expectedPROJString = "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=2 " "+x_0=3 +y_0=4 +datum=WGS84 +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_polar_stereographic_scale_factor) { auto wkt = "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",90],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",0.99],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto projString = crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()); auto expectedPROJString = "+proj=stere +lat_0=90 +lon_0=2 +k=0.99 +x_0=3 " "+y_0=4 +datum=WGS84 +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_Spherical_Cross_Track_Height) { auto wkt = "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Spherical_Cross_Track_Height\"],\n" " PARAMETER[\"peg_point_latitude\",1],\n" " PARAMETER[\"peg_point_longitude\",2],\n" " PARAMETER[\"peg_point_heading\",3],\n" " PARAMETER[\"peg_point_height\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto projString = crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()); auto expectedPROJString = "+proj=sch +plat_0=1 +plon_0=2 +phdg_0=3 +h_0=4 " "+datum=WGS84 +units=m +no_defs +type=crs"; EXPECT_EQ(projString, expectedPROJString); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_hotine_oblique_mercator_without_rectified_grid_angle) { auto wkt = "PROJCS[\"NAD_1983_Michigan_GeoRef_Meters\"," "GEOGCS[\"NAD83(1986)\"," "DATUM[\"North_American_Datum_1983\"," "SPHEROID[\"GRS_1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0]," "UNIT[\"Degree\",0.017453292519943295]]," "PROJECTION[\"Hotine_Oblique_Mercator\"]," "PARAMETER[\"false_easting\",2546731.496]," "PARAMETER[\"false_northing\",-4354009.816]," "PARAMETER[\"latitude_of_center\",45.30916666666666]," "PARAMETER[\"longitude_of_center\",-86]," "PARAMETER[\"azimuth\",-22.74444]," "PARAMETER[\"scale_factor\",0.9996]," "UNIT[\"Meter\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // Check that we have added automatically rectified_grid_angle auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(got_wkt.find("PARAMETER[\"rectified_grid_angle\",-22.74444]") != std::string::npos) << got_wkt; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_hotine_oblique_mercator_with_rectified_grid_angle) { auto wkt = "PROJCS[\"NAD_1983_Michigan_GeoRef_Meters\"," "GEOGCS[\"NAD83(1986)\"," "DATUM[\"North_American_Datum_1983\"," "SPHEROID[\"GRS_1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0]," "UNIT[\"Degree\",0.017453292519943295]]," "PROJECTION[\"Hotine_Oblique_Mercator\"]," "PARAMETER[\"false_easting\",2546731.496]," "PARAMETER[\"false_northing\",-4354009.816]," "PARAMETER[\"latitude_of_center\",45.30916666666666]," "PARAMETER[\"longitude_of_center\",-86]," "PARAMETER[\"azimuth\",-22.74444]," "PARAMETER[\"rectified_grid_angle\",-23]," "PARAMETER[\"scale_factor\",0.9996]," "UNIT[\"Meter\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // Check that we have not overridden rectified_grid_angle auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(got_wkt.find("PARAMETER[\"rectified_grid_angle\",-23]") != std::string::npos) << got_wkt; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_hotine_oblique_mercator_azimuth_center_with_rectified_grid_angle) { auto wkt = "PROJCS[\"unknown\"," "GEOGCS[\"unknown\"," " DATUM[\"WGS_1984\"," " SPHEROID[\"WGS 84\",6378137,298.257223563]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"degree\",0.0174532925199433]]," "PROJECTION[\"Hotine_Oblique_Mercator_Azimuth_Center\"]," "PARAMETER[\"latitude_of_center\",0]," "PARAMETER[\"longitude_of_center\",0]," "PARAMETER[\"azimuth\",30]," "PARAMETER[\"rectified_grid_angle\",0]," "PARAMETER[\"scale_factor\",1]," "PARAMETER[\"false_easting\",0]," "PARAMETER[\"false_northing\",0]," "UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // Check that we have not overridden rectified_grid_angle auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(got_wkt.find("PARAMETER[\"rectified_grid_angle\",0]") != std::string::npos) << got_wkt; } // --------------------------------------------------------------------------- TEST(proj_export, wkt2_hotine_oblique_mercator_without_rectified_grid_angle) { auto wkt = "PROJCRS[\"NAD_1983_Michigan_GeoRef_Meters\",\n" " BASEGEOGCRS[\"NAD83(1986)\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS_1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6269]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Hotine Oblique Mercator (variant A)\",\n" " ID[\"EPSG\",9812]],\n" " PARAMETER[\"False easting\",2546731.496,\n" " LENGTHUNIT[\"Meter\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",-4354009.816,\n" " LENGTHUNIT[\"Meter\",1],\n" " ID[\"EPSG\",8807]],\n" " PARAMETER[\"Latitude of projection centre\"," " 45.3091666666667,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection centre\",-86,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth of initial line\",-22.74444,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Scale factor on initial line\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"Meter\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"Meter\",1]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // We don't do any particular handling of missing Angle from Rectified // to Skew Grid on import, but on export to PROJ string, // check that we don't add a dummy gamma value. auto expectedPROJString = "+proj=omerc +no_uoff +lat_0=45.3091666666667 " "+lonc=-86 +alpha=-22.74444 " "+k=0.9996 +x_0=2546731.496 +y_0=-4354009.816 " "+datum=NAD83 +units=m +no_defs +type=crs"; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), expectedPROJString); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_projected) { auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",7030]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" // Voluntary omit LENGTHUNIT to check the WKT grammar accepts // Check that we default to degree //" ANGLEUNIT[\"degree\",0.0174532925199433,\n" //" ID[\"EPSG\",9122]],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" // Check that we default to unity //" SCALEUNIT[\"unity\",1,\n" //" ID[\"EPSG\",9201]],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" // Voluntary omit LENGTHUNIT to check the WKT grammar accepts // Check that we default to metre //" LENGTHUNIT[\"metre\",1,\n" //" ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16031]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " ID[\"EPSG\",32631]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkProjected(crs, /*checkEPSGCodes = */ false); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_projected_with_id_in_basegeodcrs) { auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " ID[\"EPSG\",4326]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0],\n" " PARAMETER[\"Longitude of natural origin\",3],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996],\n" " PARAMETER[\"False easting\",500000],\n" " PARAMETER[\"False northing\",0]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",32631]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); ASSERT_EQ(crs->baseCRS()->identifiers().size(), 1U); EXPECT_EQ(crs->baseCRS()->identifiers().front()->code(), "4326"); { auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_TRUE(got_wkt.find("ID[\"EPSG\",4326]]") != std::string::npos) << got_wkt; } { auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019_SIMPLIFIED) .get()); EXPECT_TRUE(got_wkt.find("ID[\"EPSG\",4326]]") == std::string::npos) << got_wkt; } } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_projected_no_id_but_id_in_basegeodcrs) { auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " ID[\"EPSG\",4326]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0],\n" " PARAMETER[\"Longitude of natural origin\",3],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996],\n" " PARAMETER[\"False easting\",500000],\n" " PARAMETER[\"False northing\",0]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_TRUE(got_wkt.find("ID[\"EPSG\",4326]]") != std::string::npos) << got_wkt; } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_simplified_projected) { auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0],\n" " PARAMETER[\"Longitude of natural origin\",3],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996],\n" " PARAMETER[\"False easting\",500000],\n" " PARAMETER[\"False northing\",0]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",32631]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); checkProjected(crs, /*checkEPSGCodes = */ false); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_projected_3D) { auto wkt = "PROJCRS[\"WGS 84 (G1762) / UTM zone 31N 3D\"," " BASEGEOGCRS[\"WGS 84\"," " DATUM[\"World Geodetic System of 1984 (G1762)\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1.0]]]]," " CONVERSION[\"Some conversion 3D\"," " METHOD[\"Transverse Mercator (3D)\"]," " PARAMETER[\"Latitude of origin\",0.0," " ANGLEUNIT[\"degree\",0.0174532925199433]]," " PARAMETER[\"Longitude of origin\",3.0," " ANGLEUNIT[\"degree\",0.0174532925199433]]," " PARAMETER[\"Scale factor\",1,SCALEUNIT[\"unity\",1.0]]," " PARAMETER[\"False easting\",0.0," " LENGTHUNIT[\"metre\",1.0]]," " PARAMETER[\"False northing\",0.0,LENGTHUNIT[\"metre\",1.0]]]," " CS[Cartesian,3]," " AXIS[\"(E)\",east,ORDER[1]]," " AXIS[\"(N)\",north,ORDER[2]]," " AXIS[\"ellipsoidal height (h)\",up,ORDER[3]]," " LENGTHUNIT[\"metre\",1.0]" "]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=tmerc +lat_0=0 +lon_0=3 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 " "+units=m +no_defs +type=crs"); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()), FormattingException); EXPECT_NO_THROW(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get())); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_projected_utm_3D) { // Example from WKT2:2019 auto wkt = "PROJCRS[\"WGS 84 (G1762) / UTM zone 31N 3D\"," " BASEGEOGCRS[\"WGS 84\"," " DATUM[\"World Geodetic System of 1984 (G1762)\"," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1.0]]]]," " CONVERSION[\"UTM zone 31N 3D\"," " METHOD[\"Transverse Mercator (3D)\"]," " PARAMETER[\"Latitude of origin\",0.0," " ANGLEUNIT[\"degree\",0.0174532925199433]]," " PARAMETER[\"Longitude of origin\",3.0," " ANGLEUNIT[\"degree\",0.0174532925199433]]," " PARAMETER[\"Scale factor\",0.9996,SCALEUNIT[\"unity\",1.0]]," " PARAMETER[\"False easting\",500000.0," " LENGTHUNIT[\"metre\",1.0]]," " PARAMETER[\"False northing\",0.0,LENGTHUNIT[\"metre\",1.0]]]," " CS[Cartesian,3]," " AXIS[\"(E)\",east,ORDER[1]]," " AXIS[\"(N)\",north,ORDER[2]]," " AXIS[\"ellipsoidal height (h)\",up,ORDER[3]]," " LENGTHUNIT[\"metre\",1.0]" "]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=utm +zone=31 +ellps=WGS84 +units=m +no_defs +type=crs"); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()), FormattingException); EXPECT_NO_THROW(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get())); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_projected_with_base_geocentric) { auto wkt = "PROJCRS[\"EPSG topocentric example B\",\n" " BASEGEODCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4978]],\n" " CONVERSION[\"EPSG topocentric example B\",\n" " METHOD[\"Geocentric/topocentric conversions\",\n" " ID[\"EPSG\",9836]],\n" " PARAMETER[\"Geocentric X of topocentric origin\",3771793.97,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8837]],\n" " PARAMETER[\"Geocentric Y of topocentric origin\",140253.34,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8838]],\n" " PARAMETER[\"Geocentric Z of topocentric origin\",5124304.35,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8839]]],\n" " CS[Cartesian,3],\n" " AXIS[\"topocentric East (U)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"topocentric North (V)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"topocentric height (W)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Example only (fictitious).\"],\n" " AREA[\"Description of the extent of the CRS.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",5820]]"; auto dbContext = DatabaseContext::create(); // Need a database so that EPSG:4978 is resolved auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->baseCRS()->isGeocentric()); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt2_2019_eqdc_non_epsg) { // Example from WKT2:2019 auto wkt = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Equidistant Conic\"],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=eqdc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6 " "+datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, projected_angular_unit_from_primem) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"NTF (Paris) / Lambert Nord France\",\n" " BASEGEODCRS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n" " ELLIPSOID[\"Clarke 1880 " "(IGN)\",6378249.2,293.4660213,LENGTHUNIT[\"metre\",1.0]]],\n" " PRIMEM[\"Paris\",2.5969213,ANGLEUNIT[\"grad\",0.015707963268]]],\n" " CONVERSION[\"Lambert Nord France\",\n" " METHOD[\"Lambert Conic Conformal (1SP)\",ID[\"EPSG\",9801]],\n" " PARAMETER[\"Latitude of natural " "origin\",55,ANGLEUNIT[\"grad\",0.015707963268]],\n" " PARAMETER[\"Longitude of natural " "origin\",0,ANGLEUNIT[\"grad\",0.015707963268]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.999877341,SCALEUNIT[\"unity\",1.0]],\n" " PARAMETER[\"False easting\",600000,LENGTHUNIT[\"metre\",1.0]],\n" " PARAMETER[\"False northing\",200000,LENGTHUNIT[\"metre\",1.0]]],\n" " CS[cartesian,2],\n" " AXIS[\"easting (X)\",east,ORDER[1]],\n" " AXIS[\"northing (Y)\",north,ORDER[2]],\n" " LENGTHUNIT[\"metre\",1.0],\n" " ID[\"EPSG\",27561]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->coordinateSystem()->axisList()[0]->unit(), UnitOfMeasure::GRAD); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_MERIDIAN) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1.0]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting " "(X)\",south,MERIDIAN[90,ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " AXIS[\"northing (Y)\",north],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",32631]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 2U); auto axis = crs->coordinateSystem()->axisList()[0]; auto meridian = axis->meridian(); ASSERT_TRUE(meridian != nullptr); EXPECT_EQ(meridian->longitude().value(), 90.0); EXPECT_EQ(meridian->longitude().unit(), UnitOfMeasure::DEGREE); ASSERT_TRUE(crs->coordinateSystem()->axisList()[1]->meridian() == nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_multiple_ID) { auto wkt = "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " AXIS[\"(Z)\",geocentricZ],\n" " UNIT[\"metre\",1],\n" " ID[\"authorityA\",\"codeA\"],\n" " ID[\"authorityB\",\"codeB\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84"); ASSERT_EQ(crs->identifiers().size(), 2U); EXPECT_EQ(crs->identifiers()[0]->code(), "codeA"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "authorityA"); EXPECT_EQ(crs->identifiers()[1]->code(), "codeB"); EXPECT_EQ(*(crs->identifiers()[1]->codeSpace()), "authorityB"); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_AXISMINVAL_AXISMAXVAL_RANGEMEANING) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMINVALUE[0],\n" " AXISMAXVALUE[360],\n" // nominal value is 'wraparound' lower case " RANGEMEANING[wrapAround]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 2U); { auto axis = crs->coordinateSystem()->axisList()[0]; EXPECT_FALSE(axis->minimumValue().has_value()); EXPECT_FALSE(axis->maximumValue().has_value()); EXPECT_FALSE(axis->rangeMeaning().has_value()); } { auto axis = crs->coordinateSystem()->axisList()[1]; ASSERT_TRUE(axis->minimumValue().has_value()); EXPECT_EQ(*axis->minimumValue(), 0); ASSERT_TRUE(axis->maximumValue().has_value()); EXPECT_EQ(*axis->maximumValue(), 360); ASSERT_TRUE(axis->rangeMeaning().has_value()); EXPECT_EQ(axis->rangeMeaning()->toString(), "wraparound"); } EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), replaceAll(wkt, "wrapAround", "wraparound")); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_invalid_AXISMINVAL_string) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMINVALUE[invalid]]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_invalid_AXISMINVAL_too_many_children) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMINVALUE[1,2]]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_invalid_AXISMAXVAL_string) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMAXVALUE[invalid]]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_invalid_AXISMAXVAL_too_many_children) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMAXVALUE[1,2]]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_invalid_RANGEMEANING) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMINVALUE[0],\n" " AXISMAXVALUE[360],\n" " RANGEMEANING[invalid]]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, cs_with_invalid_RANGEMEANING_too_many_children) { auto wkt = "PROJCRS[\"dummy\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"dummy\",\n" " METHOD[\"dummy\"],\n" " PARAMETER[\"dummy\",1]],\n" " CS[Cartesian,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " AXISMINVALUE[0],\n" " AXISMAXVALUE[360],\n" " RANGEMEANING[exact,unexpected_value]]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT2) { auto wkt = "VERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5701]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "ODN height"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "5701"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn"); // ASSERT_EQ(datum->identifiers().size(), 1U); // EXPECT_EQ(datum->identifiers()[0]->code(), "5101"); // EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), "H"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_VRF_WKT2) { auto wkt = "VERTCRS[\"ODN height\",\n" " VRF[\"Ordnance Datum Newlyn\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5701]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_with_GEOIDMODEL) { auto wkt = "VERTCRS[\"CGVD2013\",\n" " VDATUM[\"Canadian Geodetic Vertical Datum of 2013\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " GEOIDMODEL[\"CGG2013\",\n" " ID[\"EPSG\",6648]],\n" " GEOIDMODEL[\"other\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT1_GDAL) { auto wkt = "VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n" " AUTHORITY[\"EPSG\",\"5101\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5701\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "ODN height"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "5701"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn"); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "5101"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height"); EXPECT_EQ(cs->axisList()[0]->abbreviation(), ""); // "H" in WKT2 EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT1_GDAL_minimum) { auto wkt = "VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_EQ(crs->nameStr(), "ODN height"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); EXPECT_EQ(cs->axisList()[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT1_GDAL_missing_unit_and_axis) { auto wkt = "VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005]]"; // Missing UNIT[] is illegal in strict mode EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); auto obj = WKTParser().setStrict(false).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_EQ(crs->nameStr(), "ODN height"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); EXPECT_EQ(cs->axisList()[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT1_GDAl_missing_unit_with_axis) { auto wkt = "VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005],\n" " AXIS[\"gravity-related height\",UP]]"; // Missing UNIT[] is illegal in strict mode EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); auto obj = WKTParser().setStrict(false).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_EQ(crs->nameStr(), "ODN height"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); EXPECT_EQ(cs->axisList()[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, VERTCS_WKT1_ESRI) { auto wkt = "VERTCS[\"EGM2008_Geoid\",VDATUM[\"EGM2008_Geoid\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "EGM2008_Geoid"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "EGM2008_Geoid"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); EXPECT_EQ(WKTParser().guessDialect(wkt), WKTParser::WKTGuessedDialect::WKT1_ESRI); } // --------------------------------------------------------------------------- TEST(wkt_parse, VERTCS_WKT1_ESRI_context) { auto wkt = "VERTCS[\"EGM2008_Geoid\",VDATUM[\"EGM2008_Geoid\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "EGM2008 height"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "EGM2008 geoid"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP); EXPECT_EQ(WKTParser().guessDialect(wkt), WKTParser::WKTGuessedDialect::WKT1_ESRI); } // --------------------------------------------------------------------------- TEST(wkt_parse, VERTCS_WKT1_ESRI_down) { auto wkt = "VERTCS[\"Caspian\",VDATUM[\"Caspian_Sea\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",-1.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::DOWN); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT1_LAS_ftUS) { auto wkt = "VERT_CS[\"NAVD88 - Geoid03 (Feet)\"," " VERT_DATUM[\"unknown\",2005]," " UNIT[\"US survey foot\",0.3048006096012192," " AUTHORITY[\"EPSG\",\"9003\"]]," " AXIS[\"Up\",UP]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 height (ftUS)"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "6360"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); const auto &geoidModel = crs->geoidModel(); ASSERT_TRUE(!geoidModel.empty()); EXPECT_EQ(geoidModel[0]->nameStr(), "GEOID03"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "North American Vertical Datum 1988"); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "5103"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); const auto &axis = crs->coordinateSystem()->axisList()[0]; EXPECT_EQ(axis->direction(), AxisDirection::UP); EXPECT_EQ(axis->unit().name(), "US survey foot"); EXPECT_NEAR(axis->unit().conversionToSI(), 0.3048006096012192, 1e-16); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_WKT1_LAS_metre) { auto wkt = "VERT_CS[\"NAVD88 via Geoid09\"," " VERT_DATUM[\"unknown\",2005]," " UNIT[\"metre\",1.0," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Up\",UP]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 height"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "5703"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); const auto &geoidModel = crs->geoidModel(); ASSERT_TRUE(!geoidModel.empty()); EXPECT_EQ(geoidModel[0]->nameStr(), "GEOID09"); auto datum = crs->datum(); EXPECT_EQ(datum->nameStr(), "North American Vertical Datum 1988"); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "5103"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); const auto &axis = crs->coordinateSystem()->axisList()[0]; EXPECT_EQ(axis->direction(), AxisDirection::UP); EXPECT_EQ(axis->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, compoundcrs_WKT1_LAS_only_geoid_name) { // Yes, this WKT is quite odd... auto wkt = "COMPD_CS[\"NAD83 / NAD83 / South Dakota South / Geoid 2012A (ftUS)\"," "PROJCS[\"NAD83 / NAD83 / South Dakota South / Geoid 2012A (ftUS)\"," "GEOGCS[\"NAD83 / NAD83 / South Dakota South / Geoid 2012A (ftUS)\"," "DATUM[\"NAD83\",SPHEROID[\"GRS80\",6378137.000,298.257222100," "AUTHORITY[\"EPSG\",\"0\"]],AUTHORITY[\"EPSG\",\"0\"]]," "PRIMEM[\"Greenwich\",0.0000000000000000," "AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"US survey foot\",0.30480060960121918567," "AUTHORITY[\"EPSG\",\"9003\"]]," "AUTHORITY[\"EPSG\",\"0\"]]," "PROJECTION[\"Lambert_Conformal_Conic_2SP\"," "AUTHORITY[\"EPSG\",\"9802\"]]," "PARAMETER[\"standard_parallel_1\",44.4000000000000057]," "PARAMETER[\"standard_parallel_2\",42.8333333333333357]," "PARAMETER[\"latitude_of_origin\",42.3333333333333499]," "PARAMETER[\"central_meridian\",-100.3333333333333428]," "PARAMETER[\"false_easting\",1968500.000]," "PARAMETER[\"false_northing\",0.000]," "UNIT[\"US survey foot\",0.30480060960121918567," "AUTHORITY[\"EPSG\",\"9003\"]]," "AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]," "AUTHORITY[\"EPSG\",\"0\"]]," "VERT_CS[\"Geoid 2012A\",VERT_DATUM[\"Geoid 2012A\",2005]," "UNIT[\"US survey foot\",0.30480060960121918567," "AUTHORITY[\"EPSG\",\"9003\"]],AXIS[\"Gravity-related height\",UP]," "AUTHORITY[\"EPSG\",\"0\"]],AUTHORITY[\"EPSG\",\"0\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto compoundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(compoundCRS != nullptr); auto vcrs = nn_dynamic_pointer_cast( compoundCRS->componentReferenceSystems()[1]); ASSERT_TRUE(vcrs != nullptr); EXPECT_EQ(vcrs->nameStr(), "NAVD88 height (ftUS)"); ASSERT_EQ(vcrs->identifiers().size(), 1U); EXPECT_EQ(*(vcrs->identifiers()[0]->codeSpace()), "EPSG"); const auto &geoidModel = vcrs->geoidModel(); ASSERT_TRUE(!geoidModel.empty()); EXPECT_EQ(geoidModel[0]->nameStr(), "GEOID12A"); auto datum = vcrs->datum(); EXPECT_EQ(datum->nameStr(), "North American Vertical Datum 1988"); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "5103"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); const auto &axis = vcrs->coordinateSystem()->axisList()[0]; EXPECT_EQ(axis->direction(), AxisDirection::UP); EXPECT_EQ(axis->unit().name(), "US survey foot"); EXPECT_NEAR(axis->unit().conversionToSI(), 0.3048006096012192, 1e-16); } // --------------------------------------------------------------------------- TEST(wkt_parse, dynamic_vertical_reference_frame) { auto obj = WKTParser().createFromWKT( "VERTCRS[\"RH2000\"," " DYNAMIC[FRAMEEPOCH[2000.0],MODEL[\"NKG2016LU\"]]," " VDATUM[\"Rikets Hojdsystem 2000\",ANCHOR[\"my anchor\"]]," " CS[vertical,1]," " AXIS[\"gravity-related height (H)\",up]," " LENGTHUNIT[\"metre\",1.0]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto dgrf = std::dynamic_pointer_cast(crs->datum()); ASSERT_TRUE(dgrf != nullptr); auto anchor = dgrf->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "my anchor"); EXPECT_TRUE(dgrf->frameReferenceEpoch() == Measure(2000.0, UnitOfMeasure::YEAR)); auto model = dgrf->deformationModelName(); EXPECT_TRUE(model.has_value()); EXPECT_EQ(*model, "NKG2016LU"); } // --------------------------------------------------------------------------- TEST(wkt_parse, vertcrs_with_ensemble) { auto obj = WKTParser().createFromWKT( "VERTCRS[\"unnamed\",\n" " ENSEMBLE[\"unnamed\",\n" " MEMBER[\"vdatum1\"],\n" " MEMBER[\"vdatum2\"],\n" " ENSEMBLEACCURACY[100]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); ASSERT_TRUE(crs->datum() == nullptr); ASSERT_TRUE(crs->datumEnsemble() != nullptr); EXPECT_EQ(crs->datumEnsemble()->datums().size(), 2U); } // --------------------------------------------------------------------------- TEST(wkt_parse, vdatum_with_ANCHOR) { auto obj = WKTParser().createFromWKT("VDATUM[\"Ordnance Datum Newlyn\",\n" " ANCHOR[\"my anchor\"],\n" " ID[\"EPSG\",5101]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); auto anchor = datum->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "my anchor"); EXPECT_FALSE(datum->anchorEpoch().has_value()); } // --------------------------------------------------------------------------- TEST(wkt_parse, vdatum_with_ANCHOREPOCH) { auto obj = WKTParser().createFromWKT("VDATUM[\"my_datum\",\n" " ANCHOREPOCH[2002.5]]"); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); auto anchorEpoch = datum->anchorEpoch(); EXPECT_TRUE(anchorEpoch.has_value()); ASSERT_EQ(anchorEpoch->convertToUnit(UnitOfMeasure::YEAR), 2002.5); EXPECT_FALSE(datum->anchorDefinition().has_value()); } // --------------------------------------------------------------------------- TEST(wkt_parse, engineeringCRS_WKT2_affine_CS) { auto wkt = "ENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"],\n" " CS[affine,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPOUNDCRS) { auto obj = WKTParser().createFromWKT( "COMPOUNDCRS[\"horizontal + vertical\",\n" " PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]],\n" " ID[\"codespace\",\"code\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "horizontal + vertical"); EXPECT_EQ(crs->componentReferenceSystems().size(), 2U); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "code"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "codespace"); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPOUNDCRS_spatio_parametric_2015) { auto obj = WKTParser().createFromWKT( "COMPOUNDCRS[\"ICAO layer 0\",\n" " GEODETICCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " PARAMETRICCRS[\"WMO standard atmosphere\",\n" " PARAMETRICDATUM[\"Mean Sea Level\",\n" " ANCHOR[\"Mean Sea Level = 1013.25 hPa\"]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (P)\",unspecified,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPOUNDCRS_spatio_parametric_2019) { auto obj = WKTParser().createFromWKT( "COMPOUNDCRS[\"ICAO layer 0\",\n" " GEOGRAPHICCRS[\"WGS 84\",\n" " DYNAMIC[FRAMEEPOCH[2005]],\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " PARAMETRICCRS[\"WMO standard atmosphere\",\n" " PARAMETRICDATUM[\"Mean Sea Level\",\n" " ANCHOR[\"Mean Sea Level = 1013.25 hPa\"]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (P)\",unspecified,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPOUNDCRS_spatio_temporal_2015) { auto obj = WKTParser().createFromWKT( "COMPOUNDCRS[\"GPS position and time\",\n" " GEODCRS[\"WGS 84 (G1762)\",\n" " DATUM[\"World Geodetic System 1984 (G1762)\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " TIMECRS[\"GPS Time\",\n" " TIMEDATUM[\"Time origin\",TIMEORIGIN[1980-01-01]],\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPOUNDCRS_spatio_temporal_2019) { auto obj = WKTParser().createFromWKT( "COMPOUNDCRS[\"2D GPS position with civil time in ISO 8601 format\",\n" " GEOGCRS[\"WGS 84 (G1762)\",\n" " DATUM[\"World Geodetic System 1984 (G1762)\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " TIMECRS[\"DateTime\",\n" " TDATUM[\"Gregorian Calendar\"],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPD_CS) { auto obj = WKTParser().createFromWKT( "COMPD_CS[\"horizontal + vertical\",\n" " PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]],\n" " VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n" " AUTHORITY[\"EPSG\",\"5101\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5701\"]],\n" " AUTHORITY[\"codespace\",\"code\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "horizontal + vertical"); EXPECT_EQ(crs->componentReferenceSystems().size(), 2U); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "code"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "codespace"); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPD_CS_non_conformant_horizontal_plus_horizontal_as_in_LAS) { auto obj = WKTParser().createFromWKT( "COMPD_CS[\"horizontal + vertical\",\n" " PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]],\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N"); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPD_CS_non_conformant_horizontal_TOWGS84_plus_horizontal_as_in_LAS) { const auto wkt = "COMPD_CS[\"WGS 84 + WGS 84\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto baseCRS = nn_dynamic_pointer_cast(crs->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_EQ(baseCRS->nameStr(), "WGS 84"); EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPD_CS_horizontal_bound_geog_plus_vertical_ellipsoidal_height) { // See https://github.com/OSGeo/PROJ/issues/2228 const char *wkt = "COMPD_CS[\"NAD83 + Ellipsoid (Meters)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " TOWGS84[0,0,0,0,0,0,0],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"Ellipsoid (Meters)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Up\",UP]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto baseCRS = nn_dynamic_pointer_cast(crs->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_EQ(baseCRS->nameStr(), "NAD83"); EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ(replaceAll(crs->exportToWKT( WKTFormatter::create( WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), "ellipsoidal height", "Up"), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPD_CS_horizontal_projected_plus_vertical_ellipsoidal_height) { // Variant of above const char *wkt = "COMPD_CS[\"WGS 84 / UTM zone 31N + Ellipsoid (Meters)\",\n" " PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]],\n" " VERT_CS[\"Ellipsoid (Meters)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Up\",UP]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N"); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ(replaceAll(crs->exportToWKT( WKTFormatter::create( WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), "ellipsoidal height", "Up"), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, COMPD_CS_horizontal_geog_plus_vertical_ellipsoidal_height_non_metre) { // See https://github.com/OSGeo/PROJ/issues/2232 const char *wkt = "COMPD_CS[\"NAD83 + Ellipsoid (US Feet)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"Ellipsoid (US Feet)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"US survey foot\",0.304800609601219,\n" " AUTHORITY[\"EPSG\",\"9003\"]],\n" " AXIS[\"Up\",UP]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAD83 (Ellipsoid (US Feet))"); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); EXPECT_NEAR(crs->coordinateSystem()->axisList()[2]->unit().conversionToSI(), 0.304800609601219, 1e-15); EXPECT_EQ(replaceAll(crs->exportToWKT( WKTFormatter::create( WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), "ellipsoidal height", "Up"), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, implicit_compound_CRS_ESRI) { // See https://lists.osgeo.org/pipermail/gdal-dev/2020-October/052843.html // and https://pro.arcgis.com/en/pro-app/arcpy/classes/spatialreference.htm const char *wkt = "PROJCS[\"NAD_1983_2011_StatePlane_Colorado_Central_FIPS_0502_Ft_US\"," "GEOGCS[\"GCS_NAD_1983_2011\",DATUM[\"D_NAD_1983_2011\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",3000000.00031608]," "PARAMETER[\"False_Northing\",999999.999996]," "PARAMETER[\"Central_Meridian\",-105.5]," "PARAMETER[\"Standard_Parallel_1\",38.45]," "PARAMETER[\"Standard_Parallel_2\",39.75]," "PARAMETER[\"Latitude_Of_Origin\",37.8333333333333]," "UNIT[\"US survey foot\",0.304800609601219]]," "VERTCS[\"CGVD2013_height\"," "VDATUM[\"Canadian_Geodetic_Vertical_Datum_of_2013\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAD83(2011) / Colorado Central (ftUS) + " "CGVD2013(CGG2013) height"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, VERTCS_with_ellipsoidal_height_ESRI) { const char *wkt = "VERTCS[\"WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); const char *expected_wkt1 = "VERT_CS[\"WGS_1984\",\n" " VERT_DATUM[\"World Geodetic System 1984\",2002],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"ellipsoidal height\",UP]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), expected_wkt1); } // --------------------------------------------------------------------------- TEST(wkt_parse, implicit_compound_CRS_geographic_with_ellipsoidal_height_ESRI) { const char *wkt = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "VERTCS[\"WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext)); f->setAllowLINUNITNode(false); EXPECT_EQ(crs->exportToWKT(f.get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, implicit_compound_CRS_projected_with_ellipsoidal_height_ESRI) { const char *wkt = "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",500000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",3.0]," "PARAMETER[\"Scale_Factor\",0.9996]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]]," "VERTCS[\"WGS_1984\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, COORDINATEOPERATION) { std::string src_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get()); } std::string dst_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get()); } std::string interpolation_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); interpolation_wkt = GeographicCRS::EPSG_4979->exportToWKT(formatter.get()); } auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[\"operationMethodName\",\n" " ID[\"codeSpaceOperationMethod\",\"codeOperationMethod\"]],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n" " INTERPOLATIONCRS[" + interpolation_wkt + "],\n" " OPERATIONACCURACY[0.1],\n" " ID[\"codeSpaceTransformation\",\"codeTransformation\"],\n" " REMARK[\"my remarks\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->nameStr(), "transformationName"); ASSERT_EQ(transf->identifiers().size(), 1U); EXPECT_EQ(transf->identifiers()[0]->code(), "codeTransformation"); EXPECT_EQ(*(transf->identifiers()[0]->codeSpace()), "codeSpaceTransformation"); ASSERT_EQ(transf->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(transf->coordinateOperationAccuracies()[0]->value(), "0.1"); EXPECT_EQ(transf->sourceCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), GeographicCRS::EPSG_4807->nameStr()); ASSERT_TRUE(transf->interpolationCRS() != nullptr); EXPECT_EQ(transf->interpolationCRS()->nameStr(), GeographicCRS::EPSG_4979->nameStr()); EXPECT_EQ(transf->method()->nameStr(), "operationMethodName"); EXPECT_EQ(transf->parameterValues().size(), 1U); { auto outWkt = transf->exportToWKT(WKTFormatter::create().get()); EXPECT_EQ(replaceAll(replaceAll(outWkt, "\n", ""), " ", ""), replaceAll(replaceAll(wkt, "\n", ""), " ", "")); } } // --------------------------------------------------------------------------- TEST(wkt_parse, COORDINATEOPERATION_with_interpolation_as_parameter) { auto wkt = "COORDINATEOPERATION[\"SHGD2015 to SHGD2015 + SHVD2015 height (1)\",\n" " VERSION[\"ENRD-Shn Hel\"],\n" " SOURCECRS[\n" " GEOGCRS[\"SHGD2015\",\n" " DATUM[\"St. Helena Geodetic Datum 2015\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",7885]]],\n" " TARGETCRS[\n" " COMPOUNDCRS[\"SHMG2015 + SHVD2015 height\",\n" " PROJCRS[\"SHMG2015\",\n" " BASEGEOGCRS[\"SHGD2015\",\n" " DATUM[\"St. Helena Geodetic Datum 2015\",\n" " ELLIPSOID[\"GRS " "1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",7886]],\n" " CONVERSION[\"UTM zone 30S\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",10000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"SHVD2015 height\",\n" " VDATUM[\"St. Helena Vertical Datum 2015\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]],\n" " ID[\"EPSG\",7956]]],\n" " METHOD[\"Geog3D to Geog2D+GravityRelatedHeight (EGM2008)\",\n" " ID[\"EPSG\",1092]],\n" " PARAMETERFILE[\"Geoid (height correction) model file\"," "\"Und_min2.5x2.5_egm2008_isw=82_WGS84_TideFree.gz\"],\n" " PARAMETER[\"EPSG code for Interpolation CRS\",7886,\n" " ID[\"EPSG\",1048]],\n" " OPERATIONACCURACY[0],\n" " ID[\"EPSG\",9617]]"; { auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_TRUE(transf->interpolationCRS() == nullptr); EXPECT_EQ(transf->parameterValues().size(), 2U); } { auto dbContext = DatabaseContext::create(); // Need a database so that the interpolation CRS EPSG:7886 is resolved auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_TRUE(transf->interpolationCRS() != nullptr); EXPECT_EQ(transf->parameterValues().size(), 1U); } } // --------------------------------------------------------------------------- TEST(wkt_parse, COORDINATEOPERATION_wkt2_2019) { std::string src_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); formatter->setOutputId(false); src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get()); } std::string dst_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); formatter->setOutputId(false); dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get()); } std::string interpolation_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); formatter->setOutputId(false); interpolation_wkt = GeographicCRS::EPSG_4979->exportToWKT(formatter.get()); } auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " VERSION[\"my version\"],\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[\"operationMethodName\",\n" " ID[\"codeSpaceOperationMethod\",\"codeOperationMethod\"]],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n" " INTERPOLATIONCRS[" + interpolation_wkt + "],\n" " OPERATIONACCURACY[0.1],\n" " ID[\"codeSpaceTransformation\",\"codeTransformation\"],\n" " REMARK[\"my remarks\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->nameStr(), "transformationName"); EXPECT_EQ(*transf->operationVersion(), "my version"); ASSERT_EQ(transf->identifiers().size(), 1U); EXPECT_EQ(transf->identifiers()[0]->code(), "codeTransformation"); EXPECT_EQ(*(transf->identifiers()[0]->codeSpace()), "codeSpaceTransformation"); ASSERT_EQ(transf->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(transf->coordinateOperationAccuracies()[0]->value(), "0.1"); EXPECT_EQ(transf->sourceCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), GeographicCRS::EPSG_4807->nameStr()); ASSERT_TRUE(transf->interpolationCRS() != nullptr); EXPECT_EQ(transf->interpolationCRS()->nameStr(), GeographicCRS::EPSG_4979->nameStr()); EXPECT_EQ(transf->method()->nameStr(), "operationMethodName"); EXPECT_EQ(transf->parameterValues().size(), 1U); { auto outWkt = transf->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_EQ(replaceAll(replaceAll(outWkt, "\n", ""), " ", ""), replaceAll(replaceAll(wkt, "\n", ""), " ", "")); } { auto outWkt = transf->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()); EXPECT_FALSE(outWkt.find("VERSION[\"my version\"],") != std::string::npos); } } // --------------------------------------------------------------------------- TEST(wkt_parse, conversion_proj_based) { auto wkt = "CONVERSION[\"PROJ-based coordinate operation\",\n" " METHOD[\"PROJ-based operation method: +proj=merc\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc"); } // --------------------------------------------------------------------------- TEST(wkt_parse, conversion_utm_zone_south_wrong_id) { auto wkt = "CONVERSION[\"UTM zone 55S\"," " METHOD[\"Transverse Mercator\"," " ID[\"EPSG\",9807]]," " PARAMETER[\"Latitude of natural origin\",0," " ANGLEUNIT[\"Degree\",0.0174532925199433]," " ID[\"EPSG\",8801]]," " PARAMETER[\"Longitude of natural origin\",147," " ANGLEUNIT[\"Degree\",0.0174532925199433]," " ID[\"EPSG\",8802]]," " PARAMETER[\"Scale factor at natural origin\",0.9996," " SCALEUNIT[\"unity\",1]," " ID[\"EPSG\",8805]]," " PARAMETER[\"False easting\",500000," " LENGTHUNIT[\"metre\",1]," " ID[\"EPSG\",8806]]," " PARAMETER[\"False northing\",10000000," " LENGTHUNIT[\"metre\",1]," " ID[\"EPSG\",8807]]," " ID[\"EPSG\",17055]]"; // wrong code auto obj = WKTParser().createFromWKT(wkt); auto conv = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(conv != nullptr); EXPECT_EQ(conv->getEPSGCode(), 16155); // code fixed on import } // --------------------------------------------------------------------------- TEST(wkt_parse, CONCATENATEDOPERATION) { auto transf_1 = Transformation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_1"), nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), nullptr, PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector()); auto transf_2 = Transformation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_2"), nn_static_pointer_cast(GeographicCRS::EPSG_4807), nn_static_pointer_cast(GeographicCRS::EPSG_4979), nullptr, PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector()); auto concat_in = ConcatenatedOperation::create( PropertyMap() .set(Identifier::CODESPACE_KEY, "codeSpace") .set(Identifier::CODE_KEY, "code") .set(IdentifiedObject::NAME_KEY, "name") .set(IdentifiedObject::REMARKS_KEY, "my remarks"), std::vector{transf_1, transf_2}, std::vector{ PositionalAccuracy::create("0.1")}); auto wkt = concat_in->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); auto obj = WKTParser().createFromWKT(wkt); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ(concat->nameStr(), "name"); EXPECT_FALSE(concat->operationVersion().has_value()); ASSERT_EQ(concat->identifiers().size(), 1U); EXPECT_EQ(concat->identifiers()[0]->code(), "code"); EXPECT_EQ(*(concat->identifiers()[0]->codeSpace()), "codeSpace"); ASSERT_EQ(concat->operations().size(), 2U); ASSERT_EQ(concat->operations()[0]->nameStr(), transf_1->nameStr()); ASSERT_EQ(concat->operations()[1]->nameStr(), transf_2->nameStr()); ASSERT_TRUE(concat->sourceCRS() != nullptr); ASSERT_TRUE(concat->targetCRS() != nullptr); ASSERT_EQ(concat->sourceCRS()->nameStr(), transf_1->sourceCRS()->nameStr()); ASSERT_EQ(concat->targetCRS()->nameStr(), transf_2->targetCRS()->nameStr()); } // --------------------------------------------------------------------------- TEST(wkt_parse, CONCATENATEDOPERATION_with_conversion_and_conversion) { auto wkt = "CONCATENATEDOPERATION[\"Inverse of UTM zone 31N + UTM zone 32N\",\n" " SOURCECRS[\n" " PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32631]]],\n" " TARGETCRS[\n" " PROJCRS[\"WGS 84 / UTM zone 32N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 32N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",9,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32632]]],\n" " STEP[\n" " CONVERSION[\"Inverse of UTM zone 31N\",\n" " METHOD[\"Inverse of Transverse Mercator\",\n" " ID[\"INVERSE(EPSG)\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"INVERSE(EPSG)\",16031]]],\n" " STEP[\n" " CONVERSION[\"UTM zone 32N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",9,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16032]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ(concat->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 " "+step +proj=utm +zone=32 +ellps=WGS84"); auto outWkt = concat->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_EQ(wkt, outWkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, CONCATENATEDOPERATION_with_conversion_coordinateoperation_conversion) { auto wkt = "CONCATENATEDOPERATION[\"Inverse of UTM zone 11N + NAD27 to WGS 84 " "(79) + UTM zone 11N\",\n" " VERSION[\"my version\"],\n" " SOURCECRS[\n" " PROJCRS[\"NAD27 / UTM zone 11N\",\n" " BASEGEOGCRS[\"NAD27\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke " "1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",26711]]],\n" " TARGETCRS[\n" " PROJCRS[\"WGS 84 / UTM zone 11N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32611]]],\n" " STEP[\n" " CONVERSION[\"Inverse of UTM zone 11N\",\n" " METHOD[\"Inverse of Transverse Mercator\",\n" " ID[\"INVERSE(EPSG)\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"INVERSE(EPSG)\",16011]]],\n" " STEP[\n" " COORDINATEOPERATION[\"NAD27 to WGS 84 (79)\",\n" " SOURCECRS[\n" " GEOGCRS[\"NAD27\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke " "1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " METHOD[\"CTABLE2\"],\n" " PARAMETERFILE[\"Latitude and longitude difference " "file\",\"conus\"],\n" " ID[\"DERIVED_FROM(EPSG)\",15851]]],\n" " STEP[\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16011]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ(*concat->operationVersion(), "my version"); EXPECT_EQ(concat->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=11 +ellps=clrk66 " "+step +proj=hgridshift +grids=conus +step +proj=utm " "+zone=11 +ellps=WGS84"); auto outWkt = concat->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_EQ(wkt, outWkt); } // --------------------------------------------------------------------------- TEST( wkt_parse, CONCATENATEDOPERATION_with_conversion_coordinateoperation_to_inverse_conversion) { auto wkt = "CONCATENATEDOPERATION[\"Inverse of UTM zone 11N + NAD27 to WGS 84 " "(79) + UTM zone 11N\",\n" " SOURCECRS[\n" " PROJCRS[\"WGS 84 / UTM zone 11N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32611]]],\n" " TARGETCRS[\n" " PROJCRS[\"NAD27 / UTM zone 11N\",\n" " BASEGEOGCRS[\"NAD27\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke " "1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",26711]]],\n" " STEP[\n" " CONVERSION[\"Inverse of UTM zone 11N\",\n" " METHOD[\"Inverse of Transverse Mercator\",\n" " ID[\"INVERSE(EPSG)\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"INVERSE(EPSG)\",16011]]],\n" " STEP[\n" " COORDINATEOPERATION[\"NAD27 to WGS 84 (79)\",\n" " SOURCECRS[\n" " GEOGCRS[\"NAD27\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke " "1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " METHOD[\"CTABLE2\"],\n" " PARAMETERFILE[\"Latitude and longitude difference " "file\",\"conus\"],\n" " ID[\"DERIVED_FROM(EPSG)\",15851]]],\n" " STEP[\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16011]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ(concat->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=utm +zone=11 +ellps=WGS84 " "+step +inv +proj=hgridshift +grids=conus +step " "+proj=utm +zone=11 +ellps=clrk66"); } // --------------------------------------------------------------------------- TEST(wkt_parse, CONCATENATEDOPERATION_with_inverse_conversion_of_compound) { auto wkt = "CONCATENATEDOPERATION[\"Inverse of RD New + Amersfoort to ETRS89 (9) " "+ Inverse of ETRS89 to NAP height (2) + ETRS89 to WGS 84 (1)\",\n" " SOURCECRS[\n" " COMPOUNDCRS[\"Amersfoort / RD New + NAP height\",\n" " PROJCRS[\"Amersfoort / RD New\",\n" " BASEGEOGCRS[\"Amersfoort\",\n" " DATUM[\"Amersfoort\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4289]],\n" " CONVERSION[\"RD New\",\n" " METHOD[\"Oblique Stereographic\",\n" " ID[\"EPSG\",9809]],\n" " PARAMETER[\"Latitude of natural " "origin\",52.1561605555556,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural " "origin\",5.38763888888889,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9999079,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",155000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",463000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"NAP height\",\n" " VDATUM[\"Normaal Amsterdams Peil\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]],\n" " ID[\"EPSG\",7415]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84 (3D)\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree minute second " "hemisphere\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Long)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree minute second " "hemisphere\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4329]]],\n" " STEP[\n" " CONVERSION[\"Inverse of RD New\",\n" " METHOD[\"Inverse of Oblique Stereographic\",\n" " ID[\"INVERSE(EPSG)\",9809]],\n" " PARAMETER[\"Latitude of natural " "origin\",52.1561605555556,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural " "origin\",5.38763888888889,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9999079,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",155000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",463000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"INVERSE(EPSG)\",19914]]],\n" " STEP[\n" " COORDINATEOPERATION[\"PROJ-based coordinate operation\",\n" " SOURCECRS[\n" " COMPOUNDCRS[\"Amersfoort + NAP height\",\n" " GEOGCRS[\"Amersfoort\",\n" " DATUM[\"Amersfoort\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4289]],\n" " VERTCRS[\"NAP height\",\n" " VDATUM[\"Normaal Amsterdams Peil\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5709]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84 (3D)\",\n" " ENSEMBLE[\"World Geodetic System 1984 " "ensemble\",\n" " MEMBER[\"World Geodetic System 1984 " "(Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G730)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G873)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G2139)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree minute second " "hemisphere\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Long)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree minute second " "hemisphere\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4329]]],\n" " METHOD[\"PROJ-based operation method: +proj=pipeline " "+step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg " "+xy_out=rad +step +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif " "+step +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1\"],\n" " OPERATIONACCURACY[1.002]]],\n" " USAGE[\n" " SCOPE[\"unknown\"],\n" " AREA[\"Netherlands - onshore, including Waddenzee, Dutch " "Wadden Islands and 12-mile offshore coastal zone.\"],\n" " BBOX[50.75,3.2,53.7,7.22]]]"; auto obj = WKTParser().createFromWKT(wkt); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ(concat->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=sterea +lat_0=52.1561605555556 " "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 " "+ellps=bessel " "+step +proj=hgridshift +grids=nl_nsgi_rdtrans2018.tif " "+step +proj=vgridshift +grids=nl_nsgi_nlgeo2018.tif " "+multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(wkt_parse, BOUNDCRS_transformation_from_names) { auto projcrs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"), GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto wkt = "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"Transformation to WGS84\",\n" " METHOD[\"Coordinate Frame\"],\n" " PARAMETER[\"X-axis translation\",1],\n" " PARAMETER[\"Y-axis translation\",2],\n" " PARAMETER[\"Z-axis translation\",3],\n" " PARAMETER[\"X-axis rotation\",-4],\n" " PARAMETER[\"Y-axis rotation\",-5],\n" " PARAMETER[\"Z-axis rotation\",-6],\n" " PARAMETER[\"Scale difference\",1.000007]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr()); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), projcrs->baseCRS()->nameStr()); auto params = crs->transformation()->getTOWGS84Parameters(true); auto expected = std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; ASSERT_EQ(params.size(), expected.size()); for (int i = 0; i < 7; i++) { EXPECT_NEAR(params[i], expected[i], 1e-10); } } // --------------------------------------------------------------------------- TEST(wkt_parse, BOUNDCRS_transformation_from_codes) { auto projcrs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"), GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto wkt = "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"Transformation to WGS84\",\n" " METHOD[\"bla\",ID[\"EPSG\",1032]],\n" " PARAMETER[\"tx\",1,ID[\"EPSG\",8605]],\n" " PARAMETER[\"ty\",2,ID[\"EPSG\",8606]],\n" " PARAMETER[\"tz\",3,ID[\"EPSG\",8607]],\n" " PARAMETER[\"rotx\",-4,ID[\"EPSG\",8608]],\n" " PARAMETER[\"roty\",-5,ID[\"EPSG\",8609]],\n" " PARAMETER[\"rotz\",-6,ID[\"EPSG\",8610]],\n" " PARAMETER[\"scale\",1.000007,ID[\"EPSG\",8611]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr()); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), projcrs->baseCRS()->nameStr()); auto params = crs->transformation()->getTOWGS84Parameters(true); auto expected = std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; ASSERT_EQ(params.size(), expected.size()); for (int i = 0; i < 7; i++) { EXPECT_NEAR(params[i], expected[i], 1e-10); } } // --------------------------------------------------------------------------- TEST(wkt_parse, BOUNDCRS_with_interpolation_as_parameter) { auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"unknown\",\n" " VDATUM[\"unknown using geoidgrids=@foo.gtx\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"unknown to WGS84 ellipsoidal height\",\n" " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"@foo.gtx\",\n" " ID[\"EPSG\",8666]],\n" " PARAMETER[\"EPSG code for Interpolation CRS\",7886]]]"; { auto obj = WKTParser().createFromWKT(wkt); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); EXPECT_TRUE(boundCRS->transformation()->interpolationCRS() == nullptr); EXPECT_EQ(boundCRS->transformation()->parameterValues().size(), 2U); } { auto dbContext = DatabaseContext::create(); // Need a database so that the interpolation CRS EPSG:7886 is resolved auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); EXPECT_TRUE(boundCRS->transformation()->interpolationCRS() != nullptr); EXPECT_EQ(boundCRS->transformation()->parameterValues().size(), 1U); // Check that on export, the interpolation CRS is exported as a // parameter auto exportedWKT = boundCRS->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_TRUE( exportedWKT.find( "PARAMETER[\"EPSG code for Interpolation CRS\",7886,") != std::string::npos) << exportedWKT; } } // --------------------------------------------------------------------------- TEST(wkt_parse, boundcrs_of_verticalcrs_to_geog3Dcrs) { auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"my_height\",\n" " VDATUM[\"my_height\"],\n" " CS[vertical,1],\n" " AXIS[\"up\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " TARGETCRS[\n" " GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"my_height height to WGS84 ellipsoidal " "height\",\n" " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n" " PARAMETERFILE[\"Geoid (height correction) model file\"," " \"./tmp/fake.gtx\",\n" " ID[\"EPSG\",8666]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), "my_height"); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4979->nameStr()); } // --------------------------------------------------------------------------- TEST(wkt_parse, geogcs_TOWGS84_3terms) { auto wkt = "GEOGCS[\"my GEOGCRS\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563],\n" " TOWGS84[1,2,3]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), "my GEOGCRS"); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "my GEOGCRS"); auto params = crs->transformation()->getTOWGS84Parameters(true); auto expected = std::vector{1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0}; ASSERT_EQ(params.size(), expected.size()); for (int i = 0; i < 7; i++) { EXPECT_NEAR(params[i], expected[i], 1e-10); } } // --------------------------------------------------------------------------- TEST(wkt_parse, projcs_TOWGS84_7terms) { auto wkt = "PROJCS[\"my PROJCRS\",\n" " GEOGCS[\"my GEOGCRS\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " TOWGS84[1,2,3,4,5,6,7],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), "my PROJCRS"); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "my GEOGCRS"); auto params = crs->transformation()->getTOWGS84Parameters(true); auto expected = std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; ASSERT_EQ(params.size(), expected.size()); for (int i = 0; i < 7; i++) { EXPECT_NEAR(params[i], expected[i], 1e-10); } } // --------------------------------------------------------------------------- TEST(io, projcs_TOWGS84_7terms_autocorrect) { // Auto-correct wrong sign for rotation terms // Cf https://github.com/OSGeo/PROJ/issues/4170 auto wkt = "PROJCS[\"BD72 / Belgian Lambert 72\",\n" " GEOGCS[\"BD72\",\n" " DATUM[\"Reseau_National_Belge_1972\",\n" " SPHEROID[\"International 1924\",6378388,297],\n" " " "TOWGS84[-106.8686,52.2978,-103.7239,-0.3366,0.457,-1.8422,-1.2747]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4313\"]],\n" " PROJECTION[\"Lambert_Conformal_Conic_2SP\"],\n" " PARAMETER[\"latitude_of_origin\",90],\n" " PARAMETER[\"central_meridian\",4.36748666666667],\n" " PARAMETER[\"standard_parallel_1\",51.1666672333333],\n" " PARAMETER[\"standard_parallel_2\",49.8333339],\n" " PARAMETER[\"false_easting\",150000.013],\n" " PARAMETER[\"false_northing\",5400088.438],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"31370\"]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto params = crs->transformation()->getTOWGS84Parameters(true); auto expected = std::vector{-106.8686, 52.2978, -103.7239, 0.3366, -0.457, 1.8422, -1.2747}; ASSERT_EQ(params.size(), expected.size()); for (int i = 0; i < 7; i++) { EXPECT_NEAR(params[i], expected[i], 1e-10); } } // --------------------------------------------------------------------------- TEST(wkt_parse, WKT1_VERT_DATUM_EXTENSION) { auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n" " VERT_DATUM[\"EGM2008 geoid\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n" " AUTHORITY[\"EPSG\",\"1027\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Up\",UP],\n" " AUTHORITY[\"EPSG\",\"3855\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), "EGM2008 geoid height"); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4979->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), crs->baseCRS()->nameStr()); ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr); EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(), crs->hubCRS()->nameStr()); EXPECT_EQ(crs->transformation()->nameStr(), "EGM2008 geoid height to WGS 84 ellipsoidal height"); EXPECT_EQ(crs->transformation()->method()->nameStr(), "GravityRelatedHeight to Geographic3D"); ASSERT_EQ(crs->transformation()->parameterValues().size(), 1U); { const auto &opParamvalue = nn_dynamic_pointer_cast( crs->transformation()->parameterValues()[0]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8666); EXPECT_EQ(paramName, "Geoid (height correction) model file"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::FILENAME); EXPECT_EQ(parameterValue->valueFile(), "egm08_25.gtx"); } } // --------------------------------------------------------------------------- TEST(wkt_parse, WKT1_VERT_DATUM_EXTENSION_units_ftUS) { auto wkt = "VERT_CS[\"NAVD88 height (ftUS)\"," " VERT_DATUM[\"North American Vertical Datum 1988\",2005," " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"]," " AUTHORITY[\"EPSG\",\"5103\"]]," " UNIT[\"US survey foot\",0.304800609601219," " AUTHORITY[\"EPSG\",\"9003\"]]," " AXIS[\"Gravity-related height\",UP]," " AUTHORITY[\"EPSG\",\"6360\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->transformation()->nameStr(), "NAVD88 height to WGS 84 ellipsoidal height"); // no (ftUS) auto sourceTransformationCRS = crs->transformation()->sourceCRS(); auto sourceTransformationVertCRS = nn_dynamic_pointer_cast(sourceTransformationCRS); EXPECT_EQ( sourceTransformationVertCRS->coordinateSystem()->axisList()[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(wkt_parse, WKT1_COMPD_CS_VERT_DATUM_EXTENSION) { auto wkt = "COMPD_CS[\"NAD83 + NAVD88 height\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n" " AUTHORITY[\"EPSG\",\"7019\"]],\n" " AUTHORITY[\"EPSG\",\"6269\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4269\"]],\n" " VERT_CS[\"NAVD88 height\",\n" " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"@foo.gtx\"],\n" " AUTHORITY[\"EPSG\",\"5103\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5703\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto boundVertCRS = nn_dynamic_pointer_cast(crs->componentReferenceSystems()[1]); ASSERT_TRUE(boundVertCRS != nullptr); EXPECT_EQ(boundVertCRS->transformation()->nameStr(), "NAVD88 height to NAD83 ellipsoidal height"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, WKT1_DATUM_EXTENSION) { auto wkt = "PROJCS[\"unnamed\",\n" " GEOGCS[\"International 1909 (Hayford)\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"intl\",6378388,297],\n" " EXTENSION[\"PROJ4_GRIDS\",\"nzgd2kgrid0005.gsb\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"New_Zealand_Map_Grid\"],\n" " PARAMETER[\"latitude_of_origin\",-41],\n" " PARAMETER[\"central_meridian\",173],\n" " PARAMETER[\"false_easting\",2510000],\n" " PARAMETER[\"false_northing\",6023150],\n" " UNIT[\"Meter\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->nameStr(), "unnamed"); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "International 1909 (Hayford)"); ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr); EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(), crs->hubCRS()->nameStr()); EXPECT_EQ(crs->transformation()->nameStr(), "International 1909 (Hayford) to WGS84"); EXPECT_EQ(crs->transformation()->method()->nameStr(), "NTv2"); ASSERT_EQ(crs->transformation()->parameterValues().size(), 1U); { const auto &opParamvalue = nn_dynamic_pointer_cast( crs->transformation()->parameterValues()[0]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8656); EXPECT_EQ(paramName, "Latitude and longitude difference file"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::FILENAME); EXPECT_EQ(parameterValue->valueFile(), "nzgd2kgrid0005.gsb"); } } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedGeographicCRS_WKT2) { auto wkt = "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"Atlantic pole\",\n" " METHOD[\"Pole rotation\"],\n" " PARAMETER[\"Latitude of rotated pole\",52,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Longitude of rotated pole\",-30,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Axis rotation\",-25,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WMO Atlantic Pole"); EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->baseCRS()) != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "Atlantic pole"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedGeographicCRS_WKT2_2019) { auto wkt = "GEOGCRS[\"WMO Atlantic Pole\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"Atlantic pole\",\n" " METHOD[\"Pole rotation\"],\n" " PARAMETER[\"Latitude of rotated pole\",52,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Longitude of rotated pole\",-30,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Axis rotation\",-25,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WMO Atlantic Pole"); EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->baseCRS()) != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "Atlantic pole"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedGeodeticCRS) { auto wkt = "GEODCRS[\"Derived geodetic CRS\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"Some conversion\",\n" " METHOD[\"Some method\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Derived geodetic CRS"); EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->baseCRS()) != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "Some conversion"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->coordinateSystem()) != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedGeodeticCRS_where_base_is_geocentric) { auto wkt = "GEODCRS[\"Local CRS derived from WGS-84\",\n" " BASEGEODCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"Local origin shift\",\n" " METHOD[\"Position Vector transformation (geocentric " "domain)\",\n" " ID[\"EPSG\",1033]],\n" " PARAMETER[\"X-axis translation\",10,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",20,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8610]]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto baseCRS = nn_dynamic_pointer_cast(crs->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_TRUE(baseCRS->isGeocentric()); auto exportedWKT = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_STREQ(exportedWKT.c_str(), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedGeographicCRS_GDAL_PROJ4_EXSTENSION_hack) { // Note the lack of UNIT[] node auto wkt = "PROJCS[\"unnamed\"," " GEOGCS[\"unknown\"," " DATUM[\"unnamed\"," " SPHEROID[\"Spheroid\",6367470,594.313048347956]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]]," " PROJECTION[\"Rotated_pole\"]," " EXTENSION[\"PROJ4\",\"+proj=ob_tran +o_proj=longlat +lon_0=18 " "+o_lon_p=0 +o_lat_p=39.25 +a=6367470 +b=6367470 " "+to_meter=0.0174532925199 +wktext\"]]"; auto obj = WKTParser().setStrict(false).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto obj2 = PROJStringParser().createFromPROJString( "+proj=ob_tran +o_proj=longlat +lon_0=18 " "+o_lon_p=0 +o_lat_p=39.25 +a=6367470 +b=6367470 " "+to_meter=0.0174532925199 +wktext +type=crs"); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); EXPECT_TRUE( crs->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); { auto op = CoordinateOperationFactory::create()->createOperation( crs->baseCRS(), NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=ob_tran +o_proj=longlat +lon_0=18 +o_lon_p=0 " "+o_lat_p=39.25 +R=6367470 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } { auto op = CoordinateOperationFactory::create()->createOperation( NN_NO_CHECK(crs), crs->baseCRS()); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=ob_tran +o_proj=longlat +lon_0=18 +o_lon_p=0 " "+o_lat_p=39.25 +R=6367470 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedProjectedCRS) { auto wkt = "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "derived projectedCRS"); EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84 / UTM zone 31N"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->baseCRS()) != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "unnamed"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->coordinateSystem()) != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedProjectedCRS_base_crs_cs_non_metre_from_conversion) { auto wkt = "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" " BASEPROJCRS[\"foo\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 Idaho West zone (US Survey feet)\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural " "origin\",41.6666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-115.75,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.999933333,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",2624666.667,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8807]]]],\n" " DERIVINGCONVERSION[\"Grid to ground\",\n" " METHOD[\"Similarity transformation\",\n" " ID[\"EPSG\",9621]],\n" " PARAMETER[\"Ordinate 1 of evaluation point in target " "CRS\",1000,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8621]],\n" " PARAMETER[\"Ordinate 2 of evaluation point in target " "CRS\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8622]],\n" " PARAMETER[\"Scale factor for source CRS axes\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",1061]],\n" " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" " ANGLEUNIT[\"degree\",0],\n" " ID[\"EPSG\",8614]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); const auto &axisList = crs->baseCRS()->coordinateSystem()->axisList(); ASSERT_EQ(axisList.size(), 2U); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::US_FOOT); // Check that we emit a BASEPROJCRS.CS node const char *expected = "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" " BASEPROJCRS[\"foo\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"SPCS83 Idaho West zone (US Survey feet)\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural " "origin\",41.6666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-115.75,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.999933333,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",2624666.667,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]]],\n" " DERIVINGCONVERSION[\"Grid to ground\",\n" " METHOD[\"Similarity transformation\",\n" " ID[\"EPSG\",9621]],\n" " PARAMETER[\"Ordinate 1 of evaluation point in target " "CRS\",1000,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8621]],\n" " PARAMETER[\"Ordinate 2 of evaluation point in target " "CRS\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8622]],\n" " PARAMETER[\"Scale factor for source CRS axes\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",1061]],\n" " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" " ANGLEUNIT[\"degree\",0],\n" " ID[\"EPSG\",8614]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); auto dbContext = DatabaseContext::create(); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019, dbContext) .get()), expected); } // --------------------------------------------------------------------------- TEST( wkt_parse, DerivedProjectedCRS_base_crs_cs_non_metre_from_conversion_context_from_baseprojcrs_name) { auto wkt = "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" " BASEPROJCRS[\"NAD83(2011) / Idaho West (ftUS)\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"incomplete conversion\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]]]],\n" " DERIVINGCONVERSION[\"Grid to ground\",\n" " METHOD[\"Similarity transformation\",\n" " ID[\"EPSG\",9621]],\n" " PARAMETER[\"Ordinate 1 of evaluation point in target " "CRS\",1000,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8621]],\n" " PARAMETER[\"Ordinate 2 of evaluation point in target " "CRS\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8622]],\n" " PARAMETER[\"Scale factor for source CRS axes\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",1061]],\n" " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" " ANGLEUNIT[\"degree\",0],\n" " ID[\"EPSG\",8614]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); const auto &axisList = crs->baseCRS()->coordinateSystem()->axisList(); ASSERT_EQ(axisList.size(), 2U); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::US_FOOT); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST( wkt_parse, DerivedProjectedCRS_base_crs_cs_non_metre_from_conversion_context_from_baseprojcrs_id) { auto wkt = "DERIVEDPROJCRS[\"Ground for NAD83(2011) / Idaho West (ftUS)\",\n" " BASEPROJCRS[\"foo\",\n" " BASEGEOGCRS[\"NAD83(2011)\",\n" " DATUM[\"NAD83 (National Spatial Reference System " "2011)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"incomplete conversion\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]]],\n" " ID[\"EPSG\",6453]],\n" " DERIVINGCONVERSION[\"Grid to ground\",\n" " METHOD[\"Similarity transformation\",\n" " ID[\"EPSG\",9621]],\n" " PARAMETER[\"Ordinate 1 of evaluation point in target " "CRS\",1000,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8621]],\n" " PARAMETER[\"Ordinate 2 of evaluation point in target " "CRS\",0,\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219],\n" " ID[\"EPSG\",8622]],\n" " PARAMETER[\"Scale factor for source CRS axes\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",1061]],\n" " PARAMETER[\"Rotation angle of source CRS axes\",0,\n" " ANGLEUNIT[\"degree\",0],\n" " ID[\"EPSG\",8614]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"US survey foot\",0.304800609601219]]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); const auto &axisList = crs->baseCRS()->coordinateSystem()->axisList(); ASSERT_EQ(axisList.size(), 2U); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::US_FOOT); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedProjectedCRS_ordinal) { auto wkt = "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"BASEPROJCRS\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[ordinal,2],\n" " AXIS[\"inline (I)\",northNorthWest,\n" " ORDER[1]],\n" " AXIS[\"crossline (J)\",westSouthWest,\n" " ORDER[2]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast(crs->coordinateSystem()) != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, TemporalDatum) { auto wkt = "TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"my calendar\"],\n" " TIMEORIGIN[0000-01-01]]"; auto obj = WKTParser().createFromWKT(wkt); auto tdatum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(tdatum != nullptr); EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01"); EXPECT_EQ(tdatum->calendar(), "my calendar"); } // --------------------------------------------------------------------------- TEST(wkt_parse, TemporalDatum_no_calendar) { auto wkt = "TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]]"; auto obj = WKTParser().createFromWKT(wkt); auto tdatum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(tdatum != nullptr); EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01"); EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian"); } // --------------------------------------------------------------------------- TEST(wkt_parse, dateTimeTemporalCRS_WKT2_2015) { auto wkt = "TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Temporal CRS"); auto tdatum = crs->datum(); EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01"); EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1U); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().type(), UnitOfMeasure::Type::NONE); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), ""); } // --------------------------------------------------------------------------- TEST(wkt_parse, dateTimeTemporalCRS_WKT2_2019) { auto wkt = "TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Temporal CRS"); auto tdatum = crs->datum(); EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01"); EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1U); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().type(), UnitOfMeasure::Type::NONE); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), ""); } // --------------------------------------------------------------------------- TEST(wkt_parse, temporalCountCRSWithConvFactor_WKT2_2019) { auto wkt = "TIMECRS[\"GPS milliseconds\",\n" " TDATUM[\"GPS time origin\",\n" " TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n" " CS[TemporalCount,1],\n" " AXIS[\"(T)\",future,\n" " TIMEUNIT[\"milliseconds (ms)\",0.001]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "GPS milliseconds"); auto tdatum = crs->datum(); EXPECT_EQ(tdatum->nameStr(), "GPS time origin"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "1980-01-01T00:00:00.0Z"); EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1U); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "milliseconds (ms)"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().conversionToSI(), 0.001); } // --------------------------------------------------------------------------- TEST(wkt_parse, temporalCountCRSWithoutConvFactor_WKT2_2019) { auto wkt = "TIMECRS[\"Calendar hours from 1979-12-29\",\n" " TDATUM[\"29 December 1979\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[1979-12-29T00Z]],\n" " CS[TemporalCount,1],\n" " AXIS[\"time\",future,\n" " TIMEUNIT[\"hour\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Calendar hours from 1979-12-29"); auto tdatum = crs->datum(); EXPECT_EQ(tdatum->nameStr(), "29 December 1979"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "1979-12-29T00Z"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1U); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "hour"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().conversionToSI(), 0.0); } // --------------------------------------------------------------------------- TEST(wkt_parse, temporalMeasureCRS_WKT2_2015) { auto wkt = "TIMECRS[\"GPS Time\",\n" " TDATUM[\"Time origin\",\n" " TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n" " CS[temporal,1],\n" " AXIS[\"time\",future],\n" " TIMEUNIT[\"day\",86400.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "GPS Time"); auto tdatum = crs->datum(); EXPECT_EQ(tdatum->nameStr(), "Time origin"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "1980-01-01T00:00:00.0Z"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); auto axis = cs->axisList()[0]; EXPECT_EQ(axis->nameStr(), "Time"); EXPECT_EQ(axis->unit().name(), "day"); EXPECT_EQ(axis->unit().conversionToSI(), 86400.0); } // --------------------------------------------------------------------------- TEST(wkt_parse, temporalMeasureCRSWithoutConvFactor_WKT2_2019) { auto wkt = "TIMECRS[\"Decimal Years CE\",\n" " TIMEDATUM[\"Common Era\",\n" " TIMEORIGIN[0000]],\n" " CS[TemporalMeasure,1],\n" " AXIS[\"decimal years (a)\",future,\n" " TIMEUNIT[\"year\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Decimal Years CE"); auto tdatum = crs->datum(); EXPECT_EQ(tdatum->nameStr(), "Common Era"); EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000"); EXPECT_TRUE(nn_dynamic_pointer_cast( crs->coordinateSystem()) != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); auto axis = cs->axisList()[0]; EXPECT_EQ(axis->nameStr(), "Decimal years"); EXPECT_EQ(axis->unit().name(), "year"); EXPECT_EQ(axis->unit().conversionToSI(), 0.0); } // --------------------------------------------------------------------------- TEST(wkt_parse, EDATUM) { auto wkt = "EDATUM[\"Engineering datum\",\n" " ANCHOR[\"my anchor\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto edatum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(edatum != nullptr); EXPECT_EQ(edatum->nameStr(), "Engineering datum"); auto anchor = edatum->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "my anchor"); } // --------------------------------------------------------------------------- TEST(wkt_parse, ENGINEERINGDATUM) { auto wkt = "ENGINEERINGDATUM[\"Engineering datum\"]"; auto obj = WKTParser().createFromWKT(wkt); auto edatum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(edatum != nullptr); EXPECT_EQ(edatum->nameStr(), "Engineering datum"); auto anchor = edatum->anchorDefinition(); EXPECT_TRUE(!anchor.has_value()); } // --------------------------------------------------------------------------- TEST(wkt_parse, ENGCRS) { auto wkt = "ENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Engineering CRS"); EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); } // --------------------------------------------------------------------------- TEST(wkt_parse, ENGINEERINGCRS) { auto wkt = "ENGINEERINGCRS[\"Engineering CRS\",\n" " ENGINEERINGDATUM[\"Engineering datum\"],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Engineering CRS"); EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); } // --------------------------------------------------------------------------- TEST(wkt_parse, ENGCRS_unknown_unit) { auto wkt = "ENGCRS[\"Undefined Cartesian SRS with unknown unit\",\n" " EDATUM[\"Unknown engineering datum\"],\n" " CS[Cartesian,2],\n" " AXIS[\"X\",unspecified,\n" " ORDER[1],\n" " LENGTHUNIT[\"unknown\",0]],\n" " AXIS[\"Y\",unspecified,\n" " ORDER[2],\n" " LENGTHUNIT[\"unknown\",0]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Undefined Cartesian SRS with unknown unit"); EXPECT_EQ(crs->datum()->nameStr(), "Unknown engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); auto axis0 = cs->axisList()[0]; EXPECT_EQ(axis0->nameStr(), "X"); EXPECT_EQ(axis0->direction(), AxisDirection::UNSPECIFIED); EXPECT_EQ(axis0->unit().name(), "unknown"); EXPECT_EQ(axis0->unit().conversionToSI(), 0.0); auto axis1 = cs->axisList()[1]; EXPECT_EQ(axis1->nameStr(), "Y"); EXPECT_EQ(axis1->direction(), AxisDirection::UNSPECIFIED); EXPECT_EQ(axis1->unit().name(), "unknown"); EXPECT_EQ(axis1->unit().conversionToSI(), 0.0); } // --------------------------------------------------------------------------- TEST(wkt_parse, LOCAL_CS_short) { auto wkt = "LOCAL_CS[\"Engineering CRS\"]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Engineering CRS"); EXPECT_EQ(crs->datum()->nameStr(), "Unknown engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); auto expected_wkt = "LOCAL_CS[\"Engineering CRS\",\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected_wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, LOCAL_CS_long_one_axis) { auto wkt = "LOCAL_CS[\"Engineering CRS\",\n" " LOCAL_DATUM[\"Engineering datum\",12345],\n" " UNIT[\"meter\",1],\n" " AXIS[\"height\",up]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Engineering CRS"); EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); } // --------------------------------------------------------------------------- TEST(wkt_parse, LOCAL_CS_long_two_axis) { auto wkt = "LOCAL_CS[\"Engineering CRS\",\n" " LOCAL_DATUM[\"Engineering datum\",12345],\n" " UNIT[\"meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Engineering CRS"); EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); } // --------------------------------------------------------------------------- TEST(wkt_parse, LOCAL_CS_long_three_axis) { auto wkt = "LOCAL_CS[\"Engineering CRS\",\n" " LOCAL_DATUM[\"Engineering datum\",12345],\n" " UNIT[\"meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AXIS[\"Elevation\",UP]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Engineering CRS"); EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum"); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 3U); } // --------------------------------------------------------------------------- TEST(wkt_parse, PDATUM) { auto wkt = "PDATUM[\"Parametric datum\",\n" " ANCHOR[\"my anchor\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->nameStr(), "Parametric datum"); auto anchor = datum->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "my anchor"); } // --------------------------------------------------------------------------- TEST(wkt_parse, PARAMETRICDATUM) { auto wkt = "PARAMETRICDATUM[\"Parametric datum\",\n" " ANCHOR[\"my anchor\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto datum = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(datum != nullptr); EXPECT_EQ(datum->nameStr(), "Parametric datum"); auto anchor = datum->anchorDefinition(); EXPECT_TRUE(anchor.has_value()); EXPECT_EQ(*anchor, "my anchor"); } // --------------------------------------------------------------------------- TEST(wkt_parse, PARAMETRICCRS) { auto wkt = "PARAMETRICCRS[\"WMO standard atmosphere layer 0\"," " PDATUM[\"Mean Sea Level\",ANCHOR[\"1013.25 hPa at 15°C\"]]," " CS[parametric,1]," " AXIS[\"pressure (hPa)\",up]," " PARAMETRICUNIT[\"HectoPascal\",100.0]" " ]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WMO standard atmosphere layer 0"); EXPECT_EQ(crs->datum()->nameStr(), "Mean Sea Level"); auto cs = crs->coordinateSystem(); EXPECT_TRUE(nn_dynamic_pointer_cast(cs) != nullptr); ASSERT_EQ(cs->axisList().size(), 1U); auto axis = cs->axisList()[0]; EXPECT_EQ(axis->nameStr(), "Pressure"); EXPECT_EQ(axis->unit().name(), "HectoPascal"); EXPECT_EQ(axis->unit().type(), UnitOfMeasure::Type::PARAMETRIC); EXPECT_EQ(axis->unit().conversionToSI(), 100.0); } // --------------------------------------------------------------------------- TEST(wkt_parse, PARAMETRICCRS_PARAMETRICDATUM) { auto wkt = "PARAMETRICCRS[\"WMO standard atmosphere layer 0\"," " PARAMETRICDATUM[\"Mean Sea Level\"]," " CS[parametric,1]," " AXIS[\"pressure (hPa)\",up]," " PARAMETRICUNIT[\"HectoPascal\",100.0]" " ]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedVerticalCRS) { auto wkt = "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedVerticalCRS_EPSG_code_for_horizontal_CRS) { auto wkt = "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\",\n" " ID[\"EPSG\",5101]]],\n" " DERIVINGCONVERSION[\"Conv Vertical Offset and Slope\",\n" " METHOD[\"Vertical Offset and Slope\",\n" " ID[\"EPSG\",1046]],\n" " PARAMETER[\"Ordinate 1 of evaluation point\",40.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8617]],\n" " PARAMETER[\"EPSG code for Horizontal CRS\",4277,\n" " ID[\"EPSG\",1037]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // "EPSG code for Horizontal CRS" is removed and set as interpolation CRS EXPECT_EQ(crs->derivingConversion()->parameterValues().size(), 1U); EXPECT_TRUE(crs->derivingConversion()->interpolationCRS() != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedEngineeringCRS) { auto wkt = "ENGCRS[\"Derived EngineeringCRS\",\n" " BASEENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedParametricCRS) { auto wkt = "PARAMETRICCRS[\"Derived ParametricCRS\",\n" " BASEPARAMCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, DerivedTemporalCRS) { auto wkt = "TIMECRS[\"Derived TemporalCRS\",\n" " BASETIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } // --------------------------------------------------------------------------- TEST(wkt_parse, ensemble) { auto wkt = "ENSEMBLE[\"test\",\n" " MEMBER[\"World Geodetic System 1984\",\n" " ID[\"EPSG\",6326]],\n" " MEMBER[\"other datum\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ENSEMBLEACCURACY[100]]"; auto obj = WKTParser().createFromWKT(wkt); auto ensemble = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ensemble != nullptr); ASSERT_EQ(ensemble->datums().size(), 2U); auto firstDatum = nn_dynamic_pointer_cast(ensemble->datums()[0]); ASSERT_TRUE(firstDatum != nullptr); EXPECT_EQ(firstDatum->nameStr(), "World Geodetic System 1984"); ASSERT_EQ(firstDatum->identifiers().size(), 1U); EXPECT_EQ(firstDatum->identifiers()[0]->code(), "6326"); EXPECT_EQ(*(firstDatum->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(firstDatum->ellipsoid()->nameStr(), "WGS 84"); EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100"); } // --------------------------------------------------------------------------- TEST(wkt_parse, ensemble_vdatum) { auto wkt = "ENSEMBLE[\"unnamed\",\n" " MEMBER[\"vdatum1\"],\n" " MEMBER[\"vdatum2\"],\n" " ENSEMBLEACCURACY[100]]"; auto obj = WKTParser().createFromWKT(wkt); auto ensemble = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ensemble != nullptr); ASSERT_EQ(ensemble->datums().size(), 2U); auto firstDatum = nn_dynamic_pointer_cast(ensemble->datums()[0]); ASSERT_TRUE(firstDatum != nullptr); EXPECT_EQ(firstDatum->nameStr(), "vdatum1"); EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100"); } // --------------------------------------------------------------------------- TEST(wkt_parse, esri_geogcs_datum_spheroid_name_hardcoded_substitution) { auto wkt = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"; // Test substitutions of CRS, datum and ellipsoid names from ESRI names // to EPSG names. auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84"); EXPECT_EQ(crs->datum()->nameStr(), "World Geodetic System 1984"); EXPECT_EQ(crs->ellipsoid()->nameStr(), "WGS 84"); } // --------------------------------------------------------------------------- TEST(wkt_parse, esri_geogcs_datum_spheroid_name_from_db_substitution) { auto wkt = "GEOGCS[\"GCS_WGS_1966\",DATUM[\"D_WGS_1966\"," "SPHEROID[\"WGS_1966\",6378145.0,298.25]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"; // Test substitutions of CRS, datum and ellipsoid names from ESRI names // to EPSG names. auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 66"); EXPECT_EQ(crs->datum()->nameStr(), "World Geodetic System 1966"); EXPECT_EQ(crs->ellipsoid()->nameStr(), "WGS_1966"); } // --------------------------------------------------------------------------- TEST(wkt_parse, esri_datum_name_with_prime_meridian) { auto wkt = "GEOGCS[\"GCS_NTF_Paris\",DATUM[\"D_NTF\"," "SPHEROID[\"Clarke_1880_IGN\",6378249.2,293.4660212936265]]," "PRIMEM[\"Paris\",2.337229166666667]," "UNIT[\"Grad\",0.01570796326794897]]"; // D_NTF normally translates to "Nouvelle Triangulation Francaise", // but as we have a non-Greenwich prime meridian, we also test if // "Nouvelle Triangulation Francaise (Paris)" is not a registered datum name auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NTF (Paris)"); EXPECT_EQ(crs->datum()->nameStr(), "Nouvelle Triangulation Francaise (Paris)"); EXPECT_EQ(crs->ellipsoid()->nameStr(), "Clarke 1880 (IGN)"); } // --------------------------------------------------------------------------- static const struct { const char *esriProjectionName; std::vector> esriParams; const char *wkt2ProjectionName; std::vector> wkt2Params; } esriProjDefs[] = { {"Plate_Carree", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Equidistant Cylindrical", { {"Latitude of 1st standard parallel", 0}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Equidistant_Cylindrical", { {"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, }, "Equidistant Cylindrical", { {"Latitude of 1st standard parallel", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Miller_Cylindrical", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Miller Cylindrical", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Mercator", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}}, "Mercator (variant B)", { {"Latitude of 1st standard parallel", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Gauss_Kruger", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}}, "Transverse Mercator", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Transverse_Mercator", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}}, "Transverse Mercator", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Transverse_Mercator_Complex", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}}, "Transverse Mercator", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Albers", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, {"Standard_Parallel_2", 5}, {"Latitude_Of_Origin", 6}}, "Albers Equal Area", { {"Latitude of false origin", 6}, {"Longitude of false origin", 3}, {"Latitude of 1st standard parallel", 4}, {"Latitude of 2nd standard parallel", 5}, {"Easting at false origin", 1}, {"Northing at false origin", 2}, }}, {"Sinusoidal", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Sinusoidal", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Mollweide", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Mollweide", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Eckert_I", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Eckert I", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, // skipping Eckert_II to Eckert_VI {"Gall_Stereographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Gall Stereographic", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Patterson", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Patterson", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Natural_Earth", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Natural Earth", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Natural_Earth_II", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Natural Earth II", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Compact_Miller", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Compact Miller", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Times", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Times", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Flat_Polar_Quartic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Flat Polar Quartic", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Behrmann", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Lambert Cylindrical Equal Area", { {"Latitude of 1st standard parallel", 30}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Winkel_I", { {"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, }, "Winkel I", { {"Longitude of natural origin", 3}, {"Latitude of 1st standard parallel", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Winkel_II", { {"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, }, "Winkel II", { {"Longitude of natural origin", 3}, {"Latitude of 1st standard parallel", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Lambert_Conformal_Conic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, {"Scale_Factor", 5}, {"Latitude_Of_Origin", 4}}, "Lambert Conic Conformal (1SP)", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 5}, {"False easting", 1}, {"False northing", 2}, }}, {"Lambert_Conformal_Conic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, {"Standard_Parallel_2", 5}, {"Latitude_Of_Origin", 6}}, "Lambert Conic Conformal (2SP)", { {"Latitude of false origin", 6}, {"Longitude of false origin", 3}, {"Latitude of 1st standard parallel", 4}, {"Latitude of 2nd standard parallel", 5}, {"Easting at false origin", 1}, {"Northing at false origin", 2}, }}, // Unusual variant of above with Scale_Factor=1 {"Lambert_Conformal_Conic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, {"Standard_Parallel_2", 5}, {"Scale_Factor", 1.0}, {"Latitude_Of_Origin", 6}}, "Lambert Conic Conformal (2SP)", { {"Latitude of false origin", 6}, {"Longitude of false origin", 3}, {"Latitude of 1st standard parallel", 4}, {"Latitude of 2nd standard parallel", 5}, {"Easting at false origin", 1}, {"Northing at false origin", 2}, }}, {"Polyconic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Latitude_Of_Origin", 4}}, "American Polyconic", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Quartic_Authalic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Quartic Authalic", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Loximuthal", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Central_Parallel", 4}}, "Loximuthal", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Bonne", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}}, "Bonne", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin", {{"False_Easting", 1}, {"False_Northing", 2}, {"Latitude_Of_1st_Point", 3}, {"Latitude_Of_2nd_Point", 4}, {"Scale_Factor", 5}, {"Longitude_Of_1st_Point", 6}, {"Longitude_Of_2nd_Point", 7}, {"Latitude_Of_Center", 8}}, "Hotine Oblique Mercator Two Point Natural Origin", { {"Latitude of projection centre", 8}, {"Latitude of 1st point", 3}, {"Longitude of 1st point", 6}, {"Latitude of 2nd point", 4}, {"Longitude of 2nd point", 7}, {"Scale factor at projection centre", 5}, {"Easting at projection centre", 1}, {"Northing at projection centre", 2}, }}, {"Stereographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}}, "Stereographic", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, // Non standard parameter names longitude_of_center/latitude_of_center // used in https://github.com/OSGeo/PROJ/issues/3210 {"Stereographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"longitude_of_center", 3}, {"Scale_Factor", 4}, {"latitude_of_center", 5}}, "Stereographic", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Stereographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 90}}, "Polar Stereographic (variant A)", { {"Latitude of natural origin", 90}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Stereographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", -90}}, "Polar Stereographic (variant A)", { {"Latitude of natural origin", -90}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Equidistant_Conic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, {"Standard_Parallel_2", 5}, {"Latitude_Of_Origin", 6}}, "Equidistant Conic", { {"Latitude of false origin", 6}, {"Longitude of false origin", 3}, {"Latitude of 1st standard parallel", 4}, {"Latitude of 2nd standard parallel", 5}, {"Easting at false origin", 1}, {"Northing at false origin", 2}, }}, {"Cassini", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 1}, {"Latitude_Of_Origin", 4}}, "Cassini-Soldner", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Van_der_Grinten_I", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Van Der Grinten", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Robinson", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Robinson", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Two_Point_Equidistant", {{"False_Easting", 1}, {"False_Northing", 2}, {"Latitude_Of_1st_Point", 3}, {"Latitude_Of_2nd_Point", 4}, {"Longitude_Of_1st_Point", 5}, {"Longitude_Of_2nd_Point", 6}}, "Two Point Equidistant", { {"Latitude of 1st point", 3}, {"Longitude of 1st point", 5}, {"Latitude of 2nd point", 4}, {"Longitude of 2nd point", 6}, {"False easting", 1}, {"False northing", 2}, }}, {"Azimuthal_Equidistant", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Latitude_Of_Origin", 4}}, "Azimuthal Equidistant", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Lambert_Azimuthal_Equal_Area", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Latitude_Of_Origin", 4}}, "Lambert Azimuthal Equal Area", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Cylindrical_Equal_Area", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}}, "Lambert Cylindrical Equal Area", { {"Latitude of 1st standard parallel", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, // Untested: Hotine_Oblique_Mercator_Two_Point_Center {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin", {{"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 3}, {"Azimuth", 4}, {"Longitude_Of_Center", 5}, {"Latitude_Of_Center", 6}}, "Hotine Oblique Mercator (variant A)", { {"Latitude of projection centre", 6}, {"Longitude of projection centre", 5}, {"Azimuth at projection centre", 4}, {"Angle from Rectified to Skew Grid", 4}, {"Scale factor at projection centre", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Hotine_Oblique_Mercator_Azimuth_Center", {{"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 3}, {"Azimuth", 4}, {"Longitude_Of_Center", 5}, {"Latitude_Of_Center", 6}}, "Hotine Oblique Mercator (variant B)", { {"Latitude of projection centre", 6}, {"Longitude of projection centre", 5}, {"Azimuth at projection centre", 4}, {"Angle from Rectified to Skew Grid", 4}, {"Scale factor at projection centre", 3}, {"Easting at projection centre", 1}, {"Northing at projection centre", 2}, }}, {"Double_Stereographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}}, "Oblique Stereographic", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Krovak", {{"False_Easting", 1}, {"False_Northing", 2}, {"Pseudo_Standard_Parallel_1", 3}, {"Scale_Factor", 4}, {"Azimuth", 5}, {"Longitude_Of_Center", 6}, {"Latitude_Of_Center", 7}, {"X_Scale", 1}, {"Y_Scale", 1}, {"XY_Plane_Rotation", 0}}, "Krovak", { {"Latitude of projection centre", 7}, {"Longitude of origin", 6}, {"Co-latitude of cone axis", 5}, {"Latitude of pseudo standard parallel", 3}, {"Scale factor on pseudo standard parallel", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Krovak", {{"False_Easting", 1}, {"False_Northing", 2}, {"Pseudo_Standard_Parallel_1", 3}, {"Scale_Factor", 4}, {"Azimuth", 5}, {"Longitude_Of_Center", 6}, {"Latitude_Of_Center", 7}, {"X_Scale", -1}, {"Y_Scale", 1}, {"XY_Plane_Rotation", 90}}, "Krovak (North Orientated)", { {"Latitude of projection centre", 7}, {"Longitude of origin", 6}, {"Co-latitude of cone axis", 5}, {"Latitude of pseudo standard parallel", 3}, {"Scale factor on pseudo standard parallel", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"New_Zealand_Map_Grid", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Origin", 3}, {"Latitude_Of_Origin", 4}}, "New Zealand Map Grid", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Orthographic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Center", 3}, {"Latitude_Of_Center", 4}}, "Orthographic (Spherical)", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Local", {{"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 1.25}, {"Azimuth", 15}, {"Longitude_Of_Center", 3}, {"Latitude_Of_Center", 4}}, "Local Orthographic", { {"Latitude of projection centre", 4}, {"Longitude of projection centre", 3}, {"Azimuth at projection centre", 15}, {"Scale factor at projection centre", 1.25}, {"Easting at projection centre", 1}, {"Northing at projection centre", 2}, }}, {"Winkel_Tripel", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}}, "Winkel Tripel", { {"Longitude of natural origin", 3}, {"Latitude of 1st standard parallel", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Aitoff", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Aitoff", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Craster_Parabolic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Craster Parabolic", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Gnomonic", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Center", 3}, {"Latitude_Of_Center", 4}}, "Gnomonic", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Stereographic_North_Pole", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}}, "Polar Stereographic (variant B)", { {"Latitude of standard parallel", 4}, {"Longitude of origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Stereographic_South_Pole", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", -4}}, "Polar Stereographic (variant B)", { {"Latitude of standard parallel", -4}, {"Longitude of origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Rectified_Skew_Orthomorphic_Natural_Origin", { {"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 3}, {"Azimuth", 4}, {"Longitude_Of_Center", 5}, {"Latitude_Of_Center", 6}, {"XY_Plane_Rotation", 7}, }, "Hotine Oblique Mercator (variant A)", { {"Latitude of projection centre", 6}, {"Longitude of projection centre", 5}, {"Azimuth at projection centre", 4}, {"Angle from Rectified to Skew Grid", 7}, {"Scale factor at projection centre", 3}, {"False easting", 1}, {"False northing", 2}, }}, // Temptative mapping {"Rectified_Skew_Orthomorphic_Center", { {"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 3}, {"Azimuth", 4}, {"Longitude_Of_Center", 5}, {"Latitude_Of_Center", 6}, {"XY_Plane_Rotation", 7}, }, "Hotine Oblique Mercator (variant B)", { {"Latitude of projection centre", 6}, {"Longitude of projection centre", 5}, {"Azimuth at projection centre", 4}, {"Angle from Rectified to Skew Grid", 7}, {"Scale factor at projection centre", 3}, {"Easting at projection centre", 1}, {"Northing at projection centre", 2}, }}, {"Goode_Homolosine", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Option", 0.0}}, "Goode Homolosine", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Goode_Homolosine", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Option", 1.0}}, "Interrupted Goode Homolosine", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Goode_Homolosine", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Option", 2.0}}, "Interrupted Goode Homolosine Ocean", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Equidistant_Cylindrical_Ellipsoidal", { {"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, }, "Equidistant Cylindrical", { {"Latitude of 1st standard parallel", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Laborde_Oblique_Mercator", {{"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 3}, {"Azimuth", 4}, {"Longitude_Of_Center", 5}, {"Latitude_Of_Center", 6}}, "Laborde Oblique Mercator", { {"Latitude of projection centre", 6}, {"Longitude of projection centre", 5}, {"Azimuth at projection centre", 4}, {"Scale factor at projection centre", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Mercator_Variant_A", {{"False_Easting", 1}, {"False_Northing", 2}, {"Scale_Factor", 3}, {"Central_Meridian", 4}}, "Mercator (variant A)", { {"Longitude of natural origin", 4}, {"Scale factor at natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Mercator_Variant_C", {{"False_Easting", 1}, {"False_Northing", 2}, {"Standard_Parallel_1", 3}, {"Central_Meridian", 4}}, "Mercator (variant B)", { {"Latitude of 1st standard parallel", 3}, {"Longitude of natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Transverse_Cylindrical_Equal_Area", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}}, "Transverse Cylindrical Equal Area", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Gnomonic_Ellipsoidal", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Center", 3}, {"Latitude_Of_Center", 4}}, "Gnomonic", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Wagner_IV", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Latitude_Of_Center", 0}}, "Wagner IV", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Wagner_V", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Wagner V", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Wagner_VII", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Wagner VII", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Geostationary_Satellite", { {"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Center", 3}, {"Height", 4}, {"Option", 0.0}, }, "Geostationary Satellite (Sweep Y)", { {"Longitude of natural origin", 3}, {"Satellite Height", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Mercator_Auxiliary_Sphere", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Standard_Parallel_1", 4}, {"Auxiliary_Sphere_Type", 0}}, "Popular Visualisation Pseudo Mercator", { {"Latitude of natural origin", 4}, {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Vertical_Near_Side_Perspective", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Center", 3}, {"Latitude_Of_Center", 4}, {"Height", 5}}, "Vertical Perspective", { {"Latitude of topocentric origin", 4}, {"Longitude of topocentric origin", 3}, {"Viewpoint height", 5}, {"False easting", 1}, {"False northing", 2}, }}, {"Equal_Earth", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}}, "Equal Earth", { {"Longitude of natural origin", 3}, {"False easting", 1}, {"False northing", 2}, }}, {"Peirce_Quincuncial", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}, {"Option", 0}}, "Peirce Quincuncial (Square)", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, {"Peirce_Quincuncial", {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}, {"Scale_Factor", 4}, {"Latitude_Of_Origin", 5}, {"Option", 1}}, "Peirce Quincuncial (Diamond)", { {"Latitude of natural origin", 5}, {"Longitude of natural origin", 3}, {"Scale factor at natural origin", 4}, {"False easting", 1}, {"False northing", 2}, }}, { "Unknown_Method", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Origin", 3}, {"Latitude_Of_Origin", 4}}, "Unknown_Method", {{"False_Easting", 1}, {"False_Northing", 2}, {"Longitude_Of_Origin", 3}, {"Latitude_Of_Origin", 4}}, }, }; TEST(wkt_parse, esri_projcs) { for (const auto &projDef : esriProjDefs) { std::string wkt("PROJCS[\""); if (strcmp(projDef.esriProjectionName, "Plate_Carree") == 0) wkt += "Plate Carree"; else wkt += "unnamed"; wkt += "\",GEOGCS[\"unnamed\"," "DATUM[\"unnamed\",SPHEROID[\"unnamed\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\""; wkt += projDef.esriProjectionName; wkt += "\"],"; for (const auto ¶m : projDef.esriParams) { wkt += "PARAMETER[\""; wkt += param.first; wkt += "\","; wkt += toString(param.second); wkt += "],"; } wkt += "UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conv = crs->derivingConversion(); auto method = conv->method(); EXPECT_EQ(method->nameStr(), projDef.wkt2ProjectionName) << wkt; auto values = conv->parameterValues(); EXPECT_EQ(values.size(), projDef.wkt2Params.size()) << wkt; if (values.size() == projDef.wkt2Params.size()) { for (size_t i = 0; i < values.size(); i++) { const auto &opParamvalue = nn_dynamic_pointer_cast(values[i]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_EQ(paramName, projDef.wkt2Params[i].first) << wkt; EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.value(), projDef.wkt2Params[i].second) << wkt; } } auto wkt1Esri = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get()); const char *expectedESRIProjectionName = projDef.esriProjectionName; // Not totally sure about the below exceptions. They just capture the // current state of things. if (strcmp(projDef.esriProjectionName, "Transverse_Mercator_Complex") == 0) expectedESRIProjectionName = "Transverse_Mercator"; else if (strcmp(projDef.esriProjectionName, "Equidistant_Cylindrical_Ellipsoidal") == 0) expectedESRIProjectionName = "Equidistant_Cylindrical"; else if (strcmp(projDef.esriProjectionName, "Mercator_Variant_C") == 0) expectedESRIProjectionName = "Mercator"; else if (strcmp(projDef.esriProjectionName, "Gnomonic_Ellipsoidal") == 0) expectedESRIProjectionName = "Gnomonic"; EXPECT_TRUE(wkt1Esri.find(std::string("PROJECTION[\"") .append(expectedESRIProjectionName)) != std::string::npos) << "input: " << wkt << std::endl << "output: " << wkt1Esri; } } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_case_insensitive_names) { auto wkt = "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0," "298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\"," "0.0174532925199433]],PROJECTION[\"transverse_mercator\"]," "PARAMETER[\"false_easting\",500000.0]," "PARAMETER[\"false_northing\",0.0]," "PARAMETER[\"central_meridian\",3.0]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"latitude_of_origin\",0.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); int zone = 0; bool north = false; EXPECT_TRUE(crs->derivingConversion()->isUTM(zone, north)); EXPECT_EQ(zone, 31); EXPECT_TRUE(north); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_non_expected_param_name) { // We try to be lax on parameter names. auto wkt = "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0," "298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\"," "0.0174532925199433]],PROJECTION[\"transverse_mercator\"]," "PARAMETER[\"false_easting\",500000.0]," "PARAMETER[\"false_northing\",0.0]," "PARAMETER[\"longitude_of_center\",3.0]," // should be Central_Meridian "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"latitude_of_origin\",0.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); int zone = 0; bool north = false; EXPECT_TRUE(crs->derivingConversion()->isUTM(zone, north)); EXPECT_EQ(zone, 31); EXPECT_TRUE(north); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_krovak_south_west) { auto wkt = "PROJCS[\"S-JTSK_Krovak\",GEOGCS[\"GCS_S_JTSK\"," "DATUM[\"D_S_JTSK\"," "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Krovak\"],PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Azimuth\",30.28813975277778]," "PARAMETER[\"Longitude_Of_Center\",24.83333333333333]," "PARAMETER[\"Latitude_Of_Center\",49.5]," "PARAMETER[\"X_Scale\",1.0]," "PARAMETER[\"Y_Scale\",1.0]," "PARAMETER[\"XY_Plane_Rotation\",0.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak"); auto expected_wkt2 = "PROJCRS[\"S-JTSK / Krovak\",\n" " BASEGEODCRS[\"S-JTSK\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6156]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Krovak\",\n" " ID[\"EPSG\",9819]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"southing\",south,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"westing\",west,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_krovak_east_north_non_standard_likely_from_GDAL_wkt1) { auto wkt = "PROJCS[\"S_JTSK_Krovak_East_North\",GEOGCS[\"GCS_S-JTSK\"," "DATUM[\"D_S_JTSK\",SPHEROID[\"Bessel_1841\"," "6377397.155,299.1528128]],PRIMEM[\"Greenwich\",0]," "UNIT[\"Degree\",0.017453292519943295]],PROJECTION[\"Krovak\"]," "PARAMETER[\"latitude_of_center\",49.5]," "PARAMETER[\"longitude_of_center\",24.83333333333333]," "PARAMETER[\"azimuth\",30.2881397527778]," "PARAMETER[\"pseudo_standard_parallel_1\",78.5]," "PARAMETER[\"scale_factor\",0.9999]," "PARAMETER[\"false_easting\",0]," "PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak (North Orientated)"); auto expected_wkt2 = "PROJCRS[\"S_JTSK_Krovak_East_North\",\n" " BASEGEODCRS[\"GCS_S-JTSK\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6156]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Krovak (North Orientated)\",\n" " ID[\"EPSG\",1041]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_normalize_unit) { auto wkt = "PROJCS[\"Accra_Ghana_Grid\",GEOGCS[\"GCS_Accra\"," "DATUM[\"D_Accra\",SPHEROID[\"War_Office\",6378300.0,296.0]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",900000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",-1.0]," "PARAMETER[\"Scale_Factor\",0.99975]," "PARAMETER[\"Latitude_Of_Origin\",4.666666666666667]," "UNIT[\"Foot_Gold_Coast\",0.3047997101815088]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "Gold Coast foot"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_ups_north) { // EPSG:32661 auto wkt = "PROJCS[\"UPS_North\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Stereographic\"]," "PARAMETER[\"False_Easting\",2000000.0]," "PARAMETER[\"False_Northing\",2000000.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Scale_Factor\",0.994]," "PARAMETER[\"Latitude_Of_Origin\",90.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS North (N,E)"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(), AxisDirection::SOUTH); // Yes, inconsistency between the name (coming from EPSG) and the fact // that with ESRI CRS, we always output E, N axis order EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E"); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(), AxisDirection::SOUTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_ups_south) { // EPSG:32671 auto wkt = "PROJCS[\"UPS_South\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Stereographic\"]," "PARAMETER[\"False_Easting\",2000000.0]," "PARAMETER[\"False_Northing\",2000000.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Scale_Factor\",0.994]," "PARAMETER[\"Latitude_Of_Origin\",-90.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS South (N,E)"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(), AxisDirection::NORTH); // Yes, inconsistency between the name (coming from EPSG) and the fact // that with ESRI CRS, we always output E, N axis order EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E"); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(), AxisDirection::NORTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_wgs_1984_ups_north_E_N) { // EPSG:5041 auto wkt = "PROJCS[\"WGS_1984_UPS_North_(E-N)\"," "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Polar_Stereographic_Variant_A\"]," "PARAMETER[\"False_Easting\",2000000.0]," "PARAMETER[\"False_Northing\",2000000.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Scale_Factor\",0.994]," "PARAMETER[\"Latitude_Of_Origin\",90.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS North (E,N)"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(), AxisDirection::SOUTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E"); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(), AxisDirection::SOUTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_wgs_1984_ups_south_E_N) { // EPSG:5042 auto wkt = "PROJCS[\"WGS_1984_UPS_South_(E-N)\"," "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Polar_Stereographic_Variant_A\"]," "PARAMETER[\"False_Easting\",2000000.0]," "PARAMETER[\"False_Northing\",2000000.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Scale_Factor\",0.994]," "PARAMETER[\"Latitude_Of_Origin\",-90.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS South (E,N)"); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E"); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(), AxisDirection::NORTH); EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_gauss_kruger) { auto wkt = "PROJCS[\"ETRS_1989_UWPP_2000_PAS_8\",GEOGCS[\"GCS_ETRS_1989\"," "DATUM[\"D_ETRS_1989\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Gauss_Kruger\"]," "PARAMETER[\"False_Easting\",8500000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",24.0]," "PARAMETER[\"Scale_Factor\",0.999923]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); auto crs2 = AuthorityFactory::create(dbContext, "ESRI") ->createProjectedCRS("102177"); EXPECT_EQ( crs2->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_esri_goode_homolosine_without_option_0) { // Not sure if it is really valid to not have PARAMETER["Option",0.0] // but it seems reasonable to check that we understand that as // Goode Homolosine and not Interrupted Goode Homolosine (option 1) auto wkt = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Goode_Homolosine\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "UNIT[\"Meter\",1.0]]"; auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Goode Homolosine"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_oracle) { // WKT from mdsys.cs_srs Oracle table auto wkt = "PROJCS[\"RGF93 / Lambert-93\", GEOGCS [ \"RGF93\", " "DATUM [\"Reseau Geodesique Francais 1993 (EPSG ID 6171)\", " "SPHEROID [\"GRS 1980 (EPSG ID 7019)\", 6378137.0, " "298.257222101]], PRIMEM [ \"Greenwich\", 0.000000000 ], " "UNIT [\"Decimal Degree\", 0.0174532925199433]], " "PROJECTION [\"Lambert Conformal Conic\"], " "PARAMETER [\"Latitude_Of_Origin\", 46.5], " "PARAMETER [\"Central_Meridian\", 3.0], " "PARAMETER [\"Standard_Parallel_1\", 49.0], " "PARAMETER [\"Standard_Parallel_2\", 44.0], " "PARAMETER [\"False_Easting\", 700000.0], " "PARAMETER [\"False_Northing\", 6600000.0], " "UNIT [\"Meter\", 1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->datum()->nameStr(), "Reseau Geodesique Francais 1993"); EXPECT_EQ(crs->baseCRS()->datum()->getEPSGCode(), 6171); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Lambert Conic Conformal (2SP)"); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2154); EXPECT_EQ(res.front().second, 90); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_oracle_albers_conical_equal_area) { // WKT from mdsys.cs_srs Oracle table: // https://lists.osgeo.org/pipermail/qgis-user/2024-June/054599.html auto wkt = "PROJCS[\"NAD83 / BC Albers\",GEOGCS[\"NAD83\"," "DATUM[\"North_American_Datum_1983\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101," "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6269\"]]," "PRIMEM[\"Greenwich\",0]," "UNIT[\"Decimal Degree\",0.0174532925199433]]," "PROJECTION[\"Albers_Conical_Equal_Area\"]," "PARAMETER[\"Latitude_Of_Origin\",45]," "PARAMETER[\"Central_Meridian\",-126]," "PARAMETER[\"Standard_Parallel_1\",50]," "PARAMETER[\"Standard_Parallel_2\",58.5]," "PARAMETER[\"False_Easting\",1000000]," "PARAMETER[\"False_Northing\",0]," "UNIT[\"Meter\",1]," "AXIS[\"Easting\",EAST]," "AXIS[\"Northing\",NORTH]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Albers Equal Area"); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3005); EXPECT_EQ(res.front().second, 100); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_lcc_1sp_without_1sp_suffix) { // WKT from Trimble auto wkt = "PROJCS[\"TWM-Madison Co LDP\"," "GEOGCS[\"WGS 1984\"," "DATUM[\"WGS 1984\"," "SPHEROID[\"World Geodetic System 1984\"," "6378137,298.257223563]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"Degree\",0.01745329251994," "AUTHORITY[\"EPSG\",\"9102\"]]," "AXIS[\"Long\",EAST],AXIS[\"Lat\",NORTH]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",103000.0000035]," "PARAMETER[\"False_Northing\",79000.00007055]," "PARAMETER[\"Latitude_Of_Origin\",38.83333333333]," "PARAMETER[\"Central_Meridian\",-89.93333333333]," "PARAMETER[\"Scale_Factor\",1.000019129]," "UNIT[\"Foot_US\",0.3048006096012,AUTHORITY[\"EPSG\",\"9003\"]]," "AXIS[\"East\",EAST],AXIS[\"North\",NORTH]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Lambert Conic Conformal (1SP)"); } // --------------------------------------------------------------------------- TEST(wkt_parse, wkt1_pseudo_wkt1_gdal_pseudo_wkt1_esri) { // WKT from https://github.com/OSGeo/PROJ/issues/3186 auto wkt = "PROJCS[\"Equidistant_Cylindrical\"," "GEOGCS[\"WGS 84\",DATUM[\"wgs_1984\"," "SPHEROID[\"WGS 84\",6378137,298.257223563," "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree\",0.0174532925199433," "AUTHORITY[\"EPSG\",\"9102\"]]," "AUTHORITY[\"EPSG\",\"4326\"]]," "PROJECTION[\"Equidistant_Cylindrical\"]," "PARAMETER[\"false_easting\",0]," "PARAMETER[\"false_northing\",0]," "PARAMETER[\"central_meridian\",0]," "PARAMETER[\"standard_parallel_1\",37]," "UNIT[\"Meter\",1,AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Equidistant Cylindrical"); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), 1028); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid) { EXPECT_THROW(WKTParser().createFromWKT(""), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("A"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("UNKNOWN[\"foo\"]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("INVALID["), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("INVALID[]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_SPHEROID) { EXPECT_NO_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",1,0.5]")); EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\"]"), ParsingException); // major axis not number EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",\"1\",0.5]"), ParsingException); // major axis not number EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",1,\"0.5\"]"), ParsingException); // reverse flatting not number } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DATUM) { EXPECT_NO_THROW( WKTParser().createFromWKT("DATUM[\"x\",SPHEROID[\"x\",1,0.5]]")); EXPECT_THROW(WKTParser().createFromWKT("DATUM[\"x\"]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("DATUM[\"x\",FOO[]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_ENSEMBLE) { EXPECT_THROW(WKTParser().createFromWKT("ENSEMBLE[]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("ENSEMBLE[\"x\"]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT( "ENSEMBLE[\"x\",MEMBER[\"vdatum1\"],MEMBER[\"vdatum2\"]]"), ParsingException); EXPECT_THROW( WKTParser().createFromWKT("ENSEMBLE[\"x\",MEMBER[],MEMBER[\"vdatum2\"]," "ENSEMBLEACCURACY[\"100\"]]"), ParsingException); EXPECT_THROW( WKTParser().createFromWKT("ENSEMBLE[\"x\",MEMBER[\"vdatum1\"],MEMBER[" "\"vdatum2\"],ENSEMBLEACCURACY[]]"), ParsingException); EXPECT_THROW( WKTParser().createFromWKT("ENSEMBLE[\"x\",ENSEMBLEACCURACY[\"100\"]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_BBOX) { EXPECT_NO_THROW(WKTParser().createFromWKT( "GEOGCRS[\"x\",DATUM[\"x\",ELLIPSOID[\"x\",1,0.5," "LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]]," "PRIMEM[\"x\",0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "CS[ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "AXIS[\"latitude\",north,ORDER[2]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "USAGE[SCOPE[\"unknown\"],BBOX[1,2,3,4]]]")); EXPECT_THROW(WKTParser().createFromWKT( "GEOGCRS[\"x\",DATUM[\"x\",ELLIPSOID[\"x\",1,0.5," "LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]]," "PRIMEM[\"x\",0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "CS[ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "AXIS[\"latitude\",north,ORDER[2]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "USAGE[SCOPE[\"unknown\"],BBOX[1,2,3]]]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT( "GEOGCRS[\"x\",DATUM[\"x\",ELLIPSOID[\"x\",1,0.5," "LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]]," "PRIMEM[\"x\",0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "CS[ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "AXIS[\"latitude\",north,ORDER[2]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "USAGE[SCOPE[\"unknown\"],BBOX[1,2,3,a]]]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT( "GEOGCRS[\"x\",DATUM[\"x\",ELLIPSOID[\"x\",1,0.5," "LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]]," "PRIMEM[\"x\",0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "CS[ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "AXIS[\"latitude\",north,ORDER[2]," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "USAGE[SCOPE[\"unknown\"],BBOX[1,2,-1,4]]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_GEOGCS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433]]")); // missing PRIMEM EXPECT_THROW( WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0." "5]],UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // missing UNIT EXPECT_THROW( WKTParser().createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0]]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\"]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",FOO[]]"), ParsingException); // not enough children for DATUM EXPECT_THROW( WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\"],PRIMEM[\"x\",0]," "UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // not enough children for AUTHORITY EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID[" "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433]," "AUTHORITY[\"x\"]]"), ParsingException); // not enough children for AUTHORITY, but ignored EXPECT_NO_THROW(WKTParser().setStrict(false).createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433],AUTHORITY[\"x\"]]")); EXPECT_NO_THROW(WKTParser().createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433]]")); // PRIMEM not numeric EXPECT_THROW( WKTParser().createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0." "5]],PRIMEM[\"x\",\"a\"],UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // not enough children for PRIMEM EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID[" "\"x\",1,0.5]],PRIMEM[\"x\"],UNIT[" "\"degree\",0.0174532925199433]]"), ParsingException); EXPECT_NO_THROW(WKTParser().createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433],AXIS[\"latitude\"," "NORTH],AXIS[\"longitude\",EAST]]")); // one axis only EXPECT_THROW(WKTParser().createFromWKT( "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0." "5]],PRIMEM[\"x\",0],UNIT[\"degree\",0.0174532925199433]," "AXIS[\"latitude\",NORTH]]"), ParsingException); // invalid axis EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID[" "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433]," "AXIS[\"latitude\"," "NORTH],AXIS[\"longitude\"]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_UNIT) { std::string startWKT("GEODCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],CS[" "ellipsoidal,2],AXIS[\"latitude\",north],AXIS[" "\"longitude\",east],"); EXPECT_NO_THROW(WKTParser().createFromWKT( startWKT + "UNIT[\"degree\",0.0174532925199433]]")); // not enough children EXPECT_THROW(WKTParser().createFromWKT(startWKT + "UNIT[\"x\"]]]"), ParsingException); // invalid conversion factor EXPECT_THROW( WKTParser().createFromWKT(startWKT + "UNIT[\"x\",\"invalid\"]]]"), ParsingException); // invalid ID EXPECT_THROW( WKTParser().createFromWKT(startWKT + "UNIT[\"x\",1,ID[\"x\"]]]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_GEOCCS) { EXPECT_NO_THROW( WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0." "5]],PRIMEM[\"x\",0],UNIT[\"metre\",1]]")); // missing PRIMEM EXPECT_THROW(WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID[" "\"x\",1,0.5]],UNIT[\"metre\",1]]"), ParsingException); // missing UNIT EXPECT_THROW( WKTParser().createFromWKT( "GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0]]"), ParsingException); // ellipsoidal CS is invalid in a GEOCCS EXPECT_THROW(WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID[" "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT[" "\"degree\",0.0174532925199433]," "AXIS[\"latitude\"," "NORTH],AXIS[\"longitude\",EAST]]"), ParsingException); // ellipsoidal CS is invalid in a GEOCCS EXPECT_THROW(WKTParser().createFromWKT( "GEOCCS[\"WGS 84\",DATUM[\"World Geodetic System 1984\"," "ELLIPSOID[\"WGS 84\",6378274,298.257223564," "LENGTHUNIT[\"metre\",1]]]," "CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north," "ANGLEUNIT[\"degree\",0.0174532925199433]]," "AXIS[\"geodetic longitude (Lon)\",east," "ANGLEUNIT[\"degree\",0.0174532925199433]]]"), ParsingException); // 3 axis required EXPECT_THROW(WKTParser().createFromWKT( "GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[" "\"x\",0],UNIT[\"metre\",1],AXIS[" "\"Geocentric X\",OTHER],AXIS[\"Geocentric Y\",OTHER]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_CS_of_GEODCRS) { std::string startWKT("GEODCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]]"); // missing CS EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException); // CS: not enough children EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[x]]"), ParsingException); // CS: invalid type EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[x,2]]"), ParsingException); // CS: invalid number of axis EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[ellipsoidal,0]]"), ParsingException); // CS: number of axis is not a number EXPECT_THROW( WKTParser().createFromWKT(startWKT + ",CS[ellipsoidal,\"x\"]]"), ParsingException); // CS: invalid CS type EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[invalid,2],AXIS[\"latitude\"," "north],AXIS[\"longitude\",east]]"), ParsingException); // CS: OK EXPECT_NO_THROW(WKTParser().createFromWKT( startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS[" "\"longitude\",east],UNIT[\"degree\",0.0174532925199433]]")); // CS: Cartesian with 2 axis unexpected EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",CS[Cartesian,2],AXIS[\"latitude\"," "north],AXIS[\"longitude\",east]," "UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // CS: missing axis EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north]," "UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // not enough children in AXIS EXPECT_THROW( WKTParser().createFromWKT( startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS[\"longitude\"]," "UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // not enough children in ORDER EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[]],AXIS[" "\"longitude\",east]," "UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // invalid value in ORDER EXPECT_THROW( WKTParser().createFromWKT( startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[\"x\"]],AXIS[" "\"longitude\",east],UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // unexpected ORDER value EXPECT_THROW( WKTParser().createFromWKT( startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[2]],AXIS[" "\"longitude\",east],UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // Invalid CS type EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up],\n" " UNIT[\"metre\",1]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_CS_of_GEOGRAPHICCRS) { // A GeographicCRS must have an ellipsoidal CS EXPECT_THROW(WKTParser().createFromWKT( "GEOGRAPHICCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]]," "CS[Cartesian,3],AXIS[\"(X)\",geocentricX],AXIS[\"(Y)\"," "geocentricY],AXIS[\"(Z)\",geocentricZ]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DYNAMIC) { std::string prefix("GEOGCRS[\"WGS 84 (G1762)\","); std::string suffix( "TRF[\"World Geodetic System 1984 (G1762)\"," "ELLIPSOID[\"WGS 84\",6378137,298.257223563]]," "CS[ellipsoidal,3]," " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]" "]"); EXPECT_NO_THROW(WKTParser().createFromWKT( prefix + "DYNAMIC[FRAMEEPOCH[2015]]," + suffix)); EXPECT_THROW(WKTParser().createFromWKT(prefix + "DYNAMIC[]," + suffix), ParsingException); EXPECT_THROW( WKTParser().createFromWKT(prefix + "DYNAMIC[FRAMEEPOCH[]]," + suffix), ParsingException); EXPECT_THROW(WKTParser().createFromWKT( prefix + "DYNAMIC[FRAMEEPOCH[\"invalid\"]]," + suffix), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_PROJCRS) { // missing BASEGEODCRS EXPECT_THROW( WKTParser().createFromWKT("PROJCRS[\"WGS 84 / UTM zone 31N\"]"), ParsingException); std::string startWKT("PROJCRS[\"WGS 84 / UTM zone 31N\",BASEGEOGCRS[\"WGS " "84\",DATUM[\"WGS_1984\",ELLIPSOID[\"WGS " "84\",6378137,298.257223563]],UNIT[\"degree\",0." "0174532925199433]]"); // missing CONVERSION EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException); // not enough children in CONVERSION EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\"]]"), ParsingException); // not enough children in METHOD EXPECT_THROW( WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\",METHOD[]]]"), ParsingException); // not enough children in PARAMETER EXPECT_THROW( WKTParser().createFromWKT( startWKT + ",CONVERSION[\"x\",METHOD[\"y\"],PARAMETER[\"z\"]]]"), ParsingException); // non numeric value for PARAMETER EXPECT_THROW( WKTParser().createFromWKT( startWKT + ",CONVERSION[\"x\",METHOD[\"y\"],PARAMETER[\"z\",\"foo\"]]]"), ParsingException); // missing CS EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]]]"), ParsingException); // CS is not Cartesian EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS[" "ellipsoidal,2],AXIS[\"latitude\",north],AXIS[" "\"longitude\",east]]"), ParsingException); // not enough children in MERIDIAN EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS[" "Cartesian,2],AXIS[\"easting (X)\",south," "MERIDIAN[90]],AXIS[" "\"northing (Y)\",south]]"), ParsingException); // non numeric angle value for MERIDIAN EXPECT_THROW( WKTParser().createFromWKT( startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS[" "Cartesian,2],AXIS[\"easting (X)\",south," "MERIDIAN[\"x\",UNIT[\"degree\",0.0174532925199433]]],AXIS[" "\"northing (Y)\",south]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_PROJCS) { std::string startWKT( "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"x\",0],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"latitude\",NORTH],\n" " AXIS[\"longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]]\n"); // missing PROJECTION EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException); // not enough children in PROJECTION EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",PROJECTION[],UNIT[\"metre\",1]]"), ParsingException); // not enough children in PARAMETER EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",PROJECTION[\"x\"],PARAMETER[\"z\"],UNIT[\"metre\",1]]"), ParsingException); // not enough children in PARAMETER EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",PROJECTION[\"x\"],PARAMETER[\"z\"],UNIT[\"metre\",1]]"), ParsingException); EXPECT_NO_THROW(WKTParser().createFromWKT( startWKT + ",PROJECTION[\"x\"],PARAMETER[\"z\",1],UNIT[\"metre\",1]]")); // missing UNIT EXPECT_THROW(WKTParser().createFromWKT( startWKT + ",PROJECTION[\"x\"],PARAMETER[\"z\",1]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_VERTCRS) { // missing VDATUM EXPECT_THROW(WKTParser().createFromWKT( "VERTCRS[\"foo\",CS[vertical,1],AXIS[\"x\",up]]"), ParsingException); // missing CS EXPECT_THROW(WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"]]"), ParsingException); // CS is not of type vertical EXPECT_THROW(WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"],CS[" "ellipsoidal,2],AXIS[\"latitude\"," "north],AXIS[" "\"longitude\",east]]"), ParsingException); // verticalCS should have only 1 axis EXPECT_THROW( WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"],CS[vertical," "2],AXIS[\"latitude\",north],AXIS[" "\"longitude\",east]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_esri_VERTCS) { // VDATUM without child EXPECT_THROW(WKTParser().createFromWKT( "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "VERTCS[\"EGM96_Geoid\",VDATUM," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_VERT_CS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "VERT_CS[\"x\",VERT_DATUM[\"y\",2005],UNIT[\"metre\",1]]")); // Missing VERT_DATUM EXPECT_THROW(WKTParser().createFromWKT("VERT_CS[\"x\",UNIT[\"metre\",1]]"), ParsingException); // Missing UNIT EXPECT_THROW( WKTParser().createFromWKT("VERT_CS[\"x\",VERT_DATUM[\"y\",2005]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_COORDINATEOPERATION) { std::string src_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get()); } std::string dst_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get()); } std::string interpolation_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); interpolation_wkt = GeographicCRS::EPSG_4979->exportToWKT(formatter.get()); } // Valid { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[\"operationMethodName\"],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"]]"; EXPECT_NO_THROW(WKTParser().createFromWKT(wkt)); } // Missing SOURCECRS { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[\"operationMethodName\"],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // Invalid content in SOURCECRS { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[FOO],\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[\"operationMethodName\"],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // Missing TARGETCRS { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " METHOD[\"operationMethodName\"],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // Invalid content in TARGETCRS { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[FOO],\n" " METHOD[\"operationMethodName\"],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // Missing METHOD { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // Invalid METHOD { auto wkt = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_CONCATENATEDOPERATION) { // No STEP EXPECT_THROW(WKTParser().createFromWKT("CONCATENATEDOPERATION[\"name\"]"), ParsingException); auto transf_1 = Transformation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_1"), nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), nullptr, PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector()); // One single STEP { auto wkt = "CONCATENATEDOPERATION[\"name\",\n" " SOURCECRS[" + transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " TARGETCRS[" + transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " STEP[" + transf_1->exportToWKT(WKTFormatter::create().get()) + "],\n" " ID[\"codeSpace\",\"code\"],\n" " REMARK[\"my remarks\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // empty STEP { auto wkt = "CONCATENATEDOPERATION[\"name\",\n" " SOURCECRS[" + transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " TARGETCRS[" + transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " STEP[],\n" " STEP[],\n" " ID[\"codeSpace\",\"code\"],\n" " REMARK[\"my remarks\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } // Invalid content in STEP { auto wkt = "CONCATENATEDOPERATION[\"name\",\n" " SOURCECRS[" + transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " TARGETCRS[" + transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " STEP[" + transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " STEP[" + transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) + "],\n" " ID[\"codeSpace\",\"code\"],\n" " REMARK[\"my remarks\"]]"; EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException); } } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_BOUNDCRS) { auto projcrs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"), GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto valid_wkt = "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"foo\",\n" " METHOD[\"bar\"],PARAMETER[\"foo\",1.0]]]"; EXPECT_NO_THROW(WKTParser().createFromWKT(valid_wkt)) << valid_wkt; // Missing SOURCECRS EXPECT_THROW( WKTParser().createFromWKT("BOUNDCRS[TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT( WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"foo\",\n" " METHOD[\"bar\"]," "PARAMETER[\"foo\",1.0]]]"), ParsingException); // Invalid SOURCECRS EXPECT_THROW( WKTParser().createFromWKT("BOUNDCRS[SOURCECRS[foo], TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT( WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"foo\",\n" " METHOD[\"bar\"]," "PARAMETER[\"foo\",1.0]]]"), ParsingException); // Missing TARGETCRS EXPECT_THROW(WKTParser().createFromWKT( "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"foo\",\n" " METHOD[\"bar\"]," "PARAMETER[\"foo\",1.0]]]"), ParsingException); // Invalid TARGETCRS EXPECT_THROW(WKTParser().createFromWKT( "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],TARGETCRS[\"foo\"],\n" " ABRIDGEDTRANSFORMATION[\"foo\",\n" " METHOD[\"bar\"]," "PARAMETER[\"foo\",1.0]]]"), ParsingException); // Missing ABRIDGEDTRANSFORMATION EXPECT_THROW(WKTParser().createFromWKT( "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT( WKTFormatter::create().get()) + "]]"), ParsingException); // Missing METHOD EXPECT_THROW(WKTParser().createFromWKT( "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT( WKTFormatter::create().get()) + "]," "ABRIDGEDTRANSFORMATION[\"foo\"]," "PARAMETER[\"foo\",1.0]]"), ParsingException); // Invalid METHOD EXPECT_THROW(WKTParser().createFromWKT( "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT( WKTFormatter::create().get()) + "]," "ABRIDGEDTRANSFORMATION[\"foo\",METHOD[]," "PARAMETER[\"foo\",1.0]]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_TOWGS84) { EXPECT_THROW(WKTParser().createFromWKT( "GEOGCS[\"WGS 84\"," " DATUM[\"WGS_1984\"," " SPHEROID[\"WGS 84\",6378137,298.257223563]," " TOWGS84[0]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"degree\",0.0174532925199433]]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT( "GEOGCS[\"WGS 84\"," " DATUM[\"WGS_1984\"," " SPHEROID[\"WGS 84\",6378137,298.257223563]," " TOWGS84[0,0,0,0,0,0,\"foo\"]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"degree\",0.0174532925199433]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DerivedGeographicCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n" " DERIVINGCONVERSION[\"foo\",\n" " METHOD[\"bar\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " UNIT[\"degree\",0.0174532925199433]]")); // Missing DERIVINGCONVERSION EXPECT_THROW( WKTParser().createFromWKT( "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " UNIT[\"degree\",0.0174532925199433]]"), ParsingException); // Missing CS EXPECT_THROW( WKTParser().createFromWKT( "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n" " DERIVINGCONVERSION[\"foo\",\n" " METHOD[\"bar\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]]"), ParsingException); // CS should be ellipsoidal given root node is GEOGCRS EXPECT_THROW( WKTParser().createFromWKT( "GEOGCRS[\"WMO Atlantic Pole\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n" " DERIVINGCONVERSION[\"foo\",\n" " METHOD[\"bar\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " AXIS[\"(Z)\",geocentricZ],\n" " UNIT[\"metre\",1]]"), ParsingException); // CS should have 3 axis EXPECT_THROW( WKTParser().createFromWKT( "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n" " DERIVINGCONVERSION[\"foo\",\n" " METHOD[\"bar\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " UNIT[\"metre\",1]]"), ParsingException); // Invalid CS type EXPECT_THROW( WKTParser().createFromWKT( "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n" " DERIVINGCONVERSION[\"foo\",\n" " METHOD[\"bar\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up],\n" " UNIT[\"metre\",1]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_TemporalCRS) { EXPECT_NO_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]")); // Missing TDATUM EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]"), ParsingException); // Missing CS EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]]]"), ParsingException); // CS should be temporal EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[Cartesian,2],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " UNIT[\"metre\",1]]"), ParsingException); // CS should have 1 axis EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[temporal,2],\n" " AXIS[\"time (T)\",future],\n" " AXIS[\"time2 (T)\",future]]"), ParsingException); // CS should have 1 axis EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[TemporalDateTime,2],\n" " AXIS[\"time (T)\",future],\n" " AXIS[\"time2 (T)\",future]]"), ParsingException); // CS should have 1 axis EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[TemporalCount,2],\n" " AXIS[\"time (T)\",future],\n" " AXIS[\"time2 (T)\",future]]"), ParsingException); // CS should have 1 axis EXPECT_THROW( WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[TemporalMeasure,2],\n" " AXIS[\"time (T)\",future],\n" " AXIS[\"time2 (T)\",future]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_EngineeingCRS) { EXPECT_NO_THROW( WKTParser().createFromWKT("ENGCRS[\"name\",\n" " EDATUM[\"name\"],\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]")); // Missing EDATUM EXPECT_THROW( WKTParser().createFromWKT("ENGCRS[\"name\",\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]"), ParsingException); // Missing CS EXPECT_THROW(WKTParser().createFromWKT("ENGCRS[\"name\",\n" " EDATUM[\"name\"]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_LOCAL_CS) { EXPECT_THROW( WKTParser().createFromWKT("LOCAL_CS[\"name\",\n" " LOCAL_DATUM[\"name\",1234],\n" " AXIS[\"Geodetic latitude\",NORTH],\n" " AXIS[\"Geodetic longitude\",EAST],\n" " AXIS[\"Ellipsoidal height\",UP]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_ParametricCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"name\",\n" " PDATUM[\"name\"],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]")); // Missing PDATUM EXPECT_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"name\",\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"), ParsingException); // Missing CS EXPECT_THROW(WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n" " PDATUM[\"name\"]]"), ParsingException); // Invalid number of axis for CS EXPECT_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"name\",\n" " PDATUM[\"name\"],\n" " CS[parametric,2],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"), ParsingException); // Invalid CS type EXPECT_THROW( WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n" " PDATUM[\"name\"],\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DERIVEDPROJCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"BASEPROJCRS\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1]]")); EXPECT_THROW(WKTParser().createFromWKT( "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1]]"), ParsingException); // Missing DERIVINGCONVERSION EXPECT_THROW( WKTParser().createFromWKT( "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"BASEPROJCRS\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1]]"), ParsingException); // Missing CS EXPECT_THROW( WKTParser().createFromWKT( "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"BASEPROJCRS\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DerivedVerticalCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up],\n" " UNIT[\"metre\",1]]")); // Missing DERIVINGCONVERSION EXPECT_THROW(WKTParser().createFromWKT( "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up],\n" " UNIT[\"metre\",1]]"), ParsingException); // Missing CS EXPECT_THROW(WKTParser().createFromWKT( "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]]"), ParsingException); // Wrong CS type EXPECT_THROW(WKTParser().createFromWKT( "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[parametric,1],\n" " AXIS[\"gravity-related height (H)\",up],\n" " UNIT[\"metre\",1]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DerivedEngineeringCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "ENGCRS[\"Derived EngineeringCRS\",\n" " BASEENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " LENGTHUNIT[\"metre\",1]]")); // Missing DERIVINGCONVERSION EXPECT_THROW( WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n" " BASEENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " LENGTHUNIT[\"metre\",1]]"), ParsingException); // Missing CS EXPECT_THROW(WKTParser().createFromWKT( "ENGCRS[\"Derived EngineeringCRS\",\n" " BASEENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DerivedParametricCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"Derived ParametricCRS\",\n" " BASEPARAMCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]")); // Missing DERIVINGCONVERSION EXPECT_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"Derived ParametricCRS\",\n" " BASEPARAMCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"), ParsingException); // Missing CS EXPECT_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"Derived ParametricCRS\",\n" " BASEPARAMCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]]"), ParsingException); // Wrong CS type EXPECT_THROW(WKTParser().createFromWKT( "PARAMETRICCRS[\"Derived ParametricCRS\",\n" " BASEPARAMCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_DerivedTemporalCRS) { EXPECT_NO_THROW(WKTParser().createFromWKT( "TIMECRS[\"Derived TemporalCRS\",\n" " BASETIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]")); // Missing DERIVINGCONVERSION EXPECT_THROW(WKTParser().createFromWKT( "TIMECRS[\"Derived TemporalCRS\",\n" " BASETIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"), ParsingException); // Missing CS EXPECT_THROW(WKTParser().createFromWKT( "TIMECRS[\"Derived TemporalCRS\",\n" " BASETIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]]]"), ParsingException); // Wrong CS type EXPECT_THROW(WKTParser().createFromWKT( "TIMECRS[\"Derived TemporalCRS\",\n" " BASETIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"],\n" " PARAMETER[\"foo\",1.0,UNIT[\"metre\",1]]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(wkt_parse, invalid_CoordinateMetadata) { EXPECT_THROW(WKTParser().createFromWKT("COORDINATEMETADATA[]"), ParsingException); EXPECT_THROW(WKTParser().createFromWKT("COORDINATEMETADATA[ELLIPSOID[\"GRS " "1980\",6378137,298.257222101]]"), ParsingException); // Empty epoch EXPECT_THROW( WKTParser().createFromWKT( "COORDINATEMETADATA[\n" " GEOGCRS[\"ITRF2014\",\n" " DYNAMIC[\n" " FRAMEEPOCH[2010]],\n" " DATUM[\"International Terrestrial Reference Frame " "2014\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " USAGE[\n" " SCOPE[\"Geodesy.\"],\n" " AREA[\"World.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",9000]],\n" " EPOCH[]]"), ParsingException); // Invalid epoch EXPECT_THROW( WKTParser().createFromWKT( "COORDINATEMETADATA[\n" " GEOGCRS[\"ITRF2014\",\n" " DYNAMIC[\n" " FRAMEEPOCH[2010]],\n" " DATUM[\"International Terrestrial Reference Frame " "2014\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " USAGE[\n" " SCOPE[\"Geodesy.\"],\n" " AREA[\"World.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",9000]],\n" " EPOCH[invalid]]"), ParsingException); } // --------------------------------------------------------------------------- TEST(io, projstringformatter) { { auto fmt = PROJStringFormatter::create(); fmt->addStep("my_proj"); EXPECT_EQ(fmt->toString(), "+proj=my_proj"); } { auto fmt = PROJStringFormatter::create(); fmt->addStep("my_proj"); fmt->setCurrentStepInverted(true); EXPECT_EQ(fmt->toString(), "+proj=pipeline +step +inv +proj=my_proj"); } { auto fmt = PROJStringFormatter::create(); fmt->addStep("my_proj1"); fmt->addStep("my_proj2"); EXPECT_EQ(fmt->toString(), "+proj=pipeline +step +proj=my_proj1 +step +proj=my_proj2"); } { auto fmt = PROJStringFormatter::create(); fmt->addStep("my_proj1"); fmt->setCurrentStepInverted(true); fmt->addStep("my_proj2"); EXPECT_EQ( fmt->toString(), "+proj=pipeline +step +inv +proj=my_proj1 +step +proj=my_proj2"); } { auto fmt = PROJStringFormatter::create(); fmt->startInversion(); fmt->addStep("my_proj1"); fmt->setCurrentStepInverted(true); fmt->addStep("my_proj2"); fmt->stopInversion(); EXPECT_EQ( fmt->toString(), "+proj=pipeline +step +inv +proj=my_proj2 +step +proj=my_proj1"); } { auto fmt = PROJStringFormatter::create(); fmt->startInversion(); fmt->addStep("my_proj1"); fmt->setCurrentStepInverted(true); fmt->startInversion(); fmt->addStep("my_proj2"); fmt->stopInversion(); fmt->stopInversion(); EXPECT_EQ(fmt->toString(), "+proj=pipeline +step +proj=my_proj2 +step +proj=my_proj1"); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_helmert_3_param_noop) { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 0); fmt->addParam("y", 0); fmt->addParam("z", 0); EXPECT_EQ(fmt->toString(), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_helmert_7_param_noop) { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 0); fmt->addParam("y", 0); fmt->addParam("z", 0); fmt->addParam("rx", 0); fmt->addParam("ry", 0); fmt->addParam("rz", 0); fmt->addParam("s", 0); fmt->addParam("convention", "position_vector"); EXPECT_EQ(fmt->toString(), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_merge_consecutive_helmert_3_param) { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addStep("helmert"); fmt->addParam("x", -1); fmt->addParam("y", -2); fmt->addParam("z", -3); EXPECT_EQ(fmt->toString(), "+proj=helmert +x=9 +y=18 +z=27"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_merge_consecutive_helmert_3_param_noop) { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addStep("helmert"); fmt->addParam("x", -10); fmt->addParam("y", -20); fmt->addParam("z", -30); EXPECT_EQ(fmt->toString(), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_merge_inverted_helmert_with_opposite_conventions) { { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); fmt->addParam("ry", 2); fmt->addParam("rz", 3); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); fmt->addStep("helmert"); fmt->setCurrentStepInverted(true); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", -1); fmt->addParam("ry", -2); fmt->addParam("rz", -3); fmt->addParam("s", 4); fmt->addParam("convention", "coordinate_frame"); EXPECT_EQ(fmt->toString(), "+proj=noop"); } { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->setCurrentStepInverted(true); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); fmt->addParam("ry", 2); fmt->addParam("rz", 3); fmt->addParam("s", 4); fmt->addParam("convention", "coordinate_frame"); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", -1); fmt->addParam("ry", -2); fmt->addParam("rz", -3); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); EXPECT_EQ(fmt->toString(), "+proj=noop"); } // Cannot be optimized { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); fmt->addParam("ry", 2); fmt->addParam("rz", 3); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); fmt->addStep("helmert"); // fmt->setCurrentStepInverted(true); <== CAUSE fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", -1); fmt->addParam("ry", -2); fmt->addParam("rz", -3); fmt->addParam("s", 4); fmt->addParam("convention", "coordinate_frame"); EXPECT_TRUE(fmt->toString() != "+proj=noop"); } // Cannot be optimized { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); fmt->addParam("ry", 2); fmt->addParam("rz", 3); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); fmt->addStep("helmert"); fmt->setCurrentStepInverted(true); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", -1); fmt->addParam("ry", -2); fmt->addParam("rz", -3); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); // <== CAUSE EXPECT_TRUE(fmt->toString() != "+proj=noop"); } // Cannot be optimized { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); fmt->addParam("ry", 2); fmt->addParam("rz", 3); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); fmt->addStep("helmert"); fmt->setCurrentStepInverted(true); fmt->addParam("x", -10); // <== CAUSE fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", -1); fmt->addParam("ry", -2); fmt->addParam("rz", -3); fmt->addParam("s", 4); fmt->addParam("convention", "coordinate_frame"); EXPECT_TRUE(fmt->toString() != "+proj=noop"); } // Cannot be optimized { auto fmt = PROJStringFormatter::create(); fmt->addStep("helmert"); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); fmt->addParam("ry", 3); fmt->addParam("rz", 2); fmt->addParam("s", 4); fmt->addParam("convention", "position_vector"); fmt->addStep("helmert"); fmt->setCurrentStepInverted(true); fmt->addParam("x", 10); fmt->addParam("y", 20); fmt->addParam("z", 30); fmt->addParam("rx", 1); // <== CAUSE fmt->addParam("ry", -2); fmt->addParam("rz", -3); fmt->addParam("s", 4); fmt->addParam("convention", "coordinate_frame"); EXPECT_TRUE(fmt->toString() != "+proj=noop"); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_cart_grs80_wgs84) { auto fmt = PROJStringFormatter::create(); fmt->addStep("cart"); fmt->addParam("ellps", "WGS84"); fmt->addStep("cart"); fmt->setCurrentStepInverted(true); fmt->addParam("ellps", "GRS80"); EXPECT_EQ(fmt->toString(), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_axisswap_unitconvert_axisswap) { auto fmt = PROJStringFormatter::create(); fmt->addStep("axisswap"); fmt->addParam("order", "2,1"); fmt->addStep("unitconvert"); fmt->addParam("xy_in", "rad"); fmt->addParam("xy_out", "deg"); fmt->addStep("axisswap"); fmt->addParam("order", "2,1"); EXPECT_EQ(fmt->toString(), "+proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_axisswap_one_minus_two_inv) { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline +step +inv +proj=axisswap +order=1,-2"); EXPECT_EQ(fmt->toString(), "+proj=axisswap +order=1,-2"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_axisswap_two_one_followed_two_minus_one) { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString("+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=axisswap +order=2,-1"); EXPECT_EQ(fmt->toString(), "+proj=axisswap +order=1,-2"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_axisswap_minus_two_one_followed_two_one) { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString("+proj=pipeline " "+step +proj=axisswap +order=-2,1 " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(fmt->toString(), "+proj=axisswap +order=1,-2"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_axisswap_two_minus_one_followed_minus_two_one) { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString("+proj=pipeline " "+step +proj=axisswap +order=2,-1 " "+step +proj=axisswap +order=-2,1"); EXPECT_EQ(fmt->toString(), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_axisswap_two_minus_one_followed_one_minus_two) { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString("+proj=pipeline " "+step +proj=axisswap +order=2,-1 " "+step +proj=axisswap +order=1,-2"); EXPECT_EQ(fmt->toString(), "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(io, projstringformatter_unitconvert) { // +step +proj=unitconvert +xy_in=X1 +xy_out=X2 // +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2 // ==> // +step +proj=unitconvert +z_in=Z1 +z_out=Z2 { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=ft"); EXPECT_EQ(fmt->toString(), "+proj=unitconvert +z_in=m +z_out=ft"); } // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2 // +step +proj=unitconvert +z_in=Z2 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 // +z_out=Z3 { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=ft " "+step +proj=unitconvert +z_in=ft +z_out=us-ft"); EXPECT_EQ( fmt->toString(), "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=us-ft"); } // +step +proj=unitconvert +z_in=Z1 +z_out=Z2 // +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X2 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 // +z_out=Z3 { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString("+proj=pipeline " "+step +proj=unitconvert +z_in=ft +z_out=m " "+step +proj=unitconvert +xy_in=deg +z_in=m " "+xy_out=rad +z_out=us-ft "); EXPECT_EQ( fmt->toString(), "+proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=us-ft"); } // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2 // +step +proj=unitconvert +xy_in=X2 +xy_out=X3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 // +z_out=Z2 { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=ft " "+step +proj=unitconvert +xy_in=rad +xy_out=grad"); EXPECT_EQ( fmt->toString(), "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=grad +z_out=ft"); } // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2 // +step +proj=unitconvert +xy_in=X2 +z_in=Z3 +xy_out=X3 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 +z_out=Z2 { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad " "+z_out=us-ft " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=grad +z_out=m"); EXPECT_EQ( fmt->toString(), "+proj=unitconvert +xy_in=deg +z_in=ft +xy_out=grad +z_out=us-ft"); } // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z1 // +step +proj=unitconvert +xy_in=X2 +z_in=Z2 +xy_out=X3 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X3 +z_out=Z3 { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad " "+z_out=m " "+step +proj=unitconvert +xy_in=rad +z_in=ft +xy_out=grad " "+z_out=us-ft"); EXPECT_EQ( fmt->toString(), "+proj=unitconvert +xy_in=deg +z_in=ft +xy_out=grad +z_out=us-ft"); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_unmodified) { const char *const strs[] = {"+proj=pipeline " "+step +proj=axisswap +order=2,-1 " "+step +proj=axisswap +order=2,1", "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=axisswap +order=-2,1", "+proj=pipeline " "+step +inv +proj=axisswap +order=-2,1 " "+step +proj=axisswap +order=2,1"}; for (const char *str : strs) { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString(str); EXPECT_EQ(fmt->toString(), str); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_optim_hgridshift_vgridshift_hgridshift_inv) { // Nominal case { auto fmt = PROJStringFormatter::create(); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); fmt->addStep("vgridshift"); fmt->addParam("grids", "bar"); fmt->startInversion(); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); fmt->stopInversion(); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +proj=push +v_1 +v_2 " "+step +proj=hgridshift +grids=foo +omit_inv " "+step +proj=vgridshift +grids=bar " "+step +inv +proj=hgridshift +grids=foo +omit_fwd " "+step +proj=pop +v_1 +v_2"); } // Test omit_fwd->omit_inv when inversing the pipeline { auto fmt = PROJStringFormatter::create(); fmt->startInversion(); fmt->ingestPROJString("+proj=hgridshift +grids=foo +omit_fwd"); fmt->stopInversion(); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +inv +proj=hgridshift +grids=foo +omit_inv"); } // Test omit_inv->omit_fwd when inversing the pipeline { auto fmt = PROJStringFormatter::create(); fmt->startInversion(); fmt->ingestPROJString("+proj=hgridshift +grids=foo +omit_inv"); fmt->stopInversion(); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +inv +proj=hgridshift +grids=foo +omit_fwd"); } // Variant with first hgridshift inverted, and second forward { auto fmt = PROJStringFormatter::create(); fmt->startInversion(); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); fmt->stopInversion(); fmt->addStep("vgridshift"); fmt->addParam("grids", "bar"); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +proj=push +v_1 +v_2 " "+step +inv +proj=hgridshift +grids=foo +omit_inv " "+step +proj=vgridshift +grids=bar " "+step +proj=hgridshift +grids=foo +omit_fwd " "+step +proj=pop +v_1 +v_2"); } // Do not apply ! not same grid name { auto fmt = PROJStringFormatter::create(); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); fmt->addStep("vgridshift"); fmt->addParam("grids", "bar"); fmt->startInversion(); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo2"); fmt->stopInversion(); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +proj=hgridshift +grids=foo " "+step +proj=vgridshift +grids=bar " "+step +inv +proj=hgridshift +grids=foo2"); } // Do not apply ! missing inversion { auto fmt = PROJStringFormatter::create(); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); fmt->addStep("vgridshift"); fmt->addParam("grids", "bar"); fmt->addStep("hgridshift"); fmt->addParam("grids", "foo"); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +proj=hgridshift +grids=foo " "+step +proj=vgridshift +grids=bar " "+step +proj=hgridshift +grids=foo"); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_optim_as_uc_vgridshift_uc_as_push_as_uc) { // Nominal case { auto fmt = PROJStringFormatter::create(); fmt->addStep("axisswap"); fmt->addParam("order", "2,1"); fmt->addStep("unitconvert"); fmt->addParam("xy_in", "deg"); fmt->addParam("xy_out", "rad"); fmt->addStep("vgridshift"); fmt->addParam("grids", "foo"); fmt->addStep("unitconvert"); fmt->addParam("xy_in", "rad"); fmt->addParam("xy_out", "deg"); fmt->addStep("axisswap"); fmt->addParam("order", "2,1"); fmt->addStep("push"); fmt->addParam("v_1"); fmt->addParam("v_2"); fmt->addStep("axisswap"); fmt->addParam("order", "2,1"); fmt->addStep("unitconvert"); fmt->addParam("xy_in", "deg"); fmt->addParam("xy_out", "rad"); EXPECT_EQ(fmt->toString(), "+proj=pipeline " "+step +proj=push +v_1 +v_2 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=foo"); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_krovak_to_krovak_east_north) { // Working case { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +inv +proj=krovak +axis=swu +lat_0=49.5 " "+lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel"); EXPECT_EQ(fmt->toString(), "+proj=axisswap +order=-2,-1"); } // Missing parameter { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +inv +proj=krovak +axis=swu +lat_0=49.5 " "+lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 "); // Not equal EXPECT_NE(fmt->toString(), "+proj=axisswap +order=-2,-1"); } // Different parameter values { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +inv +proj=krovak +axis=swu +lat_0=49.5 " "+lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=krovak +lat_0=FOO +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel"); // Not equal EXPECT_NE(fmt->toString(), "+proj=axisswap +order=-2,-1"); } } // --------------------------------------------------------------------------- TEST(io, projstringformatter_krovak_east_north_to_krovak) { // Working case { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +inv +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=krovak +axis=swu +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel"); EXPECT_EQ(fmt->toString(), "+proj=axisswap +order=-2,-1"); } // Missing parameter { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +inv +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=krovak +axis=swu +lat_0=FOO +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0"); // Not equal EXPECT_NE(fmt->toString(), "+proj=axisswap +order=-2,-1"); } // Different parameter values { auto fmt = PROJStringFormatter::create(); fmt->ingestPROJString( "+proj=pipeline " "+step +inv +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel " "+step +proj=krovak +axis=swu +lat_0=FOO +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel"); // Not equal EXPECT_NE(fmt->toString(), "+proj=axisswap +order=-2,-1"); } } // --------------------------------------------------------------------------- TEST(io, projparse_longlat) { auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; { auto obj = PROJStringParser().createFromPROJString("+proj=longlat +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), expected); } { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), expected); } } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_datum_NAD83) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=NAD83 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"unknown\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6269]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_datum_NAD27) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=NAD27 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"unknown\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke 1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6267]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_datum_other) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=carthage +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"unknown\",\n" " DATUM[\"Carthage\",\n" " ELLIPSOID[\"Clarke 1880 (IGN)\",6378249.2,293.4660213,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6223]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_ellps_WGS84) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"Unknown based on WGS 84 ellipsoid\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_ellps_GRS80) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"Unknown based on GRS 1980 ellipsoid\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_b) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +b=1.5 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"unknown\",\n" " ELLIPSOID[\"unknown\",2,4,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); EXPECT_EQ(crs->ellipsoid()->celestialBody(), "Non-Earth body"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_rf_WGS84) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=6378137 +rf=298.257223563 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"Unknown based on WGS 84 ellipsoid\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); EXPECT_EQ(crs->ellipsoid()->celestialBody(), Ellipsoid::EARTH); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_rf) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +rf=4 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"unknown\",\n" " ELLIPSOID[\"unknown\",2,4,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_f_zero) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +f=0 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"unknown\",\n" " ELLIPSOID[\"unknown\",2,0,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_f_non_zero) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +f=0.5 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis().getSIValue(), 2); auto rf = crs->ellipsoid()->computedInverseFlattening(); EXPECT_EQ(rf, 2) << rf; } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_e) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +e=0.5 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis().getSIValue(), 2); auto rf = crs->ellipsoid()->computedInverseFlattening(); EXPECT_NEAR(rf, 7.46410161513775, 1e-14) << rf; } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_es) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +es=0.5 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis().getSIValue(), 2); auto rf = crs->ellipsoid()->computedInverseFlattening(); EXPECT_NEAR(rf, 3.4142135623730958, 1e-14) << rf; } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_R) { auto obj = PROJStringParser().createFromPROJString("+proj=longlat +R=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->ellipsoid()->isSphere()); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis().getSIValue(), 2); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a) { auto obj = PROJStringParser().createFromPROJString("+proj=longlat +a=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->ellipsoid()->isSphere()); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis().getSIValue(), 2); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_a_override_ellps) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=2 +ellps=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(!crs->ellipsoid()->isSphere()); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis().getSIValue(), 2); EXPECT_EQ(crs->ellipsoid()->computedInverseFlattening(), 298.25722356300003) << crs->ellipsoid()->computedInverseFlattening(); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_pm_paris) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +pm=paris +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"Unknown based on WGS 84 ellipsoid\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Paris\",2.5969213,\n" " ANGLEUNIT[\"grad\",0.015707963267949]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_pm_ferro) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=bessel +pm=ferro +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"Unknown based on Bessel 1841 ellipsoid\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Ferro\",-17.6666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_pm_numeric) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +pm=2.5 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "GEODCRS[\"unknown\",\n" " DATUM[\"Unknown based on WGS 84 ellipsoid\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"unknown\",2.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_pm_overriding_datum) { // It is arguable that we allow the prime meridian of a datum defined by // its name to be overridden, but this is found at least in a regression // test // of GDAL. So let's keep the ellipsoid part of the datum in that case and // use the specified prime meridian. auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +pm=ferro +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->datum()->nameStr(), "Unknown based on WGS 84 ellipsoid"); EXPECT_EQ(crs->datum()->primeMeridian()->nameStr(), "Ferro"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_complex) { std::string input = "+step +proj=longlat +ellps=clrk80ign " "+pm=paris +step +proj=unitconvert +xy_in=rad +xy_out=grad +step " "+proj=axisswap +order=2,1"; auto obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline " + input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad " + input); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_towgs84_3_terms) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Geocentric translations") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1.2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") != std::string::npos) << wkt; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,0,0,0,0 +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_towgs84_7_terms) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Position Vector transformation") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1.2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Scale difference\",1.000007") != std::string::npos) << wkt; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7 +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_towgs84_7_terms_autocorrect) { // Auto-correct wrong sign for rotation terms // Cf https://github.com/OSGeo/PROJ/issues/4170 auto dbContext = DatabaseContext::create(); auto obj = createFromUserInput( "+proj=lcc +lat_0=90 +lon_0=4.36748666666667 +lat_1=51.1666672333333 " "+lat_2=49.8333339 +x_0=150000.013 +y_0=5400088.438 +ellps=intl " "+towgs84=-106.8686,52.2978,-103.7239,-0.3366,0.457,-1.8422,-1.2747 " "+units=m +no_defs +type=crs", dbContext, true); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=lcc +lat_0=90 +lon_0=4.36748666666667 +lat_1=51.1666672333333 " "+lat_2=49.8333339 +x_0=150000.013 +y_0=5400088.438 +ellps=intl " "+towgs84=-106.8686,52.2978,-103.7239,0.3366,-0.457,1.8422,-1.2747 " "+units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_nadgrids) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"NTv2\"") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETERFILE[\"Latitude and longitude difference " "file\",\"foo.gsb\"]") != std::string::npos) << wkt; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_nadgrids_towgs84_ignored) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +towgs84=1,2,3 +nadgrids=foo.gsb " "+type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(dynamic_cast(crs->baseCRS().get()) != nullptr); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_geoidgrids) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("ABRIDGEDTRANSFORMATION[\"unknown to WGS 84 " "ellipsoidal height\"") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETERFILE[\"Geoid (height correction) model " "file\",\"foo.gtx\"]") != std::string::npos) << wkt; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +geoid_crs=WGS84 " "+vunits=m +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_geoidgrids_vunits) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=ft +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"gravity-related height " "(H)\",up,LENGTHUNIT[\"foot\",0.3048]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_vunits) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +vunits=ft +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"ellipsoidal height " "(h)\",up,ORDER[3],LENGTHUNIT[\"foot\",0.3048]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_vunits) { auto obj = PROJStringParser().createFromPROJString("+vunits=ft +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+vunits=ft +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_vto_meter) { auto obj = PROJStringParser().createFromPROJString("+vto_meter=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+vto_meter=2 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_axis_enu) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +axis=enu +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"longitude\",east,ORDER[1]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"latitude\",north,ORDER[2]") != std::string::npos) << wkt; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_axis_neu) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +axis=neu +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"latitude\",north,ORDER[1]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"longitude\",east,ORDER[2]") != std::string::npos) << wkt; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_axis_swu) { auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +axis=swu +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"latitude\",south,ORDER[1]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"longitude\",west,ORDER[2]") != std::string::npos) << wkt; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=axisswap +order=-2,-1"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_unitconvert_deg) { auto obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=longlat +ellps=GRS80 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_unitconvert_grad) { auto obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=longlat +ellps=GRS80 +step " "+proj=unitconvert +xy_in=rad +xy_out=grad"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step " "+proj=unitconvert +xy_in=rad +xy_out=grad"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_unitconvert_rad) { auto obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=longlat +ellps=GRS80 +step " "+proj=unitconvert +xy_in=rad +xy_out=rad"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_axisswap) { for (auto order1 : {"1", "-1", "2", "-2"}) { for (auto order2 : {"1", "-1", "2", "-2"}) { if (std::abs(atoi(order1) * atoi(order2)) == 2 && !(atoi(order1) == 1 && atoi(order2) == 2)) { auto str = "+type=crs +proj=pipeline +step +proj=longlat +ellps=GRS80 " "+step +proj=axisswap +order=" + std::string(order1) + "," + order2; auto obj = PROJStringParser().createFromPROJString(str); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), (atoi(order1) == 2 && atoi(order2) == 1) ? "+proj=noop" : (atoi(order1) == 2 && atoi(order2) == -1) ? "+proj=axisswap +order=1,-2" : "+proj=pipeline +step +proj=axisswap " "+order=2,1 " "+step +proj=axisswap +order=" + std::string(order1) + "," + order2); } } } } // --------------------------------------------------------------------------- TEST(io, projparse_tmerc) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +x_0=1 +lat_0=1 +k_0=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "PROJCRS[\"unknown\",\n" " BASEGEODCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",2,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_tmerc_south_oriented) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +axis=wsu +x_0=1 +lat_0=1 +k_0=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "PROJCRS[\"unknown\",\n" " BASEGEODCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Transverse Mercator (South Orientated)\",\n" " ID[\"EPSG\",9808]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",2,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"westing\",west,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"southing\",south,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; EXPECT_EQ(f->toString(), expected); obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=tmerc +x_0=1 +lat_0=1 +k_0=2 " "+step " "+proj=axisswap +order=-1,-2"); crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Transverse Mercator (South Orientated)"); } // --------------------------------------------------------------------------- TEST(io, projparse_lcc_as_lcc1sp) { auto obj = PROJStringParser().createFromPROJString( "+proj=lcc +lat_0=45 +lat_1=45 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Lambert Conic Conformal (1SP)") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_lcc_as_lcc1sp_variant_b) { auto obj = PROJStringParser().createFromPROJString( "+proj=lcc +lat_0=45 +lat_1=46 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Lambert Conic Conformal (1SP variant B)") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_lcc_as_lcc2sp) { auto obj = PROJStringParser().createFromPROJString( "+proj=lcc +lat_0=45 +lat_1=46 +lat_2=44 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Lambert Conic Conformal (2SP)") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_lcc_as_lcc2sp_michigan) { auto obj = PROJStringParser().createFromPROJString( "+proj=lcc +lat_0=45 +lat_1=46 +lat_2=44 +k_0=1.02 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Lambert Conic Conformal (2SP Michigan)") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_aeqd_guam) { auto obj = PROJStringParser().createFromPROJString("+proj=aeqd +guam +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Guam Projection") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_cea_spherical) { const std::string input( "+proj=cea +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +R=6371228 +units=m " "+no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); auto crs2 = ProjectedCRS::create( PropertyMap(), crs->baseCRS(), Conversion::createLambertCylindricalEqualArea( PropertyMap(), Angle(0), Angle(0), Length(0), Length(0)), crs->coordinateSystem()); EXPECT_EQ(crs2->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA); EXPECT_TRUE( crs->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(io, projparse_cea_spherical_on_ellipsoid) { std::string input("+proj=cea +R_A +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 " "+ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_cea_ellipsoidal) { auto obj = PROJStringParser().createFromPROJString( "+proj=cea +ellps=GRS80 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find( "METHOD[\"Lambert Cylindrical Equal Area\",ID[\"EPSG\",9835]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_cea_ellipsoidal_with_k_0) { auto obj = PROJStringParser().createFromPROJString( "+proj=cea +ellps=GRS80 +k_0=0.99 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find("PARAMETER[\"Latitude of 1st standard parallel\",8.1365") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_merc_spherical_on_ellipsoid) { std::string input("+proj=merc +R_C +lat_0=1 +lon_0=2 +x_0=3 +y_0=4 " "+ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_MERCATOR_SPHERICAL); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_geos_sweep_x) { auto obj = PROJStringParser().createFromPROJString( "+proj=geos +sweep=x +h=1 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Geostationary Satellite (Sweep X)") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_geos_sweep_y) { auto obj = PROJStringParser().createFromPROJString("+proj=geos +h=1 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("Geostationary Satellite (Sweep Y)") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_omerc_nouoff) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +no_uoff +alpha=2 +gamma=3 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant " "A)\",ID[\"EPSG\",9812]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Azimuth at projection centre\",2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Angle from Rectified to Skew Grid\",3") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_omerc_tpno) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +lat_1=1 +lat_2=2 +lon_1=3 +lon_2=4 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find( "METHOD[\"Hotine Oblique Mercator Two Point Natural Origin\"]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_omerc_variant_b) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +alpha=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant " "B)\",ID[\"EPSG\",9815]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Angle from Rectified to Skew Grid\",2") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_somerc) { auto obj = PROJStringParser().createFromPROJString( "+proj=somerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant " "B)\",ID[\"EPSG\",9815]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Latitude of projection centre\",1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Longitude of projection centre\",2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Scale factor at projection centre\",3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Azimuth at projection centre\",90") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Angle from Rectified to Skew Grid\",90") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Easting at projection centre\",4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Northing at projection centre\",5") != std::string::npos) << wkt; auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(wkt1.find("EXTENSION") == std::string::npos) << wkt1; } // --------------------------------------------------------------------------- TEST(io, projparse_krovak) { auto obj = PROJStringParser().createFromPROJString("+proj=krovak +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find("METHOD[\"Krovak (North Orientated)\",ID[\"EPSG\",1041]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_krovak_axis_swu) { auto obj = PROJStringParser().createFromPROJString( "+proj=krovak +axis=swu +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Krovak\",ID[\"EPSG\",9819]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_krovak_czech) { auto obj = PROJStringParser().createFromPROJString( "+proj=krovak +czech +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=krovak +czech +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 " "+ellps=bessel +units=m +no_defs +type=crs"); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Krovak\",ID[\"EPSG\",9819]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find(",AXIS[\"westing\",west,ORDER[1]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find(",AXIS[\"southing\",south,ORDER[2]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_krovak_modified) { auto obj = PROJStringParser().createFromPROJString("+proj=mod_krovak +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Krovak Modified (North " "Orientated)\",ID[\"EPSG\",1043]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_krovak_modified_axis_swu) { auto obj = PROJStringParser().createFromPROJString( "+proj=mod_krovak +axis=swu +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Krovak Modified\",ID[\"EPSG\",1042]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_krovak_modified_czech) { auto obj = PROJStringParser().createFromPROJString( "+proj=mod_krovak +czech +x_0=5000000 +y_0=5000000 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=mod_krovak +czech +lat_0=49.5 +lon_0=24.8333333333333 " "+alpha=30.2881397527778 +k=0.9999 +x_0=5000000 +y_0=5000000 " "+ellps=bessel +units=m +no_defs +type=crs"); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Krovak Modified\",ID[\"EPSG\",1042]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find(",AXIS[\"westing\",west,ORDER[1]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find(",AXIS[\"southing\",south,ORDER[2]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_etmerc) { auto obj = PROJStringParser().createFromPROJString("+proj=etmerc +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt2 = crs->exportToWKT( &WKTFormatter::create()->simulCurNodeHasId().setMultiLine(false)); EXPECT_TRUE( wkt2.find("METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]]") != std::string::npos) << wkt2; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=tmerc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 " "+datum=WGS84 +units=m +no_defs +type=crs"); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(wkt1.find("EXTENSION[\"PROJ4\"") == std::string::npos) << wkt1; } // --------------------------------------------------------------------------- TEST(io, projparse_tmerc_approx) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +approx +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt2 = crs->exportToWKT( &WKTFormatter::create()->simulCurNodeHasId().setMultiLine(false)); EXPECT_TRUE( wkt2.find("METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]]") != std::string::npos) << wkt2; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=tmerc +approx +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 " "+datum=WGS84 +units=m +no_defs +type=crs"); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(wkt1.find("EXTENSION[\"PROJ4\",\"+proj=tmerc +approx +lat_0=0 " "+lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m " "+no_defs\"]") != std::string::npos) << wkt1; } // --------------------------------------------------------------------------- TEST(io, projparse_merc_variant_B) { auto obj = PROJStringParser().createFromPROJString( "+proj=merc +lat_ts=1 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find("METHOD[\"Mercator (variant B)\",ID[\"EPSG\",9805]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of 1st standard parallel\",1") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_merc_google_mercator) { auto projString = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 " "+k=1 +units=m +nadgrids=@null +no_defs +type=crs"; auto obj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Popular Visualisation Pseudo " "Mercator\",ID[\"EPSG\",1024]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("DATUM[\"World Geodetic System 1984\"") != std::string::npos) << wkt; EXPECT_EQ( replaceAll(crs->exportToPROJString(PROJStringFormatter::create().get()), " +wktext", ""), projString); } // --------------------------------------------------------------------------- TEST(io, projparse_merc_google_mercator_non_metre_unit) { auto projString = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 " "+k=1 +units=ft +nadgrids=@null +no_defs +type=crs"; auto obj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / Pseudo-Mercator (unit ft)"); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Popular Visualisation Pseudo Mercator"); EXPECT_EQ( replaceAll(crs->exportToPROJString(PROJStringFormatter::create().get()), " +wktext", ""), projString); auto wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); auto expected_wkt = "PROJCRS[\"WGS 84 / Pseudo-Mercator (unit ft)\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" " ID[\"EPSG\",1024]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"foot\",0.3048,\n" " ID[\"EPSG\",9002]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"foot\",0.3048,\n" " ID[\"EPSG\",9002]]]]"; EXPECT_EQ(wkt, expected_wkt); auto objFromWkt = WKTParser().createFromWKT(wkt); auto crsFromWkt = nn_dynamic_pointer_cast(objFromWkt); ASSERT_TRUE(crsFromWkt != nullptr); EXPECT_TRUE(crs->isEquivalentTo(crsFromWkt.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(io, projparse_merc_not_quite_google_mercator) { auto projString = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=10 +x_0=0 +y_0=0 " "+k=1 +units=m +nadgrids=@null +no_defs +type=crs"; auto obj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Popular Visualisation Pseudo " "Mercator\",ID[\"EPSG\",1024]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("DATUM[\"unknown using nadgrids=@null\",") != std::string::npos) << wkt; EXPECT_EQ( replaceAll(crs->exportToPROJString(PROJStringFormatter::create().get()), " +wktext", ""), projString); } // --------------------------------------------------------------------------- TEST(io, projparse_merc_stere_polar_variant_B) { auto obj = PROJStringParser().createFromPROJString( "+proj=stere +lat_0=90 +lat_ts=70 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find( "METHOD[\"Polar Stereographic (variant B)\",ID[\"EPSG\",9829]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_merc_stere_polar_variant_A) { auto obj = PROJStringParser().createFromPROJString( "+proj=stere +lat_0=-90 +k=0.994 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find( "METHOD[\"Polar Stereographic (variant A)\",ID[\"EPSG\",9810]]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_merc_stere_polar_k_and_lat_ts) { auto obj = PROJStringParser().createFromPROJString( "+proj=stere +lat_0=90 +lat_ts=90 +k=1 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt = crs->exportToWKT( &(WKTFormatter::create()->simulCurNodeHasId().setMultiLine(false))); EXPECT_TRUE( wkt.find( "METHOD[\"Polar Stereographic (variant B)\",ID[\"EPSG\",9829]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of standard parallel\",90") != std::string::npos) << wkt; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 " "+units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_merc_stere_polar_k_and_lat_ts_incompatible) { EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=stere +lat_0=90 +lat_ts=70 +k=0.994 +type=crs"), ParsingException); } // --------------------------------------------------------------------------- TEST(io, projparse_merc_stere) { auto obj = PROJStringParser().createFromPROJString( "+proj=stere +lat_0=30 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("METHOD[\"Stereographic\"]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_utm) { auto obj = PROJStringParser().createFromPROJString("+proj=utm +zone=1 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("CONVERSION[\"UTM zone 1N\",METHOD[\"Transverse " "Mercator\",ID[\"EPSG\",9807]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Longitude of natural origin\",-177,") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"False northing\",0,") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_utm_south) { auto obj = PROJStringParser().createFromPROJString( "+proj=utm +zone=1 +south +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("CONVERSION[\"UTM zone 1S\",METHOD[\"Transverse " "Mercator\",ID[\"EPSG\",9807]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Longitude of natural origin\",-177,") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"False northing\",10000000,") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_laea_north_pole) { auto obj = PROJStringParser().createFromPROJString( "+proj=laea +lat_0=90 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"(E)\",south") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"(N)\",south") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_laea_south_pole) { auto obj = PROJStringParser().createFromPROJString( "+proj=laea +lat_0=-90 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("AXIS[\"(E)\",north") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"(N)\",north") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_laea_spherical) { auto obj = PROJStringParser().createFromPROJString( "+proj=laea +R=6371228 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL); auto crs2 = ProjectedCRS::create( PropertyMap(), crs->baseCRS(), Conversion::createLambertAzimuthalEqualArea( PropertyMap(), Angle(0), Angle(0), Length(0), Length(0)), crs->coordinateSystem()); EXPECT_EQ(crs2->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA); EXPECT_TRUE( crs->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(io, projparse_laea_ellipsoidal) { auto obj = PROJStringParser().createFromPROJString( "+proj=laea +ellps=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA); } // --------------------------------------------------------------------------- TEST(io, projparse_laea_spherical_on_ellipsoid) { std::string input("+proj=laea +R_A +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_eqc_spherical) { auto obj = PROJStringParser().createFromPROJString( "+proj=eqc +R=6371228 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL); auto crs2 = ProjectedCRS::create( PropertyMap(), crs->baseCRS(), Conversion::createEquidistantCylindrical( PropertyMap(), Angle(0), Angle(0), Length(0), Length(0)), crs->coordinateSystem()); EXPECT_EQ(crs2->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL); EXPECT_TRUE( crs->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(io, projparse_eqc_ellipsoidal) { auto obj = PROJStringParser().createFromPROJString( "+proj=eqc +ellps=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL); } // --------------------------------------------------------------------------- TEST(io, projparse_non_earth_ellipsoid) { std::string input("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +R=1 +units=m " "+no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_ortho_ellipsoidal) { std::string input("+proj=ortho +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_ORTHOGRAPHIC); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_ortho_with_alpha) { std::string input("+proj=ortho +lat_0=0 +lon_0=0 +alpha=12 +k=1 +x_0=0 " "+y_0=0 +ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_ortho_with_scale) { std::string input("+proj=ortho +lat_0=0 +lon_0=0 +alpha=0 +k=0.9 +x_0=0 " "+y_0=0 +ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->getEPSGCode(), EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_ortho_spherical_on_ellipsoid) { std::string input("+proj=ortho +f=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+ellps=WGS84 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_ortho_spherical_on_sphere) { std::string input("+proj=ortho +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 " "+R=6378137 +units=m +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_peirce_q_square) { std::string input("+proj=peirce_q +shape=square +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=peirce_q +shape=square +lat_0=90 +lon_0=0 +k_0=1 +x_0=0 +y_0=0 " "+datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_peirce_q_diamond) { std::string input("+proj=peirce_q +shape=diamond +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=peirce_q +shape=diamond +lat_0=90 +lon_0=0 +k_0=1 +x_0=0 +y_0=0 " "+datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_peirce_q_horizontal) { std::string input("+proj=peirce_q +shape=horizontal +datum=WGS84 +units=m " "+no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_peirce_q_invalid_lat_0) { std::string input("+proj=peirce_q +lat_0=0 +shape=square +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), FormattingException); } // --------------------------------------------------------------------------- TEST(io, projparse_peirce_q_invalid_k_0) { std::string input("+proj=peirce_q +k_0=0.5 +shape=square +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), FormattingException); } // --------------------------------------------------------------------------- TEST(io, projparse_axisswap_unitconvert_longlat_proj) { std::string input = "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat " "+ellps=clrk80ign +pm=paris +step +proj=lcc +lat_1=49.5 " "+lat_0=49.5 +lon_0=0 +k_0=0.999877341 +x_0=600000 +y_0=200000 " "+ellps=clrk80ign +pm=paris"; auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=lcc " "+lat_1=49.5 +lat_0=49.5 +lon_0=0 +k_0=0.999877341 +x_0=600000 " "+y_0=200000 +ellps=clrk80ign +pm=paris"); } // --------------------------------------------------------------------------- TEST(io, projparse_axisswap_unitconvert_proj_axisswap) { std::string input = "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh " "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=axisswap +order=2,1"; auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh " "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(io, projparse_axisswap_unitconvert_proj_unitconvert) { std::string input = "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh " "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m " "+xy_out=ft"; auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh " "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert " "+xy_in=m +xy_out=ft"); } // --------------------------------------------------------------------------- TEST(io, projparse_axisswap_unitconvert_proj_unitconvert_numeric_axisswap) { std::string input = "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh " "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m " "+xy_out=2.5 +step +proj=axisswap +order=-2,-1"; auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh " "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert " "+xy_in=m +xy_out=2.5 +step +proj=axisswap " "+order=-2,-1"); } // --------------------------------------------------------------------------- TEST(io, projparse_projected_units) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +x_0=0.304800609601219 +units=us-ft +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("PARAMETER[\"False easting\",1,LENGTHUNIT[\"US survey " "foot\",0.304800609601219]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"US survey " "foot\",0.304800609601219]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_projected_to_meter_known) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +to_meter=0.304800609601219 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE(wkt.find("PARAMETER[\"False easting\",0,LENGTHUNIT[\"US survey " "foot\",0.304800609601219]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"US survey " "foot\",0.304800609601219]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_projected_to_meter_unknown) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +to_meter=0.1234 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find( "PARAMETER[\"False easting\",0,LENGTHUNIT[\"unknown\",0.1234]") != std::string::npos) << wkt; EXPECT_TRUE( wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"unknown\",0.1234]") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(io, projparse_projected_vunits) { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +vunits=ft +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto cs = crs->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 3U); EXPECT_EQ(cs->axisList()[2]->unit().name(), "foot"); } // --------------------------------------------------------------------------- TEST(io, projparse_projected_unknown) { auto obj = PROJStringParser().createFromPROJString( "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 +y_0=0 " "+datum=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); { WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s\"]," "PARAMETER[\"lat_0\",45,ANGLEUNIT[" "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\"," "0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER[" "\"x_0\",10,LENGTHUNIT[\"metre\",1]],PARAMETER[\"y_0\"," "0,LENGTHUNIT[\"metre\",1]]]") != std::string::npos) << wkt; } std::string expected_wkt1 = "PROJCS[\"unknown\",GEOGCS[\"unknown\",DATUM[\"WGS_1984\",SPHEROID[" "\"WGS " "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[" "\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\"," "\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\"," "\"9122\"]]]," "PROJECTION[\"custom_proj4\"],UNIT[\"metre\",1,AUTHORITY[\"EPSG\"," "\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION[" "\"PROJ4\",\"+proj=mbt_s +lat_0=45 " "+lon_0=0 +k=1 +x_0=10 +y_0=0 +datum=WGS84\"]]"; { WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_EQ(wkt, expected_wkt1); } EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=mbt_s +lat_0=45 +lon_0=0 +k=1 +x_0=10 " "+y_0=0 +datum=WGS84 +type=crs"); { auto obj2 = WKTParser().createFromWKT(expected_wkt1); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs2->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s\"]," "PARAMETER[\"lat_0\",45,ANGLEUNIT[" "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\"," "0,ANGLEUNIT[\"degree\",0.0174532925199433]]," "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER[" "\"x_0\",10,LENGTHUNIT[\"metre\",1]],PARAMETER[\"y_0\"," "0,LENGTHUNIT[\"metre\",1]]]") != std::string::npos) << wkt; } } // --------------------------------------------------------------------------- TEST(io, projparse_geocent) { auto obj = PROJStringParser().createFromPROJString( "+proj=geocent +ellps=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_EQ(wkt, "GEODCRS[\"unknown\",DATUM[\"Unknown based on WGS 84 " "ellipsoid\",ELLIPSOID[\"WGS " "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]]," "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0." "0174532925199433]],CS[Cartesian,3],AXIS[\"(X)\"," "geocentricX,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"(Y)\"," "geocentricY,ORDER[2],LENGTHUNIT[\"metre\",1]],AXIS[\"(Z)\"," "geocentricZ,ORDER[3],LENGTHUNIT[\"metre\",1]]]"); } // --------------------------------------------------------------------------- TEST(io, projparse_geocent_towgs84) { auto obj = PROJStringParser().createFromPROJString( "+proj=geocent +ellps=WGS84 +towgs84=1,2,3 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f(WKTFormatter::create()); f->simulCurNodeHasId(); f->setMultiLine(false); crs->exportToWKT(f.get()); auto wkt = f->toString(); EXPECT_TRUE( wkt.find("METHOD[\"Geocentric translations (geocentric domain)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") != std::string::npos) << wkt; EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=geocent +ellps=WGS84 +towgs84=1,2,3,0,0,0,0 +units=m +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_cart_unit) { std::string input( "+proj=pipeline +step +proj=cart +ellps=WGS84 +step " "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km"); } // --------------------------------------------------------------------------- TEST(io, projparse_cart_unit_numeric) { std::string input( "+proj=pipeline +step +proj=cart +ellps=WGS84 +step " "+proj=unitconvert +xy_in=m +z_in=m +xy_out=500 +z_out=500"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=m +z_in=m +xy_out=500 +z_out=500"); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_wktext) { std::string input("+proj=longlat +foo +wktext +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +datum=WGS84 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_geocent_wktext) { std::string input("+proj=geocent +foo +wktext +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=geocent +datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_geoc) { std::string input("+proj=longlat +geoc +datum=WGS84 +no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->isSphericalPlanetocentric()); #if 1 EXPECT_THROW( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), FormattingException); #else EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), input); #endif } // --------------------------------------------------------------------------- TEST(io, projparse_topocentric) { auto obj = PROJStringParser().createFromPROJString( "+proj=topocentric +datum=WGS84 +X_0=-3982059.42 +Y_0=3331314.88 " "+Z_0=3692463.58 +no_defs +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCRS[\"unknown\",\n" " BASEGEODCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Geocentric/topocentric conversions\",\n" " ID[\"EPSG\",9836]],\n" " PARAMETER[\"Geocentric X of topocentric " "origin\",-3982059.42,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8837]],\n" " PARAMETER[\"Geocentric Y of topocentric origin\",3331314.88,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8838]],\n" " PARAMETER[\"Geocentric Z of topocentric origin\",3692463.58,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8839]]],\n" " CS[Cartesian,3],\n" " AXIS[\"topocentric East (U)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"topocentric North (V)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"topocentric Up (W)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(io, projparse_projected_wktext) { std::string input("+proj=merc +foo +wktext +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_ob_tran_longlat) { for (const char *o_proj : {"longlat", "lonlat", "latlong", "latlon"}) { std::string input( "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran " "+o_proj="); input += o_proj; input += " +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 +ellps=WGS84 " "+step +proj=axisswap +order=2,1"; auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); std::string expected( "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran " "+o_proj="); expected += o_proj; expected += " +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 " "+ellps=WGS84 +step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"; EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), expected); } } // --------------------------------------------------------------------------- TEST(io, projparse_ob_tran_rhealpix) { std::string input( "+proj=ob_tran +o_proj=rhealpix +o_lat_p=90 +o_lon_p=-180 +lon_0=180 " "+north_square=1 +south_square=0 +ellps=WGS84 +type=crs"); auto obj = PROJStringParser().createFromPROJString(input); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), input); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_title) { std::string projString("+title=Ile d'Amsterdam 1963 +proj=longlat " "+towgs84=109.7530,-528.1330,-362.2440 " "+a=6378388.0000 +rf=297.0000000000000 +units=m " "+no_defs +type=crs"); auto obj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto baseCRS = nn_dynamic_pointer_cast(crs->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_EQ(baseCRS->nameStr(), "Ile d'Amsterdam 1963"); EXPECT_EQ(baseCRS->datum()->nameStr(), "Ile d'Amsterdam 1963"); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +ellps=intl +towgs84=109.753,-528.133,-362.244,0,0,0,0 " "+no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_projected_title) { std::string projString( "+title=Amsterdam 1963 +proj=tmerc " "+towgs84=109.7530,-528.1330,-362.2440 +a=6378388.0000 " "+rf=297.0000000000000 +lat_0=0.000000000 +lon_0=75.000000000 " "+k_0=0.99960000 +x_0=500000.000 +y_0=10000000.000 +units=m +no_defs " "+type=crs"); auto obj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto baseCRS = nn_dynamic_pointer_cast(crs->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_EQ(baseCRS->nameStr(), "Amsterdam 1963"); EXPECT_EQ(baseCRS->baseCRS()->nameStr(), "unknown"); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=utm +zone=43 +south +ellps=intl " "+towgs84=109.753,-528.133,-362.244,0,0,0,0 +units=m +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_init) { auto dbContext = DatabaseContext::create(); // Not allowed in non-compatibillity mode EXPECT_THROW( PROJStringParser().createFromPROJString("init=epsg:4326 +type=crs"), ParsingException); { // EPSG:4326 is normally latitude-longitude order with degree, // but in compatibillity mode it will be long-lat auto obj = createFromUserInput("init=epsg:4326", dbContext, true); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::DEGREE) .get())); } { // Test that +no_defs +type=crs have no effect auto obj = createFromUserInput( "+init=epsg:4326 +no_defs +type=crs +wktext", dbContext, true); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt = crs->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt.find("GEODCRS[\"WGS 84\"") == 0) << wkt; } { // EPSG:3040 is normally northing-easting order, but in compatibillity // mode it will be easting-northing auto obj = createFromUserInput("init=epsg:3040 +type=crs", dbContext, true); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( CartesianCS::createEastingNorthing(UnitOfMeasure::METRE).get())); } { auto obj = PROJStringParser().createFromPROJString("init=ITRF2000:ITRF2005"); auto co = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(co != nullptr); EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()), "+proj=helmert +x=-0.0001 +y=0.0008 +z=0.0058 +s=-0.0004 " "+dx=0.0002 +dy=-0.0001 +dz=0.0018 +ds=-0.00008 " "+t_epoch=2000.0 +convention=position_vector"); } { auto obj = createFromUserInput("+title=mytitle +init=epsg:4326 +over", dbContext, true); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "mytitle"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +datum=WGS84 +over +no_defs +type=crs"); } { auto obj = createFromUserInput( "proj=pipeline step init=epsg:4326 step proj=longlat ellps=WGS84", dbContext, true); auto co = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(co != nullptr); EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +init=epsg:4326 +step +proj=longlat " "+ellps=WGS84"); } } // --------------------------------------------------------------------------- TEST(io, projparse_errors) { EXPECT_THROW(PROJStringParser().createFromPROJString(""), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString("foo"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString("inv"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString("step"), ParsingException); EXPECT_THROW( PROJStringParser().createFromPROJString("proj=unknown +type=crs"), ParsingException); EXPECT_THROW( PROJStringParser().createFromPROJString( "proj=pipeline step proj=unitconvert step proj=longlat a=invalid"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "proj=pipeline step proj=pipeline"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "proj=pipeline step init=epsg:4326 init=epsg:4326"), ParsingException); EXPECT_THROW( PROJStringParser().createFromPROJString("proj=\tinit= +type=crs"), ParsingException); } // --------------------------------------------------------------------------- TEST(io, projparse_longlat_errors) { EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +datum=unknown +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +ellps=unknown +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +a=invalid +b=1 +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +a=1 +b=invalid +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +a=invalid +rf=1 +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +a=1 +rf=invalid +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +a=1 +f=invalid +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +R=invalid +type=crs"), ParsingException); EXPECT_THROW( PROJStringParser().createFromPROJString("+proj=longlat +b=1 +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +rf=1 +type=crs"), ParsingException); EXPECT_THROW( PROJStringParser().createFromPROJString("+proj=longlat +f=0 +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +pm=unknown +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 " "+towgs84=1.2,2,3,4,5,6,invalid +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=longlat +axis=foo +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step " "+proj=unitconvert +xy_in=rad +xy_out=foo"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step " "+proj=axisswap"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step " "+proj=axisswap +order=0,0"), ParsingException); // We just want to check that we don't loop forever PROJStringParser().createFromPROJString( "+=x;proj=pipeline step proj=push +type=crs"); } // --------------------------------------------------------------------------- TEST(io, projparse_projected_errors) { EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=tmerc +units=foo +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=tmerc +x_0=foo +type=crs"), ParsingException); EXPECT_THROW(PROJStringParser().createFromPROJString( "+proj=tmerc +lat_0=foo +type=crs"), ParsingException); // Inconsistent pm values between geogCRS and projectedCRS EXPECT_THROW( PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=longlat +ellps=WGS84 " "+step +proj=tmerc +ellps=WGS84 +lat_0=0 +pm=paris"), ParsingException); } // --------------------------------------------------------------------------- TEST(io, createFromUserInput) { auto dbContext = DatabaseContext::create(); EXPECT_THROW(createFromUserInput("foo", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("GEOGCRS", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("+proj=unhandled +type=crs", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("EPSG:4326", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("ESRI:103668+EPSG:5703", nullptr), ParsingException); EXPECT_THROW( createFromUserInput("urn:ogc:def:unhandled:EPSG::4326", dbContext), ParsingException); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs:non_existing_auth::4326", dbContext), NoSuchAuthorityCodeException); EXPECT_THROW(createFromUserInput( "urn:ogc:def:crs,crs:EPSG::2393,unhandled_type:EPSG::5717", dbContext), ParsingException); EXPECT_THROW(createFromUserInput( "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::unexisting_code", dbContext), NoSuchAuthorityCodeException); EXPECT_THROW( createFromUserInput( "urn:ogc:def:crs,crs:EPSG::2393::extra_element,crs:EPSG::EPSG", dbContext), ParsingException); EXPECT_THROW(createFromUserInput("urn:ogc:def:coordinateOperation," "coordinateOperation:EPSG::3895," "unhandled_type:EPSG::1618", dbContext), ParsingException); EXPECT_THROW( createFromUserInput("urn:ogc:def:coordinateOperation," "coordinateOperation:EPSG::3895," "coordinateOperation:EPSG::unexisting_code", dbContext), NoSuchAuthorityCodeException); EXPECT_THROW( createFromUserInput("urn:ogc:def:coordinateOperation," "coordinateOperation:EPSG::3895::extra_element," "coordinateOperation:EPSG::1618", dbContext), ParsingException); EXPECT_NO_THROW(createFromUserInput("+proj=longlat", nullptr)); EXPECT_NO_THROW(createFromUserInput("EPSG:4326", dbContext)); EXPECT_NO_THROW(createFromUserInput("epsg:4326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:crs:EPSG::4326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:crs:EPSG:10:4326", dbContext)); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs:EPSG::4326", nullptr), ParsingException); EXPECT_NO_THROW(createFromUserInput( "urn:ogc:def:coordinateOperation:EPSG::1671", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:datum:EPSG::6326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:ensemble:EPSG::6326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:meridian:EPSG::8901", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:ellipsoid:EPSG::7030", dbContext)); EXPECT_NO_THROW(createFromUserInput("IAU:1000", dbContext)); EXPECT_NO_THROW(createFromUserInput("IAU_2015:1000", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:crs:IAU::1000", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:crs:IAU_2015::1000", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:crs:IAU:2015:1000", dbContext)); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs:IAU_2015::xxxx", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs:IAU:xxxx:1000", nullptr), ParsingException); // Found as srsName in some GMLs... EXPECT_NO_THROW( createFromUserInput("URN:OGC:DEF:CRS:OGC:1.3:CRS84", dbContext)); // Legacy formulations EXPECT_NO_THROW( createFromUserInput("urn:x-ogc:def:crs:EPSG::4326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:opengis:def:crs:EPSG::4326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:opengis:crs:EPSG::4326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:x-ogc:def:crs:EPSG:4326", dbContext)); EXPECT_THROW(createFromUserInput("urn:opengis:crs:EPSG::4326", nullptr), ParsingException); EXPECT_THROW( createFromUserInput("urn:opengis:unhandled:EPSG::4326", dbContext), ParsingException); { auto obj = createFromUserInput("EPSG:2393+5717", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "KKJ / Finland Uniform Coordinate System + N60 height"); } { auto obj = createFromUserInput("EPSG:2393+EPSG:5717", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "KKJ / Finland Uniform Coordinate System + N60 height"); } { auto obj = createFromUserInput("ESRI:103668+EPSG:5703", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAD_1983_HARN_Adj_MN_Ramsey_Meters + NAVD88 height"); } EXPECT_THROW(createFromUserInput("ESRI:42+EPSG:5703", dbContext), NoSuchAuthorityCodeException); EXPECT_THROW(createFromUserInput("ESRI:103668+EPSG:999999", dbContext), NoSuchAuthorityCodeException); { auto obj = createFromUserInput( "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "KKJ / Finland Uniform Coordinate System + N60 height"); } { auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::4979," "cs:PROJ::ENh," "coordinateOperation:EPSG::16031", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 (3D) / UTM zone 31N"); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 4979); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 16031); } // We accept non-conformant EPSG:4326+4326 { auto obj = createFromUserInput("EPSG:4326+4326", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84"); EXPECT_EQ(crs->getEPSGCode(), 4979); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); const auto wkt = "COMPD_CS[\"WGS 84 + WGS 84\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]]]"; EXPECT_EQ( crs->exportToWKT(WKTFormatter::create( WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), wkt); } // Non consistent EXPECT_THROW(createFromUserInput("EPSG:4326+4258", dbContext), InvalidCompoundCRSException); // We accept non-conformant EPSG:32631+4326 { auto obj = createFromUserInput("EPSG:32631+4326", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N"); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 4979); EXPECT_EQ(crs->baseCRS()->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); const auto wkt = "COMPD_CS[\"WGS 84 / UTM zone 31N + WGS 84\",\n" " PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]],\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]]]"; EXPECT_EQ( crs->exportToWKT(WKTFormatter::create( WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()), wkt); } EXPECT_THROW(createFromUserInput( "urn:ogc:def:crs,crs:EPSG::4979," "cs:PROJ::ENh," "coordinateOperation:EPSG::1024", // not a conversion dbContext), ParsingException); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs,crs:," "cs:PROJ::ENh," "coordinateOperation:EPSG::16031", dbContext), ParsingException); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs,crs:EPSG::4979," "cs:," "coordinateOperation:EPSG::16031", dbContext), ParsingException); EXPECT_THROW(createFromUserInput("urn:ogc:def:crs,crs:EPSG::4979," "cs:PROJ::ENh," "coordinateOperation:", dbContext), ParsingException); { // Completely nonsensical from a geodesic point of view... auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::4978," "cs:EPSG::6500," "coordinateOperation:EPSG::16031", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 4978); EXPECT_EQ(crs->coordinateSystem()->getEPSGCode(), 6500); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 16031); } { // Completely nonsensical from a geodesic point of view... auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::4979," "cs:EPSG::6423," "coordinateOperation:EPSG::16031", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 4979); EXPECT_EQ(crs->coordinateSystem()->getEPSGCode(), 6423); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 16031); } { // Completely nonsensical from a geodesic point of view... auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::32631," "cs:EPSG::4400," "coordinateOperation:EPSG::16031", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 32631); EXPECT_EQ(crs->coordinateSystem()->getEPSGCode(), 4400); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 16031); } { // DerivedVerticalCRS based on "NAVD88 height", using a foot UP axis, // and EPSG:7813 "Vertical Axis Unit Conversion" conversion auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::5703," "cs:EPSG::1030," "coordinateOperation:EPSG::7813", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 height (ft)"); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 5703); EXPECT_EQ(crs->coordinateSystem()->getEPSGCode(), 1030); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 7813); } { // DerivedVerticalCRS based on "NAVD88 height", using a ftUS UP axis, // and EPSG:7813 "Vertical Axis Unit Conversion" conversion auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::5703," "cs:EPSG::6497," "coordinateOperation:EPSG::7813", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 height (ftUS)"); } { // DerivedVerticalCRS based on "NAVD88 height (ftUS)", using a metre UP // axis, and EPSG:7813 "Vertical Axis Unit Conversion" conversion auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::6360," "cs:EPSG::6499," "coordinateOperation:EPSG::7813", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 height"); } { // DerivedVerticalCRS based on "NAVD88 height", using a metre DOWN axis, // and EPSG:7812 "Height / Depth reversal" conversion auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::5703," "cs:EPSG::6498," "coordinateOperation:EPSG::7812", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 depth"); } { // DerivedVerticalCRS based on "NAVD88 height (ftUS)", using a ftUS DOWN // axis, and EPSG:7812 "Height / Depth reversal" conversion auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::6360," "cs:EPSG::1043," "coordinateOperation:EPSG::7812", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 depth (ftUS)"); } { // DerivedVerticalCRS based on "NAVD88 depth (ftUS)", using a ftUS UP // axis, and EPSG:7812 "Height / Depth reversal" conversion auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::6358," "cs:EPSG::6497," "coordinateOperation:EPSG::7812", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NAVD88 height (ftUS)"); } { auto obj = createFromUserInput("urn:ogc:def:coordinateOperation," "coordinateOperation:EPSG::3895," "coordinateOperation:EPSG::1618", dbContext); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ(concat->nameStr(), "MGI (Ferro) to MGI (1) + MGI to WGS 84 (3)"); } EXPECT_NO_THROW(createFromUserInput( " \n\t\rGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " UNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]", nullptr)); // Search names in the database EXPECT_THROW(createFromUserInput("foobar", dbContext), ParsingException); { // Official name auto obj = createFromUserInput("WGS 84", dbContext); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_TRUE(crs != nullptr); } { // PROJ alias auto obj = createFromUserInput("WGS84", dbContext); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_TRUE(crs != nullptr); } EXPECT_NO_THROW(createFromUserInput("UTM zone 31N", dbContext)); EXPECT_THROW(createFromUserInput("UTM zone 31", dbContext), ParsingException); EXPECT_NO_THROW(createFromUserInput("WGS84 UTM zone 31N", dbContext)); EXPECT_NO_THROW(createFromUserInput("ID74", dbContext)); { // Approximate match of a vertical CRS auto obj = createFromUserInput("NGF IGN69 height", dbContext); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NGF-IGN69 height"); // EPSG:5720 } { // Approximate match of a vertical CRS auto obj = createFromUserInput("NGF IGN1969", dbContext); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "NGF-IGN 1969"); // IGNF69:IGN69 } { // Approximate match of a vertical CRS auto obj = createFromUserInput("NGF IGN69", dbContext); auto crs = nn_dynamic_pointer_cast(obj); EXPECT_TRUE(crs != nullptr); // Questionnable if we shouldn't match EPSG:5720 instead EXPECT_EQ(crs->nameStr(), "NGF-IGN 1969"); // IGNF69:IGN69 } { // Exact match on each piece of the compound CRS auto obj = createFromUserInput("WGS 84 + EGM96 height", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM96 height"); } { // Approximate match auto obj = createFromUserInput("WGS 84 + EGM96", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM96 height"); } { // Approximate match on each piece of the compound CRS auto obj = createFromUserInput("WGS84 + NAVD88 height", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 + NAVD88 height"); } { // Exact match of a CompoundCRS object auto obj = createFromUserInput( "WGS 84 / World Mercator + EGM2008 height", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->identifiers().size(), 1U); } EXPECT_THROW(createFromUserInput("WGS 84 + foobar", dbContext), ParsingException); EXPECT_THROW(createFromUserInput("foobar + EGM96 height", dbContext), ParsingException); { auto obj = createFromUserInput("World Geodetic System 1984 ensemble", dbContext); auto ensemble = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ensemble != nullptr); EXPECT_EQ(ensemble->identifiers().size(), 1U); } // Check that "foo" doesn't match with "Amersfoort" EXPECT_THROW(createFromUserInput("foo", dbContext), ParsingException); // Check that "omerc" doesn't match with "WGS 84 / Pseudo-Mercator" EXPECT_THROW(createFromUserInput("omerc", dbContext), ParsingException); // Missing space, dash: OK EXPECT_NO_THROW(createFromUserInput("WGS84 PseudoMercator", dbContext)); // Invalid CoordinateMetadata EXPECT_THROW(createFromUserInput("@", dbContext), ParsingException); // Invalid CoordinateMetadata EXPECT_THROW(createFromUserInput("ITRF2014@", dbContext), ParsingException); // Invalid CoordinateMetadata EXPECT_THROW(createFromUserInput("ITRF2014@foo", dbContext), ParsingException); // Invalid CoordinateMetadata EXPECT_THROW(createFromUserInput("foo@2025", dbContext), ParsingException); // Invalid CoordinateMetadata EXPECT_THROW(createFromUserInput("@2025", dbContext), ParsingException); // Invalid CoordinateMetadata: static CRS not allowed EXPECT_THROW(createFromUserInput("RGF93@2025", dbContext), ParsingException); { auto obj = createFromUserInput("ITRF2014@2025.1", dbContext); auto coordinateMetadata = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(coordinateMetadata != nullptr); EXPECT_EQ(coordinateMetadata->coordinateEpochAsDecimalYear(), 2025.1); } { // Allow spaces before and after @ auto obj = createFromUserInput("ITRF2014 @ 2025.1", dbContext); auto coordinateMetadata = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(coordinateMetadata != nullptr); EXPECT_EQ(coordinateMetadata->coordinateEpochAsDecimalYear(), 2025.1); } { auto obj = createFromUserInput("EPSG:9000 @ 2025.1", dbContext); auto coordinateMetadata = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(coordinateMetadata != nullptr); EXPECT_EQ(coordinateMetadata->coordinateEpochAsDecimalYear(), 2025.1); } { // Approximate match involving using "north" instead of N and lacking // "zone" auto obj = createFromUserInput("WGS 84 UTM 31 north", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N"); } // Should not match WGS84 or IGM85 EXPECT_THROW(createFromUserInput("WGS 85", dbContext), ParsingException); } // --------------------------------------------------------------------------- TEST(io, createFromUserInput_ogc_crs_url) { auto dbContext = DatabaseContext::create(); { auto obj = createFromUserInput( "http://www.opengis.net/def/crs/EPSG/0/4326", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } { auto obj = createFromUserInput( "http://www.opengis.net/def/crs/IAU/2015/49900", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } { // Not sure if this is intended to be valid (version=0), but let's // imitate the logic of EPSG, this will use the latest version of IAU // (if/when there will be several of them) auto obj = createFromUserInput( "http://www.opengis.net/def/crs/IAU/0/49900", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); } EXPECT_THROW( createFromUserInput("http://www.opengis.net/def/crs", dbContext), ParsingException); EXPECT_THROW( createFromUserInput("http://www.opengis.net/def/crs/EPSG/0", dbContext), ParsingException); EXPECT_THROW(createFromUserInput( "http://www.opengis.net/def/crs/EPSG/0/XXXX", dbContext), NoSuchAuthorityCodeException); EXPECT_THROW( createFromUserInput("http://www.opengis.net/def/crs/IAU/2015/invalid", dbContext), NoSuchAuthorityCodeException); { auto obj = createFromUserInput( "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/" "def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM2008 height"); } // No part EXPECT_THROW(createFromUserInput("http://www.opengis.net/def/crs-compound?", dbContext), ParsingException); // Just one part EXPECT_THROW( createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://" "www.opengis.net/def/crs/EPSG/0/4326", dbContext), InvalidCompoundCRSException); // Invalid compound CRS EXPECT_THROW( createFromUserInput( "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/" "def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/4326", dbContext), InvalidCompoundCRSException); // Missing 2= EXPECT_THROW( createFromUserInput( "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/" "def/crs/EPSG/0/4326&3=http://www.opengis.net/def/crs/EPSG/0/3855", dbContext), ParsingException); // Invalid query parameter EXPECT_THROW( createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://" "www.opengis.net/def/crs/EPSG/0/4326&bla", dbContext), ParsingException); // Invalid query parameter EXPECT_THROW( createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://" "www.opengis.net/def/crs/EPSG/0/4326&two=http://" "www.opengis.net/def/crs/EPSG/0/3855", dbContext), ParsingException); } // --------------------------------------------------------------------------- TEST(io, createFromUserInput_OGC_AUTO) { // UTM north { auto obj = createFromUserInput("AUTO:42001,-117,33", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=11 +datum=WGS84 +units=m +no_defs +type=crs"); } // UTM south { auto obj = createFromUserInput("AUTO:42001,-117,-33", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11S"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=11 +south +datum=WGS84 +units=m +no_defs " "+type=crs"); } // Explicit unit: metre { auto obj = createFromUserInput("AUTO:42001,9001,-117,33", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=11 +datum=WGS84 +units=m +no_defs +type=crs"); } // Explicit unit: foot { auto obj = createFromUserInput("AUTO:42001,9002,-117,33", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=11 +datum=WGS84 +units=ft +no_defs +type=crs"); } // Explicit unit: US survery foot { auto obj = createFromUserInput("AUTO:42001,9003,-117,33", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=11 +datum=WGS84 +units=us-ft +no_defs +type=crs"); } // Explicit unit: invalid EXPECT_THROW(createFromUserInput("AUTO:42001,0,-117,33", nullptr), ParsingException); // Invalid longitude EXPECT_THROW(createFromUserInput("AUTO:42001,-180.01,33", nullptr), ParsingException); EXPECT_NO_THROW(createFromUserInput("AUTO:42001,-180,33", nullptr)); EXPECT_THROW(createFromUserInput("AUTO:42001,180,33", nullptr), ParsingException); EXPECT_NO_THROW(createFromUserInput("AUTO:42001,179.999,33", nullptr)); // Too short EXPECT_THROW(createFromUserInput("AUTO:42001", nullptr), ParsingException); // TMerc / north { auto obj = createFromUserInput("AUTO:42002,1,2", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "Transverse Mercator"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tmerc +lat_0=0 +lon_0=1 +k=0.9996 +x_0=500000 +y_0=0 " "+datum=WGS84 +units=m +no_defs +type=crs"); } // TMerc / south { auto obj = createFromUserInput("AUTO:42002,1,-2", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->derivingConversion()->nameStr(), "Transverse Mercator"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tmerc +lat_0=0 +lon_0=1 +k=0.9996 +x_0=500000 " "+y_0=10000000 +datum=WGS84 +units=m +no_defs +type=crs"); } // Orthographic { auto obj = createFromUserInput("AUTO:42003,1,2", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=ortho +lat_0=2 +lon_0=1 +x_0=0 +y_0=0 +datum=WGS84 " "+units=m +no_defs +type=crs"); } // Equirectangular { auto obj = createFromUserInput("AUTO:42004,1,0", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=1 +x_0=0 +y_0=0 " "+datum=WGS84 +units=m +no_defs +type=crs"); } // Mollweide { auto obj = createFromUserInput("AUTO:42005,1", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=moll +lon_0=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m " "+no_defs +type=crs"); } // Mollweide with explicit unit { auto obj = createFromUserInput("AUTO:42005,9001,1", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=moll +lon_0=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m " "+no_defs +type=crs"); } // Invalid method id EXPECT_THROW(createFromUserInput("AUTO:42999,1,0", nullptr), ParsingException); // As urn:ogc:def:crs:OGC::AUTOxxxx:.... { auto obj = createFromUserInput("urn:ogc:def:crs:OGC::AUTO42001:-117:33", nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=11 +datum=WGS84 +units=m +no_defs +type=crs"); } } // --------------------------------------------------------------------------- TEST(io, createFromUserInput_hack_EPSG_102100) { auto dbContext = DatabaseContext::create(); auto obj = createFromUserInput("EPSG:102100", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); const auto &ids = crs->identifiers(); ASSERT_EQ(ids.size(), 1U); // we do not lie on the real authority EXPECT_EQ(*ids[0]->codeSpace(), "ESRI"); EXPECT_EQ(ids[0]->code(), "102100"); } // --------------------------------------------------------------------------- TEST(io, guessDialect) { EXPECT_EQ(WKTParser().guessDialect("LOCAL_CS[\"foo\"]"), WKTParser::WKTGuessedDialect::WKT1_GDAL); EXPECT_EQ(WKTParser().guessDialect( "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_" "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"), WKTParser::WKTGuessedDialect::WKT1_ESRI); EXPECT_EQ(WKTParser().guessDialect( "GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north],\n" " AXIS[\"geodetic longitude (Lon)\",east],\n" " UNIT[\"degree\",0.0174532925199433]]"), WKTParser::WKTGuessedDialect::WKT2_2019); EXPECT_EQ( WKTParser().guessDialect("TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"), WKTParser::WKTGuessedDialect::WKT2_2019); EXPECT_EQ(WKTParser().guessDialect( "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north],\n" " AXIS[\"geodetic longitude (Lon)\",east],\n" " UNIT[\"degree\",0.0174532925199433]]"), WKTParser::WKTGuessedDialect::WKT2_2015); EXPECT_EQ(WKTParser().guessDialect("foo"), WKTParser::WKTGuessedDialect::NOT_WKT); EXPECT_EQ(WKTParser().guessDialect("ID74"), WKTParser::WKTGuessedDialect::NOT_WKT); } // --------------------------------------------------------------------------- // GDAL MITAB driver requires on rather excessive precision on parameter // values to implement a nasty trick... TEST(wkt_export, precision) { auto wkt = "PROJCS[\"RGF93 / Lambert-93\",\n" " GEOGCS[\"RGF93\",\n" " DATUM[\"Reseau_Geodesique_Francais_1993\",\n" " SPHEROID[\"GRS 80\",6378137,298.257222101],\n" " AUTHORITY[\"EPSG\",\"6171\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Lambert_Conformal_Conic_2SP\"],\n" " PARAMETER[\"standard_parallel_1\",49.00000000001],\n" " PARAMETER[\"standard_parallel_2\",44],\n" " PARAMETER[\"latitude_of_origin\",46.5],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"false_easting\",700000],\n" " PARAMETER[\"false_northing\",6600000],\n" " UNIT[\"Meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); } // --------------------------------------------------------------------------- // Avoid division by zero TEST(wkt_export, invalid_linear_unit) { auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"foo\",0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- // Avoid division by zero TEST(wkt_export, invalid_angular_unit) { auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"foo\",0]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"meter\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(json_import, ellipsoid_flattened_sphere) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7030\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, ellipsoid_major_minor_custom_unit) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"foo\",\n" " \"semi_major_axis\": 6378137,\n" " \"semi_minor_axis\": {\n" " \"value\": 6370000,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"my_unit\",\n" " \"conversion_factor\": 2\n" " }\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, ellipsoid_sphere) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"Sphere\",\n" " \"radius\": 6371000,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7035\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, ellipsoid_errors) { EXPECT_THROW(createFromUserInput("{", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("{}", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("{ \"type\": \"Ellipsoid\" }", nullptr), ParsingException); EXPECT_THROW(createFromUserInput( "{ \"type\": \"Ellipsoid\", \"name\": \"foo\" }", nullptr), ParsingException); EXPECT_THROW( createFromUserInput( "{ \"type\": \"Ellipsoid\", \"name\": \"foo\", \"radius\": null }", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("{ \"type\": \"Ellipsoid\", \"name\": " "\"foo\", \"semi_major_axis\": 1 }", nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_meridian) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"south\",\n" " \"meridian\": {\n" " \"longitude\": 180\n" " },\n" " \"unit\": \"metre\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto axis = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(axis != nullptr); EXPECT_EQ(axis->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_meridian_with_unit) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"south\",\n" " \"meridian\": {\n" " \"longitude\": {\n" " \"value\": 200,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"grad\",\n" " \"conversion_factor\": 0.0157079632679489\n" " }\n" " }\n" " },\n" " \"unit\": \"metre\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto axis = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(axis != nullptr); EXPECT_EQ(axis->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_minimum_value_maximum_value_range_meaning) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\",\n" " \"minimum_value\": 0,\n" " \"maximum_value\": 360,\n" " \"range_meaning\": \"wraparound\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto axis = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(axis != nullptr); EXPECT_EQ(axis->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_invalid_minimum_value) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\",\n" " \"minimum_value\": \"invalid\"\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_invalid_maximum_value) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\",\n" " \"maximum_value\": \"invalid\"\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_invalid_range_meaning_str) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\",\n" " \"range_meaning\": \"invalid\"\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, axis_with_invalid_range_meaning_number) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Axis\",\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\",\n" " \"range_meaning\": 1\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, prime_meridian) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"PrimeMeridian\",\n" " \"name\": \"Paris\",\n" " \"longitude\": {\n" " \"value\": 2.5969213,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"grad\",\n" " \"conversion_factor\": 0.0157079632679489\n" " }\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto pm = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(pm != nullptr); EXPECT_EQ(pm->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, prime_meridian_errors) { EXPECT_THROW(createFromUserInput("{ \"type\": \"PrimeMeridian\", \"name\": " "\"foo\" }", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("{ \"type\": \"PrimeMeridian\", \"name\": " "\"foo\", \"longitude\": null }", nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, geodetic_reference_frame_with_implicit_prime_meridian) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto grf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(grf != nullptr); EXPECT_EQ(grf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, invalid_bbox) { { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"bbox\": {\n" " \"south_latitude\": -90,\n" " \"west_longitude\": -180,\n" " \"north_latitude\": 90,\n" " \"east_longitude\": 180\n" " }\n" "}"; EXPECT_NO_THROW(createFromUserInput(json, nullptr)); } { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"bbox\": {\n" " \"south_latitude\": 90,\n" " \"west_longitude\": -180,\n" " \"north_latitude\": -90,\n" " \"east_longitude\": 180\n" " }\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } } // --------------------------------------------------------------------------- TEST(json_import, geodetic_reference_frame_with_explicit_prime_meridian) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Nouvelle Triangulation Francaise (Paris)\",\n" " \"ellipsoid\": {\n" " \"name\": \"Clarke 1880 (IGN)\",\n" " \"semi_major_axis\": 6378249.2,\n" " \"semi_minor_axis\": 6356515\n" " },\n" " \"prime_meridian\": {\n" " \"name\": \"Paris\",\n" " \"longitude\": {\n" " \"value\": 2.5969213,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"grad\",\n" " \"conversion_factor\": 0.0157079632679489\n" " }\n" " }\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto grf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(grf != nullptr); EXPECT_EQ(grf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geodetic_reference_frame_with_anchor_epoch) { // Use dummy anchor_epoch = 0 to avoid fp issues on some architectures // (cf https://github.com/OSGeo/PROJ/issues/3632) auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"my_name\",\n" " \"anchor_epoch\": 0,\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto grf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(grf != nullptr); EXPECT_EQ(grf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geodetic_reference_frame_with_invalid_anchor_epoch) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"my_name\",\n" " \"anchor_epoch\": \"invalid\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, dynamic_geodetic_reference_frame_with_implicit_prime_meridian) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DynamicGeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"frame_reference_epoch\": 1,\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto dgrf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(dgrf != nullptr); EXPECT_EQ(dgrf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_extent) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"vertical_extent\": {\n" " \"minimum\": -1000,\n" " \"maximum\": 0\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gdrf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gdrf != nullptr); EXPECT_EQ(gdrf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_extent_with_unit) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"vertical_extent\": {\n" " \"minimum\": -1000,\n" " \"maximum\": 0,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"my_metre\",\n" " \"conversion_factor\": 1\n" " }\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gdrf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gdrf != nullptr); EXPECT_EQ(gdrf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, temporal_extent) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"temporal_extent\": {\n" " \"start\": \"my start\",\n" " \"end\": \"my end\"\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gdrf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gdrf != nullptr); EXPECT_EQ(gdrf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geodetic_reference_frame_errors) { EXPECT_THROW( createFromUserInput( "{ \"type\": \"GeodeticReferenceFrame\", \"name\": \"foo\" }", nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, dynamic_vertical_reference_frame) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DynamicVerticalReferenceFrame\",\n" " \"name\": \"bar\",\n" " \"frame_reference_epoch\": 1\n" "}"; auto obj = createFromUserInput(json, nullptr); auto dvrf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(dvrf != nullptr); EXPECT_EQ(dvrf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, several_usages) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"usages\": [\n" " {\n" " \"area\": \"World\",\n" " \"bbox\": {\n" " \"south_latitude\": -90,\n" " \"west_longitude\": -180,\n" " \"north_latitude\": 90,\n" " \"east_longitude\": 180\n" " }\n" " },\n" " {\n" " \"scope\": \"my_scope\",\n" " \"area\": \"my_area\"\n" " }\n" " ]\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gdr = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gdr != nullptr); EXPECT_EQ(gdr->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geographic_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"area\": \"World\",\n" " \"bbox\": {\n" " \"south_latitude\": -90,\n" " \"west_longitude\": -180,\n" " \"north_latitude\": 90,\n" " \"east_longitude\": 180\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4326\n" " },\n" " \"remarks\": \"my_remarks\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gcrs != nullptr); EXPECT_EQ(gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geographic_crs_with_deformation_models) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"test\",\n" " \"datum\": {\n" " \"type\": \"DynamicGeodeticReferenceFrame\",\n" " \"name\": \"test\",\n" " \"frame_reference_epoch\": 2005,\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"deformation_models\": [\n" " {\n" " \"name\": \"my_model\"\n" " }\n" " ]\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gcrs != nullptr); EXPECT_EQ(gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, spherical_planetocentric) { const auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticCRS\",\n" " \"name\": \"Mercury (2015) / Ocentric\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Mercury (2015)\",\n" " \"anchor\": \"Hun Kal: 20.0\",\n" " \"ellipsoid\": {\n" " \"name\": \"Mercury (2015)\",\n" " \"semi_major_axis\": 2440530,\n" " \"inverse_flattening\": 1075.12334801762\n" " },\n" " \"prime_meridian\": {\n" " \"name\": \"Reference Meridian\",\n" " \"longitude\": 0\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"spherical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Planetocentric latitude\",\n" " \"abbreviation\": \"U\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Planetocentric longitude\",\n" " \"abbreviation\": \"V\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"IAU\",\n" " \"code\": 19902\n" " },\n" " \"remarks\": \"Source of IAU Coordinate systems: " "doi://10.1007/s10569-017-9805-5\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gcrs != nullptr); EXPECT_TRUE(gcrs->isSphericalPlanetocentric()); EXPECT_EQ(gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geographic_crs_errors) { EXPECT_THROW( createFromUserInput( "{ \"type\": \"GeographicCRS\", \"name\": \"foo\" }", nullptr), ParsingException); EXPECT_THROW( createFromUserInput("{\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " }\n" "}", nullptr), ParsingException); EXPECT_THROW( createFromUserInput("{\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}", nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, geocentric_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeodeticCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geocentric X\",\n" " \"abbreviation\": \"X\",\n" " \"direction\": \"geocentricX\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Geocentric Y\",\n" " \"abbreviation\": \"Y\",\n" " \"direction\": \"geocentricY\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Geocentric Z\",\n" " \"abbreviation\": \"Z\",\n" " \"direction\": \"geocentricZ\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto gdcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gdcrs != nullptr); EXPECT_EQ(gdcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, projected_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"WGS 84 / UTM zone 31N\",\n" " \"base_crs\": {\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4326\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"UTM zone 31N\",\n" " \"method\": {\n" " \"name\": \"Transverse Mercator\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9807\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of natural origin\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8801\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of natural origin\",\n" " \"value\": 3,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8802\n" " }\n" " },\n" " {\n" " \"name\": \"Scale factor at natural origin\",\n" " \"value\": 0.9996,\n" " \"unit\": \"unity\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8805\n" " }\n" " },\n" " {\n" " \"name\": \"False easting\",\n" " \"value\": 500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8806\n" " }\n" " },\n" " {\n" " \"name\": \"False northing\",\n" " \"value\": 0,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8807\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto pcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(pcrs != nullptr); std::string got_json = pcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))); const char *typeGeogCRS = " \"type\": \"GeographicCRS\",\n"; const auto posTypeGeogCRS = got_json.find(typeGeogCRS); EXPECT_TRUE(posTypeGeogCRS != std::string::npos) << got_json; got_json = got_json.substr(0, posTypeGeogCRS) + got_json.substr(posTypeGeogCRS + strlen(typeGeogCRS)); EXPECT_STREQ(got_json.c_str(), json); } // --------------------------------------------------------------------------- TEST(json_import, conversion_utm_zone_south_wrong_id) { auto json = "{\n" " \"type\": \"Conversion\",\n" " \"name\": \"UTM zone 55S\",\n" " \"method\": {\n" " \"name\": \"Transverse Mercator\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9807\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of natural origin\",\n" " \"value\": 0,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"Degree\",\n" " \"conversion_factor\": 0.0174532925199433\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8801\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of natural origin\",\n" " \"value\": 147,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"Degree\",\n" " \"conversion_factor\": 0.0174532925199433\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8802\n" " }\n" " },\n" " {\n" " \"name\": \"Scale factor at natural origin\",\n" " \"value\": 0.9996,\n" " \"unit\": \"unity\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8805\n" " }\n" " },\n" " {\n" " \"name\": \"False easting\",\n" " \"value\": 500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8806\n" " }\n" " },\n" " {\n" " \"name\": \"False northing\",\n" " \"value\": 10000000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8807\n" " }\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 17055\n" // wrong code " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto conv = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(conv != nullptr); EXPECT_EQ(conv->getEPSGCode(), 16155); // code fixed on import } // --------------------------------------------------------------------------- TEST(json_import, projected_crs_with_geocentric_base) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"EPSG topocentric example B\",\n" " \"base_crs\": {\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"World Geodetic System 1984 ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"World Geodetic System 1984 (Transit)\"\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G730)\"\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G873)\"\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1150)\"\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1674)\"\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1762)\"\n" " }\n" " ],\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2.0\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geocentric X\",\n" " \"abbreviation\": \"X\",\n" " \"direction\": \"geocentricX\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Geocentric Y\",\n" " \"abbreviation\": \"Y\",\n" " \"direction\": \"geocentricY\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Geocentric Z\",\n" " \"abbreviation\": \"Z\",\n" " \"direction\": \"geocentricZ\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4978\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"EPSG topocentric example B\",\n" " \"method\": {\n" " \"name\": \"Geocentric/topocentric conversions\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9836\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Geocentric X of topocentric origin\",\n" " \"value\": 3771793.97,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8837\n" " }\n" " },\n" " {\n" " \"name\": \"Geocentric Y of topocentric origin\",\n" " \"value\": 140253.34,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8838\n" " }\n" " },\n" " {\n" " \"name\": \"Geocentric Z of topocentric origin\",\n" " \"value\": 5124304.35,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8839\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Topocentric East\",\n" " \"abbreviation\": \"U\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Topocentric North\",\n" " \"abbreviation\": \"V\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Topocentric height\",\n" " \"abbreviation\": \"W\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"scope\": \"Example only (fictitious).\",\n" " \"area\": \"Description of the extent of the CRS.\",\n" " \"bbox\": {\n" " \"south_latitude\": -90,\n" " \"west_longitude\": -180,\n" " \"north_latitude\": 90,\n" " \"east_longitude\": 180\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 5820\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto pcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(pcrs != nullptr); EXPECT_TRUE(pcrs->baseCRS()->isGeocentric()); std::string got_json = pcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))); const char *typeGeodCRS = " \"type\": \"GeodeticCRS\",\n"; const auto posTypeGeodCRS = got_json.find(typeGeodCRS); EXPECT_TRUE(posTypeGeodCRS != std::string::npos) << got_json; got_json = got_json.substr(0, posTypeGeodCRS) + got_json.substr(posTypeGeodCRS + strlen(typeGeodCRS)); EXPECT_STREQ(got_json.c_str(), json); } // --------------------------------------------------------------------------- TEST(json_import, compound_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"CompoundCRS\",\n" " \"name\": \"WGS 84 + EGM2008 height\",\n" " \"components\": [\n" " {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4326\n" " }\n" " },\n" " {\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"EGM2008 height\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"EGM2008 geoid\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 3855\n" " }\n" " }\n" " ]\n" "}"; auto obj = createFromUserInput(json, nullptr); auto compoundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(compoundCRS != nullptr); EXPECT_EQ( compoundCRS->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, bound_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"BoundCRS\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"unknown\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Unknown based on GRS80 ellipsoid\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7019\n" " }\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4326\n" " }\n" " },\n" " \"transformation\": {\n" " \"name\": \"unknown to WGS84\",\n" " \"method\": {\n" " \"name\": \"NTv2\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9615\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude and longitude difference file\",\n" " \"value\": \"@foo\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8656\n" " }\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ( boundCRS->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, bound_crs_with_name_and_usage) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"BoundCRS\",\n" " \"name\": \"my bound crs\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"unknown\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Unknown based on GRS80 ellipsoid\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7019\n" " }\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4326\n" " }\n" " },\n" " \"transformation\": {\n" " \"name\": \"unknown to WGS84\",\n" " \"method\": {\n" " \"name\": \"NTv2\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9615\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude and longitude difference file\",\n" " \"value\": \"@foo\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8656\n" " }\n" " }\n" " ]\n" " },\n" " \"scope\": \"Example only (fictitious).\",\n" " \"area\": \"Description of the extent of the CRS.\",\n" " \"bbox\": {\n" " \"south_latitude\": -90,\n" " \"west_longitude\": -180,\n" " \"north_latitude\": 90,\n" " \"east_longitude\": 180\n" " },\n" " \"id\": {\n" " \"authority\": \"foo\",\n" " \"code\": 1234\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ( boundCRS->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, bound_crs_with_source_crs_in_transformation) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"BoundCRS\",\n" " \"source_crs\": {\n" " \"type\": \"DerivedGeographicCRS\",\n" " \"name\": \"CH1903+ with height offset\",\n" " \"base_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"CH1903+\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"CH1903+\",\n" " \"ellipsoid\": {\n" " \"name\": \"Bessel 1841\",\n" " \"semi_major_axis\": 6377397.155,\n" " \"inverse_flattening\": 299.1528128\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"Ellipsoidal to gravity related height\",\n" " \"method\": {\n" " \"name\": \"Geographic3D offsets\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9660\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude offset\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8601\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude offset\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8602\n" " }\n" " },\n" " {\n" " \"name\": \"Vertical Offset\",\n" " \"value\": 10,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8603\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"World Geodetic System 1984 ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"World Geodetic System 1984 (Transit)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1166\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G730)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1152\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G873)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1153\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1150)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1154\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1674)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1155\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G1762)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1156\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G2139)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1309\n" " }\n" " }\n" " ],\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2.0\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4979\n" " }\n" " },\n" " \"transformation\": {\n" " \"name\": \"CH1903+ to WGS 84 (1)\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"CH1903+\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"CH1903+\",\n" " \"ellipsoid\": {\n" " \"name\": \"Bessel 1841\",\n" " \"semi_major_axis\": 6377397.155,\n" " \"inverse_flattening\": 299.1528128\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"method\": {\n" " \"name\": \"Geocentric translations (geog2D domain)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9603\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"X-axis translation\",\n" " \"value\": 674.374,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8605\n" " }\n" " },\n" " {\n" " \"name\": \"Y-axis translation\",\n" " \"value\": 15.056,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8606\n" " }\n" " },\n" " {\n" " \"name\": \"Z-axis translation\",\n" " \"value\": 405.346,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8607\n" " }\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1676\n" " }\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ( boundCRS->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, transformation) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Transformation\",\n" " \"name\": \"GDA94 to GDA2020 (1)\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"GDA94\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Geocentric Datum of Australia 1994\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"GDA2020\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Geocentric Datum of Australia 2020\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"method\": {\n" " \"name\": \"Coordinate Frame rotation (geog2D domain)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9607\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"X-axis translation\",\n" " \"value\": 61.55,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"millimetre\",\n" " \"conversion_factor\": 0.001\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8605\n" " }\n" " },\n" " {\n" " \"name\": \"Y-axis translation\",\n" " \"value\": -10.87,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"millimetre\",\n" " \"conversion_factor\": 0.001\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8606\n" " }\n" " },\n" " {\n" " \"name\": \"Z-axis translation\",\n" " \"value\": -40.19,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"millimetre\",\n" " \"conversion_factor\": 0.001\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8607\n" " }\n" " },\n" " {\n" " \"name\": \"X-axis rotation\",\n" " \"value\": -39.4924,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"milliarc-second\",\n" " \"conversion_factor\": 4.84813681109536e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8608\n" " }\n" " },\n" " {\n" " \"name\": \"Y-axis rotation\",\n" " \"value\": -32.7221,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"milliarc-second\",\n" " \"conversion_factor\": 4.84813681109536e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8609\n" " }\n" " },\n" " {\n" " \"name\": \"Z-axis rotation\",\n" " \"value\": -32.8979,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"milliarc-second\",\n" " \"conversion_factor\": 4.84813681109536e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8610\n" " }\n" " },\n" " {\n" " \"name\": \"Scale difference\",\n" " \"value\": -9.994,\n" " \"unit\": {\n" " \"type\": \"ScaleUnit\",\n" " \"name\": \"parts per billion\",\n" " \"conversion_factor\": 1e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8611\n" " }\n" " }\n" " ],\n" " \"accuracy\": \"0.01\",\n" " \"scope\": \"scope\",\n" " \"area\": \"Australia - GDA\",\n" " \"bbox\": {\n" " \"south_latitude\": -60.56,\n" " \"west_longitude\": 93.41,\n" " \"north_latitude\": -8.47,\n" " \"east_longitude\": 173.35\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8048\n" " },\n" " \"remarks\": \"foo\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ( transf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, concatenated_operation) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"ConcatenatedOperation\",\n" " \"name\": \"Inverse of Vicgrid + GDA94 to GDA2020 (1)\",\n" " \"source_crs\": {\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"GDA94 / Vicgrid\",\n" " \"base_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"GDA94\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Geocentric Datum of Australia 1994\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4283\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"Vicgrid\",\n" " \"method\": {\n" " \"name\": \"Lambert Conic Conformal (2SP)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9802\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of false origin\",\n" " \"value\": -37,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8821\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of false origin\",\n" " \"value\": 145,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8822\n" " }\n" " },\n" " {\n" " \"name\": \"Latitude of 1st standard parallel\",\n" " \"value\": -36,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8823\n" " }\n" " },\n" " {\n" " \"name\": \"Latitude of 2nd standard parallel\",\n" " \"value\": -38,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8824\n" " }\n" " },\n" " {\n" " \"name\": \"Easting at false origin\",\n" " \"value\": 2500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8826\n" " }\n" " },\n" " {\n" " \"name\": \"Northing at false origin\",\n" " \"value\": 2500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8827\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 3111\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"GDA2020\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Geocentric Datum of Australia 2020\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7844\n" " }\n" " },\n" " \"steps\": [\n" " {\n" " \"type\": \"Conversion\",\n" " \"name\": \"Inverse of Vicgrid\",\n" " \"method\": {\n" " \"name\": \"Inverse of Lambert Conic Conformal (2SP)\",\n" " \"id\": {\n" " \"authority\": \"INVERSE(EPSG)\",\n" " \"code\": 9802\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of false origin\",\n" " \"value\": -37,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8821\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of false origin\",\n" " \"value\": 145,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8822\n" " }\n" " },\n" " {\n" " \"name\": \"Latitude of 1st standard parallel\",\n" " \"value\": -36,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8823\n" " }\n" " },\n" " {\n" " \"name\": \"Latitude of 2nd standard parallel\",\n" " \"value\": -38,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8824\n" " }\n" " },\n" " {\n" " \"name\": \"Easting at false origin\",\n" " \"value\": 2500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8826\n" " }\n" " },\n" " {\n" " \"name\": \"Northing at false origin\",\n" " \"value\": 2500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8827\n" " }\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"INVERSE(EPSG)\",\n" " \"code\": 17361\n" " }\n" " },\n" " {\n" " \"type\": \"Transformation\",\n" " \"name\": \"GDA94 to GDA2020 (1)\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"GDA94\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Geocentric Datum of Australia 1994\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4283\n" " }\n" " },\n" " \"target_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"GDA2020\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Geocentric Datum of Australia 2020\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7844\n" " }\n" " },\n" " \"method\": {\n" " \"name\": \"Coordinate Frame rotation (geog2D domain)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9607\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"X-axis translation\",\n" " \"value\": 61.55,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"millimetre\",\n" " \"conversion_factor\": 0.001\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8605\n" " }\n" " },\n" " {\n" " \"name\": \"Y-axis translation\",\n" " \"value\": -10.87,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"millimetre\",\n" " \"conversion_factor\": 0.001\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8606\n" " }\n" " },\n" " {\n" " \"name\": \"Z-axis translation\",\n" " \"value\": -40.19,\n" " \"unit\": {\n" " \"type\": \"LinearUnit\",\n" " \"name\": \"millimetre\",\n" " \"conversion_factor\": 0.001\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8607\n" " }\n" " },\n" " {\n" " \"name\": \"X-axis rotation\",\n" " \"value\": -39.4924,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"milliarc-second\",\n" " \"conversion_factor\": 4.84813681109536e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8608\n" " }\n" " },\n" " {\n" " \"name\": \"Y-axis rotation\",\n" " \"value\": -32.7221,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"milliarc-second\",\n" " \"conversion_factor\": 4.84813681109536e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8609\n" " }\n" " },\n" " {\n" " \"name\": \"Z-axis rotation\",\n" " \"value\": -32.8979,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"milliarc-second\",\n" " \"conversion_factor\": 4.84813681109536e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8610\n" " }\n" " },\n" " {\n" " \"name\": \"Scale difference\",\n" " \"value\": -9.994,\n" " \"unit\": {\n" " \"type\": \"ScaleUnit\",\n" " \"name\": \"parts per billion\",\n" " \"conversion_factor\": 1e-09\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8611\n" " }\n" " }\n" " ],\n" " \"accuracy\": \"0.01\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8048\n" " },\n" " \"remarks\": \"remarks\"\n" " }\n" " ],\n" " \"accuracy\": \"0.02\",\n" " \"area\": \"Australia - GDA\",\n" " \"bbox\": {\n" " \"south_latitude\": -60.56,\n" " \"west_longitude\": 93.41,\n" " \"north_latitude\": -8.47,\n" " \"east_longitude\": 173.35\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto concat = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(concat != nullptr); EXPECT_EQ( concat->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, geographic_crs_with_datum_ensemble) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"WGS 84 ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"World Geodetic System 1984 (Transit)\"\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G730)\"\n" " },\n" " {\n" " \"name\": \"Some unknown ensemble with unknown id\",\n" " \"id\": {\n" " \"authority\": \"UNKNOWN\",\n" " \"code\": 1234\n" " }\n" " }\n" " ],\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" "}"; auto expected_json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"WGS 84 ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"World Geodetic System 1984 (Transit)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1166\n" " }\n" " },\n" " {\n" " \"name\": \"World Geodetic System 1984 (G730)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1152\n" " }\n" " },\n" " {\n" " \"name\": \"Some unknown ensemble with unknown id\",\n" " \"id\": {\n" " \"authority\": \"UNKNOWN\",\n" " \"code\": 1234\n" " }\n" " }\n" " ],\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7030\n" " }\n" " },\n" " \"accuracy\": \"2\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" "}"; { // No database auto obj = createFromUserInput(json, nullptr); auto gcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gcrs != nullptr); EXPECT_EQ( gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } { auto obj = createFromUserInput(json, DatabaseContext::create()); auto gcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gcrs != nullptr); EXPECT_EQ( gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), expected_json); } { auto obj = createFromUserInput(expected_json, DatabaseContext::create()); auto gcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(gcrs != nullptr); EXPECT_EQ( gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), expected_json); } } // --------------------------------------------------------------------------- TEST(json_import, datum_ensemble_without_ellipsoid) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DatumEnsemble\",\n" " \"name\": \"ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"member1\"\n" " },\n" " {\n" " \"name\": \"member2\"\n" " }\n" " ],\n" " \"accuracy\": \"2\"\n" "}"; // No database auto obj = createFromUserInput(json, nullptr); auto ensemble = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ensemble != nullptr); EXPECT_EQ( ensemble->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, ensemble_without_members) { auto json = "{\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"World Geodetic System 1984 ensemble\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2.0\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6326\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, DatabaseContext::create()); auto crs = dynamic_cast(obj.get()); ASSERT_TRUE(crs != nullptr); EXPECT_GE(crs->datumEnsemble()->datums().size(), 2U); } // --------------------------------------------------------------------------- TEST(json_import, ensemble_without_members_no_db) { auto json = "{\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"World Geodetic System 1984 ensemble\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2.0\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6326\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, ensemble_without_members_unknown_name) { auto json = "{\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum_ensemble\": {\n" " \"name\": \"i do not exist\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " },\n" " \"accuracy\": \"2.0\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6326\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" "}"; EXPECT_THROW(createFromUserInput(json, DatabaseContext::create()), ParsingException); } // --------------------------------------------------------------------------- TEST(json_import, vertical_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"EGM2008 height\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"EGM2008 geoid\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); auto datum = crs->datum(); auto datum_json = datum->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))); auto datum_obj = createFromUserInput(datum_json, nullptr); auto datum_got = nn_dynamic_pointer_cast(datum_obj); ASSERT_TRUE(datum_got != nullptr); } // --------------------------------------------------------------------------- TEST(json_import, vertical_crs_with_datum_ensemble) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"foo\",\n" " \"datum_ensemble\": {\n" " \"name\": \"ensemble\",\n" " \"members\": [\n" " {\n" " \"name\": \"member1\"\n" " },\n" " {\n" " \"name\": \"member2\"\n" " }\n" " ],\n" " \"accuracy\": \"2\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; // No database auto obj = createFromUserInput(json, nullptr); auto vcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(vcrs != nullptr); EXPECT_EQ(vcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_crs_with_geoid_model) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"CGVD2013\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"Canadian Geodetic Vertical Datum of 2013\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"geoid_model\": {\n" " \"name\": \"CGG2013\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6648\n" " }\n" " }\n" "}"; // No database auto obj = createFromUserInput(json, nullptr); auto vcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(vcrs != nullptr); EXPECT_EQ(vcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_crs_with_geoid_models) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"CGVD2013\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"Canadian Geodetic Vertical Datum of 2013\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"geoid_models\": [\n" " {\n" " \"name\": \"CGG2013\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6648\n" " }\n" " },\n" " {\n" " \"name\": \"other\"\n" " }\n" " ]\n" "}"; // No database auto obj = createFromUserInput(json, nullptr); auto vcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(vcrs != nullptr); EXPECT_EQ(vcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_crs_with_deformation_models) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"test\",\n" " \"datum\": {\n" " \"type\": \"DynamicVerticalReferenceFrame\",\n" " \"name\": \"test\",\n" " \"frame_reference_epoch\": 2005\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"deformation_models\": [\n" " {\n" " \"name\": \"my_model\"\n" " }\n" " ]\n" "}"; // No database auto obj = createFromUserInput(json, nullptr); auto vcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(vcrs != nullptr); EXPECT_EQ(vcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_crs_with_geoid_model_and_interpolation_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"foo\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"bar\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"geoid_model\": {\n" " \"name\": \"baz\",\n" " \"interpolation_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"NAD83(2011)\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"NAD83 (National Spatial Reference System " "2011)\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6319\n" " }\n" " }\n" " }\n" "}"; // No database auto obj = createFromUserInput(json, nullptr); auto vcrs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(vcrs != nullptr); EXPECT_EQ(vcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, vertical_reference_frame_with_anchor_epoch) { // Use dummy anchor_epoch = 0 to avoid fp issues on some architectures // (cf https://github.com/OSGeo/PROJ/issues/3632) auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"my_name\",\n" " \"anchor\": \"my_anchor_definition\",\n" " \"anchor_epoch\": 0\n" "}"; auto obj = createFromUserInput(json, nullptr); auto vrf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(vrf != nullptr); EXPECT_EQ(vrf->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, parametric_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"ParametricCRS\",\n" " \"name\": \"WMO standard atmosphere layer 0\",\n" " \"datum\": {\n" " \"name\": \"Mean Sea Level\",\n" " \"anchor\": \"1013.25 hPa at 15°C\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"parametric\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Pressure\",\n" " \"abbreviation\": \"hPa\",\n" " \"direction\": \"up\",\n" " \"unit\": {\n" " \"type\": \"ParametricUnit\",\n" " \"name\": \"HectoPascal\",\n" " \"conversion_factor\": 100\n" " }\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); auto datum = crs->datum(); auto datum_json = datum->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))); auto datum_obj = createFromUserInput(datum_json, nullptr); auto datum_got = nn_dynamic_pointer_cast(datum_obj); ASSERT_TRUE(datum_got != nullptr); } // --------------------------------------------------------------------------- TEST(json_import, engineering_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"EngineeringCRS\",\n" " \"name\": \"Engineering CRS\",\n" " \"datum\": {\n" " \"name\": \"Engineering datum\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); auto datum = crs->datum(); auto datum_json = datum->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))); auto datum_obj = createFromUserInput(datum_json, nullptr); auto datum_got = nn_dynamic_pointer_cast(datum_obj); ASSERT_TRUE(datum_got != nullptr); } // --------------------------------------------------------------------------- TEST(json_import, engineering_crs_affine_CS) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"EngineeringCRS\",\n" " \"name\": \"myEngCRS\",\n" " \"datum\": {\n" " \"name\": \"myEngDatum\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"affine\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, temporal_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"TemporalCRS\",\n" " \"name\": \"Temporal CRS\",\n" " \"datum\": {\n" " \"name\": \"Gregorian calendar\",\n" " \"calendar\": \"proleptic Gregorian\",\n" " \"time_origin\": \"0000-01-01\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"TemporalDateTime\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Time\",\n" " \"abbreviation\": \"T\",\n" " \"direction\": \"future\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); auto datum = crs->datum(); auto datum_json = datum->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))); auto datum_obj = createFromUserInput(datum_json, nullptr); auto datum_got = nn_dynamic_pointer_cast(datum_obj); ASSERT_TRUE(datum_got != nullptr); } // --------------------------------------------------------------------------- TEST(json_import, derived_geodetic_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedGeodeticCRS\",\n" " \"name\": \"Derived geodetic CRS\",\n" " \"base_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"Some conversion\",\n" " \"method\": {\n" " \"name\": \"Some method\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"foo\",\n" " \"value\": 1,\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geocentric X\",\n" " \"abbreviation\": \"X\",\n" " \"direction\": \"geocentricX\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Geocentric Y\",\n" " \"abbreviation\": \"Y\",\n" " \"direction\": \"geocentricY\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Geocentric Z\",\n" " \"abbreviation\": \"Z\",\n" " \"direction\": \"geocentricZ\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_geographic_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedGeographicCRS\",\n" " \"name\": \"WMO Atlantic Pole\",\n" " \"base_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"Atlantic pole\",\n" " \"method\": {\n" " \"name\": \"Pole rotation\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of rotated pole\",\n" " \"value\": 52,\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude of rotated pole\",\n" " \"value\": -30,\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Axis rotation\",\n" " \"value\": -25,\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_projected_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedProjectedCRS\",\n" " \"name\": \"derived projectedCRS\",\n" " \"base_crs\": {\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"WGS 84 / UTM zone 31N\",\n" " \"base_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" " \"ellipsoid\": {\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Latitude\",\n" " \"abbreviation\": \"lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Longitude\",\n" " \"abbreviation\": \"lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"UTM zone 31N\",\n" " \"method\": {\n" " \"name\": \"Transverse Mercator\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9807\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Latitude of natural origin\",\n" " \"value\": 0,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8801\n" " }\n" " },\n" " {\n" " \"name\": \"Longitude of natural origin\",\n" " \"value\": 3,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8802\n" " }\n" " },\n" " {\n" " \"name\": \"Scale factor at natural origin\",\n" " \"value\": 0.9996,\n" " \"unit\": \"unity\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8805\n" " }\n" " },\n" " {\n" " \"name\": \"False easting\",\n" " \"value\": 500000,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8806\n" " }\n" " },\n" " {\n" " \"name\": \"False northing\",\n" " \"value\": 0,\n" " \"unit\": \"metre\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8807\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"unnamed\",\n" " \"method\": {\n" " \"name\": \"PROJ unimplemented\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"foo\",\n" " \"value\": 1,\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_vertical_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedVerticalCRS\",\n" " \"name\": \"Derived vertCRS\",\n" " \"base_crs\": {\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"ODN height\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"Ordnance Datum Newlyn\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"unnamed\",\n" " \"method\": {\n" " \"name\": \"PROJ unimplemented\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"foo\",\n" " \"value\": 1,\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_vertical_crs_EPSG_code_for_horizontal_CRS) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedVerticalCRS\",\n" " \"name\": \"Derived vertCRS\",\n" " \"base_crs\": {\n" " \"type\": \"VerticalCRS\",\n" " \"name\": \"ODN height\",\n" " \"datum\": {\n" " \"type\": \"VerticalReferenceFrame\",\n" " \"name\": \"Ordnance Datum Newlyn\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 5101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"Conv Vertical Offset and Slope\",\n" " \"method\": {\n" " \"name\": \"Vertical Offset and Slope\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1046\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Ordinate 1 of evaluation point\",\n" " \"value\": 40.5,\n" " \"unit\": \"degree\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8617\n" " }\n" " },\n" " {\n" " \"name\": \"EPSG code for Horizontal CRS\",\n" " \"value\": 4277,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1037\n" " }\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"vertical\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Gravity-related height\",\n" " \"abbreviation\": \"H\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, DatabaseContext::create()); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // "EPSG code for Horizontal CRS" is removed and set as interpolation CRS EXPECT_EQ(crs->derivingConversion()->parameterValues().size(), 1U); EXPECT_TRUE(crs->derivingConversion()->interpolationCRS() != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_engineering_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedEngineeringCRS\",\n" " \"name\": \"Derived EngineeringCRS\",\n" " \"base_crs\": {\n" " \"type\": \"EngineeringCRS\",\n" " \"name\": \"Engineering CRS\",\n" " \"datum\": {\n" " \"name\": \"Engineering datum\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"unnamed\",\n" " \"method\": {\n" " \"name\": \"PROJ unimplemented\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"foo\",\n" " \"value\": 1,\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"Cartesian\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Easting\",\n" " \"abbreviation\": \"E\",\n" " \"direction\": \"east\",\n" " \"unit\": \"metre\"\n" " },\n" " {\n" " \"name\": \"Northing\",\n" " \"abbreviation\": \"N\",\n" " \"direction\": \"north\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_parametric_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedParametricCRS\",\n" " \"name\": \"Derived ParametricCRS\",\n" " \"base_crs\": {\n" " \"type\": \"ParametricCRS\",\n" " \"name\": \"Parametric CRS\",\n" " \"datum\": {\n" " \"name\": \"Parametric datum\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"parametric\",\n" " \"axis\": [\n" " {\n" " \"name\": \"unknown parametric\",\n" " \"abbreviation\": \"\",\n" " \"direction\": \"unspecified\",\n" " \"unit\": {\n" " \"type\": \"ParametricUnit\",\n" " \"name\": \"unknown\",\n" " \"conversion_factor\": 1\n" " }\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"unnamed\",\n" " \"method\": {\n" " \"name\": \"PROJ unimplemented\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"foo\",\n" " \"value\": 1,\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"parametric\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Pressure\",\n" " \"abbreviation\": \"hPa\",\n" " \"direction\": \"up\",\n" " \"unit\": {\n" " \"type\": \"ParametricUnit\",\n" " \"name\": \"HectoPascal\",\n" " \"conversion_factor\": 100\n" " }\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, derived_temporal_crs) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"DerivedTemporalCRS\",\n" " \"name\": \"Derived TemporalCRS\",\n" " \"base_crs\": {\n" " \"type\": \"TemporalCRS\",\n" " \"name\": \"Temporal CRS\",\n" " \"datum\": {\n" " \"name\": \"Gregorian calendar\",\n" " \"calendar\": \"proleptic Gregorian\",\n" " \"time_origin\": \"0000-01-01\"\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"TemporalDateTime\",\n" " \"axis\": [\n" " {\n" " \"name\": \"unknown temporal\",\n" " \"abbreviation\": \"\",\n" " \"direction\": \"future\",\n" " \"unit\": {\n" " \"type\": \"TimeUnit\",\n" " \"name\": \"unknown\",\n" " \"conversion_factor\": 1\n" " }\n" " }\n" " ]\n" " }\n" " },\n" " \"conversion\": {\n" " \"name\": \"unnamed\",\n" " \"method\": {\n" " \"name\": \"PROJ unimplemented\"\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"foo\",\n" " \"value\": 1,\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"TemporalDateTime\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Time\",\n" " \"abbreviation\": \"T\",\n" " \"direction\": \"future\"\n" " }\n" " ]\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, id) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6326,\n" " \"version\": 1,\n" " \"authority_citation\": \"my citation\",\n" " \"uri\": \"my uri\"\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, id_code_string_version_string) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": \"abc\",\n" " \"version\": \"def\",\n" " \"authority_citation\": \"my citation\",\n" " \"uri\": \"my uri\"\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, id_code_string_version_double) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": \"abc\",\n" " \"version\": 9.8,\n" " \"authority_citation\": \"my citation\",\n" " \"uri\": \"my uri\"\n" " }\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, multiple_ids) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"ids\": [\n" " {\n" " \"authority\": \"EPSG\",\n" " \"code\": 4326\n" " },\n" " {\n" " \"authority\": \"FOO\",\n" " \"code\": \"BAR\"\n" " }\n" " ]\n" "}"; auto obj = createFromUserInput(json, nullptr); auto ellps = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(ellps != nullptr); EXPECT_EQ(ellps->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_export, coordinate_system_id) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"CoordinateSystem\",\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ],\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 6422\n" " }\n" "}"; auto dbContext = DatabaseContext::create(); auto obj = createFromUserInput("EPSG:4326", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto cs = crs->coordinateSystem(); ASSERT_TRUE(cs != nullptr); EXPECT_EQ(cs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } // --------------------------------------------------------------------------- TEST(json_import, invalid_CoordinateMetadata) { { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"CoordinateMetadata\"\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"CoordinateMetadata\",\n" " \"crs\": \"not quite a CRS...\"\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } { auto json = "{\n" " \"$schema\": " "\"https://proj.org/schemas/v0.6/projjson.schema.json\",\n" " \"type\": \"CoordinateMetadata\",\n" " \"crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"ITRF2014\",\n" " \"datum\": {\n" " \"type\": \"DynamicGeodeticReferenceFrame\",\n" " \"name\": \"International Terrestrial Reference " "Frame 2014\",\n" " \"frame_reference_epoch\": 2010,\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 9000\n" " }\n" " },\n" " \"coordinateEpoch\": \"this should be a number\"\n" "}"; EXPECT_THROW(createFromUserInput(json, nullptr), ParsingException); } } // --------------------------------------------------------------------------- TEST(io, EXTENSION_PROJ4) { // Check that the PROJ string is preserved in the remarks auto obj = PROJStringParser().createFromPROJString( "+proj=utm +datum=NAD27 +zone=11 +over +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->remarks(), "PROJ CRS string: +proj=utm +datum=NAD27 +zone=11 +over"); // Chat that the PROJ string is detected when ingesting a WKT2 with // a REMARKS node that contains it auto wkt2 = crs->exportToWKT(WKTFormatter::create().get()); auto obj2 = WKTParser().createFromWKT(wkt2); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); EXPECT_EQ(crs2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +datum=NAD27 +zone=11 +over +type=crs"); // Chat that the PROJ string is detected when ingesting a WKT2 with // a REMARKS node that contains it (in the middle of the remarks) auto wkt3 = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke 1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6267]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 11N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-117,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16011]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " REMARK[\"Prefix. PROJ CRS string: +proj=utm +datum=NAD27 +zone=11 " "+over. Suffix\"]]"; auto obj3 = WKTParser().createFromWKT(wkt3); auto crs3 = nn_dynamic_pointer_cast(obj3); ASSERT_TRUE(crs3 != nullptr); EXPECT_EQ(crs3->remarks(), "Prefix. PROJ CRS string: +proj=utm " "+datum=NAD27 +zone=11 +over. Suffix"); EXPECT_EQ(crs3->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +datum=NAD27 +zone=11 +over +type=crs"); } // --------------------------------------------------------------------------- TEST(wkt_parse, PointMotionOperation) { auto wkt = "POINTMOTIONOPERATION[\"Canada velocity grid v7\",\n" " SOURCECRS[\n" " GEOGCRS[\"NAD83(CSRS)v7\",\n" " DATUM[\"North American Datum of 1983 (CSRS) version 7\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",8254]]],\n" " METHOD[\"Point motion by grid (Canada NTv2_Vel)\",\n" " ID[\"EPSG\",1070]],\n" " PARAMETERFILE[\"Point motion velocity grid file\",\"foo.tif\"],\n" " OPERATIONACCURACY[0.01],\n" " USAGE[\n" " SCOPE[\"scope\"],\n" " AREA[\"area\"],\n" " BBOX[38.21,-141.01,86.46,-40.73]],\n" " ID[\"DERIVED_FROM(EPSG)\",9483],\n" " REMARK[\"remark.\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto pmo = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(pmo != nullptr); EXPECT_EQ( pmo->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt); } // --------------------------------------------------------------------------- TEST(json_import, PointMotionOperation) { auto json = "{\n" " \"$schema\": \"foo\",\n" " \"type\": \"PointMotionOperation\",\n" " \"name\": \"Canada velocity grid v7\",\n" " \"source_crs\": {\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"NAD83(CSRS)v7\",\n" " \"datum\": {\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"North American Datum of 1983 (CSRS) version 7\",\n" " \"ellipsoid\": {\n" " \"name\": \"GRS 1980\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257222101\n" " }\n" " },\n" " \"coordinate_system\": {\n" " \"subtype\": \"ellipsoidal\",\n" " \"axis\": [\n" " {\n" " \"name\": \"Geodetic latitude\",\n" " \"abbreviation\": \"Lat\",\n" " \"direction\": \"north\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Geodetic longitude\",\n" " \"abbreviation\": \"Lon\",\n" " \"direction\": \"east\",\n" " \"unit\": \"degree\"\n" " },\n" " {\n" " \"name\": \"Ellipsoidal height\",\n" " \"abbreviation\": \"h\",\n" " \"direction\": \"up\",\n" " \"unit\": \"metre\"\n" " }\n" " ]\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8254\n" " }\n" " },\n" " \"method\": {\n" " \"name\": \"Point motion by grid (Canada NTv2_Vel)\",\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 1070\n" " }\n" " },\n" " \"parameters\": [\n" " {\n" " \"name\": \"Point motion velocity grid file\",\n" " \"value\": \"foo.tif\"\n" " }\n" " ],\n" " \"accuracy\": \"0.01\",\n" " \"scope\": \"scope\",\n" " \"area\": \"area\",\n" " \"bbox\": {\n" " \"south_latitude\": 38.21,\n" " \"west_longitude\": -141.01,\n" " \"north_latitude\": 86.46,\n" " \"east_longitude\": -40.73\n" " },\n" " \"id\": {\n" " \"authority\": \"DERIVED_FROM(EPSG)\",\n" " \"code\": 9483\n" " },\n" " \"remarks\": \"remark.\"\n" "}"; auto obj = createFromUserInput(json, nullptr); auto pmo = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(pmo != nullptr); EXPECT_EQ(pmo->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), json); } proj-9.8.1/test/unit/test_misc.cpp000664 001750 001750 00000004342 15166171715 017112 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2021, Even Rouault * * 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 "gtest_include.h" #include "proj.h" namespace { TEST(misc, version) { EXPECT_EQ(PROJ_COMPUTE_VERSION(2200, 98, 76), 22009876); EXPECT_EQ(PROJ_VERSION_NUMBER, PROJ_VERSION_MAJOR * 10000 + PROJ_VERSION_MINOR * 100 + PROJ_VERSION_PATCH); EXPECT_TRUE(PROJ_AT_LEAST_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR, PROJ_VERSION_PATCH)); EXPECT_TRUE(PROJ_AT_LEAST_VERSION(PROJ_VERSION_MAJOR - 1, PROJ_VERSION_MINOR, PROJ_VERSION_PATCH)); EXPECT_FALSE(PROJ_AT_LEAST_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR, PROJ_VERSION_PATCH + 1)); } } // namespace proj-9.8.1/test/unit/gtest_include.h000664 001750 001750 00000003237 15166171715 017420 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Wrapper for gtest/gtest.h * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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. ****************************************************************************/ // Disable all warnings for gtest.h, so as to be able to still use them for // our own code. #if defined(__GNUC__) #pragma GCC system_header #endif #include "gtest/gtest.h" proj-9.8.1/test/unit/test_c_api.cpp000664 001750 001750 00001113274 15166171715 017240 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include #include #include #include "proj.h" #include "proj_constants.h" #include "proj_experimental.h" #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include #if !defined(_WIN32) #include #endif #ifndef __MINGW32__ #include #endif using namespace osgeo::proj::common; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; namespace { class CApi : public ::testing::Test { static void DummyLogFunction(void *, int, const char *) {} protected: void SetUp() override { m_ctxt = proj_context_create(); proj_log_func(m_ctxt, nullptr, DummyLogFunction); } void TearDown() override { if (m_ctxt) proj_context_destroy(m_ctxt); } static BoundCRSNNPtr createBoundCRS() { return BoundCRS::create( GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326, Transformation::create( PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326, nullptr, PropertyMap(), {OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "foo"))}, {ParameterValue::create( Measure(1.0, UnitOfMeasure::SCALE_UNITY))}, {})); } static ProjectedCRSNNPtr createProjectedCRS() { PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 32631) .set(IdentifiedObject::NAME_KEY, "WGS 84 / UTM zone 31N"); return ProjectedCRS::create( propertiesCRS, GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } static DerivedProjectedCRSNNPtr createDerivedProjectedCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "derived projectedCRS"), createProjectedCRS(), derivingConversion, CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } static VerticalCRSNNPtr createVerticalCRS() { PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create(propertiesVDatum); PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5701) .set(IdentifiedObject::NAME_KEY, "ODN height"); return VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); } static CompoundCRSNNPtr createCompoundCRS() { PropertyMap properties; properties.set(Identifier::CODESPACE_KEY, "codespace") .set(Identifier::CODE_KEY, "code") .set(IdentifiedObject::NAME_KEY, "horizontal + vertical"); return CompoundCRS::create( properties, std::vector{createProjectedCRS(), createVerticalCRS()}); } PJ_CONTEXT *m_ctxt = nullptr; struct ObjectKeeper { PJ *m_obj = nullptr; explicit ObjectKeeper(PJ *obj) : m_obj(obj) {} ~ObjectKeeper() { proj_destroy(m_obj); } void clear() { proj_destroy(m_obj); m_obj = nullptr; } ObjectKeeper(const ObjectKeeper &) = delete; ObjectKeeper &operator=(const ObjectKeeper &) = delete; }; struct PjContextKeeper { PJ_CONTEXT *m_ctxt = nullptr; explicit PjContextKeeper(PJ_CONTEXT *ctxt) : m_ctxt(ctxt) {} ~PjContextKeeper() { proj_context_destroy(m_ctxt); } PjContextKeeper(const PjContextKeeper &) = delete; PjContextKeeper &operator=(const PjContextKeeper &) = delete; }; struct ContextKeeper { PJ_OPERATION_FACTORY_CONTEXT *m_op_ctxt = nullptr; explicit ContextKeeper(PJ_OPERATION_FACTORY_CONTEXT *op_ctxt) : m_op_ctxt(op_ctxt) {} ~ContextKeeper() { proj_operation_factory_context_destroy(m_op_ctxt); } ContextKeeper(const ContextKeeper &) = delete; ContextKeeper &operator=(const ContextKeeper &) = delete; }; struct ObjListKeeper { PJ_OBJ_LIST *m_res = nullptr; explicit ObjListKeeper(PJ_OBJ_LIST *res) : m_res(res) {} ~ObjListKeeper() { proj_list_destroy(m_res); } ObjListKeeper(const ObjListKeeper &) = delete; ObjListKeeper &operator=(const ObjListKeeper &) = delete; }; }; // --------------------------------------------------------------------------- TEST_F(CApi, proj_create) { proj_destroy(nullptr); EXPECT_EQ(proj_create(m_ctxt, "invalid"), nullptr); { auto obj = proj_create(m_ctxt, GeographicCRS::EPSG_4326 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); // Check that functions that operate on 'non-C++' PJ don't crash constexpr double DEG_TO_RAD = .017453292519943296; PJ_COORD coord1; coord1.xyzt.x = 2 * DEG_TO_RAD; coord1.xyzt.y = 49 * DEG_TO_RAD; coord1.xyzt.z = 0; coord1.xyzt.t = 0; PJ_COORD coord2; coord2.xyzt.x = 2 * DEG_TO_RAD; coord2.xyzt.y = 50 * DEG_TO_RAD; coord2.xyzt.z = 0; coord2.xyzt.t = 0; EXPECT_EQ(proj_trans(obj, PJ_FWD, coord1).xyzt.x, std::numeric_limits::infinity()); // and those ones actually work just fine PJ_XYZT geod = proj_geod(obj, coord1, coord2).xyzt; EXPECT_NEAR(geod.x, 111219.409, 1e-3); EXPECT_NEAR(geod.y, 0, 1e-3); // also test that direct geodesic problem returns original results PJ_XYZT coord3 = proj_geod_direct(obj, coord1, geod.y, geod.x).xyzt; EXPECT_NEAR(coord3.x, coord2.xyzt.x, 1e-3); EXPECT_NEAR(coord3.y, coord2.xyzt.y, 1e-3); // Test distance EXPECT_NEAR(proj_lp_dist(obj, coord1, coord2), 111219.409, 1e-3); auto info = proj_pj_info(obj); EXPECT_EQ(info.id, nullptr); ASSERT_NE(info.description, nullptr); EXPECT_EQ(info.description, std::string("WGS 84")); ASSERT_NE(info.definition, nullptr); EXPECT_EQ(info.definition, std::string("")); } { auto obj = proj_create(m_ctxt, "EPSG:4326"); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); } { PJ_CONTEXT *ctxt = proj_context_create(); std::string s; proj_log_func(ctxt, &s, [](void *user_data, int, const char *msg) { *static_cast(user_data) = msg; }); auto crs = proj_create(ctxt, "EPSG:i_do_not_exist"); proj_destroy(crs); proj_context_destroy(ctxt); EXPECT_EQ(crs, nullptr); EXPECT_STREQ(s.c_str(), "proj_create: crs not found: EPSG:i_do_not_exist"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_from_wkt) { { EXPECT_EQ( proj_create_from_wkt(m_ctxt, "invalid", nullptr, nullptr, nullptr), nullptr); } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; EXPECT_EQ(proj_create_from_wkt(m_ctxt, "invalid", nullptr, &warningList, &errorList), nullptr); EXPECT_EQ(warningList, nullptr); proj_string_list_destroy(warningList); EXPECT_NE(errorList, nullptr); proj_string_list_destroy(errorList); } { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); } { auto obj = proj_create_from_wkt( m_ctxt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\"unused\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]]", nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; auto obj = proj_create_from_wkt( m_ctxt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\"unused\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]]", nullptr, &warningList, &errorList); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); EXPECT_EQ(warningList, nullptr); proj_string_list_destroy(warningList); EXPECT_NE(errorList, nullptr); proj_string_list_destroy(errorList); } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; const char *const options[] = {"STRICT=NO", nullptr}; auto obj = proj_create_from_wkt( m_ctxt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\"unused\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]]", options, &warningList, &errorList); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); EXPECT_EQ(warningList, nullptr); proj_string_list_destroy(warningList); EXPECT_NE(errorList, nullptr); proj_string_list_destroy(errorList); } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; const char *const options[] = { "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO", nullptr}; auto wkt = "PROJCS[\"Merchich / Nord Maroc\"," " GEOGCS[\"Merchich\"," " DATUM[\"Merchich\"," " SPHEROID[\"Clarke 1880 (IGN)\"," "6378249.2,293.466021293627]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"grad\",0.015707963267949," " AUTHORITY[\"EPSG\",\"9105\"]]," " AUTHORITY[\"EPSG\",\"4261\"]]," " PROJECTION[\"Lambert_Conformal_Conic_1SP\"]," " PARAMETER[\"latitude_of_origin\",37]," " PARAMETER[\"central_meridian\",-6]," " PARAMETER[\"scale_factor\",0.999625769]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",300000]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]]"; auto obj = proj_create_from_wkt(m_ctxt, wkt, options, &warningList, &errorList); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); EXPECT_EQ(warningList, nullptr); proj_string_list_destroy(warningList); EXPECT_EQ(errorList, nullptr); proj_string_list_destroy(errorList); } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; const char *const options[] = { "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=YES", nullptr}; auto wkt = "PROJCS[\"Merchich / Nord Maroc\"," " GEOGCS[\"Merchich\"," " DATUM[\"Merchich\"," " SPHEROID[\"Clarke 1880 (IGN)\"," "6378249.2,293.466021293627]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"grad\",0.015707963267949," " AUTHORITY[\"EPSG\",\"9105\"]]," " AUTHORITY[\"EPSG\",\"4261\"]]," " PROJECTION[\"Lambert_Conformal_Conic_1SP\"]," " PARAMETER[\"latitude_of_origin\",37]," " PARAMETER[\"central_meridian\",-6]," " PARAMETER[\"scale_factor\",0.999625769]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",300000]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]]"; auto obj = proj_create_from_wkt(m_ctxt, wkt, options, &warningList, &errorList); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); EXPECT_NE(warningList, nullptr); proj_string_list_destroy(warningList); EXPECT_EQ(errorList, nullptr); proj_string_list_destroy(errorList); } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, &warningList, &errorList); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); EXPECT_EQ(warningList, nullptr); EXPECT_EQ(errorList, nullptr); } // Warnings: missing projection parameters { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; auto obj = proj_create_from_wkt( m_ctxt, "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",31],\n" " UNIT[\"metre\",1]]", nullptr, &warningList, &errorList); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); EXPECT_NE(warningList, nullptr); proj_string_list_destroy(warningList); EXPECT_EQ(errorList, nullptr); proj_string_list_destroy(errorList); } { auto obj = proj_create_from_wkt( m_ctxt, "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",31],\n" " UNIT[\"metre\",1]]", nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); } { // Invalid ellipsoidal parameter (semi major axis) auto obj = proj_create_from_wkt( m_ctxt, "GEOGCS[\"test\",\n" " DATUM[\"test\",\n" " SPHEROID[\"test\",0,298.257223563,\"unused\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]]", nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); EXPECT_EQ(obj, nullptr); } { // Invalid ellipsoidal parameter (inverse flattening) auto obj = proj_create_from_wkt( m_ctxt, "GEOGCS[\"test\",\n" " DATUM[\"test\",\n" " SPHEROID[\"test\",6378137,-1,\"unused\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]]", nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); EXPECT_EQ(obj, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_wkt) { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); { auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEOGCRS[") == 0) << wkt; } { auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2019_SIMPLIFIED, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEOGCRS[") == 0) << wkt; EXPECT_TRUE(std::string(wkt).find("ANGULARUNIT[") == std::string::npos) << wkt; } { auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2015, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEODCRS[") == 0) << wkt; } { auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2015_SIMPLIFIED, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEODCRS[") == 0) << wkt; EXPECT_TRUE(std::string(wkt).find("ANGULARUNIT[") == std::string::npos) << wkt; } { auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"WGS 84\"") == 0) << wkt; } { auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_ESRI, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"GCS_WGS_1984\"") == 0) << wkt; } // MULTILINE=NO { const char *const options[] = {"MULTILINE=NO", nullptr}; auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("\n") == std::string::npos) << wkt; } // INDENTATION_WIDTH=2 { const char *const options[] = {"INDENTATION_WIDTH=2", nullptr}; auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("\n DATUM") != std::string::npos) << wkt; } // OUTPUT_AXIS=NO { const char *const options[] = {"OUTPUT_AXIS=NO", nullptr}; auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt; } // OUTPUT_AXIS=AUTO { const char *const options[] = {"OUTPUT_AXIS=AUTO", nullptr}; auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt; } // OUTPUT_AXIS=YES { const char *const options[] = {"OUTPUT_AXIS=YES", nullptr}; auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("AXIS") != std::string::npos) << wkt; } auto crs4979 = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper_crs4979(crs4979); ASSERT_NE(crs4979, nullptr); EXPECT_EQ(proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, nullptr), nullptr); // STRICT=NO { const char *const options[] = {"STRICT=NO", nullptr}; auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"WGS 84\"") == 0) << wkt; } // ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES { const char *const options[] = { "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES", nullptr}; auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find( "COMPD_CS[\"WGS 84 + Ellipsoid (metre)\"") == 0) << wkt; } // ALLOW_LINUNIT_NODE default value { auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_ESRI, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("LINUNIT") != std::string::npos) << wkt; } // ALLOW_LINUNIT_NODE=NO { const char *const options[] = {"ALLOW_LINUNIT_NODE=NO", nullptr}; auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_ESRI, options); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("LINUNIT") == std::string::npos) << wkt; } // unsupported option { const char *const options[] = {"unsupported=yes", nullptr}; auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2019, options); EXPECT_EQ(wkt, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_wkt_check_db_use) { auto obj = proj_create_from_wkt( m_ctxt, "GEOGCS[\"AGD66\",DATUM[\"Australian_Geodetic_Datum_1966\"," "SPHEROID[\"Australian National Spheroid\",6378160,298.25]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]", nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_ESRI, nullptr); EXPECT_EQ(std::string(wkt), "GEOGCS[\"GCS_Australian_1966\",DATUM[\"D_Australian_1966\"," "SPHEROID[\"Australian\",6378160.0,298.25]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_wkt_incompatible_WKT1) { auto wkt = createBoundCRS()->exportToWKT(WKTFormatter::create().get()); auto obj = proj_create_from_wkt(m_ctxt, wkt.c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr) << wkt; auto wkt1_GDAL = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, nullptr); ASSERT_EQ(wkt1_GDAL, nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_proj_string) { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); { auto proj_5 = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(std::string(proj_5), "+proj=longlat +datum=WGS84 +no_defs +type=crs"); } { auto proj_4 = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_4, nullptr); ASSERT_NE(proj_4, nullptr); EXPECT_EQ(std::string(proj_4), "+proj=longlat +datum=WGS84 +no_defs +type=crs"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_proj_string_incompatible_WKT1) { auto obj = proj_create_from_wkt( m_ctxt, createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto str = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_5, nullptr); ASSERT_EQ(str, nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_proj_string_approx_tmerc_option_yes) { auto obj = proj_create(m_ctxt, "+proj=tmerc +type=crs"); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); const char *options[] = {"USE_APPROX_TMERC=YES", nullptr}; auto str = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_4, options); ASSERT_NE(str, nullptr); EXPECT_EQ(str, std::string("+proj=tmerc +approx +lat_0=0 +lon_0=0 +k=1 +x_0=0 " "+y_0=0 +datum=WGS84 +units=m +no_defs +type=crs")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_create_bound_crs_to_WGS84) { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4807", PJ_CATEGORY_CRS, false, nullptr); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); auto res = proj_crs_create_bound_crs_to_WGS84(m_ctxt, crs, nullptr); ObjectKeeper keeper_res(res); ASSERT_NE(res, nullptr); auto proj_4 = proj_as_proj_string(m_ctxt, res, PJ_PROJ_4, nullptr); ASSERT_NE(proj_4, nullptr); EXPECT_EQ(std::string(proj_4), "+proj=longlat +ellps=clrk80ign +pm=paris " "+towgs84=-168,-60,320,0,0,0,0 +no_defs +type=crs"); auto base_crs = proj_get_source_crs(m_ctxt, res); ObjectKeeper keeper_base_crs(base_crs); ASSERT_NE(base_crs, nullptr); auto hub_crs = proj_get_target_crs(m_ctxt, res); ObjectKeeper keeper_hub_crs(hub_crs); ASSERT_NE(hub_crs, nullptr); auto transf = proj_crs_get_coordoperation(m_ctxt, res); ObjectKeeper keeper_transf(transf); ASSERT_NE(transf, nullptr); std::vector values(7, 0); EXPECT_TRUE(proj_coordoperation_get_towgs84_values(m_ctxt, transf, values.data(), 7, true)); auto expected = std::vector{-168, -60, 320, 0, 0, 0, 0}; EXPECT_EQ(values, expected); auto res2 = proj_crs_create_bound_crs(m_ctxt, base_crs, hub_crs, transf); ObjectKeeper keeper_res2(res2); ASSERT_NE(res2, nullptr); EXPECT_TRUE(proj_is_equivalent_to(res, res2, PJ_COMP_STRICT)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_create_bound_crs_to_WGS84_on_invalid_type) { auto wkt = createProjectedCRS()->derivingConversion()->exportToWKT( WKTFormatter::create().get()); auto obj = proj_create_from_wkt(m_ctxt, wkt.c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr) << wkt; auto res = proj_crs_create_bound_crs_to_WGS84(m_ctxt, obj, nullptr); ASSERT_EQ(res, nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_name) { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto name = proj_get_name(obj); ASSERT_TRUE(name != nullptr); EXPECT_EQ(name, std::string("WGS 84")); EXPECT_EQ(name, proj_get_name(obj)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_id_auth_name) { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto auth = proj_get_id_auth_name(obj, 0); ASSERT_TRUE(auth != nullptr); EXPECT_EQ(auth, std::string("EPSG")); EXPECT_EQ(auth, proj_get_id_auth_name(obj, 0)); EXPECT_EQ(proj_get_id_auth_name(obj, -1), nullptr); EXPECT_EQ(proj_get_id_auth_name(obj, 1), nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_id_code) { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto code = proj_get_id_code(obj, 0); ASSERT_TRUE(code != nullptr); EXPECT_EQ(code, std::string("4326")); EXPECT_EQ(code, proj_get_id_code(obj, 0)); EXPECT_EQ(proj_get_id_code(obj, -1), nullptr); EXPECT_EQ(proj_get_id_code(obj, 1), nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_type) { { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEOGRAPHIC_2D_CRS); } { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEOGRAPHIC_3D_CRS); } { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4978->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEOCENTRIC_CRS); } { auto obj = proj_create_from_wkt(m_ctxt, GeographicCRS::EPSG_4326->datum() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEODETIC_REFERENCE_FRAME); } { auto obj = proj_create_from_wkt(m_ctxt, GeographicCRS::EPSG_4326->ellipsoid() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_ELLIPSOID); } { auto obj = proj_create_from_wkt(m_ctxt, createProjectedCRS() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_PROJECTED_CRS); } { auto obj = proj_create_from_wkt( m_ctxt, createDerivedProjectedCRS() ->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019) .get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_DERIVED_PROJECTED_CRS); } { auto obj = proj_create_from_wkt(m_ctxt, createVerticalCRS() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_VERTICAL_CRS); } { auto wkt = "TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]"; auto datum = proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr); ObjectKeeper keeper(datum); ASSERT_NE(datum, nullptr); EXPECT_EQ(proj_get_type(datum), PJ_TYPE_TEMPORAL_DATUM); } { auto wkt = "ENGINEERINGDATUM[\"Engineering datum\"]"; auto datum = proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr); ObjectKeeper keeper(datum); EXPECT_EQ(proj_get_type(datum), PJ_TYPE_ENGINEERING_DATUM); } { auto wkt = "PDATUM[\"Mean Sea Level\",ANCHOR[\"1013.25 hPa at 15°C\"]]"; auto datum = proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr); ObjectKeeper keeper(datum); EXPECT_EQ(proj_get_type(datum), PJ_TYPE_PARAMETRIC_DATUM); } { auto obj = proj_create_from_wkt(m_ctxt, createVerticalCRS() ->datum() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_VERTICAL_REFERENCE_FRAME); } { auto obj = proj_create_from_wkt(m_ctxt, createProjectedCRS() ->derivingConversion() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_CONVERSION); } { auto obj = proj_create_from_wkt( m_ctxt, createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_BOUND_CRS); } { auto obj = proj_create_from_wkt(m_ctxt, createBoundCRS() ->transformation() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_TRANSFORMATION); } { auto obj = proj_create_from_wkt(m_ctxt, "AUTHORITY[\"EPSG\", 4326]", nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_get_type(obj), PJ_TYPE_UNKNOWN); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_from_database) { { auto crs = proj_create_from_database(m_ctxt, "EPSG", "-1", PJ_CATEGORY_CRS, false, nullptr); ASSERT_EQ(crs, nullptr); } { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_TRUE(proj_is_crs(crs)); EXPECT_FALSE(proj_is_deprecated(crs)); EXPECT_EQ(proj_get_type(crs), PJ_TYPE_GEOGRAPHIC_2D_CRS); const char *code = proj_get_id_code(crs, 0); ASSERT_NE(code, nullptr); EXPECT_EQ(std::string(code), "4326"); } { auto crs = proj_create_from_database(m_ctxt, "EPSG", "6871", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_TRUE(proj_is_crs(crs)); EXPECT_EQ(proj_get_type(crs), PJ_TYPE_COMPOUND_CRS); } { auto crs = proj_create_from_database(m_ctxt, "EPSG", "6715", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_TRUE(proj_is_crs(crs)); EXPECT_EQ(proj_get_type(crs), PJ_TYPE_ENGINEERING_CRS); } { auto ellipsoid = proj_create_from_database( m_ctxt, "EPSG", "7030", PJ_CATEGORY_ELLIPSOID, false, nullptr); ASSERT_NE(ellipsoid, nullptr); ObjectKeeper keeper(ellipsoid); EXPECT_EQ(proj_get_type(ellipsoid), PJ_TYPE_ELLIPSOID); } { auto pm = proj_create_from_database( m_ctxt, "EPSG", "8903", PJ_CATEGORY_PRIME_MERIDIAN, false, nullptr); ASSERT_NE(pm, nullptr); ObjectKeeper keeper(pm); EXPECT_EQ(proj_get_type(pm), PJ_TYPE_PRIME_MERIDIAN); } { auto datum = proj_create_from_database( m_ctxt, "EPSG", "6326", PJ_CATEGORY_DATUM, false, nullptr); ASSERT_NE(datum, nullptr); ObjectKeeper keeper(datum); EXPECT_EQ(proj_get_type(datum), PJ_TYPE_GEODETIC_REFERENCE_FRAME); } { auto ensemble = proj_create_from_database( m_ctxt, "EPSG", "6326", PJ_CATEGORY_DATUM_ENSEMBLE, false, nullptr); ASSERT_NE(ensemble, nullptr); ObjectKeeper keeper(ensemble); EXPECT_EQ(proj_get_type(ensemble), PJ_TYPE_DATUM_ENSEMBLE); } { // International Terrestrial Reference Frame 2008 auto datum = proj_create_from_database( m_ctxt, "EPSG", "1061", PJ_CATEGORY_DATUM, false, nullptr); ASSERT_NE(datum, nullptr); ObjectKeeper keeper(datum); EXPECT_EQ(proj_get_type(datum), PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME); EXPECT_EQ(proj_dynamic_datum_get_frame_reference_epoch(m_ctxt, datum), 2005.0); } #ifdef no_more_dynamic_vertical_datum { // Norway Normal Null 2000 auto datum = proj_create_from_database( m_ctxt, "EPSG", "1096", PJ_CATEGORY_DATUM, false, nullptr); ASSERT_NE(datum, nullptr); ObjectKeeper keeper(datum); EXPECT_EQ(proj_get_type(datum), PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME); EXPECT_EQ(proj_dynamic_datum_get_frame_reference_epoch(m_ctxt, datum), 2000.0); } #endif { auto op = proj_create_from_database(m_ctxt, "EPSG", "16031", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(op, nullptr); ObjectKeeper keeper(op); EXPECT_EQ(proj_get_type(op), PJ_TYPE_CONVERSION); auto info = proj_pj_info(op); EXPECT_NE(info.id, nullptr); EXPECT_EQ(info.id, std::string("utm")); ASSERT_NE(info.description, nullptr); EXPECT_EQ(info.description, std::string("UTM zone 31N")); ASSERT_NE(info.definition, nullptr); EXPECT_EQ(info.definition, std::string("proj=utm zone=31 ellps=GRS80")); EXPECT_EQ(info.accuracy, 0); } { auto op = proj_create_from_database(m_ctxt, "EPSG", "1024", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(op, nullptr); ObjectKeeper keeper(op); EXPECT_EQ(proj_get_type(op), PJ_TYPE_TRANSFORMATION); auto info = proj_pj_info(op); EXPECT_NE(info.id, nullptr); EXPECT_EQ(info.id, std::string("pipeline")); ASSERT_NE(info.description, nullptr); EXPECT_EQ(info.description, std::string("MGI to ETRS89 (4)")); ASSERT_NE(info.definition, nullptr); EXPECT_EQ( info.definition, std::string("proj=pipeline step proj=axisswap " "order=2,1 step proj=unitconvert xy_in=deg xy_out=rad " "step proj=push v_3 " "step proj=cart ellps=bessel step proj=helmert " "x=601.705 y=84.263 z=485.227 rx=-4.7354 ry=-1.3145 " "rz=-5.393 s=-2.3887 convention=coordinate_frame " "step inv proj=cart ellps=GRS80 " "step proj=pop v_3 " "step proj=unitconvert xy_in=rad xy_out=deg " "step proj=axisswap order=2,1")); EXPECT_EQ(info.accuracy, 1); } { PJ_CONTEXT *ctxt = proj_context_create(); std::string s; proj_log_func(ctxt, &s, [](void *user_data, int, const char *msg) { *static_cast(user_data) = msg; }); auto crs = proj_create_from_database(ctxt, "EPSG", "i_do_not_exist", PJ_CATEGORY_CRS, false, nullptr); proj_destroy(crs); proj_context_destroy(ctxt); EXPECT_EQ(crs, nullptr); EXPECT_STREQ( s.c_str(), "proj_create_from_database: crs not found: EPSG:i_do_not_exist"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs) { auto crs = proj_create_from_wkt( m_ctxt, createProjectedCRS() ->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()) .c_str(), nullptr, nullptr, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_TRUE(proj_is_crs(crs)); auto geodCRS = proj_crs_get_geodetic_crs(m_ctxt, crs); ASSERT_NE(geodCRS, nullptr); ObjectKeeper keeper_geogCRS(geodCRS); EXPECT_TRUE(proj_is_crs(geodCRS)); auto geogCRS_name = proj_get_name(geodCRS); ASSERT_TRUE(geogCRS_name != nullptr); EXPECT_EQ(geogCRS_name, std::string("WGS 84")); auto h_datum = proj_crs_get_horizontal_datum(m_ctxt, crs); ASSERT_NE(h_datum, nullptr); ObjectKeeper keeper_h_datum(h_datum); auto datum = proj_crs_get_datum(m_ctxt, crs); ASSERT_NE(datum, nullptr); ObjectKeeper keeper_datum(datum); EXPECT_TRUE(proj_is_equivalent_to(h_datum, datum, PJ_COMP_STRICT)); auto datum_name = proj_get_name(datum); ASSERT_TRUE(datum_name != nullptr); EXPECT_EQ(datum_name, std::string("World Geodetic System 1984")); auto ellipsoid = proj_get_ellipsoid(m_ctxt, crs); ASSERT_NE(ellipsoid, nullptr); ObjectKeeper keeper_ellipsoid(ellipsoid); auto ellipsoid_name = proj_get_name(ellipsoid); ASSERT_TRUE(ellipsoid_name != nullptr); EXPECT_EQ(ellipsoid_name, std::string("WGS 84")); auto ellipsoid_from_datum = proj_get_ellipsoid(m_ctxt, datum); ASSERT_NE(ellipsoid_from_datum, nullptr); ObjectKeeper keeper_ellipsoid_from_datum(ellipsoid_from_datum); EXPECT_EQ(proj_get_ellipsoid(m_ctxt, ellipsoid), nullptr); EXPECT_FALSE(proj_is_crs(ellipsoid)); double a; double b; int b_is_computed; double rf; EXPECT_TRUE(proj_ellipsoid_get_parameters(m_ctxt, ellipsoid, nullptr, nullptr, nullptr, nullptr)); EXPECT_TRUE(proj_ellipsoid_get_parameters(m_ctxt, ellipsoid, &a, &b, &b_is_computed, &rf)); EXPECT_FALSE(proj_ellipsoid_get_parameters(m_ctxt, crs, &a, &b, &b_is_computed, &rf)); EXPECT_EQ(a, 6378137); EXPECT_NEAR(b, 6356752.31424518, 1e-9); EXPECT_EQ(b_is_computed, 1); EXPECT_EQ(rf, 298.257223563); auto id = proj_get_id_code(ellipsoid, 0); ASSERT_TRUE(id != nullptr); EXPECT_EQ(id, std::string("7030")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_celestial_body_name) { // Geographic CRS { auto obj = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Projected CRS { auto obj = proj_create_from_database(m_ctxt, "EPSG", "32631", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Vertical CRS { auto obj = proj_create_from_database(m_ctxt, "EPSG", "3855", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Compound CRS { auto obj = proj_create_from_database(m_ctxt, "EPSG", "9518", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Geodetic datum { auto obj = proj_create_from_database(m_ctxt, "EPSG", "6267", PJ_CATEGORY_DATUM, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Datum ensemble { auto obj = proj_create_from_database( m_ctxt, "EPSG", "6326", PJ_CATEGORY_DATUM_ENSEMBLE, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Vertical datum { auto obj = proj_create_from_database(m_ctxt, "EPSG", "1027", PJ_CATEGORY_DATUM, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Ellipsoid { auto obj = proj_create_from_database( m_ctxt, "EPSG", "7030", PJ_CATEGORY_ELLIPSOID, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Earth"); } // Ellipsoid non-EARTH { auto obj = proj_create_from_database( m_ctxt, "ESRI", "107903", PJ_CATEGORY_ELLIPSOID, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_NE(celestial_body_name, nullptr); EXPECT_EQ(std::string(celestial_body_name), "Moon"); } // Coordinate operation -> error { auto obj = proj_create_from_database(m_ctxt, "EPSG", "1591", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); const char *celestial_body_name = proj_get_celestial_body_name(m_ctxt, obj); ASSERT_EQ(celestial_body_name, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_prime_meridian) { auto crs = proj_create_from_wkt( m_ctxt, createProjectedCRS() ->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()) .c_str(), nullptr, nullptr, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); auto pm = proj_get_prime_meridian(m_ctxt, crs); ASSERT_NE(pm, nullptr); ObjectKeeper keeper_pm(pm); auto pm_name = proj_get_name(pm); ASSERT_TRUE(pm_name != nullptr); EXPECT_EQ(pm_name, std::string("Greenwich")); EXPECT_EQ(proj_get_prime_meridian(m_ctxt, pm), nullptr); EXPECT_TRUE(proj_prime_meridian_get_parameters(m_ctxt, pm, nullptr, nullptr, nullptr)); double longitude = -1; double longitude_unit = 0; const char *longitude_unit_name = nullptr; EXPECT_TRUE(proj_prime_meridian_get_parameters( m_ctxt, pm, &longitude, &longitude_unit, &longitude_unit_name)); EXPECT_EQ(longitude, 0); EXPECT_NEAR(longitude_unit, UnitOfMeasure::DEGREE.conversionToSI(), 1e-10); ASSERT_TRUE(longitude_unit_name != nullptr); EXPECT_EQ(longitude_unit_name, std::string("degree")); auto datum = proj_crs_get_horizontal_datum(m_ctxt, crs); ASSERT_NE(datum, nullptr); ObjectKeeper keeper_datum(datum); auto pm_from_datum = proj_get_prime_meridian(m_ctxt, datum); ASSERT_NE(pm_from_datum, nullptr); ObjectKeeper keeper_pm_from_datum(pm_from_datum); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_compound) { auto crs = proj_create_from_wkt( m_ctxt, createCompoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_EQ(proj_get_type(crs), PJ_TYPE_COMPOUND_CRS); EXPECT_EQ(proj_crs_get_sub_crs(m_ctxt, crs, -1), nullptr); EXPECT_EQ(proj_crs_get_sub_crs(m_ctxt, crs, 2), nullptr); auto subcrs_horiz = proj_crs_get_sub_crs(m_ctxt, crs, 0); ASSERT_NE(subcrs_horiz, nullptr); ObjectKeeper keeper_subcrs_horiz(subcrs_horiz); EXPECT_EQ(proj_get_type(subcrs_horiz), PJ_TYPE_PROJECTED_CRS); EXPECT_EQ(proj_crs_get_sub_crs(m_ctxt, subcrs_horiz, 0), nullptr); auto subcrs_vertical = proj_crs_get_sub_crs(m_ctxt, crs, 1); ASSERT_NE(subcrs_vertical, nullptr); ObjectKeeper keeper_subcrs_vertical(subcrs_vertical); EXPECT_EQ(proj_get_type(subcrs_vertical), PJ_TYPE_VERTICAL_CRS); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_source_target_crs_bound_crs) { auto crs = proj_create_from_wkt( m_ctxt, createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); auto sourceCRS = proj_get_source_crs(m_ctxt, crs); ASSERT_NE(sourceCRS, nullptr); ObjectKeeper keeper_sourceCRS(sourceCRS); EXPECT_EQ(std::string(proj_get_name(sourceCRS)), "NTF (Paris)"); auto targetCRS = proj_get_target_crs(m_ctxt, crs); ASSERT_NE(targetCRS, nullptr); ObjectKeeper keeper_targetCRS(targetCRS); EXPECT_EQ(std::string(proj_get_name(targetCRS)), "WGS 84"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_source_target_crs_transformation) { auto obj = proj_create_from_wkt(m_ctxt, createBoundCRS() ->transformation() ->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); auto sourceCRS = proj_get_source_crs(m_ctxt, obj); ASSERT_NE(sourceCRS, nullptr); ObjectKeeper keeper_sourceCRS(sourceCRS); EXPECT_EQ(std::string(proj_get_name(sourceCRS)), "NTF (Paris)"); auto targetCRS = proj_get_target_crs(m_ctxt, obj); ASSERT_NE(targetCRS, nullptr); ObjectKeeper keeper_targetCRS(targetCRS); EXPECT_EQ(std::string(proj_get_name(targetCRS)), "WGS 84"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_source_crs_of_projected_crs) { auto crs = proj_create_from_wkt( m_ctxt, createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); auto sourceCRS = proj_get_source_crs(m_ctxt, crs); ASSERT_NE(sourceCRS, nullptr); ObjectKeeper keeper_sourceCRS(sourceCRS); EXPECT_EQ(std::string(proj_get_name(sourceCRS)), "WGS 84"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_source_target_crs_conversion_without_crs) { auto obj = proj_create_from_database(m_ctxt, "EPSG", "16031", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); auto sourceCRS = proj_get_source_crs(m_ctxt, obj); ASSERT_EQ(sourceCRS, nullptr); auto targetCRS = proj_get_target_crs(m_ctxt, obj); ASSERT_EQ(targetCRS, nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_source_target_crs_invalid_object) { auto obj = proj_create_from_wkt( m_ctxt, "ELLIPSOID[\"WGS 84\",6378137,298.257223563]", nullptr, nullptr, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); auto sourceCRS = proj_get_source_crs(m_ctxt, obj); ASSERT_EQ(sourceCRS, nullptr); auto targetCRS = proj_get_target_crs(m_ctxt, obj); ASSERT_EQ(targetCRS, nullptr); } // --------------------------------------------------------------------------- struct ListFreer { PROJ_STRING_LIST list; ListFreer(PROJ_STRING_LIST ptrIn) : list(ptrIn) {} ~ListFreer() { proj_string_list_destroy(list); } ListFreer(const ListFreer &) = delete; ListFreer &operator=(const ListFreer &) = delete; }; // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_authorities_from_database) { auto list = proj_get_authorities_from_database(m_ctxt); ListFreer feer(list); ASSERT_NE(list, nullptr); ASSERT_TRUE(list[0] != nullptr); EXPECT_EQ(list[0], std::string("EPSG")); ASSERT_TRUE(list[1] != nullptr); EXPECT_EQ(list[1], std::string("ESRI")); ASSERT_TRUE(list[2] != nullptr); EXPECT_EQ(list[2], std::string("IAU_2015")); ASSERT_TRUE(list[3] != nullptr); EXPECT_EQ(list[3], std::string("IGNF")); ASSERT_TRUE(list[4] != nullptr); EXPECT_EQ(list[4], std::string("NKG")); ASSERT_TRUE(list[5] != nullptr); EXPECT_EQ(list[5], std::string("NRCAN")); ASSERT_TRUE(list[6] != nullptr); EXPECT_EQ(list[6], std::string("OGC")); ASSERT_TRUE(list[7] != nullptr); EXPECT_EQ(list[7], std::string("PROJ")); EXPECT_EQ(list[8], nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_codes_from_database) { const PJ_TYPE listTypes[] = {PJ_TYPE_ELLIPSOID, PJ_TYPE_PRIME_MERIDIAN, PJ_TYPE_GEODETIC_REFERENCE_FRAME, PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME, PJ_TYPE_VERTICAL_REFERENCE_FRAME, PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME, PJ_TYPE_DATUM_ENSEMBLE, PJ_TYPE_TEMPORAL_DATUM, PJ_TYPE_ENGINEERING_DATUM, PJ_TYPE_PARAMETRIC_DATUM, PJ_TYPE_CRS, PJ_TYPE_GEODETIC_CRS, PJ_TYPE_GEOCENTRIC_CRS, PJ_TYPE_GEOGRAPHIC_CRS, PJ_TYPE_GEOGRAPHIC_2D_CRS, PJ_TYPE_GEOGRAPHIC_3D_CRS, PJ_TYPE_VERTICAL_CRS, PJ_TYPE_PROJECTED_CRS, PJ_TYPE_COMPOUND_CRS, PJ_TYPE_ENGINEERING_CRS, PJ_TYPE_TEMPORAL_CRS, PJ_TYPE_BOUND_CRS, PJ_TYPE_OTHER_CRS, PJ_TYPE_CONVERSION, PJ_TYPE_TRANSFORMATION, PJ_TYPE_CONCATENATED_OPERATION, PJ_TYPE_OTHER_COORDINATE_OPERATION, PJ_TYPE_UNKNOWN}; for (const auto &type : listTypes) { auto list = proj_get_codes_from_database(m_ctxt, "EPSG", type, true); ListFreer feer(list); if (type == PJ_TYPE_TEMPORAL_CRS || type == PJ_TYPE_BOUND_CRS || type == PJ_TYPE_UNKNOWN || type == PJ_TYPE_TEMPORAL_DATUM || type == PJ_TYPE_PARAMETRIC_DATUM) { EXPECT_EQ(list, nullptr) << type; } else { ASSERT_NE(list, nullptr) << type; ASSERT_NE(list[0], nullptr) << type; if (type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME || type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME) { auto obj = proj_create_from_database( m_ctxt, "EPSG", list[0], PJ_CATEGORY_DATUM, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); EXPECT_EQ(proj_get_type(obj), type) << type << " " << list[0]; } } } } // --------------------------------------------------------------------------- TEST_F(CApi, conversion) { auto crs = proj_create_from_database(m_ctxt, "EPSG", "32631", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); // invalid object type EXPECT_FALSE(proj_coordoperation_get_method_info(m_ctxt, crs, nullptr, nullptr, nullptr)); { auto conv = proj_crs_get_coordoperation(m_ctxt, crs); ASSERT_NE(conv, nullptr); ObjectKeeper keeper_conv(conv); ASSERT_EQ(proj_crs_get_coordoperation(m_ctxt, conv), nullptr); } auto conv = proj_crs_get_coordoperation(m_ctxt, crs); ASSERT_NE(conv, nullptr); ObjectKeeper keeper_conv(conv); EXPECT_TRUE(proj_coordoperation_get_method_info(m_ctxt, conv, nullptr, nullptr, nullptr)); const char *methodName = nullptr; const char *methodAuthorityName = nullptr; const char *methodCode = nullptr; EXPECT_TRUE(proj_coordoperation_get_method_info( m_ctxt, conv, &methodName, &methodAuthorityName, &methodCode)); ASSERT_NE(methodName, nullptr); ASSERT_NE(methodAuthorityName, nullptr); ASSERT_NE(methodCode, nullptr); EXPECT_EQ(methodName, std::string("Transverse Mercator")); EXPECT_EQ(methodAuthorityName, std::string("EPSG")); EXPECT_EQ(methodCode, std::string("9807")); EXPECT_EQ(proj_coordoperation_get_param_count(m_ctxt, conv), 5); EXPECT_EQ(proj_coordoperation_get_param_index(m_ctxt, conv, "foo"), -1); EXPECT_EQ( proj_coordoperation_get_param_index(m_ctxt, conv, "False easting"), 3); EXPECT_FALSE(proj_coordoperation_get_param( m_ctxt, conv, -1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); EXPECT_FALSE(proj_coordoperation_get_param( m_ctxt, conv, 5, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); const char *name = nullptr; const char *nameAuthorityName = nullptr; const char *nameCode = nullptr; double value = 0; const char *valueString = nullptr; double valueUnitConvFactor = 0; const char *valueUnitName = nullptr; const char *unitAuthName = nullptr; const char *unitCode = nullptr; const char *unitCategory = nullptr; EXPECT_TRUE(proj_coordoperation_get_param( m_ctxt, conv, 3, &name, &nameAuthorityName, &nameCode, &value, &valueString, &valueUnitConvFactor, &valueUnitName, &unitAuthName, &unitCode, &unitCategory)); ASSERT_NE(name, nullptr); ASSERT_NE(nameAuthorityName, nullptr); ASSERT_NE(nameCode, nullptr); EXPECT_EQ(valueString, nullptr); ASSERT_NE(valueUnitName, nullptr); ASSERT_NE(unitAuthName, nullptr); ASSERT_NE(unitCategory, nullptr); ASSERT_NE(unitCategory, nullptr); EXPECT_EQ(name, std::string("False easting")); EXPECT_EQ(nameAuthorityName, std::string("EPSG")); EXPECT_EQ(nameCode, std::string("8806")); EXPECT_EQ(value, 500000.0); EXPECT_EQ(valueUnitConvFactor, 1.0); EXPECT_EQ(valueUnitName, std::string("metre")); EXPECT_EQ(unitAuthName, std::string("EPSG")); EXPECT_EQ(unitCode, std::string("9001")); EXPECT_EQ(unitCategory, std::string("linear")); } // --------------------------------------------------------------------------- TEST_F(CApi, transformation_from_boundCRS) { auto crs = proj_create_from_wkt( m_ctxt, createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); auto transf = proj_crs_get_coordoperation(m_ctxt, crs); ASSERT_NE(transf, nullptr); ObjectKeeper keeper_transf(transf); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_from_database_grid_alternative_null_old_proj_grid_name) { // EPSG:9484 uses grid "href2008a.bin" whose grid_alternatives entry has // proj_grid_name = "no_kv_href2008a.tif" but old_proj_grid_name IS NULL. // Without the database context in pj_obj_create(), // substitutePROJAlternativeGridNames() cannot resolve the grid name, // leaving the original "href2008a.bin" in the PROJ string. With the fix, // the CDN name "no_kv_href2008a.tif" is used instead. // // Enable network so that pj_obj_create() sets defer_grid_opening=true, // allowing the pipeline to be created even without the grid file on disk. proj_context_set_enable_network(m_ctxt, 1); auto op = proj_create_from_database(m_ctxt, "EPSG", "9484", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(op, nullptr); ObjectKeeper keeper(op); auto info = proj_pj_info(op); ASSERT_NE(info.definition, nullptr); EXPECT_TRUE(std::string(info.definition).find("no_kv_href2008a.tif") != std::string::npos) << "Expected CDN grid name 'no_kv_href2008a.tif' in definition, got: " << info.definition; proj_context_set_enable_network(m_ctxt, 0); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_coordoperation_get_grid_used) { auto op = proj_create_from_database(m_ctxt, "EPSG", "1312", PJ_CATEGORY_COORDINATE_OPERATION, true, nullptr); ASSERT_NE(op, nullptr); ObjectKeeper keeper(op); const std::string old_endpoint = proj_context_get_url_endpoint(m_ctxt); proj_context_set_url_endpoint(m_ctxt, "https://example.com"); EXPECT_EQ(proj_coordoperation_get_grid_used_count(m_ctxt, op), 1); const char *shortName = nullptr; const char *fullName = nullptr; const char *packageName = nullptr; const char *url = nullptr; int directDownload = 0; int openLicense = 0; int available = 0; EXPECT_EQ(proj_coordoperation_get_grid_used(m_ctxt, op, -1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr), 0); EXPECT_EQ(proj_coordoperation_get_grid_used(m_ctxt, op, 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr), 0); EXPECT_EQ(proj_coordoperation_get_grid_used( m_ctxt, op, 0, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &available), 1); ASSERT_NE(shortName, nullptr); ASSERT_NE(fullName, nullptr); ASSERT_NE(packageName, nullptr); ASSERT_NE(url, nullptr); EXPECT_EQ(shortName, std::string("ca_nrc_ntv1_can.tif")); // EXPECT_EQ(fullName, std::string("")); EXPECT_EQ(packageName, std::string("")); EXPECT_EQ(std::string(url), "https://example.com/ca_nrc_ntv1_can.tif"); EXPECT_EQ(directDownload, 1); EXPECT_EQ(openLicense, 1); proj_context_set_url_endpoint(m_ctxt, old_endpoint.c_str()); } // --------------------------------------------------------------------------- #ifdef TIFF_ENABLED TEST_F(CApi, proj_coordoperation_get_grid_used_fullname_caching) { // Test bugfix for // https://github.com/OSGeo/PROJ/issues/3444#issuecomment-1309499342 for (int i = 0; i < 2; ++i) { const char *proj_string = "proj=vgridshift grids=tests/test_vgrid_int16.tif"; PJ *P = proj_create(m_ctxt, proj_string); ObjectKeeper keeper(P); const char *shortName = nullptr; const char *fullName = nullptr; const char *packageName = nullptr; const char *url = nullptr; int directDownload = 0; int openLicense = 0; int available = 0; proj_coordoperation_get_grid_used(m_ctxt, P, 0, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &available); EXPECT_EQ(std::string(shortName), "tests/test_vgrid_int16.tif"); EXPECT_TRUE(std::string(fullName).find("tests/test_vgrid_int16.tif") != std::string::npos) << std::string(fullName); } } #endif // --------------------------------------------------------------------------- TEST_F(CApi, proj_coordoperation_is_instantiable) { auto op = proj_create_from_database(m_ctxt, "EPSG", "1671", PJ_CATEGORY_COORDINATE_OPERATION, true, nullptr); ASSERT_NE(op, nullptr); ObjectKeeper keeper(op); EXPECT_EQ(proj_coordoperation_is_instantiable(m_ctxt, op), 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_operations) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "4267", PJ_CATEGORY_CRS, false, nullptr); // NAD27 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4269", PJ_CATEGORY_CRS, false, nullptr); // NAD83 ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 10); EXPECT_EQ(proj_list_get(m_ctxt, res, -1), nullptr); EXPECT_EQ(proj_list_get(m_ctxt, res, proj_list_get_count(res)), nullptr); { auto op = proj_list_get(m_ctxt, res, 0); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); EXPECT_FALSE( proj_coordoperation_has_ballpark_transformation(m_ctxt, op)); EXPECT_EQ(proj_get_name(op), std::string("NAD27 to NAD83 (4)")); } { PJ_COORD coord; coord.xy.x = 40; coord.xy.y = -100; int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord); ASSERT_GE(idx, 0); ASSERT_LT(idx, proj_list_get_count(res)); auto op = proj_list_get(m_ctxt, res, idx); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); // Transformation for USA, using NADCON5 EXPECT_EQ(proj_get_name(op), std::string("NAD27 to NAD83 (7)")); } { PJ_COORD coord; coord.xy.x = 40; coord.xy.y = 10; int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord); EXPECT_GE(idx, -1); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_suggested_operation_for_NAD83_to_NAD83_HARN) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "4269", PJ_CATEGORY_CRS, false, nullptr); // NAD83 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4152", PJ_CATEGORY_CRS, false, nullptr); // NAD83(HARN) ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); { PJ_COORD coord; coord.xy.x = 40; coord.xy.y = -100; int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord); ASSERT_GE(idx, 0); ASSERT_LT(idx, proj_list_get_count(res)); auto op = proj_list_get(m_ctxt, res, idx); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); // Transformation for CONUS EXPECT_STREQ(proj_get_name(op), "NAD83 to NAD83(HARN) (47)"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_operations_prime_meridian_non_greenwich) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "27562", PJ_CATEGORY_CRS, false, nullptr); // "NTF (Paris) / Lambert Centre France" ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4258", PJ_CATEGORY_CRS, false, nullptr); // ETRS89 ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); { PJ_COORD coord; // lat,long=49,-4 if using grid coord.xy.x = 136555.58288992; coord.xy.y = 463344.51894296; int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord); ASSERT_GE(idx, 0); auto op = proj_list_get(m_ctxt, res, idx); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); // Transformation using grid EXPECT_EQ(proj_coordoperation_get_grid_used_count(m_ctxt, op), 1); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_suggested_operation_with_operations_without_area_of_use) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); // NAD83(2011) geocentric auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "6317", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); // NAD83(2011) 2D auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "6318", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); PJ_COORD coord; coord.xyz.x = -463930; coord.xyz.y = -4414006; coord.xyz.z = 4562247; int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord); EXPECT_GE(idx, 0); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_operations_discard_superseded) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "4203", PJ_CATEGORY_CRS, false, nullptr); // AGD84 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); proj_operation_factory_context_set_discard_superseded(m_ctxt, ctxt, true); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 4); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_operations_dont_discard_superseded) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "4203", PJ_CATEGORY_CRS, false, nullptr); // AGD84 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); proj_operation_factory_context_set_discard_superseded(m_ctxt, ctxt, false); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 5); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_operations_with_pivot) { auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "4230", PJ_CATEGORY_CRS, false, nullptr); // ED50 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4171", PJ_CATEGORY_CRS, false, nullptr); // RGF93 v1 ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); // There is no direct transformations between both // Default behavior: allow any pivot { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); auto op = proj_list_get(m_ctxt, res, 0); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); EXPECT_EQ( proj_get_name(op), std::string( "ED50 to ETRS89 (10) + Inverse of RGF93 v1 to ETRS89 (1)")); } // Disallow pivots { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); proj_operation_factory_context_set_allow_use_intermediate_crs( m_ctxt, ctxt, PROJ_INTERMEDIATE_CRS_USE_NEVER); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); auto op = proj_list_get(m_ctxt, res, 0); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); EXPECT_EQ( proj_get_name(op), std::string("Ballpark geographic offset from ED50 to RGF93 v1")); } // Restrict pivot to ETRS89 { auto ctxt = proj_create_operation_factory_context(m_ctxt, "EPSG"); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); const char *pivots[] = {"EPSG", "4258", nullptr}; proj_operation_factory_context_set_allowed_intermediate_crs( m_ctxt, ctxt, pivots); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); auto op = proj_list_get(m_ctxt, res, 0); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); EXPECT_EQ( proj_get_name(op), std::string( "ED50 to ETRS89 (10) + Inverse of RGF93 v1 to ETRS89 (1)")); } // Restrict pivot to something unrelated { auto ctxt = proj_create_operation_factory_context(m_ctxt, "any"); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); const char *pivots[] = {"EPSG", "4267", nullptr}; // NAD27 proj_operation_factory_context_set_allowed_intermediate_crs( m_ctxt, ctxt, pivots); proj_operation_factory_context_set_allow_use_intermediate_crs( m_ctxt, ctxt, PROJ_INTERMEDIATE_CRS_USE_ALWAYS); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); auto op = proj_list_get(m_ctxt, res, 0); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); EXPECT_EQ( proj_get_name(op), std::string("Ballpark geographic offset from ED50 to RGF93 v1")); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_operations_allow_ballpark_transformations) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "4267", PJ_CATEGORY_CRS, false, nullptr); // NAD27 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "4258", PJ_CATEGORY_CRS, false, nullptr); // ETRS89 ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); // Default: allowed implicitly { auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); } // Allow explicitly { proj_operation_factory_context_set_allow_ballpark_transformations( m_ctxt, ctxt, true); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); } // Disallow { proj_operation_factory_context_set_allow_ballpark_transformations( m_ctxt, ctxt, false); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 0); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_set_database_path_null) { EXPECT_TRUE( proj_context_set_database_path(m_ctxt, nullptr, nullptr, nullptr)); auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_set_database_path_aux) { const std::string auxDbName( "file:proj_test_aux.db?mode=memory&cache=shared"); sqlite3 *dbAux = nullptr; sqlite3_open_v2( auxDbName.c_str(), &dbAux, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr); ASSERT_TRUE(dbAux != nullptr); ASSERT_TRUE(sqlite3_exec(dbAux, "BEGIN", nullptr, nullptr, nullptr) == SQLITE_OK); { auto ctxt = DatabaseContext::create(); const auto dbStructure = ctxt->getDatabaseStructure(); for (const auto &sql : dbStructure) { ASSERT_TRUE(sqlite3_exec(dbAux, sql.c_str(), nullptr, nullptr, nullptr) == SQLITE_OK); } } ASSERT_TRUE(sqlite3_exec( dbAux, "INSERT INTO geodetic_crs VALUES('OTHER','OTHER_4326','WGS " "84',NULL,'geographic 2D','EPSG','6422','EPSG','6326'," "NULL,0);", nullptr, nullptr, nullptr) == SQLITE_OK); ASSERT_TRUE(sqlite3_exec(dbAux, "COMMIT", nullptr, nullptr, nullptr) == SQLITE_OK); const char *const aux_db_list[] = {auxDbName.c_str(), nullptr}; EXPECT_TRUE( proj_context_set_database_path(m_ctxt, nullptr, aux_db_list, nullptr)); sqlite3_close(dbAux); { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(crs, nullptr); ObjectKeeper keeper_source_crs(crs); } { auto crs = proj_create_from_database(m_ctxt, "OTHER", "OTHER_4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(crs, nullptr); ObjectKeeper keeper_source_crs(crs); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_set_database_path_error_1) { EXPECT_FALSE(proj_context_set_database_path(m_ctxt, "i_do_not_exist.db", nullptr, nullptr)); // We will eventually re-open on the default DB auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_set_database_path_error_2) { const char *aux_db_list[] = {"i_do_not_exist.db", nullptr}; EXPECT_FALSE( proj_context_set_database_path(m_ctxt, nullptr, aux_db_list, nullptr)); // We will eventually re-open on the default DB auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84 ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_guess_wkt_dialect) { EXPECT_EQ(proj_context_guess_wkt_dialect(nullptr, "LOCAL_CS[\"foo\"]"), PJ_GUESSED_WKT1_GDAL); EXPECT_EQ(proj_context_guess_wkt_dialect( nullptr, "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_" "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"), PJ_GUESSED_WKT1_ESRI); EXPECT_EQ(proj_context_guess_wkt_dialect( nullptr, " \n\t\rGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north],\n" " AXIS[\"geodetic longitude (Lon)\",east],\n" " UNIT[\"degree\",0.0174532925199433]]"), PJ_GUESSED_WKT2_2019); EXPECT_EQ(proj_context_guess_wkt_dialect( nullptr, "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north],\n" " AXIS[\"geodetic longitude (Lon)\",east],\n" " UNIT[\"degree\",0.0174532925199433]]"), PJ_GUESSED_WKT2_2015); EXPECT_EQ(proj_context_guess_wkt_dialect(nullptr, "foo"), PJ_GUESSED_NOT_WKT); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_from_name) { /* PJ_OBJ_LIST *proj_create_from_name( PJ_CONTEXT *ctx, const char *auth_name, const char *searchedName, const PJ_TYPE* types, size_t typesCount, int approximateMatch, size_t limitResultCount, const char* const *options); */ { auto res = proj_create_from_name(m_ctxt, nullptr, "WGS 84", nullptr, 0, false, 0, nullptr); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 5); } { auto res = proj_create_from_name(m_ctxt, "xx", "WGS 84", nullptr, 0, false, 0, nullptr); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 0); } { const PJ_TYPE types[] = {PJ_TYPE_GEODETIC_CRS, PJ_TYPE_PROJECTED_CRS}; auto res = proj_create_from_name(m_ctxt, nullptr, "WGS 84", types, 2, true, 10, nullptr); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 10); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_identify) { auto obj = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4807->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); { auto res = proj_identify(m_ctxt, obj, nullptr, nullptr, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); } { int *confidence = nullptr; auto res = proj_identify(m_ctxt, obj, "EPSG", nullptr, &confidence); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); EXPECT_EQ(confidence[0], 100); proj_int_list_destroy(confidence); } { auto objEllps = proj_create_from_wkt( m_ctxt, Ellipsoid::GRS1980->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeperEllps(objEllps); ASSERT_NE(objEllps, nullptr); auto res = proj_identify(m_ctxt, objEllps, nullptr, nullptr, nullptr); ObjListKeeper keeper_res(res); EXPECT_EQ(res, nullptr); } { auto obj2 = proj_create( m_ctxt, "+proj=longlat +datum=WGS84 +no_defs +type=crs"); ObjectKeeper keeper2(obj2); ASSERT_NE(obj2, nullptr); int *confidence = nullptr; auto res = proj_identify(m_ctxt, obj2, nullptr, nullptr, &confidence); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 4); proj_int_list_destroy(confidence); } { auto obj2 = proj_create_from_database(m_ctxt, "IGNF", "ETRS89UTM28", PJ_CATEGORY_CRS, false, nullptr); ObjectKeeper keeper2(obj2); ASSERT_NE(obj2, nullptr); int *confidence = nullptr; auto res = proj_identify(m_ctxt, obj2, "EPSG", nullptr, &confidence); ObjListKeeper keeper_res(res); EXPECT_EQ(proj_list_get_count(res), 1); auto gotCRS = proj_list_get(m_ctxt, res, 0); ASSERT_NE(gotCRS, nullptr); ObjectKeeper keeper_gotCRS(gotCRS); auto auth = proj_get_id_auth_name(gotCRS, 0); ASSERT_TRUE(auth != nullptr); EXPECT_EQ(auth, std::string("EPSG")); auto code = proj_get_id_code(gotCRS, 0); ASSERT_TRUE(code != nullptr); EXPECT_EQ(code, std::string("25828")); EXPECT_EQ(confidence[0], 70); proj_int_list_destroy(confidence); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_domain_count) { auto crs = proj_create_from_database(m_ctxt, "EPSG", "6316", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_EQ(proj_get_domain_count(crs), 2); const char *name = nullptr; EXPECT_TRUE(proj_get_area_of_use_ex(m_ctxt, crs, 0, nullptr, nullptr, nullptr, nullptr, &name)); ASSERT_TRUE(name != nullptr); EXPECT_EQ(std::string(name), "Bosnia and Herzegovina - east of 19°30'E; Kosovo; Montenegro - " "east of 19°30'E; Serbia - between 19°30'E and 22°30'E."); const char *scope = proj_get_scope_ex(crs, 0); ASSERT_TRUE(scope != nullptr); EXPECT_STREQ(scope, "Cadastre, engineering survey, topographic mapping " "(large and medium scale)."); EXPECT_TRUE(proj_get_area_of_use_ex(m_ctxt, crs, 1, nullptr, nullptr, nullptr, nullptr, &name)); ASSERT_TRUE(name != nullptr); EXPECT_EQ(std::string(name), "North Macedonia."); scope = proj_get_scope_ex(crs, 1); ASSERT_TRUE(scope != nullptr); EXPECT_STREQ(scope, "Cadastre."); EXPECT_FALSE(proj_get_area_of_use_ex(m_ctxt, crs, -1, nullptr, nullptr, nullptr, nullptr, &name)); EXPECT_FALSE(proj_get_area_of_use_ex(m_ctxt, crs, 2, nullptr, nullptr, nullptr, nullptr, &name)); EXPECT_EQ(proj_get_scope_ex(crs, -1), nullptr); EXPECT_EQ(proj_get_scope_ex(crs, 2), nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_area_of_use) { { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_TRUE(proj_get_area_of_use(m_ctxt, crs, nullptr, nullptr, nullptr, nullptr, nullptr)); const char *name = nullptr; double w; double s; double e; double n; EXPECT_TRUE(proj_get_area_of_use(m_ctxt, crs, &w, &s, &e, &n, &name)); EXPECT_EQ(w, -180); EXPECT_EQ(s, -90); EXPECT_EQ(e, 180); EXPECT_EQ(n, 90); ASSERT_TRUE(name != nullptr); EXPECT_EQ(std::string(name), "World."); } { auto obj = proj_create(m_ctxt, "+proj=longlat +type=crs"); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_FALSE(proj_get_area_of_use(m_ctxt, obj, nullptr, nullptr, nullptr, nullptr, nullptr)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_coordoperation_get_accuracy) { { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); EXPECT_EQ(proj_coordoperation_get_accuracy(m_ctxt, crs), -1.0); } { auto obj = proj_create_from_database(m_ctxt, "EPSG", "1170", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); EXPECT_EQ(proj_coordoperation_get_accuracy(m_ctxt, obj), 16.0); } { auto obj = proj_create(m_ctxt, "+proj=helmert"); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); EXPECT_EQ(proj_coordoperation_get_accuracy(m_ctxt, obj), -1.0); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_geographic_crs) { auto cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); { auto obj = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto objRef = proj_create(m_ctxt, GeographicCRS::EPSG_4326 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeperobjRef(objRef); EXPECT_NE(objRef, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT)); auto datum = proj_crs_get_datum(m_ctxt, obj); ObjectKeeper keeper_datum(datum); ASSERT_NE(datum, nullptr); auto obj2 = proj_create_geographic_crs_from_datum(m_ctxt, "WGS 84", datum, cs); ObjectKeeper keeperObj(obj2); ASSERT_NE(obj2, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, obj2, PJ_COMP_STRICT)); } { auto obj = proj_create_geographic_crs(m_ctxt, nullptr, nullptr, nullptr, 1.0, 0.0, nullptr, 0.0, nullptr, 0.0, cs); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); } // Datum with GDAL_WKT1 spelling: special case of WGS_1984 { auto obj = proj_create_geographic_crs( m_ctxt, "WGS 84", "WGS_1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto objRef = proj_create(m_ctxt, GeographicCRS::EPSG_4326 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeperobjRef(objRef); EXPECT_NE(objRef, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT)); } // Datum with GDAL_WKT1 spelling: database query { auto obj = proj_create_geographic_crs( m_ctxt, "NAD83", "North_American_Datum_1983", "GRS 1980", 6378137, 298.257222101, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto objRef = proj_create(m_ctxt, GeographicCRS::EPSG_4269 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeperobjRef(objRef); EXPECT_NE(objRef, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT)); } // Datum with GDAL_WKT1 spelling: database query in alias_name table { auto crs = proj_create_geographic_crs( m_ctxt, "S-JTSK (Ferro)", "System_Jednotne_Trigonometricke_Site_Katastralni_Ferro", "Bessel 1841", 6377397.155, 299.1528128, "Ferro", -17.66666666666667, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); auto datum = proj_crs_get_datum(m_ctxt, crs); ASSERT_NE(datum, nullptr); ObjectKeeper keeper_datum(datum); auto datum_name = proj_get_name(datum); ASSERT_TRUE(datum_name != nullptr); EXPECT_EQ(datum_name, std::string("System of the Unified Trigonometrical Cadastral " "Network (Ferro)")); } // WKT1 with (deprecated) { auto crs = proj_create_geographic_crs( m_ctxt, "SAD69 (deprecated)", "South_American_Datum_1969", "GRS 1967", 6378160, 298.247167427, "Greenwich", 0, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); auto name = proj_get_name(crs); ASSERT_TRUE(name != nullptr); EXPECT_EQ(name, std::string("SAD69")); EXPECT_TRUE(proj_is_deprecated(crs)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_geocentric_crs) { { auto obj = proj_create_geocentric_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto objRef = proj_create(m_ctxt, GeographicCRS::EPSG_4978 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeperobjRef(objRef); EXPECT_NE(objRef, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT)); auto datum = proj_crs_get_datum(m_ctxt, obj); ObjectKeeper keeper_datum(datum); ASSERT_NE(datum, nullptr); auto obj2 = proj_create_geocentric_crs_from_datum(m_ctxt, "WGS 84", datum, "Metre", 1.0); ObjectKeeper keeperObj(obj2); ASSERT_NE(obj2, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, obj2, PJ_COMP_STRICT)); } { auto obj = proj_create_geocentric_crs(m_ctxt, nullptr, nullptr, nullptr, 1.0, 0.0, nullptr, 0.0, nullptr, 0.0, nullptr, 0.0); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, check_coord_op_obj_can_be_used_with_proj_trans) { { auto projCRS = proj_create_conversion_utm(m_ctxt, 31, true); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); { PJ *pj_used = proj_trans_get_last_used_operation(projCRS); ASSERT_EQ(pj_used, nullptr); } PJ_COORD coord; coord.xyzt.x = proj_torad(3.0); coord.xyzt.y = 0; coord.xyzt.z = 0; coord.xyzt.t = 0; EXPECT_NEAR(proj_trans(projCRS, PJ_FWD, coord).xyzt.x, 500000.0, 1e-9); { PJ *pj_used = proj_trans_get_last_used_operation(projCRS); ASSERT_TRUE( proj_is_equivalent_to(pj_used, projCRS, PJ_COMP_STRICT)); proj_destroy(pj_used); } } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_projections) { { constexpr int invalid_zone_number = 0; auto projCRS = proj_create_conversion_utm(m_ctxt, invalid_zone_number, 0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_EQ(projCRS, nullptr); } /* BEGIN: Generated by scripts/create_c_api_projections.py*/ { auto projCRS = proj_create_conversion_utm(m_ctxt, 1, 0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_transverse_mercator( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_gauss_schreiber_transverse_mercator( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_transverse_mercator_south_oriented( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_two_point_equidistant( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_tunisia_mining_grid( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_albers_equal_area( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_conic_conformal_1sp( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_conic_conformal_1sp_variant_b( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_conic_conformal_2sp( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_conic_conformal_2sp_michigan( m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_conic_conformal_2sp_belgium( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_azimuthal_equidistant( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_guam_projection( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_bonne( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_cylindrical_equal_area_spherical( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_cylindrical_equal_area( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_cassini_soldner( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_equidistant_conic( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_eckert_i( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_eckert_ii( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_eckert_iii( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_eckert_iv( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_eckert_v( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_eckert_vi( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_equidistant_cylindrical( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_equidistant_cylindrical_spherical( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_gall( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_goode_homolosine( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_interrupted_goode_homolosine( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_geostationary_satellite_sweep_x( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_geostationary_satellite_sweep_y( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_gnomonic( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_hotine_oblique_mercator_variant_a( m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_hotine_oblique_mercator_variant_b( m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin( m_ctxt, 0, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_laborde_oblique_mercator( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_international_map_world_polyconic( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_krovak_north_oriented( m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_krovak(m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_lambert_azimuthal_equal_area( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_miller_cylindrical( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_mercator_variant_a( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_mercator_variant_b( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_popular_visualisation_pseudo_mercator( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_mollweide( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_new_zealand_mapping_grid( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_oblique_stereographic( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_orthographic( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_american_polyconic( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_polar_stereographic_variant_a( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_polar_stereographic_variant_b( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_robinson( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_sinusoidal( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_stereographic( m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_van_der_grinten( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_i( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_ii( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_iii( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_iv( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_v( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_vi( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_wagner_vii( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_quadrilateralized_spherical_cube( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_spherical_cross_track_height( m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_equal_earth( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_vertical_perspective( m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_pole_rotation_grib_convention( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } { auto projCRS = proj_create_conversion_pole_rotation_netcdf_cf_convention( m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } /* END: Generated by scripts/create_c_api_projections.py*/ } // --------------------------------------------------------------------------- TEST_F(CApi, proj_cs_get_axis_info) { { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper(crs); auto cs = proj_crs_get_coordinate_system(m_ctxt, crs); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); EXPECT_EQ(proj_cs_get_type(m_ctxt, cs), PJ_CS_TYPE_ELLIPSOIDAL); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 2); EXPECT_FALSE(proj_cs_get_axis_info(m_ctxt, cs, -1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); EXPECT_FALSE(proj_cs_get_axis_info(m_ctxt, cs, 2, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); const char *name = nullptr; const char *abbrev = nullptr; const char *direction = nullptr; double unitConvFactor = 0.0; const char *unitName = nullptr; const char *unitAuthority = nullptr; const char *unitCode = nullptr; EXPECT_TRUE(proj_cs_get_axis_info( m_ctxt, cs, 0, &name, &abbrev, &direction, &unitConvFactor, &unitName, &unitAuthority, &unitCode)); ASSERT_NE(name, nullptr); ASSERT_NE(abbrev, nullptr); ASSERT_NE(direction, nullptr); ASSERT_NE(unitName, nullptr); ASSERT_NE(unitAuthority, nullptr); ASSERT_NE(unitCode, nullptr); EXPECT_EQ(std::string(name), "Geodetic latitude"); EXPECT_EQ(std::string(abbrev), "Lat"); EXPECT_EQ(std::string(direction), "north"); EXPECT_EQ(unitConvFactor, 0.017453292519943295) << unitConvFactor; EXPECT_EQ(std::string(unitName), "degree"); EXPECT_EQ(std::string(unitAuthority), "EPSG"); EXPECT_EQ(std::string(unitCode), "9122"); } // Non CRS object { auto obj = proj_create_from_database(m_ctxt, "EPSG", "1170", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ASSERT_NE(obj, nullptr); ObjectKeeper keeper(obj); EXPECT_EQ(proj_crs_get_coordinate_system(m_ctxt, obj), nullptr); EXPECT_EQ(proj_cs_get_type(m_ctxt, obj), PJ_CS_TYPE_UNKNOWN); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, obj), -1); EXPECT_FALSE(proj_cs_get_axis_info(m_ctxt, obj, 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_get_database_metadata) { EXPECT_TRUE(proj_context_get_database_metadata(m_ctxt, "IGNF.VERSION") != nullptr); EXPECT_TRUE(proj_context_get_database_metadata(m_ctxt, "FOO") == nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_get_database_structure) { auto list = proj_context_get_database_structure(m_ctxt, nullptr); ASSERT_NE(list, nullptr); ASSERT_NE(list[0], nullptr); proj_string_list_destroy(list); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_clone) { auto obj = proj_create(m_ctxt, "+proj=longlat"); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto clone = proj_clone(m_ctxt, obj); ObjectKeeper keeperClone(clone); ASSERT_NE(clone, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, clone, PJ_COMP_STRICT)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_clone_of_obj_with_alternative_operations) { // NAD27 to NAD83 auto obj = proj_create_crs_to_crs(m_ctxt, "EPSG:4267", "EPSG:4269", nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); PJ_COORD c; c.xyzt.x = 40.5; c.xyzt.y = -60; c.xyzt.z = 0; c.xyzt.t = 2021; PJ_COORD c_trans_ref = proj_trans(obj, PJ_FWD, c); EXPECT_NE(c_trans_ref.xyzt.x, c.xyzt.x); EXPECT_NEAR(c_trans_ref.xyzt.x, c.xyzt.x, 1e-3); EXPECT_NEAR(c_trans_ref.xyzt.y, c.xyzt.y, 1e-3); PJ *pj_used = proj_trans_get_last_used_operation(obj); ASSERT_NE(pj_used, nullptr); proj_destroy(pj_used); auto clone = proj_clone(m_ctxt, obj); ObjectKeeper keeperClone(clone); ASSERT_NE(clone, nullptr); EXPECT_TRUE(proj_is_equivalent_to(obj, clone, PJ_COMP_STRICT)); keeper.clear(); obj = nullptr; (void)obj; PJ_COORD c_trans = proj_trans(clone, PJ_FWD, c); EXPECT_EQ(c_trans.xyzt.x, c_trans_ref.xyzt.x); EXPECT_EQ(c_trans.xyzt.y, c_trans_ref.xyzt.y); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_alter_geodetic_crs) { auto projCRS = proj_create_from_wkt( m_ctxt, createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(projCRS); ASSERT_NE(projCRS, nullptr); auto newGeodCRS = proj_create(m_ctxt, "+proj=longlat +type=crs"); ObjectKeeper keeper_newGeodCRS(newGeodCRS); ASSERT_NE(newGeodCRS, nullptr); auto geodCRS = proj_crs_get_geodetic_crs(m_ctxt, projCRS); ObjectKeeper keeper_geodCRS(geodCRS); ASSERT_NE(geodCRS, nullptr); auto geodCRSAltered = proj_crs_alter_geodetic_crs(m_ctxt, geodCRS, newGeodCRS); ObjectKeeper keeper_geodCRSAltered(geodCRSAltered); ASSERT_NE(geodCRSAltered, nullptr); EXPECT_TRUE( proj_is_equivalent_to(geodCRSAltered, newGeodCRS, PJ_COMP_STRICT)); { auto projCRSAltered = proj_crs_alter_geodetic_crs(m_ctxt, projCRS, newGeodCRS); ObjectKeeper keeper_projCRSAltered(projCRSAltered); ASSERT_NE(projCRSAltered, nullptr); EXPECT_EQ(proj_get_type(projCRSAltered), PJ_TYPE_PROJECTED_CRS); auto projCRSAltered_geodCRS = proj_crs_get_geodetic_crs(m_ctxt, projCRSAltered); ObjectKeeper keeper_projCRSAltered_geodCRS(projCRSAltered_geodCRS); ASSERT_NE(projCRSAltered_geodCRS, nullptr); EXPECT_TRUE(proj_is_equivalent_to(projCRSAltered_geodCRS, newGeodCRS, PJ_COMP_STRICT)); } // Check that proj_crs_alter_geodetic_crs preserves deprecation flag { auto projCRSDeprecated = proj_alter_name(m_ctxt, projCRS, "new name (deprecated)"); ObjectKeeper keeper_projCRSDeprecated(projCRSDeprecated); ASSERT_NE(projCRSDeprecated, nullptr); auto projCRSAltered = proj_crs_alter_geodetic_crs(m_ctxt, projCRSDeprecated, newGeodCRS); ObjectKeeper keeper_projCRSAltered(projCRSAltered); ASSERT_NE(projCRSAltered, nullptr); EXPECT_EQ(proj_get_name(projCRSAltered), std::string("new name")); EXPECT_TRUE(proj_is_deprecated(projCRSAltered)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_alter_cs_angular_unit) { auto crs = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); { auto alteredCRS = proj_crs_alter_cs_angular_unit(m_ctxt, crs, "my unit", 2, nullptr, nullptr); ObjectKeeper keeper_alteredCRS(alteredCRS); ASSERT_NE(alteredCRS, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); double unitConvFactor = 0.0; const char *unitName = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr, nullptr, &unitConvFactor, &unitName, nullptr, nullptr)); ASSERT_NE(unitName, nullptr); EXPECT_EQ(unitConvFactor, 2) << unitConvFactor; EXPECT_EQ(std::string(unitName), "my unit"); } { auto alteredCRS = proj_crs_alter_cs_angular_unit( m_ctxt, crs, "my unit", 2, "my auth", "my code"); ObjectKeeper keeper_alteredCRS(alteredCRS); ASSERT_NE(alteredCRS, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); double unitConvFactor = 0.0; const char *unitName = nullptr; const char *unitAuthName = nullptr; const char *unitCode = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr, nullptr, &unitConvFactor, &unitName, &unitAuthName, &unitCode)); ASSERT_NE(unitName, nullptr); EXPECT_EQ(unitConvFactor, 2) << unitConvFactor; EXPECT_EQ(std::string(unitName), "my unit"); ASSERT_NE(unitAuthName, nullptr); EXPECT_EQ(std::string(unitAuthName), "my auth"); ASSERT_NE(unitCode, nullptr); EXPECT_EQ(std::string(unitCode), "my code"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_alter_cs_linear_unit) { auto crs = proj_create_from_wkt( m_ctxt, createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); { auto alteredCRS = proj_crs_alter_cs_linear_unit(m_ctxt, crs, "my unit", 2, nullptr, nullptr); ObjectKeeper keeper_alteredCRS(alteredCRS); ASSERT_NE(alteredCRS, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); double unitConvFactor = 0.0; const char *unitName = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr, nullptr, &unitConvFactor, &unitName, nullptr, nullptr)); ASSERT_NE(unitName, nullptr); EXPECT_EQ(unitConvFactor, 2) << unitConvFactor; EXPECT_EQ(std::string(unitName), "my unit"); } { auto alteredCRS = proj_crs_alter_cs_linear_unit( m_ctxt, crs, "my unit", 2, "my auth", "my code"); ObjectKeeper keeper_alteredCRS(alteredCRS); ASSERT_NE(alteredCRS, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); double unitConvFactor = 0.0; const char *unitName = nullptr; const char *unitAuthName = nullptr; const char *unitCode = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr, nullptr, &unitConvFactor, &unitName, &unitAuthName, &unitCode)); ASSERT_NE(unitName, nullptr); EXPECT_EQ(unitConvFactor, 2) << unitConvFactor; EXPECT_EQ(std::string(unitName), "my unit"); ASSERT_NE(unitAuthName, nullptr); EXPECT_EQ(std::string(unitAuthName), "my auth"); ASSERT_NE(unitCode, nullptr); EXPECT_EQ(std::string(unitCode), "my code"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_alter_parameters_linear_unit) { auto crs = proj_create_from_wkt( m_ctxt, createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); { auto alteredCRS = proj_crs_alter_parameters_linear_unit( m_ctxt, crs, "my unit", 2, nullptr, nullptr, false); ObjectKeeper keeper_alteredCRS(alteredCRS); ASSERT_NE(alteredCRS, nullptr); auto wkt = proj_as_wkt(m_ctxt, alteredCRS, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("500000") != std::string::npos) << wkt; EXPECT_TRUE(std::string(wkt).find("\"my unit\",2") != std::string::npos) << wkt; } { auto alteredCRS = proj_crs_alter_parameters_linear_unit( m_ctxt, crs, "my unit", 2, nullptr, nullptr, true); ObjectKeeper keeper_alteredCRS(alteredCRS); ASSERT_NE(alteredCRS, nullptr); auto wkt = proj_as_wkt(m_ctxt, alteredCRS, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_TRUE(std::string(wkt).find("250000") != std::string::npos) << wkt; EXPECT_TRUE(std::string(wkt).find("\"my unit\",2") != std::string::npos) << wkt; } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_engineering_crs) { auto crs = proj_create_engineering_crs(m_ctxt, "name"); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); auto wkt = proj_as_wkt(m_ctxt, crs, PJ_WKT1_GDAL, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_EQ(std::string(wkt), "LOCAL_CS[\"name\",\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]") << wkt; } // --------------------------------------------------------------------------- TEST_F(CApi, proj_alter_name) { auto cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); auto obj = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); { auto alteredObj = proj_alter_name(m_ctxt, obj, "new name"); ObjectKeeper keeper_alteredObj(alteredObj); ASSERT_NE(alteredObj, nullptr); EXPECT_EQ(std::string(proj_get_name(alteredObj)), "new name"); EXPECT_FALSE(proj_is_deprecated(alteredObj)); } { auto alteredObj = proj_alter_name(m_ctxt, obj, "new name (deprecated)"); ObjectKeeper keeper_alteredObj(alteredObj); ASSERT_NE(alteredObj, nullptr); EXPECT_EQ(std::string(proj_get_name(alteredObj)), "new name"); EXPECT_TRUE(proj_is_deprecated(alteredObj)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_alter_id) { auto cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); auto obj = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); auto alteredObj = proj_alter_id(m_ctxt, obj, "auth", "code"); ObjectKeeper keeper_alteredObj(alteredObj); ASSERT_NE(alteredObj, nullptr); EXPECT_EQ(std::string(proj_get_id_auth_name(alteredObj, 0)), "auth"); EXPECT_EQ(std::string(proj_get_id_code(alteredObj, 0)), "code"); auto alteredObj2 = proj_alter_id(m_ctxt, alteredObj, "auth2", "code2"); ObjectKeeper keeper_alteredObj2(alteredObj2); ASSERT_NE(alteredObj2, nullptr); EXPECT_EQ(std::string(proj_get_id_auth_name(alteredObj2, 0)), "auth2"); EXPECT_EQ(std::string(proj_get_id_code(alteredObj2, 0)), "code2"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_projected_crs) { PJ_PARAM_DESCRIPTION param; param.name = "param name"; param.auth_name = nullptr; param.code = nullptr; param.value = 0.99; param.unit_name = nullptr; param.unit_conv_factor = 1.0; param.unit_type = PJ_UT_SCALE; auto conv = proj_create_conversion(m_ctxt, "conv", "conv auth", "conv code", "method", "method auth", "method code", 1, ¶m); ObjectKeeper keeper_conv(conv); ASSERT_NE(conv, nullptr); auto geog_cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); ObjectKeeper keeper_geog_cs(geog_cs); ASSERT_NE(geog_cs, nullptr); auto geogCRS = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs); ObjectKeeper keeper_geogCRS(geogCRS); ASSERT_NE(geogCRS, nullptr); auto cs = proj_create_cartesian_2D_cs(m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); auto projCRS = proj_create_projected_crs(m_ctxt, "my CRS", geogCRS, conv, cs); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_transformation) { PJ_PARAM_DESCRIPTION param; param.name = "param name"; param.auth_name = nullptr; param.code = nullptr; param.value = 0.99; param.unit_name = nullptr; param.unit_conv_factor = 1.0; param.unit_type = PJ_UT_SCALE; auto geog_cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); ObjectKeeper keeper_geog_cs(geog_cs); ASSERT_NE(geog_cs, nullptr); auto source_crs = proj_create_geographic_crs( m_ctxt, "Source CRS", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs); ObjectKeeper keeper_source_crs(source_crs); ASSERT_NE(source_crs, nullptr); auto target_crs = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs); ObjectKeeper keeper_target_crs(target_crs); ASSERT_NE(target_crs, nullptr); auto interp_crs = proj_create_geographic_crs( m_ctxt, "Interpolation CRS", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs); ObjectKeeper keeper_interp_crs(interp_crs); ASSERT_NE(interp_crs, nullptr); { auto transf = proj_create_transformation( m_ctxt, "transf", "transf auth", "transf code", source_crs, target_crs, interp_crs, "method", "method auth", "method code", 1, ¶m, 0); ObjectKeeper keeper_transf(transf); ASSERT_NE(transf, nullptr); EXPECT_EQ(proj_coordoperation_get_param_count(m_ctxt, transf), 1); auto got_source_crs = proj_get_source_crs(m_ctxt, transf); ObjectKeeper keeper_got_source_crs(got_source_crs); ASSERT_NE(got_source_crs, nullptr); EXPECT_TRUE( proj_is_equivalent_to(source_crs, got_source_crs, PJ_COMP_STRICT)); auto got_target_crs = proj_get_target_crs(m_ctxt, transf); ObjectKeeper keeper_got_target_crs(got_target_crs); ASSERT_NE(got_target_crs, nullptr); EXPECT_TRUE( proj_is_equivalent_to(target_crs, got_target_crs, PJ_COMP_STRICT)); } { auto transf = proj_create_transformation( m_ctxt, "transf", "transf auth", "transf code", source_crs, target_crs, nullptr, "method", "method auth", "method code", 1, ¶m, -1); ObjectKeeper keeper_transf(transf); ASSERT_NE(transf, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_compound_crs) { auto horiz_cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); ObjectKeeper keeper_horiz_cs(horiz_cs); ASSERT_NE(horiz_cs, nullptr); auto horiz_crs = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, horiz_cs); ObjectKeeper keeper_horiz_crs(horiz_crs); ASSERT_NE(horiz_crs, nullptr); auto vert_crs = proj_create_vertical_crs(m_ctxt, "myVertCRS", "myVertDatum", nullptr, 0.0); ObjectKeeper keeper_vert_crs(vert_crs); ASSERT_NE(vert_crs, nullptr); EXPECT_EQ(proj_get_name(vert_crs), std::string("myVertCRS")); auto compound_crs = proj_create_compound_crs(m_ctxt, "myCompoundCRS", horiz_crs, vert_crs); ObjectKeeper keeper_compound_crss(compound_crs); ASSERT_NE(compound_crs, nullptr); EXPECT_EQ(proj_get_name(compound_crs), std::string("myCompoundCRS")); auto subcrs_horiz = proj_crs_get_sub_crs(m_ctxt, compound_crs, 0); ASSERT_NE(subcrs_horiz, nullptr); ObjectKeeper keeper_subcrs_horiz(subcrs_horiz); EXPECT_TRUE(proj_is_equivalent_to(subcrs_horiz, horiz_crs, PJ_COMP_STRICT)); auto subcrs_vert = proj_crs_get_sub_crs(m_ctxt, compound_crs, 1); ASSERT_NE(subcrs_vert, nullptr); ObjectKeeper keeper_subcrs_vert(subcrs_vert); EXPECT_TRUE(proj_is_equivalent_to(subcrs_vert, vert_crs, PJ_COMP_STRICT)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_convert_conversion_to_other_method) { { auto geog_cs = proj_create_ellipsoidal_2D_cs( m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); ObjectKeeper keeper_geog_cs(geog_cs); ASSERT_NE(geog_cs, nullptr); auto geogCRS = proj_create_geographic_crs( m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs); ObjectKeeper keeper_geogCRS(geogCRS); ASSERT_NE(geogCRS, nullptr); auto cs = proj_create_cartesian_2D_cs( m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); auto conv = proj_create_conversion_mercator_variant_a( m_ctxt, 0, 1, 0.99, 2, 3, "Degree", 0.0174532925199433, "Metre", 1.0); ObjectKeeper keeper_conv(conv); ASSERT_NE(conv, nullptr); auto projCRS = proj_create_projected_crs(m_ctxt, "my CRS", geogCRS, conv, cs); ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); // Wrong object type EXPECT_EQ( proj_convert_conversion_to_other_method( m_ctxt, projCRS, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, nullptr), nullptr); auto conv_in_proj = proj_crs_get_coordoperation(m_ctxt, projCRS); ObjectKeeper keeper_conv_in_proj(conv_in_proj); ASSERT_NE(conv_in_proj, nullptr); // 3rd and 4th argument both 0/null EXPECT_EQ(proj_convert_conversion_to_other_method(m_ctxt, conv_in_proj, 0, nullptr), nullptr); auto new_conv = proj_convert_conversion_to_other_method( m_ctxt, conv_in_proj, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, nullptr); ObjectKeeper keeper_new_conv(new_conv); ASSERT_NE(new_conv, nullptr); EXPECT_FALSE( proj_is_equivalent_to(new_conv, conv_in_proj, PJ_COMP_STRICT)); EXPECT_TRUE( proj_is_equivalent_to(new_conv, conv_in_proj, PJ_COMP_EQUIVALENT)); auto new_conv_from_name = proj_convert_conversion_to_other_method( m_ctxt, conv_in_proj, 0, EPSG_NAME_METHOD_MERCATOR_VARIANT_B); ObjectKeeper keeper_new_conv_from_name(new_conv_from_name); ASSERT_NE(new_conv_from_name, nullptr); EXPECT_TRUE(proj_is_equivalent_to(new_conv, new_conv_from_name, PJ_COMP_STRICT)); auto new_conv_back = proj_convert_conversion_to_other_method( m_ctxt, conv_in_proj, 0, EPSG_NAME_METHOD_MERCATOR_VARIANT_A); ObjectKeeper keeper_new_conv_back(new_conv_back); ASSERT_NE(new_conv_back, nullptr); EXPECT_TRUE( proj_is_equivalent_to(conv_in_proj, new_conv_back, PJ_COMP_STRICT)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_non_deprecated) { auto crs = proj_create_from_database(m_ctxt, "EPSG", "4226", PJ_CATEGORY_CRS, false, nullptr); ObjectKeeper keeper(crs); ASSERT_NE(crs, nullptr); auto list = proj_get_non_deprecated(m_ctxt, crs); ASSERT_NE(list, nullptr); ObjListKeeper keeper_list(list); EXPECT_EQ(proj_list_get_count(list), 2); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_query_geodetic_crs_from_datum) { { auto list = proj_query_geodetic_crs_from_datum(m_ctxt, nullptr, "EPSG", "6326", nullptr); ASSERT_NE(list, nullptr); ObjListKeeper keeper_list(list); EXPECT_GE(proj_list_get_count(list), 3); } { auto list = proj_query_geodetic_crs_from_datum(m_ctxt, "EPSG", "EPSG", "6326", "geographic 2D"); ASSERT_NE(list, nullptr); ObjListKeeper keeper_list(list); EXPECT_EQ(proj_list_get_count(list), 1); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_uom_get_info_from_database) { { EXPECT_FALSE(proj_uom_get_info_from_database( m_ctxt, "auth", "code", nullptr, nullptr, nullptr)); } { EXPECT_TRUE(proj_uom_get_info_from_database(m_ctxt, "EPSG", "9001", nullptr, nullptr, nullptr)); } { const char *name = nullptr; double conv_factor = 0.0; const char *category = nullptr; EXPECT_TRUE(proj_uom_get_info_from_database( m_ctxt, "EPSG", "9001", &name, &conv_factor, &category)); ASSERT_NE(name, nullptr); ASSERT_NE(category, nullptr); EXPECT_EQ(std::string(name), "metre"); EXPECT_EQ(conv_factor, 1.0); EXPECT_EQ(std::string(category), "linear"); } { const char *name = nullptr; double conv_factor = 0.0; const char *category = nullptr; EXPECT_TRUE(proj_uom_get_info_from_database( m_ctxt, "EPSG", "9102", &name, &conv_factor, &category)); ASSERT_NE(name, nullptr); ASSERT_NE(category, nullptr); EXPECT_EQ(std::string(name), "degree"); EXPECT_NEAR(conv_factor, UnitOfMeasure::DEGREE.conversionToSI(), 1e-10); EXPECT_EQ(std::string(category), "angular"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_grid_get_info_from_database) { { EXPECT_FALSE(proj_grid_get_info_from_database(m_ctxt, "xxx", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); } { EXPECT_TRUE(proj_grid_get_info_from_database( m_ctxt, "au_icsm_GDA94_GDA2020_conformal.tif", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); } { const char *full_name = nullptr; const char *package_name = nullptr; const char *url = nullptr; int direct_download = 0; int open_license = 0; int available = 0; EXPECT_TRUE(proj_grid_get_info_from_database( m_ctxt, "au_icsm_GDA94_GDA2020_conformal.tif", &full_name, &package_name, &url, &direct_download, &open_license, &available)); ASSERT_NE(full_name, nullptr); // empty string expected as the file is not in test data EXPECT_TRUE(full_name[0] == 0); ASSERT_NE(package_name, nullptr); EXPECT_TRUE(package_name[0] == 0); // empty string expected ASSERT_NE(url, nullptr); EXPECT_EQ(std::string(url), "https://cdn.proj.org/au_icsm_GDA94_GDA2020_conformal.tif"); EXPECT_EQ(direct_download, 1); EXPECT_EQ(open_license, 1); } // Same test as above, but with PROJ 6 grid name { const char *full_name = nullptr; const char *package_name = nullptr; const char *url = nullptr; int direct_download = 0; int open_license = 0; int available = 0; EXPECT_TRUE(proj_grid_get_info_from_database( m_ctxt, "GDA94_GDA2020_conformal.gsb", &full_name, &package_name, &url, &direct_download, &open_license, &available)); ASSERT_NE(full_name, nullptr); // empty string expected as the file is not in test data EXPECT_TRUE(full_name[0] == 0); ASSERT_NE(package_name, nullptr); EXPECT_TRUE(package_name[0] == 0); // empty string expected ASSERT_NE(url, nullptr); EXPECT_EQ(std::string(url), "https://cdn.proj.org/au_icsm_GDA94_GDA2020_conformal.tif"); EXPECT_EQ(direct_download, 1); EXPECT_EQ(open_license, 1); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_cartesian_2D_cs) { { auto cs = proj_create_cartesian_2D_cs( m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); } { auto cs = proj_create_cartesian_2D_cs( m_ctxt, PJ_CART2D_NORTHING_EASTING, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); } { auto cs = proj_create_cartesian_2D_cs( m_ctxt, PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); } { auto cs = proj_create_cartesian_2D_cs( m_ctxt, PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); } { auto cs = proj_create_cartesian_2D_cs( m_ctxt, PJ_CART2D_WESTING_SOUTHING, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_crs_info_list_from_database) { { proj_crs_info_list_destroy(nullptr); } { proj_get_crs_list_parameters_destroy(nullptr); } // All null parameters { auto list = proj_get_crs_info_list_from_database(nullptr, nullptr, nullptr, nullptr); ASSERT_NE(list, nullptr); ASSERT_NE(list[0], nullptr); EXPECT_NE(list[0]->auth_name, nullptr); EXPECT_NE(list[0]->code, nullptr); EXPECT_NE(list[0]->name, nullptr); proj_crs_info_list_destroy(list); } // Default parameters { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, &result_count); proj_get_crs_list_parameters_destroy(params); ASSERT_NE(list, nullptr); EXPECT_GT(result_count, 1); EXPECT_EQ(list[result_count], nullptr); bool found4326 = false; bool found4978 = false; bool found4979 = false; bool found32631 = false; bool found3855 = false; bool found3901 = false; for (int i = 0; i < result_count; i++) { // EPSG should only include Earth CRS, at least for now... EXPECT_EQ(std::string(list[i]->celestial_body_name), "Earth"); auto code = std::string(list[i]->code); if (code == "4326") { found4326 = true; EXPECT_EQ(std::string(list[i]->auth_name), "EPSG"); EXPECT_EQ(std::string(list[i]->name), "WGS 84"); EXPECT_EQ(list[i]->type, PJ_TYPE_GEOGRAPHIC_2D_CRS); EXPECT_EQ(list[i]->deprecated, 0); EXPECT_EQ(list[i]->bbox_valid, 1); EXPECT_EQ(list[i]->west_lon_degree, -180.0); EXPECT_EQ(list[i]->south_lat_degree, -90.0); EXPECT_EQ(list[i]->east_lon_degree, 180.0); EXPECT_EQ(list[i]->north_lat_degree, 90.0); EXPECT_TRUE(std::string(list[i]->area_name).find("World") == 0) << std::string(list[i]->area_name); EXPECT_EQ(list[i]->projection_method_name, nullptr); } else if (code == "4978") { found4978 = true; EXPECT_EQ(list[i]->type, PJ_TYPE_GEOCENTRIC_CRS); } else if (code == "4979") { found4979 = true; EXPECT_EQ(list[i]->type, PJ_TYPE_GEOGRAPHIC_3D_CRS); } else if (code == "32631") { found32631 = true; EXPECT_EQ(list[i]->type, PJ_TYPE_PROJECTED_CRS); EXPECT_EQ(std::string(list[i]->projection_method_name), "Transverse Mercator"); } else if (code == "3855") { found3855 = true; EXPECT_EQ(list[i]->type, PJ_TYPE_VERTICAL_CRS); } else if (code == "3901") { found3901 = true; EXPECT_EQ(list[i]->type, PJ_TYPE_COMPOUND_CRS); } EXPECT_EQ(list[i]->deprecated, 0); } EXPECT_TRUE(found4326); EXPECT_TRUE(found4978); EXPECT_TRUE(found4979); EXPECT_TRUE(found32631); EXPECT_TRUE(found3855); EXPECT_TRUE(found3901); proj_crs_info_list_destroy(list); } // Filter on only geodetic crs { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->typesCount = 1; auto type = PJ_TYPE_GEODETIC_CRS; params->types = &type; auto list = proj_get_crs_info_list_from_database(m_ctxt, nullptr, params, &result_count); bool foundGeog2D = false; bool foundGeog3D = false; bool foundGeocentric = false; bool foundGeodeticCRS = false; // for now, only -ocentric ellipsoidal IAU CRS for (int i = 0; i < result_count; i++) { foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS; foundGeog3D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS; foundGeocentric |= list[i]->type == PJ_TYPE_GEOCENTRIC_CRS; foundGeodeticCRS |= list[i]->type == PJ_TYPE_GEODETIC_CRS; EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS || list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS || list[i]->type == PJ_TYPE_GEOCENTRIC_CRS || list[i]->type == PJ_TYPE_GEODETIC_CRS); } EXPECT_TRUE(foundGeog2D); EXPECT_TRUE(foundGeog3D); EXPECT_TRUE(foundGeocentric); EXPECT_TRUE(foundGeodeticCRS); proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } // Filter on only geographic crs { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->typesCount = 1; auto type = PJ_TYPE_GEOGRAPHIC_CRS; params->types = &type; auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, &result_count); bool foundGeog2D = false; bool foundGeog3D = false; for (int i = 0; i < result_count; i++) { foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS; foundGeog3D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS; EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS || list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS); } EXPECT_TRUE(foundGeog2D); EXPECT_TRUE(foundGeog3D); proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } // Filter on only geographic 2D crs and projected CRS { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->typesCount = 2; const PJ_TYPE types[] = {PJ_TYPE_GEOGRAPHIC_2D_CRS, PJ_TYPE_PROJECTED_CRS}; params->types = types; auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, &result_count); bool foundGeog2D = false; bool foundProjected = false; for (int i = 0; i < result_count; i++) { foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS; foundProjected |= list[i]->type == PJ_TYPE_PROJECTED_CRS; EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS || list[i]->type == PJ_TYPE_PROJECTED_CRS); } EXPECT_TRUE(foundGeog2D); EXPECT_TRUE(foundProjected); proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } // Filter on bbox (inclusion) { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->bbox_valid = 1; params->west_lon_degree = 2; params->south_lat_degree = 49; params->east_lon_degree = 2.1; params->north_lat_degree = 49.1; params->typesCount = 1; auto type = PJ_TYPE_PROJECTED_CRS; params->types = &type; auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, &result_count); ASSERT_NE(list, nullptr); EXPECT_GT(result_count, 1); for (int i = 0; i < result_count; i++) { if (list[i]->west_lon_degree < list[i]->east_lon_degree) { EXPECT_LE(list[i]->west_lon_degree, params->west_lon_degree); EXPECT_GE(list[i]->east_lon_degree, params->east_lon_degree); } EXPECT_LE(list[i]->south_lat_degree, params->south_lat_degree); EXPECT_GE(list[i]->north_lat_degree, params->north_lat_degree); } proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } // Filter on bbox (intersection) { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->bbox_valid = 1; params->west_lon_degree = 2; params->south_lat_degree = 49; params->east_lon_degree = 2.1; params->north_lat_degree = 49.1; params->crs_area_of_use_contains_bbox = 0; params->typesCount = 1; auto type = PJ_TYPE_PROJECTED_CRS; params->types = &type; auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, &result_count); ASSERT_NE(list, nullptr); EXPECT_GT(result_count, 1); for (int i = 0; i < result_count; i++) { if (list[i]->west_lon_degree < list[i]->east_lon_degree) { EXPECT_LE(list[i]->west_lon_degree, params->west_lon_degree); EXPECT_GE(list[i]->east_lon_degree, params->east_lon_degree); } EXPECT_LE(list[i]->south_lat_degree, params->north_lat_degree); EXPECT_GE(list[i]->north_lat_degree, params->south_lat_degree); } proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } // Filter on celestial body { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->celestial_body_name = "non existing"; auto list = proj_get_crs_info_list_from_database(m_ctxt, nullptr, params, &result_count); ASSERT_NE(list, nullptr); EXPECT_EQ(result_count, 0); proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } // Filter on celestial body { int result_count = 0; auto params = proj_get_crs_list_parameters_create(); params->celestial_body_name = "Earth"; auto list = proj_get_crs_info_list_from_database(m_ctxt, nullptr, params, &result_count); ASSERT_NE(list, nullptr); EXPECT_GT(result_count, 0); proj_get_crs_list_parameters_destroy(params); proj_crs_info_list_destroy(list); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_units_from_database) { { proj_unit_list_destroy(nullptr); } { auto list = proj_get_units_from_database(nullptr, nullptr, nullptr, true, nullptr); ASSERT_NE(list, nullptr); ASSERT_NE(list[0], nullptr); ASSERT_NE(list[0]->auth_name, nullptr); ASSERT_NE(list[0]->code, nullptr); ASSERT_NE(list[0]->name, nullptr); proj_unit_list_destroy(list); } { int result_count = 0; auto list = proj_get_units_from_database(nullptr, "EPSG", "linear", false, &result_count); ASSERT_NE(list, nullptr); EXPECT_GT(result_count, 1); EXPECT_EQ(list[result_count], nullptr); bool found9001 = false; for (int i = 0; i < result_count; i++) { EXPECT_EQ(std::string(list[i]->auth_name), "EPSG"); if (std::string(list[i]->code) == "9001") { EXPECT_EQ(std::string(list[i]->name), "metre"); EXPECT_EQ(std::string(list[i]->category), "linear"); EXPECT_EQ(list[i]->conv_factor, 1.0); ASSERT_NE(list[i]->proj_short_name, nullptr); EXPECT_EQ(std::string(list[i]->proj_short_name), "m"); EXPECT_EQ(list[i]->deprecated, 0); found9001 = true; } EXPECT_EQ(list[i]->deprecated, 0); } EXPECT_TRUE(found9001); proj_unit_list_destroy(list); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_celestial_body_list_from_database) { { proj_celestial_body_list_destroy(nullptr); } { auto list = proj_get_celestial_body_list_from_database(nullptr, nullptr, nullptr); ASSERT_NE(list, nullptr); ASSERT_NE(list[0], nullptr); ASSERT_NE(list[0]->auth_name, nullptr); ASSERT_NE(list[0]->name, nullptr); proj_celestial_body_list_destroy(list); } { int result_count = 0; auto list = proj_get_celestial_body_list_from_database(nullptr, "ESRI", &result_count); ASSERT_NE(list, nullptr); EXPECT_GT(result_count, 1); EXPECT_EQ(list[result_count], nullptr); bool foundGanymede = false; for (int i = 0; i < result_count; i++) { EXPECT_EQ(std::string(list[i]->auth_name), "ESRI"); if (std::string(list[i]->name) == "Ganymede") { foundGanymede = true; } } EXPECT_TRUE(foundGanymede); proj_celestial_body_list_destroy(list); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_normalize_for_visualization) { { auto P = proj_create(m_ctxt, "+proj=utm +zone=31 +ellps=WGS84"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); EXPECT_EQ(Pnormalized, nullptr); } auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "EPSG:32631", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); ASSERT_NE(Pnormalized, nullptr); auto projstr = proj_as_proj_string(m_ctxt, Pnormalized, PJ_PROJ_5, nullptr); ASSERT_NE(projstr, nullptr); EXPECT_EQ(std::string(projstr), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=31 +ellps=WGS84"); EXPECT_TRUE(proj_degree_input(Pnormalized, PJ_FWD)); EXPECT_FALSE(proj_degree_output(Pnormalized, PJ_FWD)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_normalize_for_visualization_with_alternatives) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "EPSG:3003", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); ASSERT_NE(Pnormalized, nullptr); EXPECT_TRUE(proj_degree_input(Pnormalized, PJ_FWD)); EXPECT_FALSE(proj_degree_output(Pnormalized, PJ_FWD)); { PJ_COORD c; // Approximately Roma c.xyzt.x = 12.5; c.xyzt.y = 42; c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(Pnormalized, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 1789912.46264783037, 1e-8); EXPECT_NEAR(c.xy.y, 4655716.25402576849, 1e-8); auto projstr = proj_pj_info(Pnormalized).definition; ASSERT_NE(projstr, nullptr); EXPECT_EQ(std::string(projstr), "proj=pipeline step proj=unitconvert xy_in=deg xy_out=rad " "step proj=push v_3 step proj=cart ellps=WGS84 " "step inv proj=helmert x=-104.1 y=-49.1 z=-9.9 rx=0.971 " "ry=-2.917 rz=0.714 s=-11.68 convention=position_vector " "step inv proj=cart ellps=intl step proj=pop v_3 " "step proj=tmerc lat_0=0 lon_0=9 k=0.9996 x_0=1500000 " "y_0=0 ellps=intl"); } { PJ_COORD c; // Approximately Roma c.xyzt.x = 1789912.46264783037; c.xyzt.y = 4655716.25402576849; c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(Pnormalized, PJ_INV, c); EXPECT_NEAR(c.lp.lam, 12.5, 1e-8); EXPECT_NEAR(c.lp.phi, 42, 1e-8); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_normalize_for_visualization_with_alternatives_reverse) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:3003", "EPSG:4326", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); ASSERT_NE(Pnormalized, nullptr); EXPECT_FALSE(proj_degree_input(Pnormalized, PJ_FWD)); EXPECT_TRUE(proj_degree_output(Pnormalized, PJ_FWD)); PJ_COORD c; // Approximately Roma c.xyzt.x = 1789912.46264783037; c.xyzt.y = 4655716.25402576849; c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(Pnormalized, PJ_FWD, c); EXPECT_NEAR(c.lp.lam, 12.5, 1e-8); EXPECT_NEAR(c.lp.phi, 42, 1e-8); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_normalize_for_visualization_on_crs) { auto P = proj_create(m_ctxt, "EPSG:4326"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); ASSERT_NE(Pnormalized, nullptr); EXPECT_EQ(proj_get_id_code(Pnormalized, 0), nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, Pnormalized); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); const char *name = nullptr; ASSERT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, &name, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); ASSERT_NE(name, nullptr); EXPECT_EQ(std::string(name), "Geodetic longitude"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_coordoperation_create_inverse) { auto P = proj_create( m_ctxt, "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push " "+v_3 +step +proj=cart +ellps=evrst30 +step +proj=helmert " "+x=293 +y=836 +z=318 +rx=0.5 +ry=1.6 +rz=-2.8 +s=2.1 " "+convention=position_vector +step +inv +proj=cart " "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pinversed = proj_coordoperation_create_inverse(m_ctxt, P); ObjectKeeper keeper_Pinversed(Pinversed); ASSERT_NE(Pinversed, nullptr); const char *options[] = {"MULTILINE=YES", "INDENTATION_WIDTH=4", "MAX_LINE_LENGTH=40", nullptr}; auto projstr = proj_as_proj_string(m_ctxt, Pinversed, PJ_PROJ_5, options); ASSERT_NE(projstr, nullptr); const char *expected_projstr = "+proj=pipeline\n" " +step +proj=axisswap +order=2,1\n" " +step +proj=unitconvert +xy_in=deg\n" " +xy_out=rad\n" " +step +proj=push +v_3\n" " +step +proj=cart +ellps=WGS84\n" " +step +inv +proj=helmert +x=293\n" " +y=836 +z=318 +rx=0.5 +ry=1.6\n" " +rz=-2.8 +s=2.1\n" " +convention=position_vector\n" " +step +inv +proj=cart +ellps=evrst30\n" " +step +proj=pop +v_3\n" " +step +proj=unitconvert +xy_in=rad\n" " +xy_out=deg\n" " +step +proj=axisswap +order=2,1"; EXPECT_EQ(std::string(projstr), expected_projstr); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_remarks) { // Transformation { auto co = proj_create_from_database(m_ctxt, "EPSG", "8048", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ObjectKeeper keeper(co); ASSERT_NE(co, nullptr); auto remarks = proj_get_remarks(co); ASSERT_NE(remarks, nullptr); EXPECT_TRUE(std::string(remarks).find( "Scale difference in ppb where 1/billion = 1E-9.") == 0) << remarks; } // Conversion { auto co = proj_create_from_database(m_ctxt, "EPSG", "3811", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ObjectKeeper keeper(co); ASSERT_NE(co, nullptr); auto remarks = proj_get_remarks(co); ASSERT_NE(remarks, nullptr); EXPECT_EQ(remarks, std::string("Replaces Lambert 2005.")); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_scope) { // Transformation { auto co = proj_create_from_database(m_ctxt, "EPSG", "8048", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ObjectKeeper keeper(co); ASSERT_NE(co, nullptr); auto scope = proj_get_scope(co); ASSERT_NE(scope, nullptr); EXPECT_EQ(scope, std::string("Transformation of GDA94 coordinates that have " "been derived through GNSS CORS.")); } // Conversion { auto co = proj_create_from_database(m_ctxt, "EPSG", "3811", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ObjectKeeper keeper(co); ASSERT_NE(co, nullptr); auto scope = proj_get_scope(co); ASSERT_NE(scope, nullptr); EXPECT_EQ(scope, std::string("Engineering survey, topographic mapping.")); } { auto P = proj_create(m_ctxt, "+proj=noop"); ObjectKeeper keeper(P); ASSERT_NE(P, nullptr); auto scope = proj_get_scope(P); ASSERT_EQ(scope, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_concatoperation_get_step) { // Test on a non concatenated operation { auto co = proj_create_from_database(m_ctxt, "EPSG", "8048", PJ_CATEGORY_COORDINATE_OPERATION, false, nullptr); ObjectKeeper keeper(co); ASSERT_NE(co, nullptr); ASSERT_NE(proj_get_type(co), PJ_TYPE_CONCATENATED_OPERATION); ASSERT_EQ(proj_concatoperation_get_step_count(m_ctxt, co), 0); ASSERT_EQ(proj_concatoperation_get_step(m_ctxt, co, 0), nullptr); } // Test on a concatenated operation { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); // GDA94 / MGA zone 56 auto source_crs = proj_create_from_database( m_ctxt, "EPSG", "28356", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(source_crs, nullptr); ObjectKeeper keeper_source_crs(source_crs); // GDA2020 / MGA zone 56 auto target_crs = proj_create_from_database( m_ctxt, "EPSG", "7856", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(target_crs, nullptr); ObjectKeeper keeper_target_crs(target_crs); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt); ASSERT_NE(res, nullptr); ObjListKeeper keeper_res(res); ASSERT_GT(proj_list_get_count(res), 0); auto op = proj_list_get(m_ctxt, res, 0); ASSERT_NE(op, nullptr); ObjectKeeper keeper_op(op); ASSERT_EQ(proj_get_type(op), PJ_TYPE_CONCATENATED_OPERATION); ASSERT_EQ(proj_concatoperation_get_step_count(m_ctxt, op), 3); EXPECT_EQ(proj_concatoperation_get_step(m_ctxt, op, -1), nullptr); EXPECT_EQ(proj_concatoperation_get_step(m_ctxt, op, 3), nullptr); auto step = proj_concatoperation_get_step(m_ctxt, op, 1); ASSERT_NE(step, nullptr); ObjectKeeper keeper_step(step); const char *scope = proj_get_scope(step); EXPECT_NE(scope, nullptr); EXPECT_NE(std::string(scope), std::string()); const char *remarks = proj_get_remarks(step); EXPECT_NE(remarks, nullptr); EXPECT_NE(std::string(remarks), std::string()); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_as_projjson) { auto obj = proj_create( m_ctxt, Ellipsoid::WGS84->exportToJSON(JSONFormatter::create().get()).c_str()); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr); { auto projjson = proj_as_projjson(m_ctxt, obj, nullptr); ASSERT_NE(projjson, nullptr); EXPECT_EQ(std::string(projjson), "{\n" " \"$schema\": " "\"https://proj.org/schemas/v0.7/projjson.schema.json\",\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7030\n" " }\n" "}"); } { const char *const options[] = {"INDENTATION_WIDTH=4", "SCHEMA=", nullptr}; auto projjson = proj_as_projjson(m_ctxt, obj, options); ASSERT_NE(projjson, nullptr); EXPECT_EQ(std::string(projjson), "{\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" " \"semi_major_axis\": 6378137,\n" " \"inverse_flattening\": 298.257223563,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 7030\n" " }\n" "}"); } { const char *const options[] = {"MULTILINE=NO", "SCHEMA=", nullptr}; auto projjson = proj_as_projjson(m_ctxt, obj, options); ASSERT_NE(projjson, nullptr); EXPECT_EQ(std::string(projjson), "{\"type\":\"Ellipsoid\",\"name\":\"WGS 84\"," "\"semi_major_axis\":6378137," "\"inverse_flattening\":298.257223563," "\"id\":{\"authority\":\"EPSG\",\"code\":7030}}"); } } // --------------------------------------------------------------------------- #if !defined(EMBED_RESOURCE_FILES) && !defined(USE_ONLY_EMBEDDED_RESOURCE_FILES) TEST_F(CApi, proj_context_copy_from_default) { auto c_path = proj_context_get_database_path(m_ctxt); ASSERT_TRUE(c_path != nullptr); std::string path(c_path); FILE *f = fopen(path.c_str(), "rb"); ASSERT_NE(f, nullptr); fseek(f, 0, SEEK_END); auto length = ftell(f); std::string content; content.resize(static_cast(length)); fseek(f, 0, SEEK_SET); auto read_bytes = fread(&content[0], 1, content.size(), f); ASSERT_EQ(read_bytes, content.size()); fclose(f); const char *tempdir = getenv("TEMP"); if (!tempdir) { tempdir = getenv("TMP"); } if (!tempdir) { tempdir = "/tmp"; } std::string tmp_filename(std::string(tempdir) + "/test_proj_context_set_autoclose_database.db"); f = fopen(tmp_filename.c_str(), "wb"); if (!f) { std::cerr << "Cannot create " << tmp_filename << std::endl; return; } fwrite(content.data(), 1, content.size(), f); fclose(f); auto c_default_path = proj_context_get_database_path(nullptr); std::string default_path(c_default_path ? c_default_path : ""); EXPECT_TRUE(proj_context_set_database_path(nullptr, tmp_filename.c_str(), nullptr, nullptr)); PJ_CONTEXT *new_ctx = proj_context_create(); EXPECT_TRUE(proj_context_set_database_path( nullptr, default_path.empty() ? nullptr : default_path.c_str(), nullptr, nullptr)); EXPECT_NE(new_ctx, nullptr); PjContextKeeper keeper_ctxt(new_ctx); auto c_new_path = proj_context_get_database_path(new_ctx); ASSERT_TRUE(c_new_path != nullptr); std::string new_db_path(c_new_path); ASSERT_EQ(new_db_path, tmp_filename); } #endif // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_clone) { int new_init_rules = proj_context_get_use_proj4_init_rules(nullptr, 0) > 0 ? 0 : 1; PJ_CONTEXT *new_ctx = proj_context_create(); EXPECT_NE(new_ctx, nullptr); PjContextKeeper keeper_ctxt(new_ctx); proj_context_use_proj4_init_rules(new_ctx, new_init_rules); PJ_CONTEXT *clone_ctx = proj_context_clone(new_ctx); EXPECT_NE(clone_ctx, nullptr); PjContextKeeper keeper_clone_ctxt(clone_ctx); ASSERT_EQ(proj_context_get_use_proj4_init_rules(new_ctx, 0), proj_context_get_use_proj4_init_rules(clone_ctx, 0)); EXPECT_NE(proj_context_get_use_proj4_init_rules(NULL, 0), proj_context_get_use_proj4_init_rules(clone_ctx, 0)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_crs_to_crs_from_pj) { auto src = proj_create(m_ctxt, "EPSG:4326"); ObjectKeeper keeper_src(src); ASSERT_NE(src, nullptr); auto dst = proj_create(m_ctxt, "EPSG:32631"); ObjectKeeper keeper_dst(dst); ASSERT_NE(dst, nullptr); auto P = proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); ASSERT_NE(Pnormalized, nullptr); auto projstr = proj_as_proj_string(m_ctxt, Pnormalized, PJ_PROJ_5, nullptr); ASSERT_NE(projstr, nullptr); EXPECT_EQ(std::string(projstr), "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=31 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_crs_to_crs_from_pj_accuracy_filter) { auto src = proj_create(m_ctxt, "EPSG:4326"); // WGS 84 ObjectKeeper keeper_src(src); ASSERT_NE(src, nullptr); auto dst = proj_create(m_ctxt, "EPSG:4258"); // ETRS89 ObjectKeeper keeper_dst(dst); ASSERT_NE(dst, nullptr); // No options { auto P = proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); } { const char *const options[] = {"ACCURACY=0.05", nullptr}; auto P = proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, options); ObjectKeeper keeper_P(P); ASSERT_EQ(P, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_crs_to_crs_from_pj_ballpark_filter) { auto src = proj_create(m_ctxt, "EPSG:4267"); // NAD 27 ObjectKeeper keeper_src(src); ASSERT_NE(src, nullptr); auto dst = proj_create(m_ctxt, "EPSG:4258"); // ETRS89 ObjectKeeper keeper_dst(dst); ASSERT_NE(dst, nullptr); // No options { auto P = proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); } { const char *const options[] = {"ALLOW_BALLPARK=NO", nullptr}; auto P = proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, options); ObjectKeeper keeper_P(P); ASSERT_EQ(P, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_crs_to_crs_coordinate_metadata_in_src) { auto P = proj_create_crs_to_crs(m_ctxt, "ITRF2014@2025.0", "GDA2020", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); PJ_COORD coord; coord.xyzt.x = -30; coord.xyzt.y = 130; coord.xyzt.z = 0; coord.xyzt.t = HUGE_VAL; coord = proj_trans(P, PJ_FWD, coord); EXPECT_NEAR(coord.xyzt.x, -30.0000026655, 1e-10); EXPECT_NEAR(coord.xyzt.y, 129.9999983712, 1e-10); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_crs_to_crs_coordinate_metadata_in_target) { auto P = proj_create_crs_to_crs(m_ctxt, "GDA2020", "ITRF2014@2025.0", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); PJ_COORD coord; coord.xyzt.x = -30.0000026655; coord.xyzt.y = 129.9999983712; coord.xyzt.z = 0; coord.xyzt.t = HUGE_VAL; coord = proj_trans(P, PJ_FWD, coord); EXPECT_NEAR(coord.xyzt.x, -30, 1e-10); EXPECT_NEAR(coord.xyzt.y, 130, 1e-10); } // --------------------------------------------------------------------------- static void check_axis_is_latitude(PJ_CONTEXT *ctx, PJ *cs, int axis_number, const char *unit_name = "degree", double unit_conv_factor = 0.017453292519943295, const char *auth = "EPSG", const char *code = "9122") { const char *name = nullptr; const char *abbrev = nullptr; const char *direction = nullptr; double unitConvFactor = 0.0; const char *unitName = nullptr; const char *unitAuthority = nullptr; const char *unitCode = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(ctx, cs, axis_number, &name, &abbrev, &direction, &unitConvFactor, &unitName, &unitAuthority, &unitCode)); ASSERT_NE(name, nullptr); ASSERT_NE(abbrev, nullptr); ASSERT_NE(direction, nullptr); ASSERT_NE(unitName, nullptr); if (auth) { ASSERT_NE(unitAuthority, nullptr); ASSERT_NE(unitCode, nullptr); } EXPECT_EQ(std::string(name), "Latitude"); EXPECT_EQ(std::string(abbrev), "lat"); EXPECT_EQ(std::string(direction), "north"); EXPECT_EQ(unitConvFactor, unit_conv_factor) << unitConvFactor; EXPECT_EQ(std::string(unitName), unit_name); if (auth) { EXPECT_EQ(std::string(unitAuthority), auth); EXPECT_EQ(std::string(unitCode), code); } } // --------------------------------------------------------------------------- static void check_axis_is_longitude(PJ_CONTEXT *ctx, PJ *cs, int axis_number, const char *unit_name = "degree", double unit_conv_factor = 0.017453292519943295, const char *auth = "EPSG", const char *code = "9122") { const char *name = nullptr; const char *abbrev = nullptr; const char *direction = nullptr; double unitConvFactor = 0.0; const char *unitName = nullptr; const char *unitAuthority = nullptr; const char *unitCode = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(ctx, cs, axis_number, &name, &abbrev, &direction, &unitConvFactor, &unitName, &unitAuthority, &unitCode)); ASSERT_NE(name, nullptr); ASSERT_NE(abbrev, nullptr); ASSERT_NE(direction, nullptr); ASSERT_NE(unitName, nullptr); if (auth) { ASSERT_NE(unitAuthority, nullptr); ASSERT_NE(unitCode, nullptr); } EXPECT_EQ(std::string(name), "Longitude"); EXPECT_EQ(std::string(abbrev), "lon"); EXPECT_EQ(std::string(direction), "east"); EXPECT_EQ(unitConvFactor, unit_conv_factor) << unitConvFactor; EXPECT_EQ(std::string(unitName), unit_name); if (auth) { EXPECT_EQ(std::string(unitAuthority), auth); EXPECT_EQ(std::string(unitCode), code); } } // --------------------------------------------------------------------------- static void check_axis_is_height(PJ_CONTEXT *ctx, PJ *cs, int axis_number, const char *unit_name = "metre", double unit_conv_factor = 1, const char *auth = "EPSG", const char *code = "9001") { const char *name = nullptr; const char *abbrev = nullptr; const char *direction = nullptr; double unitConvFactor = 0.0; const char *unitName = nullptr; const char *unitAuthority = nullptr; const char *unitCode = nullptr; EXPECT_TRUE(proj_cs_get_axis_info(ctx, cs, axis_number, &name, &abbrev, &direction, &unitConvFactor, &unitName, &unitAuthority, &unitCode)); ASSERT_NE(name, nullptr); ASSERT_NE(abbrev, nullptr); ASSERT_NE(direction, nullptr); ASSERT_NE(unitName, nullptr); if (auth) { ASSERT_NE(unitAuthority, nullptr); ASSERT_NE(unitCode, nullptr); } EXPECT_EQ(std::string(name), "Ellipsoidal height"); EXPECT_EQ(std::string(abbrev), "h"); EXPECT_EQ(std::string(direction), "up"); EXPECT_EQ(unitConvFactor, unit_conv_factor) << unitConvFactor; EXPECT_EQ(std::string(unitName), unit_name); if (auth) { EXPECT_EQ(std::string(unitAuthority), auth); EXPECT_EQ(std::string(unitCode), code); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_ellipsoidal_3D_cs) { { auto cs = proj_create_ellipsoidal_3D_cs( m_ctxt, PJ_ELLPS3D_LATITUDE_LONGITUDE_HEIGHT, nullptr, 0, nullptr, 0); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); EXPECT_EQ(proj_cs_get_type(m_ctxt, cs), PJ_CS_TYPE_ELLIPSOIDAL); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3); check_axis_is_latitude(m_ctxt, cs, 0); check_axis_is_longitude(m_ctxt, cs, 1); check_axis_is_height(m_ctxt, cs, 2); } { auto cs = proj_create_ellipsoidal_3D_cs( m_ctxt, PJ_ELLPS3D_LONGITUDE_LATITUDE_HEIGHT, "foo", 0.5, "bar", 0.6); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); EXPECT_EQ(proj_cs_get_type(m_ctxt, cs), PJ_CS_TYPE_ELLIPSOIDAL); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3); check_axis_is_longitude(m_ctxt, cs, 0, "foo", 0.5, nullptr, nullptr); check_axis_is_latitude(m_ctxt, cs, 1, "foo", 0.5, nullptr, nullptr); check_axis_is_height(m_ctxt, cs, 2, "bar", 0.6, nullptr, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_promote_to_3D) { auto crs2D = proj_create(m_ctxt, GeographicCRS::EPSG_4326 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeper_crs2D(crs2D); EXPECT_NE(crs2D, nullptr); auto crs3D = proj_crs_promote_to_3D(m_ctxt, nullptr, crs2D); ObjectKeeper keeper_crs3D(crs3D); EXPECT_NE(crs3D, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3); auto code = proj_get_id_code(crs3D, 0); ASSERT_TRUE(code != nullptr); EXPECT_EQ(code, std::string("4979")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_promote_to_3D_on_coordinate_metadata) { auto cm_crs2D = proj_create(m_ctxt, "ITRF2014@2010.0"); ObjectKeeper keeper_cm_crs2D(cm_crs2D); EXPECT_NE(cm_crs2D, nullptr); auto cm_crs3D = proj_crs_promote_to_3D(m_ctxt, nullptr, cm_crs2D); ObjectKeeper keeper_cm_crs3D(cm_crs3D); EXPECT_NE(cm_crs3D, nullptr); EXPECT_NEAR(proj_coordinate_metadata_get_epoch(m_ctxt, cm_crs3D), 2010.0, 1e-10); auto crs3D = proj_get_source_crs(m_ctxt, cm_crs3D); ObjectKeeper keeper_crs3D(crs3D); EXPECT_NE(crs3D, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3); auto code = proj_get_id_code(crs3D, 0); ASSERT_TRUE(code != nullptr); EXPECT_EQ(code, std::string("7912")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_demote_to_2D) { auto crs3D = proj_create(m_ctxt, GeographicCRS::EPSG_4979 ->exportToWKT(WKTFormatter::create().get()) .c_str()); ObjectKeeper keeper_crs3D(crs3D); EXPECT_NE(crs3D, nullptr); auto crs2D = proj_crs_demote_to_2D(m_ctxt, nullptr, crs3D); ObjectKeeper keeper_crs2D(crs2D); EXPECT_NE(crs2D, nullptr); auto cs = proj_crs_get_coordinate_system(m_ctxt, crs2D); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 2); auto code = proj_get_id_code(crs2D, 0); ASSERT_TRUE(code != nullptr); EXPECT_EQ(code, std::string("4326")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_create_projected_3D_crs_from_2D) { auto projCRS = proj_create_from_database(m_ctxt, "EPSG", "32631", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(projCRS, nullptr); ObjectKeeper keeper_projCRS(projCRS); { auto geog3DCRS = proj_create_from_database( m_ctxt, "EPSG", "4979", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(geog3DCRS, nullptr); ObjectKeeper keeper_geog3DCRS(geog3DCRS); auto crs3D = proj_crs_create_projected_3D_crs_from_2D( m_ctxt, nullptr, projCRS, geog3DCRS); ObjectKeeper keeper_crs3D(crs3D); EXPECT_NE(crs3D, nullptr); EXPECT_EQ(proj_get_type(crs3D), PJ_TYPE_PROJECTED_CRS); EXPECT_EQ(std::string(proj_get_name(crs3D)), std::string(proj_get_name(projCRS))); auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3); } { auto crs3D = proj_crs_create_projected_3D_crs_from_2D(m_ctxt, nullptr, projCRS, nullptr); ObjectKeeper keeper_crs3D(crs3D); EXPECT_NE(crs3D, nullptr); EXPECT_EQ(proj_get_type(crs3D), PJ_TYPE_PROJECTED_CRS); EXPECT_EQ(std::string(proj_get_name(crs3D)), std::string(proj_get_name(projCRS))); auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D); ASSERT_NE(cs, nullptr); ObjectKeeper keeperCs(cs); EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_create_bound_vertical_crs) { auto vert_crs = proj_create_vertical_crs(m_ctxt, "myVertCRS", "myVertDatum", nullptr, 0.0); ObjectKeeper keeper_vert_crs(vert_crs); ASSERT_NE(vert_crs, nullptr); auto crs4979 = proj_create_from_wkt( m_ctxt, GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get()) .c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper_crs4979(crs4979); ASSERT_NE(crs4979, nullptr); auto bound_crs = proj_crs_create_bound_vertical_crs(m_ctxt, vert_crs, crs4979, "foo.gtx"); ObjectKeeper keeper_bound_crs(bound_crs); ASSERT_NE(bound_crs, nullptr); auto projCRS = proj_create_from_database(m_ctxt, "EPSG", "32631", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(projCRS, nullptr); ObjectKeeper keeper_projCRS(projCRS); auto compound_crs = proj_create_compound_crs(m_ctxt, "myCompoundCRS", projCRS, bound_crs); ObjectKeeper keeper_compound_crss(compound_crs); ASSERT_NE(compound_crs, nullptr); auto proj_4 = proj_as_proj_string(m_ctxt, compound_crs, PJ_PROJ_4, nullptr); ASSERT_NE(proj_4, nullptr); EXPECT_EQ(std::string(proj_4), "+proj=utm +zone=31 +datum=WGS84 +units=m +geoidgrids=foo.gtx " "+geoid_crs=WGS84 " "+vunits=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_crs_to_crs_with_only_ballpark_transformations) { // ETRS89 / UTM zone 31N + EGM96 height to WGS 84 (G1762) auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:25831+5773", "EPSG:7665", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper keeper_Pnormalized(Pnormalized); ASSERT_NE(Pnormalized, nullptr); PJ_COORD coord; coord.xyzt.x = 500000; coord.xyzt.y = 4500000; coord.xyzt.z = 0; coord.xyzt.t = 0; coord = proj_trans(Pnormalized, PJ_FWD, coord); EXPECT_NEAR(coord.xyzt.x, 3.0, 1e-9); EXPECT_NEAR(coord.xyzt.y, 40.65085651660555, 1e-9); EXPECT_NEAR(coord.xyzt.z, 47.72600023608570, 1e-3); } // --------------------------------------------------------------------------- TEST_F( CApi, proj_create_crs_to_crs_from_custom_compound_crs_with_NAD83_2011_and_geoidgrid_ref_against_WGS84_to_WGS84_G1762) { PJ *P; PJ *inCrsH = proj_create_from_database(m_ctxt, "EPSG", "6340", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(inCrsH, nullptr); PJ *inDummyCrs = proj_create_vertical_crs(m_ctxt, "VerticalDummyCrs", "DummyDatum", "metre", 1.0); ASSERT_NE(inDummyCrs, nullptr); auto crs4979 = proj_create_from_database(m_ctxt, "EPSG", "4979", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs4979, nullptr); PJ *inCrsV = proj_crs_create_bound_vertical_crs(m_ctxt, inDummyCrs, crs4979, "egm96_15.gtx"); ASSERT_NE(inCrsV, nullptr); proj_destroy(inDummyCrs); proj_destroy(crs4979); PJ *inCompound = proj_create_compound_crs(m_ctxt, "Compound", inCrsH, inCrsV); ASSERT_NE(inCompound, nullptr); proj_destroy(inCrsH); proj_destroy(inCrsV); PJ *outCrs = proj_create(m_ctxt, "EPSG:7665"); ASSERT_NE(outCrs, nullptr); // In this particular case, PROJ computes a transformation from NAD83(2011) // (EPSG:6318) to WGS84 (EPSG:4979) for the initial horizontal adjustment // before the geoidgrids application. There are 6 candidate transformations // for that in subzones of the US and one last no-op transformation flagged // as ballpark. That one used to be eliminated because by // proj_create_crs_to_crs() because there were non Ballpark transformations // available. This resulted thus in an error when transforming outside of // those few subzones. P = proj_create_crs_to_crs_from_pj(m_ctxt, inCompound, outCrs, nullptr, nullptr); ASSERT_NE(P, nullptr); proj_destroy(inCompound); proj_destroy(outCrs); PJ_COORD in_coord; in_coord.xyzt.x = 350499.911; in_coord.xyzt.y = 3884807.956; in_coord.xyzt.z = 150.072; in_coord.xyzt.t = 2010; PJ_COORD outcoord = proj_trans(P, PJ_FWD, in_coord); proj_destroy(P); EXPECT_NEAR(outcoord.xyzt.x, 35.09499307271, 1e-9); EXPECT_NEAR(outcoord.xyzt.y, -118.64014868921, 1e-9); EXPECT_NEAR(outcoord.xyzt.z, 117.655, 1e-3); } // --------------------------------------------------------------------------- TEST_F( CApi, proj_create_crs_to_crs_from_custom_compound_crs_with_NAD83_2011_and_geoidgrid_ref_against_NAD83_2011_to_WGS84_G1762) { PJ *P; // NAD83(2011) 2D PJ *inCrsH = proj_create_from_database(m_ctxt, "EPSG", "6318", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(inCrsH, nullptr); PJ *inDummyCrs = proj_create_vertical_crs(m_ctxt, "VerticalDummyCrs", "DummyDatum", "metre", 1.0); ASSERT_NE(inDummyCrs, nullptr); // NAD83(2011) 3D PJ *inGeog3DCRS = proj_create_from_database( m_ctxt, "EPSG", "6319", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(inCrsH, nullptr); // Note: this is actually a bad example since we tell here that egm96_15.gtx // is referenced against NAD83(2011) PJ *inCrsV = proj_crs_create_bound_vertical_crs( m_ctxt, inDummyCrs, inGeog3DCRS, "egm96_15.gtx"); ASSERT_NE(inCrsV, nullptr); proj_destroy(inDummyCrs); proj_destroy(inGeog3DCRS); PJ *inCompound = proj_create_compound_crs(m_ctxt, "Compound", inCrsH, inCrsV); ASSERT_NE(inCompound, nullptr); proj_destroy(inCrsH); proj_destroy(inCrsV); // WGS84 (G1762) PJ *outCrs = proj_create(m_ctxt, "EPSG:7665"); ASSERT_NE(outCrs, nullptr); P = proj_create_crs_to_crs_from_pj(m_ctxt, inCompound, outCrs, nullptr, nullptr); ASSERT_NE(P, nullptr); proj_destroy(inCompound); proj_destroy(outCrs); PJ_COORD in_coord; in_coord.xyzt.x = 35; in_coord.xyzt.y = -118; in_coord.xyzt.z = 0; in_coord.xyzt.t = 2010; PJ_COORD outcoord = proj_trans(P, PJ_FWD, in_coord); proj_destroy(P); EXPECT_NEAR(outcoord.xyzt.x, 35.000003665064803, 1e-9); EXPECT_NEAR(outcoord.xyzt.y, -118.00001414221214, 1e-9); EXPECT_NEAR(outcoord.xyzt.z, -32.8110, 1e-3); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_vertical_crs_ex) { // NAD83(2011) / UTM zone 11N auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340", PJ_CATEGORY_CRS, false, nullptr); ObjectKeeper keeper_horiz_crs(horiz_crs); ASSERT_NE(horiz_crs, nullptr); const char *options[] = {"ACCURACY=123", nullptr}; auto vert_crs = proj_create_vertical_crs_ex( m_ctxt, "myVertCRS (ftUS)", "myVertDatum", nullptr, nullptr, "US survey foot", 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr, nullptr, options); ObjectKeeper keeper_vert_crs(vert_crs); ASSERT_NE(vert_crs, nullptr); auto compound = proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs); ObjectKeeper keeper_compound(compound); ASSERT_NE(compound, nullptr); // NAD83(2011) 3D PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319"); ObjectKeeper keeper_geog_crs(geog_crs); ASSERT_NE(geog_crs, nullptr); PJ_OPERATION_FACTORY_CONTEXT *ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); PJ_OBJ_LIST *operations = proj_create_operations(m_ctxt, compound, geog_crs, ctxt); ASSERT_NE(operations, nullptr); ObjListKeeper keeper_operations(operations); EXPECT_GE(proj_list_get_count(operations), 1); auto P = proj_list_get(m_ctxt, operations, 0); ObjectKeeper keeper_transform(P); auto name = proj_get_name(P); ASSERT_TRUE(name != nullptr); EXPECT_EQ(name, std::string("Inverse of UTM zone 11N + " "Conversion from myVertCRS (ftUS) to myVertCRS + " "Transformation from myVertCRS to NAD83(2011)")); auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(std::string(proj_5), "+proj=pipeline " "+step +inv +proj=utm +zone=11 +ellps=GRS80 " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); ASSERT_EQ(proj_coordoperation_get_accuracy(m_ctxt, P), 123.0); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_vertical_crs_ex_with_geog_crs) { // NAD83(2011) / UTM zone 11N auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340", PJ_CATEGORY_CRS, false, nullptr); ObjectKeeper keeper_horiz_crs(horiz_crs); ASSERT_NE(horiz_crs, nullptr); // WGS84 PJ *wgs84 = proj_create(m_ctxt, "EPSG:4979"); ObjectKeeper keeper_wgs84(wgs84); ASSERT_NE(wgs84, nullptr); auto vert_crs = proj_create_vertical_crs_ex( m_ctxt, "myVertCRS", "myVertDatum", nullptr, nullptr, "US survey foot", 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr, wgs84, nullptr); ObjectKeeper keeper_vert_crs(vert_crs); ASSERT_NE(vert_crs, nullptr); auto compound = proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs); ObjectKeeper keeper_compound(compound); ASSERT_NE(compound, nullptr); // NAD83(2011) 3D PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319"); ObjectKeeper keeper_geog_crs(geog_crs); ASSERT_NE(geog_crs, nullptr); PJ_OPERATION_FACTORY_CONTEXT *ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); PJ_OBJ_LIST *operations = proj_create_operations(m_ctxt, compound, geog_crs, ctxt); ASSERT_NE(operations, nullptr); ObjListKeeper keeper_operations(operations); EXPECT_GE(proj_list_get_count(operations), 1); auto P = proj_list_get(m_ctxt, operations, 0); ObjectKeeper keeper_transform(P); auto name = proj_get_name(P); ASSERT_TRUE(name != nullptr); EXPECT_EQ(name, std::string("Inverse of UTM zone 11N + " "Conversion from myVertCRS to myVertCRS (metre) + " "Transformation from myVertCRS (metre) to WGS 84 " "using NAD83(2011) to WGS 84 (1)")); auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(std::string(proj_5), "+proj=pipeline " "+step +inv +proj=utm +zone=11 +ellps=GRS80 " "+step +proj=unitconvert +z_in=us-ft +z_out=m " "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); // Check that we get the same results after an export of compoundCRS to // PROJJSON and a re-import from it. auto projjson = proj_as_projjson(m_ctxt, compound, nullptr); ASSERT_NE(projjson, nullptr); auto compound_from_projjson = proj_create(m_ctxt, projjson); ObjectKeeper keeper_compound_from_projjson(compound_from_projjson); ASSERT_NE(compound_from_projjson, nullptr); PJ_OBJ_LIST *operations2 = proj_create_operations(m_ctxt, compound_from_projjson, geog_crs, ctxt); ASSERT_NE(operations2, nullptr); ObjListKeeper keeper_operations2(operations2); EXPECT_GE(proj_list_get_count(operations2), 1); auto P2 = proj_list_get(m_ctxt, operations2, 0); ObjectKeeper keeper_transform2(P2); auto name_bis = proj_get_name(P2); ASSERT_TRUE(name_bis != nullptr); EXPECT_EQ(std::string(name_bis), std::string(name)); auto proj_5_bis = proj_as_proj_string(m_ctxt, P2, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5_bis, nullptr); EXPECT_EQ(std::string(proj_5_bis), std::string(proj_5)); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_vertical_crs_ex_implied_accuracy) { PJ *crsH = proj_create(m_ctxt, "EPSG:4283"); // GDA94 ASSERT_NE(crsH, nullptr); ObjectKeeper keeper_crsH(crsH); PJ *crsV = proj_create(m_ctxt, "EPSG:5711"); // AHD height ASSERT_NE(crsV, nullptr); ObjectKeeper keeper_crsV(crsV); PJ *crsGeoid = proj_create(m_ctxt, "EPSG:4939"); // GDA94 3D ASSERT_NE(crsGeoid, nullptr); ObjectKeeper keeper_crsGeoid(crsGeoid); PJ *vertDatum = proj_crs_get_datum(m_ctxt, crsV); ObjectKeeper keeper_vertDatum(vertDatum); const char *vertDatumName = proj_get_name(vertDatum); const char *vertDatumAuthority = proj_get_id_auth_name(vertDatum, 0); const char *vertDatumCode = proj_get_id_code(vertDatum, 0); PJ *crsVGeoid = proj_create_vertical_crs_ex( m_ctxt, "Vertical", vertDatumName, vertDatumAuthority, vertDatumCode, "metre", 1.0, "PROJ au_ga_AUSGeoid09_V1.01.tif", nullptr, nullptr, crsGeoid, nullptr); ObjectKeeper keeper_crsVGeoid(crsVGeoid); PJ *crsCompoundGeoid = proj_create_compound_crs( m_ctxt, "Compound with geoid", crsH, crsVGeoid); ObjectKeeper keeper_crsCompoundGeoid(crsCompoundGeoid); PJ_OPERATION_FACTORY_CONTEXT *ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); proj_operation_factory_context_set_grid_availability_use( m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED); proj_operation_factory_context_set_spatial_criterion( m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); PJ_OBJ_LIST *operations = proj_create_operations(m_ctxt, crsCompoundGeoid, crsGeoid, ctxt); ASSERT_NE(operations, nullptr); ObjListKeeper keeper_operations(operations); EXPECT_GE(proj_list_get_count(operations), 1); PJ *transform = proj_list_get(m_ctxt, operations, 0); ObjectKeeper keeper_transform(transform); // This is the accuracy of operations EPSG:5656 / 5657 const double acc = proj_coordoperation_get_accuracy(m_ctxt, transform); EXPECT_NEAR(acc, 0.15, 1e-10); // Check there's an associated area of use double west_lon_degree = 0; double south_lat_degree = 0; double east_lon_degree = 0; double north_lat_degree = 0; ASSERT_EQ(proj_get_area_of_use(m_ctxt, transform, &west_lon_degree, &south_lat_degree, &east_lon_degree, &north_lat_degree, nullptr), true); EXPECT_LE(north_lat_degree, -10); EXPECT_GE(west_lon_degree, 110); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_derived_geographic_crs) { PJ *crs_4326 = proj_create(m_ctxt, "EPSG:4326"); ObjectKeeper keeper_crs_4326(crs_4326); ASSERT_NE(crs_4326, nullptr); PJ *conversion = proj_create_conversion_pole_rotation_grib_convention( m_ctxt, 2, 3, 4, "Degree", 0.0174532925199433); ObjectKeeper keeper_conversion(conversion); ASSERT_NE(conversion, nullptr); PJ *cs = proj_crs_get_coordinate_system(m_ctxt, crs_4326); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); ASSERT_EQ( proj_create_derived_geographic_crs(m_ctxt, "my rotated CRS", conversion, // wrong type of object conversion, cs), nullptr); ASSERT_EQ( proj_create_derived_geographic_crs(m_ctxt, "my rotated CRS", crs_4326, crs_4326, // wrong type of object cs), nullptr); ASSERT_EQ(proj_create_derived_geographic_crs( m_ctxt, "my rotated CRS", crs_4326, conversion, conversion // wrong type of object ), nullptr); PJ *derived_crs = proj_create_derived_geographic_crs( m_ctxt, "my rotated CRS", crs_4326, conversion, cs); ObjectKeeper keeper_derived_crs(derived_crs); ASSERT_NE(derived_crs, nullptr); EXPECT_FALSE(proj_is_derived_crs(m_ctxt, crs_4326)); EXPECT_TRUE(proj_is_derived_crs(m_ctxt, derived_crs)); auto wkt = proj_as_wkt(m_ctxt, derived_crs, PJ_WKT2_2019, nullptr); const char *expected_wkt = "GEOGCRS[\"my rotated CRS\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " DERIVINGCONVERSION[\"Pole rotation (GRIB convention)\",\n" " METHOD[\"Pole rotation (GRIB convention)\"],\n" " PARAMETER[\"Latitude of the southern pole (GRIB " "convention)\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Longitude of the southern pole (GRIB " "convention)\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Axis rotation (GRIB convention)\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; ASSERT_NE(wkt, nullptr); EXPECT_EQ(wkt, std::string(expected_wkt)); auto proj_5 = proj_as_proj_string(m_ctxt, derived_crs, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(proj_5, std::string("+proj=ob_tran +o_proj=longlat +o_lon_p=-4 " "+o_lat_p=-2 +lon_0=3 +datum=WGS84 +no_defs " "+type=crs")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_derived_geographic_crs_netcdf_cf) { PJ *crs_4019 = proj_create(m_ctxt, "EPSG:4019"); ObjectKeeper keeper_crs_4019(crs_4019); ASSERT_NE(crs_4019, nullptr); PJ *conversion = proj_create_conversion_pole_rotation_netcdf_cf_convention( m_ctxt, 2, 3, 4, "Degree", 0.0174532925199433); ObjectKeeper keeper_conversion(conversion); ASSERT_NE(conversion, nullptr); PJ *cs = proj_crs_get_coordinate_system(m_ctxt, crs_4019); ObjectKeeper keeper_cs(cs); ASSERT_NE(cs, nullptr); PJ *derived_crs = proj_create_derived_geographic_crs( m_ctxt, "my rotated CRS", crs_4019, conversion, cs); ObjectKeeper keeper_derived_crs(derived_crs); ASSERT_NE(derived_crs, nullptr); auto wkt = proj_as_wkt(m_ctxt, derived_crs, PJ_WKT2_2019, nullptr); const char *expected_wkt = "GEOGCRS[\"my rotated CRS\",\n" " BASEGEOGCRS[\"Unknown datum based upon the GRS 1980 ellipsoid\",\n" " DATUM[\"Not specified (based on GRS 1980 ellipsoid)\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4019]],\n" " DERIVINGCONVERSION[\"Pole rotation (netCDF CF convention)\",\n" " METHOD[\"Pole rotation (netCDF CF convention)\"],\n" " PARAMETER[\"Grid north pole latitude (netCDF CF " "convention)\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Grid north pole longitude (netCDF CF " "convention)\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"North pole grid longitude (netCDF CF " "convention)\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; ASSERT_NE(wkt, nullptr); EXPECT_EQ(wkt, std::string(expected_wkt)); auto proj_5 = proj_as_proj_string(m_ctxt, derived_crs, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(proj_5, std::string("+proj=ob_tran +o_proj=longlat +o_lon_p=4 " "+o_lat_p=2 +lon_0=183 +ellps=GRS80 +no_defs " "+type=crs")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_set_sqlite3_vfs_name) { PJ_CONTEXT *ctx = proj_context_create(); proj_log_func(ctx, nullptr, [](void *, int, const char *) -> void {}); // Set a dummy VFS and check it is taken into account // (failure to open proj.db) proj_context_set_sqlite3_vfs_name(ctx, "dummy_vfs_name"); ASSERT_EQ(proj_create(ctx, "EPSG:4326"), nullptr); // Restore default VFS proj_context_set_sqlite3_vfs_name(ctx, nullptr); PJ *crs_4326 = proj_create(ctx, "EPSG:4326"); ASSERT_NE(crs_4326, nullptr); proj_destroy(crs_4326); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_context_set_sqlite3_vfs_name__from_global_context) { // Set a dummy VFS and check it is taken into account // (failure to open proj.db) proj_context_set_sqlite3_vfs_name(nullptr, "dummy_vfs_name"); PJ_CONTEXT *ctx = proj_context_create(); proj_log_func(ctx, nullptr, [](void *, int, const char *) -> void {}); ASSERT_EQ(proj_create(ctx, "EPSG:4326"), nullptr); // Restore default VFS proj_context_set_sqlite3_vfs_name(nullptr, nullptr); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST_F(CApi, use_proj4_init_rules) { PJ_CONTEXT *ctx = proj_context_create(); proj_context_use_proj4_init_rules(ctx, true); ASSERT_TRUE(proj_context_get_use_proj4_init_rules(ctx, true)); { // Test +over auto crs = proj_create(ctx, "+init=epsg:28992 +over"); ObjectKeeper keeper_crs(crs); ASSERT_NE(crs, nullptr); auto datum = proj_crs_get_datum(ctx, crs); ASSERT_NE(datum, nullptr); ObjectKeeper keeper_datum(datum); auto datum_name = proj_get_name(datum); ASSERT_TRUE(datum_name != nullptr); EXPECT_EQ(datum_name, std::string("Amersfoort")); auto proj_5 = proj_as_proj_string(ctx, crs, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(std::string(proj_5), "+proj=sterea +lat_0=52.1561605555556 " "+lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 " "+y_0=463000 +ellps=bessel +units=m +over " "+no_defs +type=crs"); } { // Test +over on epsg:3857 auto crs = proj_create(ctx, "+init=epsg:3857 +over"); ObjectKeeper keeper_crs(crs); ASSERT_NE(crs, nullptr); auto proj_5 = proj_as_proj_string(ctx, crs, PJ_PROJ_5, nullptr); ASSERT_NE(proj_5, nullptr); EXPECT_EQ(std::string(proj_5), "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 " "+y_0=0 +k=1 +units=m +nadgrids=@null +over +wktext " "+no_defs +type=crs"); } proj_context_use_proj4_init_rules(ctx, false); ASSERT_TRUE(!proj_context_get_use_proj4_init_rules(ctx, true)); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST_F(CApi, use_proj4_init_rules_from_global_context) { int initial_rules = proj_context_get_use_proj4_init_rules(nullptr, true); proj_context_use_proj4_init_rules(nullptr, true); PJ_CONTEXT *ctx = proj_context_create(); ASSERT_TRUE(proj_context_get_use_proj4_init_rules(ctx, true)); proj_context_destroy(ctx); proj_context_use_proj4_init_rules(nullptr, false); ctx = proj_context_create(); ASSERT_TRUE(!proj_context_get_use_proj4_init_rules(ctx, true)); proj_context_destroy(ctx); proj_context_use_proj4_init_rules(nullptr, initial_rules); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_is_equivalent_to_with_ctx) { auto from_epsg = proj_create_from_database(m_ctxt, "EPSG", "7844", PJ_CATEGORY_CRS, false, nullptr); ObjectKeeper keeper_from_epsg(from_epsg); ASSERT_NE(from_epsg, nullptr); auto wkt = "GEOGCRS[\"GDA2020\",\n" " DATUM[\"GDA2020\",\n" " ELLIPSOID[\"GRS_1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto from_wkt = proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr); ObjectKeeper keeper_from_wkt(from_wkt); EXPECT_NE(from_wkt, nullptr); EXPECT_TRUE(proj_is_equivalent_to_with_ctx(m_ctxt, from_epsg, from_wkt, PJ_COMP_EQUIVALENT)); } // --------------------------------------------------------------------------- TEST_F(CApi, datum_ensemble) { auto wkt = "GEOGCRS[\"ETRS89\"," " ENSEMBLE[\"European Terrestrial Reference System 1989 ensemble\"," " MEMBER[\"European Terrestrial Reference Frame 1989\"]," " MEMBER[\"European Terrestrial Reference Frame 1990\"]," " MEMBER[\"European Terrestrial Reference Frame 1991\"]," " MEMBER[\"European Terrestrial Reference Frame 1992\"]," " MEMBER[\"European Terrestrial Reference Frame 1993\"]," " MEMBER[\"European Terrestrial Reference Frame 1994\"]," " MEMBER[\"European Terrestrial Reference Frame 1996\"]," " MEMBER[\"European Terrestrial Reference Frame 1997\"]," " MEMBER[\"European Terrestrial Reference Frame 2000\"]," " MEMBER[\"European Terrestrial Reference Frame 2005\"]," " MEMBER[\"European Terrestrial Reference Frame 2014\"]," " ELLIPSOID[\"GRS 1980\",6378137,298.257222101," " LENGTHUNIT[\"metre\",1]]," " ENSEMBLEACCURACY[0.1]]," " PRIMEM[\"Greenwich\",0," " ANGLEUNIT[\"degree\",0.0174532925199433]]," " CS[ellipsoidal,2]," " AXIS[\"geodetic latitude (Lat)\",north," " ORDER[1]," " ANGLEUNIT[\"degree\",0.0174532925199433]]," " AXIS[\"geodetic longitude (Lon)\",east," " ORDER[2]," " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto from_wkt = proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr); ObjectKeeper keeper_from_wkt(from_wkt); EXPECT_NE(from_wkt, nullptr); auto datum = proj_crs_get_datum(m_ctxt, from_wkt); ObjectKeeper keeper_datum(datum); ASSERT_EQ(datum, nullptr); auto datum_ensemble = proj_crs_get_datum_ensemble(m_ctxt, from_wkt); ObjectKeeper keeper_datum_ensemble(datum_ensemble); ASSERT_NE(datum_ensemble, nullptr); ASSERT_EQ(proj_datum_ensemble_get_member_count(m_ctxt, datum_ensemble), 11); ASSERT_EQ(proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, -1), nullptr); ASSERT_EQ(proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, 11), nullptr); { auto member = proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, 0); ObjectKeeper keeper_member(member); ASSERT_NE(member, nullptr); EXPECT_EQ(proj_get_name(member), std::string("European Terrestrial Reference Frame 1989")); } { auto member = proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, 10); ObjectKeeper keeper_member(member); ASSERT_NE(member, nullptr); EXPECT_EQ(proj_get_name(member), std::string("European Terrestrial Reference Frame 2014")); } ASSERT_EQ(proj_datum_ensemble_get_accuracy(m_ctxt, datum_ensemble), 0.1); auto datum_forced = proj_crs_get_datum_forced(m_ctxt, from_wkt); ObjectKeeper keeper_datum_forced(datum_forced); ASSERT_NE(datum_forced, nullptr); EXPECT_EQ(proj_get_name(datum_forced), std::string("European Terrestrial Reference System 1989")); auto cs = proj_crs_get_coordinate_system(m_ctxt, from_wkt); ObjectKeeper keeper_cs(cs); EXPECT_NE(cs, nullptr); { auto built_crs = proj_create_geographic_crs_from_datum( m_ctxt, proj_get_name(from_wkt), datum_ensemble, cs); ObjectKeeper keeper_built_crs(built_crs); EXPECT_NE(built_crs, nullptr); EXPECT_TRUE(proj_is_equivalent_to_with_ctx(m_ctxt, built_crs, from_wkt, PJ_COMP_EQUIVALENT)); } { auto built_crs = proj_create_geocentric_crs_from_datum( m_ctxt, proj_get_name(from_wkt), datum_ensemble, "metre", 1.0); ObjectKeeper keeper_built_crs(built_crs); EXPECT_NE(built_crs, nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_is_derived) { { auto wkt = createProjectedCRS()->exportToWKT(WKTFormatter::create().get()); auto obj = proj_create_from_wkt(m_ctxt, wkt.c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr) << wkt; EXPECT_TRUE(proj_crs_is_derived(m_ctxt, obj)); } { auto wkt = createProjectedCRS()->baseCRS()->exportToWKT( WKTFormatter::create().get()); auto obj = proj_create_from_wkt(m_ctxt, wkt.c_str(), nullptr, nullptr, nullptr); ObjectKeeper keeper(obj); ASSERT_NE(obj, nullptr) << wkt; EXPECT_FALSE(proj_crs_is_derived(m_ctxt, obj)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_insert_statements) { { auto session = proj_insert_object_session_create(nullptr); EXPECT_NE(session, nullptr); EXPECT_EQ(proj_insert_object_session_create(nullptr), nullptr); proj_insert_object_session_destroy(nullptr, session); } { proj_insert_object_session_destroy(nullptr, nullptr); } { auto wkt = "GEOGCRS[\"myGDA2020\",\n" " DATUM[\"GDA2020\",\n" " ELLIPSOID[\"GRS_1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto crs = proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr); ObjectKeeper keeper_from_wkt(crs); EXPECT_NE(crs, nullptr); { char *code = proj_suggests_code_for(m_ctxt, crs, "HOBU", false, nullptr); ASSERT_NE(code, nullptr); EXPECT_EQ(std::string(code), "MYGDA2020"); proj_string_destroy(code); } { char *code = proj_suggests_code_for(m_ctxt, crs, "HOBU", true, nullptr); ASSERT_NE(code, nullptr); EXPECT_EQ(std::string(code), "1"); proj_string_destroy(code); } const auto sizeOfStringList = [](const char *const *list) { if (list == nullptr) return -1; int size = 0; for (auto iter = list; *iter; ++iter) { size += 1; } return size; }; // No session specified: we use a temporary session for (int i = 0; i < 2; i++) { auto list = proj_get_insert_statements( m_ctxt, nullptr, crs, "HOBU", "XXXX", false, nullptr, nullptr); ASSERT_NE(list, nullptr); ASSERT_NE(list[0], nullptr); EXPECT_EQ(std::string(list[0]), "INSERT INTO geodetic_datum VALUES('HOBU'," "'GEODETIC_DATUM_XXXX','GDA2020','','EPSG','7019'," "'EPSG','8901',NULL,NULL,NULL,NULL,NULL,0);"); EXPECT_EQ(sizeOfStringList(list), 4); proj_string_list_destroy(list); } // Pass an empty list of allowed authorities // We cannot reuse the EPSG ellipsoid and prime meridian { const char *const allowed_authorities[] = {nullptr}; auto list = proj_get_insert_statements(m_ctxt, nullptr, crs, "HOBU", "XXXX", false, allowed_authorities, nullptr); EXPECT_EQ(sizeOfStringList(list), 6); proj_string_list_destroy(list); } // Allow only PROJ // We cannot reuse the EPSG ellipsoid and prime meridian { const char *const allowed_authorities[] = {"PROJ", nullptr}; auto list = proj_get_insert_statements(m_ctxt, nullptr, crs, "HOBU", "XXXX", false, allowed_authorities, nullptr); EXPECT_EQ(sizeOfStringList(list), 6); proj_string_list_destroy(list); } // Allow EPSG { const char *const allowed_authorities[] = {"EPSG", nullptr}; auto list = proj_get_insert_statements(m_ctxt, nullptr, crs, "HOBU", "XXXX", false, allowed_authorities, nullptr); EXPECT_EQ(sizeOfStringList(list), 4); proj_string_list_destroy(list); } auto session = proj_insert_object_session_create(m_ctxt); EXPECT_NE(session, nullptr); { auto list = proj_get_insert_statements( m_ctxt, session, crs, "HOBU", "XXXX", false, nullptr, nullptr); ASSERT_NE(list, nullptr); ASSERT_NE(list[0], nullptr); EXPECT_EQ(std::string(list[0]), "INSERT INTO geodetic_datum VALUES('HOBU'," "'GEODETIC_DATUM_XXXX','GDA2020','','EPSG','7019'," "'EPSG','8901',NULL,NULL,NULL,NULL,NULL,0);"); proj_string_list_destroy(list); } // Object already inserted: return empty list { auto list = proj_get_insert_statements( m_ctxt, session, crs, "HOBU", "XXXX", false, nullptr, nullptr); ASSERT_NE(list, nullptr); ASSERT_EQ(list[0], nullptr); proj_string_list_destroy(list); } proj_insert_object_session_destroy(m_ctxt, session); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_get_geoid_models_from_database) { auto findInList = [](PROJ_STRING_LIST list, const std::string &ref) { while (list && *list) { if (std::string(*list) == ref) { return true; } list++; } return false; }; auto list = proj_get_geoid_models_from_database(m_ctxt, "EPSG", "5703", nullptr); ListFreer freer(list); EXPECT_TRUE(findInList(list, "GEOID12B")); EXPECT_TRUE(findInList(list, "GEOID18")); EXPECT_TRUE(findInList(list, "GGM10")); EXPECT_FALSE(findInList(list, "OSGM15")); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_densify_0) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 " "+a=6370997 +b=6370997 +units=m +no_defs", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 40, -120, 64, -80, &out_left, &out_bottom, &out_right, &out_top, 0); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -1684649.41338, 1); EXPECT_NEAR(out_bottom, -350356.81377, 1); EXPECT_NEAR(out_right, 1684649.41338, 1); EXPECT_NEAR(out_top, 2234551.18559, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_densify_100) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 " "+a=6370997 +b=6370997 +units=m +no_defs", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 40, -120, 64, -80, &out_left, &out_bottom, &out_right, &out_top, 100); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -1684649.41338, 1); EXPECT_NEAR(out_bottom, -555777.79210, 1); EXPECT_NEAR(out_right, 1684649.41338, 1); EXPECT_NEAR(out_top, 2234551.18559, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_normalized) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 " "+a=6370997 +b=6370997 +units=m +no_defs", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto normalized_p = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper normal_keeper_P(normalized_p); ASSERT_NE(normalized_p, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, normalized_p, PJ_FWD, -120, 40, -80, 64, &out_left, &out_bottom, &out_right, &out_top, 100); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -1684649.41338, 1); EXPECT_NEAR(out_bottom, -555777.79210, 1); EXPECT_NEAR(out_right, 1684649.41338, 1); EXPECT_NEAR(out_top, 2234551.18559, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_antimeridian_xy) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4167", "EPSG:3851", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto normalized_p = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper normal_keeper_P(normalized_p); ASSERT_NE(normalized_p, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, normalized_p, PJ_FWD, 160.6, -55.95, -171.2, -25.88, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 1722483.900174921, 1); EXPECT_NEAR(out_bottom, 5228058.6143420935, 1); EXPECT_NEAR(out_right, 4624385.4948085546, 1); EXPECT_NEAR(out_top, 8692574.544944234, 1); double out_left_inv; double out_bottom_inv; double out_right_inv; double out_top_inv; int success_inv = proj_trans_bounds( m_ctxt, normalized_p, PJ_INV, 1722483.900174921, 5228058.6143420935, 4624385.494808555, 8692574.544944234, &out_left_inv, &out_bottom_inv, &out_right_inv, &out_top_inv, 21); EXPECT_TRUE(success_inv == 1); EXPECT_NEAR(out_left_inv, 153.2799922, 1); EXPECT_NEAR(out_bottom_inv, -56.7471249, 1); EXPECT_NEAR(out_right_inv, -162.1813873, 1); EXPECT_NEAR(out_top_inv, -24.6148194, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_antimeridian) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4167", "EPSG:3851", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, -55.95, 160.6, -25.88, -171.2, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 5228058.6143420935, 1); EXPECT_NEAR(out_bottom, 1722483.900174921, 1); EXPECT_NEAR(out_right, 8692574.544944234, 1); EXPECT_NEAR(out_top, 4624385.4948085546, 1); double out_left_inv; double out_bottom_inv; double out_right_inv; double out_top_inv; int success_inv = proj_trans_bounds( m_ctxt, P, PJ_INV, 5228058.6143420935, 1722483.900174921, 8692574.544944234, 4624385.494808555, &out_left_inv, &out_bottom_inv, &out_right_inv, &out_top_inv, 21); EXPECT_TRUE(success_inv == 1); EXPECT_NEAR(out_left_inv, -56.7471249, 1); EXPECT_NEAR(out_bottom_inv, 153.2799922, 1); EXPECT_NEAR(out_right_inv, -24.6148194, 1); EXPECT_NEAR(out_top_inv, -162.1813873, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_beyond_global_bounds) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:6933", "EPSG:4326", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto normalized_p = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper normal_keeper_P(normalized_p); ASSERT_NE(normalized_p, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, normalized_p, PJ_FWD, -17367531.3203125, -7314541.19921875, 17367531.3203125, 7314541.19921875, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -180, 1); EXPECT_NEAR(out_bottom, -85.0445994113099, 1); EXPECT_NEAR(out_right, 180, 1); EXPECT_NEAR(out_top, 85.0445994113099, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_ignore_inf) { auto P = proj_create_crs_to_crs(m_ctxt, "OGC:CRS84", "ESRI:102036", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; // Before the ellipsoidal version of the gnomonic projection was // implemented the WGS84 ellipsoid was treated as a sphere of radius // 6378137m and the equator was the "horizon" for the projection with the // south polar aspect. // // The boundary with ndiv = 21 then mapped into a line extending to ymin = // -89178007.2 which was the projection of lat = -90d/(ndiv+1), long = 180d. // // With the implementation of the ellipsoidal gnonomic projection, the // horizon is now at lat = +0.3035d. // // We move the north edge of the box to lat = -90+4.15*(ndiv+1) = +1.3d. // The northernmost point on the boundary which is within the horizon is // now lat = -90+4.15*ndiv = -2.85d, long = 180d for which y = -116576598.5. int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, -180.0, -90.0, 180.0, 1.3, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -49621755.4, 1); EXPECT_NEAR(out_bottom, -116576598.5, 1); EXPECT_NEAR(out_right, 49621755.4, 1); EXPECT_NEAR(out_top, 50132027.2, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_ignore_inf_geographic) { auto P = proj_create_crs_to_crs( m_ctxt, "PROJCS[\"Interrupted_Goode_Homolosine\"," "GEOGCS[\"GCS_unnamed ellipse\",DATUM[\"D_unknown\"," "SPHEROID[\"Unknown\",6378137,298.257223563]]," "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Interrupted_Goode_Homolosine\"]," "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", "OGC:CRS84", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, -15028000.0, 7515000.0, -14975000.0, 7556000.0, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -179.2133, 1); EXPECT_NEAR(out_bottom, 70.9345, 1); EXPECT_NEAR(out_right, -177.9054, 1); EXPECT_NEAR(out_top, 71.4364, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_noop_geographic) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4284", "EPSG:4284", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 19.57, 35.14, -168.97, 81.91, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 19.57, 1); EXPECT_NEAR(out_bottom, 35.14, 1); EXPECT_NEAR(out_right, -168.97, 1); EXPECT_NEAR(out_top, 81.91, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds__north_pole_xy) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:32661", "EPSG:4326", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto normalized_p = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper normal_keeper_P(normalized_p); ASSERT_NE(normalized_p, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds( m_ctxt, normalized_p, PJ_FWD, -1371213.7625429356, -1405880.71737131, 5371213.762542935, 5405880.71737131, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -180.0, 1); EXPECT_NEAR(out_bottom, 48.656, 1); EXPECT_NEAR(out_right, 180.0, 1); EXPECT_NEAR(out_top, 90.0, 1); double out_left_inv; double out_bottom_inv; double out_right_inv; double out_top_inv; int success_inv = proj_trans_bounds( m_ctxt, normalized_p, PJ_INV, -180.0, 60.0, 180.0, 90.0, &out_left_inv, &out_bottom_inv, &out_right_inv, &out_top_inv, 21); EXPECT_TRUE(success_inv == 1); EXPECT_NEAR(out_left_inv, -1371213.76, 1); EXPECT_NEAR(out_bottom_inv, -1405880.72, 1); EXPECT_NEAR(out_right_inv, 5371213.76, 1); EXPECT_NEAR(out_top_inv, 5405880.72, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds__north_pole) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:32661", "EPSG:4326", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, -1405880.71737131, -1371213.7625429356, 5405880.71737131, 5371213.762542935, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 48.656, 1); EXPECT_NEAR(out_bottom, -180.0, 1); EXPECT_NEAR(out_right, 90.0, 1); EXPECT_NEAR(out_top, 180.0, 1); double out_left_inv; double out_bottom_inv; double out_right_inv; double out_top_inv; int success_inv = proj_trans_bounds(m_ctxt, P, PJ_INV, 60.0, -180.0, 90.0, 180.0, &out_left_inv, &out_bottom_inv, &out_right_inv, &out_top_inv, 21); EXPECT_TRUE(success_inv == 1); EXPECT_NEAR(out_left_inv, -1405880.72, 1); EXPECT_NEAR(out_bottom_inv, -1371213.76, 1); EXPECT_NEAR(out_right_inv, 5405880.72, 1); EXPECT_NEAR(out_top_inv, 5371213.76, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds__south_pole_xy) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:32761", "EPSG:4326", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); auto normalized_p = proj_normalize_for_visualization(m_ctxt, P); ObjectKeeper normal_keeper_P(normalized_p); ASSERT_NE(normalized_p, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds( m_ctxt, normalized_p, PJ_FWD, -1371213.7625429356, -1405880.71737131, 5371213.762542935, 5405880.71737131, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -180.0, 1); EXPECT_NEAR(out_bottom, -90, 1); EXPECT_NEAR(out_right, 180.0, 1); EXPECT_NEAR(out_top, -48.656, 1); double out_left_inv; double out_bottom_inv; double out_right_inv; double out_top_inv; int success_inv = proj_trans_bounds( m_ctxt, normalized_p, PJ_INV, -180.0, -90.0, 180.0, -60.0, &out_left_inv, &out_bottom_inv, &out_right_inv, &out_top_inv, 21); EXPECT_TRUE(success_inv == 1); EXPECT_NEAR(out_left_inv, -1371213.76, 1); EXPECT_NEAR(out_bottom_inv, -1405880.72, 1); EXPECT_NEAR(out_right_inv, 5371213.76, 1); EXPECT_NEAR(out_top_inv, 5405880.72, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds__south_pole) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:32761", "EPSG:4326", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, -1405880.71737131, -1371213.7625429356, 5405880.71737131, 5371213.762542935, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -90.0, 1); EXPECT_NEAR(out_bottom, -180.0, 1); EXPECT_NEAR(out_right, -48.656, 1); EXPECT_NEAR(out_top, 180.0, 1); double out_left_inv; double out_bottom_inv; double out_right_inv; double out_top_inv; int success_inv = proj_trans_bounds(m_ctxt, P, PJ_INV, -90.0, -180.0, -60.0, 180.0, &out_left_inv, &out_bottom_inv, &out_right_inv, &out_top_inv, 21); EXPECT_TRUE(success_inv == 1); EXPECT_NEAR(out_left_inv, -1405880.72, 1); EXPECT_NEAR(out_bottom_inv, -1371213.76, 1); EXPECT_NEAR(out_right_inv, 5405880.72, 1); EXPECT_NEAR(out_top_inv, 5371213.76, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_to_compound_crs) { // EPSG:9707 = "WGS 84 + EGM96 height" auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "EPSG:9707", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 40, -120, 64, -80, &out_left, &out_bottom, &out_right, &out_top, 0); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 40, 1e-8); EXPECT_NEAR(out_bottom, -120, 1e-8); EXPECT_NEAR(out_right, 64, 1e-8); EXPECT_NEAR(out_top, -80, 1e-8); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_from_pipeline_lon_lat_to_x_y) { // EPSG:4326 to EPSG:3844 in GIS friendly order auto P = proj_create( m_ctxt, "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step " "+proj=push +v_3 +step +proj=cart +ellps=WGS84 +step +inv " "+proj=helmert +x=2.329 +y=-147.042 +z=-92.08 +rx=0.309 +ry=-0.325 " "+rz=-0.497 +s=5.69 +convention=coordinate_frame +step +inv +proj=cart " "+ellps=krass +step +proj=pop +v_3 +step +proj=sterea +lat_0=46 " "+lon_0=25 +k=0.99975 +x_0=500000 +y_0=500000 +ellps=krass"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_INV, 4e5, 4e5, 4.5e5, 4.5e5, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 23.717708, 1e-6); EXPECT_NEAR(out_bottom, 45.092624, 1e-6); EXPECT_NEAR(out_right, 24.363125, 1e-6); EXPECT_NEAR(out_top, 45.547942, 1e-6); } { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 23.717708, 45.092624, 24.363125, 45.547942, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 4e5, 1e3); EXPECT_NEAR(out_bottom, 4e5, 1e3); EXPECT_NEAR(out_right, 4.5e5, 1e3); EXPECT_NEAR(out_top, 4.5e5, 1e3); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_from_pipeline_lat_lon_to_x_y) { // EPSG:4326 to EPSG:3844 in authority compliant axis order auto P = proj_create( m_ctxt, "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step " "+proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=2.329 +y=-147.042 " "+z=-92.08 +rx=0.309 +ry=-0.325 +rz=-0.497 +s=5.69 " "+convention=coordinate_frame +step +inv +proj=cart +ellps=krass +step " "+proj=pop +v_3 +step +proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 " "+x_0=500000 +y_0=500000 +ellps=krass +step +proj=axisswap +order=2,1"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_INV, 4e5, 4e5, 4.5e5, 4.5e5, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 45.092624, 1e-6); EXPECT_NEAR(out_bottom, 23.717708, 1e-6); EXPECT_NEAR(out_right, 45.547942, 1e-6); EXPECT_NEAR(out_top, 24.363125, 1e-6); } { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 45.092624, 23.717708, 45.547942, 24.363125, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 4e5, 1e3); EXPECT_NEAR(out_bottom, 4e5, 1e3); EXPECT_NEAR(out_right, 4.5e5, 1e3); EXPECT_NEAR(out_top, 4.5e5, 1e3); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_from_pipeline_x_y_to_lon_lat) { // EPSG:3844 to EPSG:4326 in GIS friendly order auto P = proj_create( m_ctxt, "+proj=pipeline +step +inv +proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 " "+x_0=500000 +y_0=500000 +ellps=krass +step +proj=push +v_3 +step " "+proj=cart +ellps=krass +step +proj=helmert +x=2.329 +y=-147.042 " "+z=-92.08 +rx=0.309 +ry=-0.325 +rz=-0.497 +s=5.69 " "+convention=coordinate_frame +step +inv +proj=cart +ellps=WGS84 +step " "+proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 4e5, 4e5, 4.5e5, 4.5e5, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 23.717708, 1e-6); EXPECT_NEAR(out_bottom, 45.092624, 1e-6); EXPECT_NEAR(out_right, 24.363125, 1e-6); EXPECT_NEAR(out_top, 45.547942, 1e-6); } { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_INV, 23.717708, 45.092624, 24.363125, 45.547942, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 4e5, 1e3); EXPECT_NEAR(out_bottom, 4e5, 1e3); EXPECT_NEAR(out_right, 4.5e5, 1e3); EXPECT_NEAR(out_top, 4.5e5, 1e3); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_from_pipeline_x_y_to_lat_lon) { // EPSG:3844 to EPSG:4326 in authority compliant axis order auto P = proj_create( m_ctxt, "+proj=pipeline +step +proj=axisswap +order=2,1 +step +inv " "+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 +y_0=500000 " "+ellps=krass +step +proj=push +v_3 +step +proj=cart +ellps=krass " "+step +proj=helmert +x=2.329 +y=-147.042 +z=-92.08 +rx=0.309 " "+ry=-0.325 +rz=-0.497 +s=5.69 +convention=coordinate_frame +step +inv " "+proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, 4e5, 4e5, 4.5e5, 4.5e5, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 45.092624, 1e-6); EXPECT_NEAR(out_bottom, 23.717708, 1e-6); EXPECT_NEAR(out_right, 45.547942, 1e-6); EXPECT_NEAR(out_top, 24.363125, 1e-6); } { double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_INV, 45.092624, 23.717708, 45.547942, 24.363125, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, 4e5, 1e3); EXPECT_NEAR(out_bottom, 4e5, 1e3); EXPECT_NEAR(out_right, 4.5e5, 1e3); EXPECT_NEAR(out_top, 4.5e5, 1e3); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_world_geodetic_to_spilhaus) { auto P = proj_create_crs_to_crs(m_ctxt, "OGC:CRS84", "ESRI:54099", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; int success = proj_trans_bounds(m_ctxt, P, PJ_FWD, -180.0, -90.0, 180.0, 90.0, &out_left, &out_bottom, &out_right, &out_top, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -16336432.4, 1); EXPECT_NEAR(out_bottom, -16605405.9, 1); EXPECT_NEAR(out_right, 16574104.3, 1); EXPECT_NEAR(out_top, 16640152.9, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3d_densify_0_geog3D_to_proj2D) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4979", "+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 " "+a=6370997 +b=6370997 +units=m +no_defs", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; double zmin; double zmax; int success = proj_trans_bounds_3D(m_ctxt, P, PJ_FWD, 40, -120, 0, 64, -80, 100, &out_left, &out_bottom, &zmin, &out_right, &out_top, &zmax, 0); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -1684649.41338, 1); EXPECT_NEAR(out_bottom, -350356.81377, 1); EXPECT_NEAR(out_right, 1684649.41338, 1); EXPECT_NEAR(out_top, 2234551.18559, 1); EXPECT_EQ(zmin, 0); EXPECT_EQ(zmax, 100); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3d_noop) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4979", "EPSG:4979", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; double zmin; double zmax; int success = proj_trans_bounds_3D(m_ctxt, P, PJ_FWD, 40, -120, 0, 64, -80, 100, &out_left, &out_bottom, &zmin, &out_right, &out_top, &zmax, 0); EXPECT_TRUE(success == 1); EXPECT_EQ(out_left, 40); EXPECT_EQ(out_bottom, -120); EXPECT_EQ(out_right, 64); EXPECT_EQ(out_top, -80); EXPECT_EQ(zmin, 0); EXPECT_EQ(zmax, 100); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3d_geog3D_to_geocentric) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4979", "EPSG:4978", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double xmin; double ymin; double xmax; double ymax; double zmin; double zmax; int success = proj_trans_bounds_3D(m_ctxt, P, PJ_FWD, 49, 2, 0, 50, 3, 100, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax, 0); EXPECT_TRUE(success == 1); EXPECT_NEAR(xmin, 4102234.41, 1); EXPECT_NEAR(ymin, 143362.39, 1); EXPECT_NEAR(xmax, 4189946.59, 1); EXPECT_NEAR(ymax, 219418.53, 1); EXPECT_NEAR(zmin, 4790558.75, 1); EXPECT_NEAR(zmax, 4862865.64, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3d_inverse_of_geog3D_to_geocentric) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4978", "EPSG:4979", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double xmin; double ymin; double xmax; double ymax; double zmin; double zmax; int success = proj_trans_bounds_3D(m_ctxt, P, PJ_INV, 49, 2, 0, 50, 3, 100, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax, 0); EXPECT_TRUE(success == 1); EXPECT_NEAR(xmin, 4102234.41, 1); EXPECT_NEAR(xmax, 4189946.59, 1); EXPECT_NEAR(ymin, 143362.39, 1); EXPECT_NEAR(ymax, 219418.53, 1); EXPECT_NEAR(zmin, 4790558.75, 1); EXPECT_NEAR(zmax, 4862865.64, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3d_geocentric_to_geog3D) { auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4978", "EPSG:4979", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double xmin; double ymin; double xmax; double ymax; double zmin; double zmax; int success = proj_trans_bounds_3D( m_ctxt, P, PJ_FWD, 4102234.41, 143362.39, 4790558.75, 4189946.59, 219418.53, 4862865.64, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax, 2); EXPECT_TRUE(success == 1); EXPECT_NEAR(xmin, 49., .15); EXPECT_NEAR(ymin, 2, .15); EXPECT_NEAR(xmax, 50, .15); EXPECT_NEAR(ymax, 3., .15); EXPECT_NEAR(zmin, -57187, 1); EXPECT_NEAR(zmax, 56862.2, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3d_geocentric_to_geog2D_lon_lat_ordered) { auto P = proj_create_crs_to_crs( m_ctxt, "EPSG:4978", "+proj=longlat +datum=WGS84 +type=crs", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double xmin; double ymin; double xmax; double ymax; double zmin; double zmax; int success = proj_trans_bounds_3D( m_ctxt, P, PJ_FWD, 4102234.41, 143362.39, 4790558.75, 4189946.59, 219418.53, 4862865.64, &xmin, &ymin, &zmin, &xmax, &ymax, &zmax, 2); EXPECT_TRUE(success == 1); EXPECT_NEAR(xmin, 2, .15); EXPECT_NEAR(ymin, 49., .15); EXPECT_NEAR(xmax, 3., .15); EXPECT_NEAR(ymax, 50, .15); EXPECT_NEAR(zmin, -57187, 1); EXPECT_NEAR(zmax, 56862.2, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_trans_bounds_3D_ignore_inf) { // cf proj_trans_bounds_ignore_inf auto P = proj_create_crs_to_crs(m_ctxt, "OGC:CRS84", "ESRI:102036", nullptr); ObjectKeeper keeper_P(P); ASSERT_NE(P, nullptr); double out_left; double out_bottom; double out_right; double out_top; double z_min; double z_max; int success = proj_trans_bounds_3D( m_ctxt, P, PJ_FWD, -180.0, -90.0, 0, 180.0, 1.3, 0, &out_left, &out_bottom, &z_min, &out_right, &out_top, &z_max, 21); EXPECT_TRUE(success == 1); EXPECT_NEAR(out_left, -49621755.4, 1); EXPECT_NEAR(out_bottom, -116576598.5, 1); EXPECT_NEAR(out_right, 49621755.4, 1); EXPECT_NEAR(out_top, 50132027.2, 1); EXPECT_NEAR(z_min, 0, 1); EXPECT_NEAR(z_max, 0, 1); } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_has_point_motion_operation) { auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr); ASSERT_NE(ctxt, nullptr); ContextKeeper keeper_ctxt(ctxt); { auto crs = proj_create_from_database( m_ctxt, "EPSG", "4267", PJ_CATEGORY_CRS, false, nullptr); // NAD27 ASSERT_NE(crs, nullptr); ObjectKeeper keeper_crs(crs); EXPECT_FALSE(proj_crs_has_point_motion_operation(m_ctxt, crs)); } { // NAD83(CSRS)v7 auto crs = proj_create_from_database(m_ctxt, "EPSG", "8255", PJ_CATEGORY_CRS, false, nullptr); ASSERT_NE(crs, nullptr); ObjectKeeper keeper_crs(crs); EXPECT_TRUE(proj_crs_has_point_motion_operation(m_ctxt, crs)); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_coordoperation_requires_per_coordinate_input_time) { { PJ *P = proj_create(m_ctxt, "+proj=helmert +t_epoch=2010"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), true); } { PJ *P = proj_create(m_ctxt, "+proj=helmert"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), false); } #ifdef TIFF_ENABLED { PJ *P = proj_create(m_ctxt, "+proj=deformation +t_epoch=2010 " "+grids=tests/nkgrf03vel_realigned_extract.tif"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), true); } { PJ *P = proj_create(m_ctxt, "+proj=deformation +dt=1 " "+grids=tests/nkgrf03vel_realigned_extract.tif"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), false); } #endif { PJ *P = proj_create( m_ctxt, "+proj=defmodel +model=tests/simple_model_degree_3d.json"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), true); } { PJ *P = proj_create(m_ctxt, "+proj=pipeline +step +proj=set +v_4=2020 " "+proj=helmert +t_epoch=2010"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), false); } // Error cases { EXPECT_EQ(proj_coordoperation_requires_per_coordinate_input_time( m_ctxt, nullptr), false); } { PJ *P = proj_create(m_ctxt, "+proj=merc +type=crs"); ObjectKeeper keeper(P); EXPECT_EQ( proj_coordoperation_requires_per_coordinate_input_time(m_ctxt, P), false); } } // --------------------------------------------------------------------------- #if !defined(_WIN32) TEST_F(CApi, open_plenty_of_contexts) { // Test that we only consume 1 file handle for the connection to the // database std::vector dummyFilePointers; std::vector ctxts; // The number of file descriptors that can be opened simultaneously by a // process varies across platforms so we make use of getrlimit(2) to // retrieve it. struct rlimit open_max; getrlimit(RLIMIT_NOFILE, &open_max); // On some platforms fopen returned nullptrs before reaching limit - 50, we // can avoid this by capping the limit to 1024. if (open_max.rlim_cur > 1024) { open_max.rlim_cur = 1024; setrlimit(RLIMIT_NOFILE, &open_max); } for (rlim_t i = 0; i < open_max.rlim_cur - 50; i++) { FILE *f = fopen("/dev/null", "rb"); ASSERT_TRUE(f != nullptr); dummyFilePointers.push_back(f); } for (int i = 0; i < 100; i++) { PJ_CONTEXT *ctxt = proj_context_create(); ASSERT_TRUE(ctxt != nullptr); auto obj = proj_create(ctxt, "EPSG:4326"); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); ctxts.push_back(ctxt); } for (PJ_CONTEXT *ctxt : ctxts) { proj_context_destroy(ctxt); } for (FILE *f : dummyFilePointers) { fclose(f); } proj_cleanup(); } #endif // !defined(_WIN32) // --------------------------------------------------------------------------- #ifndef __MINGW32__ // We need std::thread support TEST_F(CApi, concurrent_context) { // Test that concurrent access to the database is thread safe. std::vector threads; for (int i = 0; i < 4; i++) { threads.emplace_back(std::thread([] { for (int j = 0; j < 60; j++) { PJ_CONTEXT *ctxt = proj_context_create(); { auto obj = proj_create(ctxt, "EPSG:4326"); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); } { auto obj = proj_create( ctxt, ("EPSG:" + std::to_string(32600 + j)).c_str()); ObjectKeeper keeper(obj); EXPECT_NE(obj, nullptr); } proj_context_destroy(ctxt); } })); } for (auto &t : threads) { t.join(); } proj_cleanup(); } #endif // __MINGW32__ // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_linear_affine_parametric_conversion) { { PJ *pj = proj_create_linear_affine_parametric_conversion( nullptr, nullptr, 1.1, nullptr, 0, 2.1, nullptr, 0, 3.1, nullptr, 0, 4.1, nullptr, 0, 5.1, nullptr, 0, 6.1, nullptr, 0); ObjectKeeper keeper(pj); auto wkt = proj_as_wkt(nullptr, pj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_STREQ(wkt, "CONVERSION[\"unnamed\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",1.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",2.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",3.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",4.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",5.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",6.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8641]]]"); } { PJ *pj = proj_create_linear_affine_parametric_conversion( m_ctxt, "conversion name", 1.1, "my unit1", 0.11, 2.1, "my unit2", 0.12, 3.1, "my unit3", 0.13, 4.1, "my unit4", 0.14, 5.1, "my unit5", 0.15, 6.1, "my unit6", 0.16); ObjectKeeper keeper(pj); auto wkt = proj_as_wkt(m_ctxt, pj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_STREQ(wkt, "CONVERSION[\"conversion name\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",1.1,\n" " LENGTHUNIT[\"my unit1\",0.11],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",2.1,\n" " SCALEUNIT[\"my unit2\",0.12],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",3.1,\n" " SCALEUNIT[\"my unit3\",0.13],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",4.1,\n" " LENGTHUNIT[\"my unit4\",0.14],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",5.1,\n" " SCALEUNIT[\"my unit5\",0.15],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",6.1,\n" " SCALEUNIT[\"my unit6\",0.16],\n" " ID[\"EPSG\",8641]]]"); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_create_derived_projected_crs) { PJ *proj_crs = proj_create(m_ctxt, "EPSG:32631"); ASSERT_NE(proj_crs, nullptr); ObjectKeeper keeper_proj_crs(proj_crs); PJ *conv = proj_create_linear_affine_parametric_conversion( m_ctxt, nullptr, 1.1, nullptr, 0, 2.1, nullptr, 0, 3.1, nullptr, 0, 4.1, nullptr, 0, 5.1, nullptr, 0, 6.1, nullptr, 0); ASSERT_NE(conv, nullptr); ObjectKeeper keeper_conv(conv); PJ *cs = proj_create_cartesian_2D_cs(m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0); ASSERT_NE(cs, nullptr); ObjectKeeper keeper_cs(cs); { PJ *pj = proj_create_derived_projected_crs(m_ctxt, "my derived crs", proj_crs, conv, cs); ASSERT_NE(pj, nullptr); ObjectKeeper keeper_pj(pj); auto wkt = proj_as_wkt(m_ctxt, pj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_STREQ( wkt, "DERIVEDPROJCRS[\"my derived crs\",\n" " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 " "(Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " ID[\"EPSG\",32631]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",1.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",2.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",3.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",4.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",5.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",6.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"); } { PJ *pj = proj_create_derived_projected_crs(m_ctxt, nullptr, proj_crs, conv, cs); ASSERT_NE(pj, nullptr); ObjectKeeper keeper_pj(pj); } // Error cases { EXPECT_EQ(proj_create_derived_projected_crs(m_ctxt, nullptr, nullptr, conv, cs), nullptr); EXPECT_EQ(proj_create_derived_projected_crs(m_ctxt, nullptr, proj_crs, nullptr, cs), nullptr); EXPECT_EQ(proj_create_derived_projected_crs(m_ctxt, nullptr, proj_crs, conv, nullptr), nullptr); EXPECT_EQ( proj_create_derived_projected_crs(m_ctxt, nullptr, conv, conv, cs), nullptr); EXPECT_EQ(proj_create_derived_projected_crs(m_ctxt, nullptr, proj_crs, cs, cs), nullptr); EXPECT_EQ(proj_create_derived_projected_crs(m_ctxt, nullptr, proj_crs, conv, proj_crs), nullptr); } } // --------------------------------------------------------------------------- TEST_F(CApi, proj_crs_add_horizontal_derived_conversion) { PJ *conv = proj_create_linear_affine_parametric_conversion( m_ctxt, nullptr, 1.1, nullptr, 0, 2.1, nullptr, 0, 3.1, nullptr, 0, 4.1, nullptr, 0, 5.1, nullptr, 0, 6.1, nullptr, 0); ASSERT_NE(conv, nullptr); ObjectKeeper keeper_conv(conv); PJ *cs = proj_create_cartesian_2D_cs(m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0); ASSERT_NE(cs, nullptr); ObjectKeeper keeper_cs(cs); { PJ *proj_crs = proj_create(m_ctxt, "EPSG:32631"); ASSERT_NE(proj_crs, nullptr); ObjectKeeper keeper_proj_crs(proj_crs); PJ *pj = proj_crs_add_horizontal_derived_conversion( m_ctxt, "my derived crs", proj_crs, conv, cs); ASSERT_NE(pj, nullptr); ObjectKeeper keeper_pj(pj); auto wkt = proj_as_wkt(m_ctxt, pj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_STREQ( wkt, "DERIVEDPROJCRS[\"my derived crs\",\n" " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 " "(Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " ID[\"EPSG\",32631]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",1.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",2.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",3.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",4.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",5.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",6.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"); } { PJ *compound_crs = proj_create(m_ctxt, "EPSG:32631+3855"); ASSERT_NE(compound_crs, nullptr); ObjectKeeper keeper_compound_crs(compound_crs); PJ *pj = proj_crs_add_horizontal_derived_conversion( m_ctxt, "my derived crs", compound_crs, conv, cs); ASSERT_NE(pj, nullptr); ObjectKeeper keeper_pj(pj); auto wkt = proj_as_wkt(m_ctxt, pj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_STREQ( wkt, "COMPOUNDCRS[\"my derived crs + EGM2008 height\",\n" " DERIVEDPROJCRS[\"my derived crs\",\n" " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 " "ensemble\",\n" " MEMBER[\"World Geodetic System 1984 " "(Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G730)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G873)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 " "(G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " ID[\"EPSG\",32631]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",1.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",2.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",3.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",4.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",5.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",6.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " VERTCRS[\"EGM2008 height\",\n" " VDATUM[\"EGM2008 geoid\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Geodesy.\"],\n" " AREA[\"World.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",3855]]]"); } { PJ *bound_crs = proj_create( m_ctxt, "+proj=utm +zone=31 +ellps=GRS80 +towgs84=0,0,0 +type=crs"); ASSERT_NE(bound_crs, nullptr); ObjectKeeper keeper_bound_crs(bound_crs); PJ *pj = proj_crs_add_horizontal_derived_conversion( m_ctxt, "my derived crs", bound_crs, conv, cs); ASSERT_NE(pj, nullptr); ObjectKeeper keeper_pj(pj); auto wkt = proj_as_wkt(m_ctxt, pj, PJ_WKT2_2019, nullptr); ASSERT_NE(wkt, nullptr); EXPECT_STREQ( wkt, "BOUNDCRS[\n" " SOURCECRS[\n" " DERIVEDPROJCRS[\"my derived crs\",\n" " BASEPROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"Unknown based on GRS 1980 ellipsoid " "using towgs84=0,0,0\",\n" " ELLIPSOID[\"GRS " "1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7019]]],\n" " PRIMEM[\"Greenwich\",0,\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " " "ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16031]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",1.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",2.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",3.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",4.1,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",5.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",6.1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " ABRIDGEDTRANSFORMATION[\"Transformation from unknown to " "WGS84\",\n" " METHOD[\"Geocentric translations (geog2D domain)\",\n" " ID[\"EPSG\",9603]],\n" " PARAMETER[\"X-axis translation\",0,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",0,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",0,\n" " ID[\"EPSG\",8607]]]]"); } // Error cases { PJ *proj_crs = proj_create(m_ctxt, "EPSG:32631"); ASSERT_NE(proj_crs, nullptr); ObjectKeeper keeper_proj_crs(proj_crs); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion(m_ctxt, nullptr, nullptr, conv, cs), nullptr); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion( m_ctxt, nullptr, proj_crs, nullptr, cs), nullptr); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion( m_ctxt, nullptr, proj_crs, conv, nullptr), nullptr); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion(m_ctxt, nullptr, conv, conv, cs), nullptr); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion(m_ctxt, nullptr, proj_crs, cs, cs), nullptr); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion( m_ctxt, nullptr, proj_crs, conv, proj_crs), nullptr); PJ *geog_crs = proj_create(m_ctxt, "EPSG:4326"); ASSERT_NE(geog_crs, nullptr); ObjectKeeper keeper_geog_crs(geog_crs); EXPECT_EQ(proj_crs_add_horizontal_derived_conversion( m_ctxt, "my derived crs", geog_crs, conv, cs), nullptr); } } } // namespace proj-9.8.1/test/unit/test_grids.cpp000664 001750 001750 00000032554 15166171715 017275 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test grids.hpp * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2020, Even Rouault * * 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 "gtest_include.h" #include "grids.hpp" #include "proj_internal.h" // M_PI namespace { // --------------------------------------------------------------------------- class GridTest : public ::testing::Test { static void DummyLogFunction(void *, int, const char *) {} protected: void SetUp() override { m_ctxt = proj_context_create(); proj_log_func(m_ctxt, nullptr, DummyLogFunction); m_ctxt2 = proj_context_create(); proj_log_func(m_ctxt2, nullptr, DummyLogFunction); } void TearDown() override { proj_context_destroy(m_ctxt); proj_context_destroy(m_ctxt2); } PJ_CONTEXT *m_ctxt = nullptr; PJ_CONTEXT *m_ctxt2 = nullptr; }; // --------------------------------------------------------------------------- TEST_F(GridTest, VerticalShiftGridSet_null) { auto gridSet = NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "null"); ASSERT_NE(gridSet, nullptr); auto grid = gridSet->gridAt(0.0, 0.0); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 3); EXPECT_EQ(grid->height(), 3); EXPECT_EQ(grid->extentAndRes().west, -M_PI); EXPECT_TRUE(grid->isNullGrid()); EXPECT_FALSE(grid->hasChanged()); float out = -1.0f; EXPECT_TRUE(grid->valueAt(0, 0, out)); EXPECT_EQ(out, 0.0f); EXPECT_FALSE(grid->isNodata(0.0f, 0.0)); gridSet->reassign_context(m_ctxt2); gridSet->reopen(m_ctxt2); } // --------------------------------------------------------------------------- TEST_F(GridTest, VerticalShiftGridSet_gtx) { ASSERT_EQ(NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "foobar"), nullptr); auto gridSet = NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "tests/test_nodata.gtx"); ASSERT_NE(gridSet, nullptr); ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); auto grid = gridSet->gridAt(4.15 / 180 * M_PI, 52.15 / 180 * M_PI); ASSERT_NE(grid, nullptr); EXPECT_TRUE(grid->isNodata(-88.8888f, 1.0)); gridSet->reassign_context(m_ctxt2); gridSet->reopen(m_ctxt2); grid = gridSet->gridAt(4.15 / 180 * M_PI, 52.15 / 180 * M_PI); EXPECT_NE(grid, nullptr); } // --------------------------------------------------------------------------- TEST_F(GridTest, HorizontalShiftGridSet_null) { auto gridSet = NS_PROJ::HorizontalShiftGridSet::open(m_ctxt, "null"); ASSERT_NE(gridSet, nullptr); auto grid = gridSet->gridAt(0.0, 0.0); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 3); EXPECT_EQ(grid->height(), 3); EXPECT_EQ(grid->extentAndRes().west, -M_PI); EXPECT_TRUE(grid->isNullGrid()); EXPECT_FALSE(grid->hasChanged()); float out1 = -1.0f; float out2 = -1.0f; EXPECT_TRUE(grid->valueAt(0, 0, false, out1, out2)); EXPECT_EQ(out1, 0.0f); EXPECT_EQ(out2, 0.0f); gridSet->reassign_context(m_ctxt2); gridSet->reopen(m_ctxt2); } // --------------------------------------------------------------------------- TEST_F(GridTest, GenericShiftGridSet_null) { auto gridSet = NS_PROJ::GenericShiftGridSet::open(m_ctxt, "null"); ASSERT_NE(gridSet, nullptr); auto grid = gridSet->gridAt(0.0, 0.0); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 3); EXPECT_EQ(grid->height(), 3); EXPECT_EQ(grid->extentAndRes().west, -M_PI); EXPECT_TRUE(grid->isNullGrid()); EXPECT_FALSE(grid->hasChanged()); float out = -1.0f; EXPECT_TRUE(grid->valueAt(0, 0, 0, out)); EXPECT_EQ(out, 0.0f); EXPECT_EQ(grid->unit(0), ""); EXPECT_EQ(grid->description(0), ""); EXPECT_EQ(grid->metadataItem("foo"), ""); EXPECT_EQ(grid->samplesPerPixel(), 0); gridSet->reassign_context(m_ctxt2); gridSet->reopen(m_ctxt2); } #ifdef TIFF_ENABLED // --------------------------------------------------------------------------- TEST_F(GridTest, HorizontalShiftGridSet_gtiff) { auto gridSet = NS_PROJ::HorizontalShiftGridSet::open(m_ctxt, "tests/test_hgrid.tif"); ASSERT_NE(gridSet, nullptr); EXPECT_EQ(gridSet->format(), "gtiff"); EXPECT_TRUE(gridSet->name().find("tests/test_hgrid.tif") != std::string::npos) << gridSet->name(); EXPECT_EQ(gridSet->grids().size(), 1U); ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); auto grid = gridSet->gridAt(5.5 / 180 * M_PI, 53.5 / 180 * M_PI); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 4); EXPECT_EQ(grid->height(), 4); EXPECT_EQ(grid->extentAndRes().west, 4.0 / 180 * M_PI); EXPECT_FALSE(grid->isNullGrid()); EXPECT_FALSE(grid->hasChanged()); float out1 = -1.0f; float out2 = -1.0f; EXPECT_TRUE(grid->valueAt(0, 3, false, out1, out2)); EXPECT_EQ(out1, static_cast(14400.0 / 3600. / 180 * M_PI)); EXPECT_EQ(out2, static_cast(900.0 / 3600. / 180 * M_PI)); gridSet->reassign_context(m_ctxt2); gridSet->reopen(m_ctxt2); grid = gridSet->gridAt(5.5 / 180 * M_PI, 53.5 / 180 * M_PI); EXPECT_NE(grid, nullptr); } // --------------------------------------------------------------------------- TEST_F(GridTest, GenericShiftGridSet_gtiff) { ASSERT_EQ(NS_PROJ::GenericShiftGridSet::open(m_ctxt, "foobar"), nullptr); auto gridSet = NS_PROJ::GenericShiftGridSet::open( m_ctxt, "tests/nkgrf03vel_realigned_extract.tif"); ASSERT_NE(gridSet, nullptr); ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); auto grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 5); EXPECT_EQ(grid->height(), 5); EXPECT_EQ(grid->extentAndRes().isGeographic, true); EXPECT_EQ(grid->extentAndRes().west, 21.0 / 180 * M_PI); EXPECT_FALSE(grid->isNullGrid()); EXPECT_FALSE(grid->hasChanged()); float out = -1.0f; EXPECT_FALSE(grid->valueAt(0, 0, grid->samplesPerPixel(), out)); EXPECT_TRUE(grid->valueAt(0, 0, 0, out)); EXPECT_EQ(out, 0.20783890783786773682f); EXPECT_TRUE(grid->valueAt(1, 0, 0, out)); EXPECT_EQ(out, 0.22427035868167877197); EXPECT_TRUE(grid->valueAt(0, 1, 0, out)); EXPECT_EQ(out, 0.19718019664287567139f); int bandZero = 0; bool nodataFound = false; EXPECT_TRUE(grid->valuesAt(0, 0, 1, 1, 1, &bandZero, &out, nodataFound)); EXPECT_EQ(out, 0.20783890783786773682f); constexpr int COUNT_X = 2; constexpr int COUNT_Y = 4; constexpr int COUNT_BANDS = 3; float values[COUNT_Y * COUNT_X * COUNT_BANDS]; const int bands[] = {0, 1, 2}; constexpr int OFFSET_X = 2; constexpr int OFFSET_Y = 1; EXPECT_TRUE(grid->valuesAt(OFFSET_X, OFFSET_Y, COUNT_X, COUNT_Y, COUNT_BANDS, bands, values, nodataFound)); int valuesIdx = 0; for (int y = 0; y < COUNT_Y; ++y) { for (int x = 0; x < COUNT_X; ++x) { for (int band = 0; band < COUNT_BANDS; ++band) { EXPECT_TRUE( grid->valueAt(OFFSET_X + x, OFFSET_Y + y, band, out)); EXPECT_EQ(out, values[valuesIdx]); ++valuesIdx; } } } EXPECT_EQ(grid->metadataItem("area_of_use"), "Nordic and Baltic countries"); EXPECT_EQ(grid->metadataItem("non_existing"), std::string()); EXPECT_EQ(grid->metadataItem("non_existing", 1), std::string()); EXPECT_EQ(grid->metadataItem("non_existing", 10), std::string()); gridSet->reassign_context(m_ctxt2); gridSet->reopen(m_ctxt2); grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI); EXPECT_NE(grid, nullptr); } // --------------------------------------------------------------------------- TEST_F(GridTest, GenericShiftGridSet_gtiff_valuesAt_tiled_optim) { auto gridSet = NS_PROJ::GenericShiftGridSet::open( m_ctxt, "tests/nkgrf03vel_realigned_extract_tiled_256x256.tif"); ASSERT_NE(gridSet, nullptr); ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); auto grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 5); EXPECT_EQ(grid->height(), 5); EXPECT_EQ(grid->extentAndRes().isGeographic, true); EXPECT_EQ(grid->extentAndRes().west, 21.0 / 180 * M_PI); EXPECT_FALSE(grid->isNullGrid()); EXPECT_FALSE(grid->hasChanged()); float out = -1.0f; EXPECT_FALSE(grid->valueAt(0, 0, grid->samplesPerPixel(), out)); EXPECT_TRUE(grid->valueAt(0, 0, 0, out)); EXPECT_EQ(out, 0.20783890783786773682f); EXPECT_TRUE(grid->valueAt(1, 0, 0, out)); EXPECT_EQ(out, 0.22427035868167877197); EXPECT_TRUE(grid->valueAt(0, 1, 0, out)); EXPECT_EQ(out, 0.19718019664287567139f); int bandZero = 0; bool nodataFound = false; EXPECT_TRUE(grid->valuesAt(0, 0, 1, 1, 1, &bandZero, &out, nodataFound)); EXPECT_EQ(out, 0.20783890783786773682f); constexpr int COUNT_X = 2; constexpr int COUNT_Y = 4; constexpr int COUNT_BANDS_THREE = 3; float values[COUNT_Y * COUNT_X * COUNT_BANDS_THREE]; const int bands[] = {0, 1, 2}; constexpr int OFFSET_X = 2; constexpr int OFFSET_Y = 1; EXPECT_TRUE(grid->valuesAt(OFFSET_X, OFFSET_Y, COUNT_X, COUNT_Y, COUNT_BANDS_THREE, bands, values, nodataFound)); for (int y = 0, valuesIdx = 0; y < COUNT_Y; ++y) { for (int x = 0; x < COUNT_X; ++x) { for (int band = 0; band < COUNT_BANDS_THREE; ++band) { EXPECT_TRUE( grid->valueAt(OFFSET_X + x, OFFSET_Y + y, band, out)); EXPECT_EQ(out, values[valuesIdx]); ++valuesIdx; } } } constexpr int COUNT_BANDS_TWO = 2; EXPECT_TRUE(grid->valuesAt(OFFSET_X, OFFSET_Y, COUNT_X, COUNT_Y, COUNT_BANDS_TWO, bands, values, nodataFound)); for (int y = 0, valuesIdx = 0; y < COUNT_Y; ++y) { for (int x = 0; x < COUNT_X; ++x) { for (int band = 0; band < COUNT_BANDS_TWO; ++band) { EXPECT_TRUE( grid->valueAt(OFFSET_X + x, OFFSET_Y + y, band, out)); EXPECT_EQ(out, values[valuesIdx]); ++valuesIdx; } } } } // --------------------------------------------------------------------------- TEST_F(GridTest, GenericShiftGridSet_gtiff_with_subgrid) { auto gridSet = NS_PROJ::GenericShiftGridSet::open( m_ctxt, "tests/test_hgrid_with_subgrid.tif"); ASSERT_NE(gridSet, nullptr); ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); auto grid = gridSet->gridAt(-115.5416667 / 180 * M_PI, 51.1666667 / 180 * M_PI); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 11); EXPECT_EQ(grid->height(), 21); EXPECT_EQ(grid->metadataItem("grid_name"), "ALbanff"); } // --------------------------------------------------------------------------- TEST_F(GridTest, GenericShiftGridSet_gtiff_with_two_level_of_subgrids_no_grid_name) { auto gridSet = NS_PROJ::GenericShiftGridSet::open( m_ctxt, "tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif"); ASSERT_NE(gridSet, nullptr); ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); auto grid = gridSet->gridAt(-45.5 / 180 * M_PI, 22.5 / 180 * M_PI); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 8); EXPECT_EQ(grid->height(), 8); } // --------------------------------------------------------------------------- TEST_F(GridTest, GenericShiftGridSet_gtiff_projected) { auto gridSet = NS_PROJ::GenericShiftGridSet::open( m_ctxt, "tests/test_3d_grid_projected.tif"); ASSERT_NE(gridSet, nullptr); ASSERT_EQ(gridSet->gridAt(-1000, -1000), nullptr); auto grid = gridSet->gridAt(1500300.0, 5400300.0); ASSERT_NE(grid, nullptr); EXPECT_EQ(grid->width(), 2); EXPECT_EQ(grid->height(), 2); EXPECT_EQ(grid->extentAndRes().isGeographic, false); EXPECT_EQ(grid->extentAndRes().west, 1500000.0); EXPECT_EQ(grid->extentAndRes().east, 1501000.0); EXPECT_EQ(grid->extentAndRes().south, 5400000.0); EXPECT_EQ(grid->extentAndRes().north, 5401000.0); EXPECT_EQ(grid->extentAndRes().resX, 1000); EXPECT_EQ(grid->extentAndRes().resY, 1000); } #endif // TIFF_ENABLED } // namespace proj-9.8.1/test/unit/main.cpp000664 001750 001750 00000003342 15166171715 016043 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: gtest main * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 #include "gtest_include.h" GTEST_API_ int main(int argc, char **argv) { // Use a potentially non-C locale to make sure we are robust setlocale(LC_ALL, ""); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } proj-9.8.1/test/unit/pj_phi2_test.cpp000664 001750 001750 00000010224 15166171715 017506 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test pj_phi2 function. * Author: Kurt Schwehr * ****************************************************************************** * Copyright (c) 2018, Google Inc. * * 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 "proj.h" #include "proj_internal.h" #include #include #include "gtest_include.h" namespace { TEST(PjPhi2Test, Basic) { PJ_CONTEXT *ctx = pj_get_default_ctx(); // Expectation is that only sane values of e (and nan is here reckoned to // be sane) are passed to pj_phi2. Thus the return value with other values // of e is "implementation dependent". constexpr auto inf = std::numeric_limits::infinity(); constexpr auto nan = std::numeric_limits::quiet_NaN(); // Strict equality is demanded here. EXPECT_EQ(M_PI_2, pj_phi2(ctx, +0.0, 0.0)); EXPECT_EQ(0.0, pj_phi2(ctx, 1.0, 0.0)); EXPECT_EQ(-M_PI_2, pj_phi2(ctx, inf, 0.0)); // We don't expect pj_phi2 to be called with negative ts (since ts = // exp(-psi)). However, in the current implementation it is odd in ts. // N.B. ts = +0.0 and ts = -0.0 return different results. EXPECT_EQ(-M_PI_2, pj_phi2(ctx, -0.0, 0.0)); EXPECT_EQ(0.0, pj_phi2(ctx, -1.0, 0.0)); EXPECT_EQ(+M_PI_2, pj_phi2(ctx, -inf, 0.0)); constexpr double e = 0.2; EXPECT_EQ(M_PI_2, pj_phi2(ctx, +0.0, e)); EXPECT_EQ(0.0, pj_phi2(ctx, 1.0, e)); EXPECT_EQ(-M_PI_2, pj_phi2(ctx, inf, e)); EXPECT_EQ(-M_PI_2, pj_phi2(ctx, -0.0, e)); EXPECT_EQ(0.0, pj_phi2(ctx, -1.0, e)); EXPECT_EQ(+M_PI_2, pj_phi2(ctx, -inf, e)); EXPECT_TRUE(std::isnan(pj_phi2(ctx, nan, 0.0))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, nan, e))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, +0.0, nan))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, 1.0, nan))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, inf, nan))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, -0.0, nan))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, -1.0, nan))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, -inf, nan))); EXPECT_TRUE(std::isnan(pj_phi2(ctx, nan, nan))); EXPECT_DOUBLE_EQ(M_PI / 3, pj_phi2(ctx, 1 / (sqrt(3.0) + 2), 0.0)); EXPECT_DOUBLE_EQ(M_PI / 4, pj_phi2(ctx, 1 / (sqrt(2.0) + 1), 0.0)); EXPECT_DOUBLE_EQ(M_PI / 6, pj_phi2(ctx, 1 / sqrt(3.0), 0.0)); EXPECT_DOUBLE_EQ(-M_PI / 3, pj_phi2(ctx, sqrt(3.0) + 2, 0.0)); EXPECT_DOUBLE_EQ(-M_PI / 4, pj_phi2(ctx, sqrt(2.0) + 1, 0.0)); EXPECT_DOUBLE_EQ(-M_PI / 6, pj_phi2(ctx, sqrt(3.0), 0.0)); // Generated with exp(e * atanh(e * sin(phi))) / (tan(phi) + sec(phi)) EXPECT_DOUBLE_EQ(M_PI / 3, pj_phi2(ctx, 0.27749174377027023413, e)); EXPECT_DOUBLE_EQ(M_PI / 4, pj_phi2(ctx, 0.42617788119104192995, e)); EXPECT_DOUBLE_EQ(M_PI / 6, pj_phi2(ctx, 0.58905302448626726064, e)); EXPECT_DOUBLE_EQ(-M_PI / 3, pj_phi2(ctx, 3.6037108218537833089, e)); EXPECT_DOUBLE_EQ(-M_PI / 4, pj_phi2(ctx, 2.3464380582241712935, e)); EXPECT_DOUBLE_EQ(-M_PI / 6, pj_phi2(ctx, 1.6976400399134411849, e)); } } // namespace proj-9.8.1/test/unit/test_projinfo_lib.cpp000664 001750 001750 00000011543 15166171715 020634 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test projinfo_lib API * Author: Javier Jimenez Shaw * ****************************************************************************** * Copyright (c) 2025, Javier Jimenez Shaw * * 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 "gtest_include.h" #include "apps/projapps_lib.h" #include // --------------------------------------------------------------------------- TEST(projinfo_lib, simple) { auto dump = [](PJ_PROJINFO_LOG_LEVEL level, const char *s, void *data) { bool *local_found = static_cast(data); if (level == PJ_PROJINFO_LOG_LEVEL_INFO) { std::string str(s); if (str.find("PROJCS[\"ETRS89 / UTM zone 32N\"") != std::string::npos) { *local_found = true; } } }; const char *argv[] = {"EPSG:25832", "-o", "WKT1_GDAL"}; constexpr int argc = sizeof(argv) / sizeof(*argv); bool found = false; int res = projinfo(nullptr, argc, (char **)argv, dump, &found); EXPECT_EQ(res, 0); EXPECT_EQ(found, true); } TEST(projinfo_lib, error) { auto dump = [](PJ_PROJINFO_LOG_LEVEL level, const char *s, void *data) { bool *local_found = static_cast(data); if (level == PJ_PROJINFO_LOG_LEVEL_ERR) { std::string str(s); if (str.find("unrecognized format / unknown name") != std::string::npos) { *local_found = true; } } }; const char *argv[] = {"doesnotwork"}; constexpr int argc = sizeof(argv) / sizeof(*argv); bool found = false; int res = projinfo(nullptr, argc, (char **)argv, dump, &found); EXPECT_EQ(res, 1); EXPECT_EQ(found, true); } TEST(projinfo_lib, warning) { auto dump = [](PJ_PROJINFO_LOG_LEVEL level, const char *s, void *data) { bool *local_found = static_cast(data); if (level == PJ_PROJINFO_LOG_LEVEL_WARN) { std::string str(s); if (str.find("Error when exporting to WKT1:GDAL") != std::string::npos) { *local_found = true; } } }; const char *argv[] = {"EPSG:4937", "-o", "WKT1_GDAL"}; constexpr int argc = sizeof(argv) / sizeof(*argv); bool found = false; int res = projinfo(nullptr, argc, (char **)argv, dump, &found); EXPECT_EQ(res, 0); EXPECT_EQ(found, true); } TEST(projinfo_lib, use_ctx) { auto dump = [](PJ_PROJINFO_LOG_LEVEL level, const char *s, void *data) { bool *local_found = static_cast(data); if (level == PJ_PROJINFO_LOG_LEVEL_INFO) { std::string str(s); if (str.find("PROJCS[\"ETRS89 / UTM zone 3") != std::string::npos) { *local_found = true; } } }; PJ_CONTEXT *ctx = proj_context_create(); { const char *argv[] = {"EPSG:25832", "-o", "WKT1_GDAL"}; constexpr int argc = sizeof(argv) / sizeof(*argv); bool found = false; int res = projinfo(ctx, argc, (char **)argv, dump, &found); EXPECT_EQ(res, 0); EXPECT_EQ(found, true); } { const char *argv[] = {"EPSG:25833", "-o", "WKT1_GDAL"}; constexpr int argc = sizeof(argv) / sizeof(*argv); bool found = false; int res = projinfo(ctx, argc, (char **)argv, dump, &found); EXPECT_EQ(res, 0); EXPECT_EQ(found, true); } proj_context_destroy(ctx); } TEST(projinfo_lib, works_without_cb) { // Testing it doesn't crash. const char *argv[] = {"EPSG:25832", "-o", "WKT1_GDAL"}; constexpr int argc = sizeof(argv) / sizeof(*argv); int res = projinfo(nullptr, argc, (char **)argv, nullptr, nullptr); EXPECT_EQ(res, 0); } proj-9.8.1/test/unit/test_defmodel.cpp000664 001750 001750 00000162011 15166171715 017734 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test deformation model * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2020, Even Rouault * * 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 "gtest_include.h" // to be able to use internal::toString #define FROM_PROJ_CPP #include "proj/internal/internal.hpp" #include "proj.h" #include "proj_internal.h" // Silence C4702 (unreachable code) due to some dummy implementation of the // interfaces of defmodel.hpp #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4702) #endif #define PROJ_COMPILATION #define DEFORMATON_MODEL_NAMESPACE TestDeformationModel #include "transformations/defmodel.hpp" using namespace DEFORMATON_MODEL_NAMESPACE; namespace { constexpr double modelMinX = 158; constexpr double modelMinY = -58; constexpr double modelMaxX = 194; constexpr double modelMaxY = -25; static json getMinValidContent() { json j; j["file_type"] = "GeoTIFF"; j["format_version"] = "1.0"; j["source_crs"] = "EPSG:4959"; j["target_crs"] = "EPSG:7907"; j["definition_crs"] = "EPSG:4959"; j["extent"]["type"] = "bbox"; j["extent"]["parameters"] = { {"bbox", {modelMinX, modelMinY, modelMaxX, modelMaxY}}}; j["time_extent"]["first"] = "1900-01-01T00:00:00Z"; j["time_extent"]["last"] = "2050-01-01T00:00:00Z"; j["components"] = json::array(); return j; } // --------------------------------------------------------------------------- constexpr int IDX_CONSTANT = 0; constexpr int IDX_VELOCITY = 1; constexpr int IDX_STEP = 2; constexpr int IDX_REVERSE_STEP = 3; constexpr int IDX_PIECEWISE = 4; constexpr int IDX_EXPONENTIAL = 5; static json getFullValidContent() { json j(getMinValidContent()); j["name"] = "name"; j["version"] = "version"; j["publication_date"] = "2018-07-01T00:00:00Z"; j["license"] = "license"; j["description"] = "description"; j["authority"]["name"] = "authority_name"; j["authority"]["url"] = "authority_url"; j["authority"]["address"] = "authority_address"; j["authority"]["email"] = "authority_email"; j["links"] = {{{"href", "href"}, {"rel", "rel"}, {"type", "type"}, {"title", "title"}}}; j["reference_epoch"] = "2000-01-01T00:00:00Z"; j["uncertainty_reference_epoch"] = "2018-12-15T00:00:00Z"; j["horizontal_offset_method"] = "addition"; j["horizontal_offset_unit"] = "metre"; j["vertical_offset_unit"] = "metre"; j["horizontal_uncertainty_type"] = "circular 95% confidence limit"; j["horizontal_uncertainty_unit"] = "metre"; j["vertical_uncertainty_type"] = "95% confidence limit"; j["vertical_uncertainty_unit"] = "metre"; j["components"] = {{ {"description", "description"}, {"displacement_type", "horizontal"}, {"uncertainty_type", "none"}, {"horizontal_uncertainty", 0.01}, {"vertical_uncertainty", 0.02}, {"extent", {{"type", "bbox"}, {"parameters", {{"bbox", {modelMinX, modelMinY, modelMaxX, modelMaxY}}}}}}, {"spatial_model", { {"type", "GeoTIFF"}, {"interpolation_method", "bilinear"}, {"filename", "nzgd2000-ndm-grid02.tif"}, {"md5_checksum", "49fce8ab267be2c8d00d43683060a032"}, }}, {"time_function", { {"type", "constant"}, {"parameters", json::object()}, }}, }}; j["components"].push_back(j["components"][0]); j["components"][IDX_VELOCITY]["time_function"] = { {"type", "velocity"}, {"parameters", {{"reference_epoch", "2000-01-01T00:00:00Z"}}}, }; j["components"].push_back(j["components"][0]); j["components"][IDX_STEP]["time_function"] = { {"type", "step"}, {"parameters", {{"step_epoch", "2000-01-01T00:00:00Z"}}}, }; j["components"].push_back(j["components"][0]); j["components"][IDX_REVERSE_STEP]["time_function"] = { {"type", "reverse_step"}, {"parameters", {{"step_epoch", "2000-01-01T00:00:00Z"}}}, }; j["components"].push_back(j["components"][0]); j["components"][IDX_PIECEWISE]["time_function"] = { {"type", "piecewise"}, {"parameters", {{"before_first", "zero"}, {"after_last", "constant"}, {"model", {{{"epoch", "2016-01-01T00:00:00Z"}, {"scale_factor", 0.5}}, {{"epoch", "2017-01-01T00:00:00Z"}, {"scale_factor", 1.0}}, {{"epoch", "2017-01-01T00:00:00Z"}, {"scale_factor", 2.0}}, {{"epoch", "2018-01-01T00:00:00Z"}, {"scale_factor", 1.0}}}}}}}; j["components"].push_back(j["components"][0]); j["components"][IDX_EXPONENTIAL]["time_function"] = { {"type", "exponential"}, {"parameters", { {"reference_epoch", "2000-01-01T00:00:00Z"}, {"end_epoch", "2001-01-01T00:00:00Z"}, {"relaxation_constant", 2.0}, {"before_scale_factor", 0.0}, {"initial_scale_factor", 1.0}, {"final_scale_factor", 3.0}, }}, }; return j; } // --------------------------------------------------------------------------- TEST(defmodel, basic) { EXPECT_THROW(MasterFile::parse("foo"), ParsingException); EXPECT_THROW(MasterFile::parse("null"), ParsingException); EXPECT_THROW(MasterFile::parse("{}"), ParsingException); const auto jMinValid(getMinValidContent()); { auto mf = MasterFile::parse(jMinValid.dump()); EXPECT_EQ(mf->fileType(), "GeoTIFF"); EXPECT_EQ(mf->formatVersion(), "1.0"); EXPECT_EQ(mf->sourceCRS(), "EPSG:4959"); EXPECT_EQ(mf->targetCRS(), "EPSG:7907"); EXPECT_EQ(mf->definitionCRS(), "EPSG:4959"); EXPECT_EQ(mf->extent().minx(), modelMinX); EXPECT_EQ(mf->extent().miny(), modelMinY); EXPECT_EQ(mf->extent().maxx(), modelMaxX); EXPECT_EQ(mf->extent().maxy(), modelMaxY); EXPECT_EQ(mf->timeExtent().first.toString(), "1900-01-01T00:00:00Z"); EXPECT_EQ(mf->timeExtent().last.toString(), "2050-01-01T00:00:00Z"); } // Check that removing one of each required key causes an exception for (const auto &kv : jMinValid.items()) { json jcopy(jMinValid); jcopy.erase(kv.key()); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["definition_crs"] = "EPSG:4326"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["file_type"] = 1; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"].erase("type"); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"].erase("parameters"); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"]["parameters"].clear(); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"]["parameters"].erase("bbox"); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"]["parameters"]["bbox"] = "foo"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"]["parameters"]["bbox"] = {0, 1, 2}; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["extent"]["parameters"]["bbox"] = {0, 1, 2, "foo"}; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["time_extent"] = "foo"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["time_extent"].erase("first"); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jMinValid); jcopy["time_extent"].erase("last"); EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } } // --------------------------------------------------------------------------- TEST(defmodel, full) { const auto jFullValid(getFullValidContent()); auto mf = MasterFile::parse(jFullValid.dump()); EXPECT_EQ(mf->name(), "name"); EXPECT_EQ(mf->version(), "version"); EXPECT_EQ(mf->publicationDate(), "2018-07-01T00:00:00Z"); EXPECT_EQ(mf->license(), "license"); EXPECT_EQ(mf->description(), "description"); EXPECT_EQ(mf->authority().name, "authority_name"); EXPECT_EQ(mf->authority().url, "authority_url"); EXPECT_EQ(mf->authority().address, "authority_address"); EXPECT_EQ(mf->authority().email, "authority_email"); EXPECT_EQ(mf->links().size(), 1U); EXPECT_EQ(mf->links()[0].href, "href"); EXPECT_EQ(mf->links()[0].rel, "rel"); EXPECT_EQ(mf->links()[0].type, "type"); EXPECT_EQ(mf->links()[0].title, "title"); EXPECT_EQ(mf->referenceEpoch(), "2000-01-01T00:00:00Z"); EXPECT_EQ(mf->uncertaintyReferenceEpoch(), "2018-12-15T00:00:00Z"); EXPECT_EQ(mf->horizontalOffsetUnit(), "metre"); EXPECT_EQ(mf->verticalOffsetUnit(), "metre"); EXPECT_EQ(mf->horizontalUncertaintyType(), "circular 95% confidence limit"); EXPECT_EQ(mf->horizontalUncertaintyUnit(), "metre"); EXPECT_EQ(mf->verticalUncertaintyType(), "95% confidence limit"); EXPECT_EQ(mf->verticalUncertaintyUnit(), "metre"); EXPECT_EQ(mf->horizontalOffsetMethod(), "addition"); ASSERT_EQ(mf->components().size(), 6U); { const auto &comp = mf->components()[IDX_CONSTANT]; EXPECT_EQ(comp.description(), "description"); EXPECT_EQ(comp.displacementType(), "horizontal"); EXPECT_EQ(comp.uncertaintyType(), "none"); EXPECT_EQ(comp.horizontalUncertainty(), 0.01); EXPECT_EQ(comp.verticalUncertainty(), 0.02); EXPECT_EQ(comp.extent().minx(), modelMinX); EXPECT_EQ(comp.extent().miny(), modelMinY); EXPECT_EQ(comp.extent().maxx(), modelMaxX); EXPECT_EQ(comp.extent().maxy(), modelMaxY); EXPECT_EQ(comp.spatialModel().type, "GeoTIFF"); EXPECT_EQ(comp.spatialModel().interpolationMethod, "bilinear"); EXPECT_EQ(comp.spatialModel().filename, "nzgd2000-ndm-grid02.tif"); EXPECT_EQ(comp.spatialModel().md5Checksum, "49fce8ab267be2c8d00d43683060a032"); ASSERT_NE(comp.timeFunction(), nullptr); ASSERT_EQ(comp.timeFunction()->type, "constant"); } { const auto &comp = mf->components()[IDX_VELOCITY]; ASSERT_NE(comp.timeFunction(), nullptr); ASSERT_EQ(comp.timeFunction()->type, "velocity"); const auto velocity = static_cast( comp.timeFunction()); EXPECT_EQ(velocity->referenceEpoch.toString(), "2000-01-01T00:00:00Z"); } { const auto &comp = mf->components()[IDX_STEP]; ASSERT_NE(comp.timeFunction(), nullptr); ASSERT_EQ(comp.timeFunction()->type, "step"); const auto step = static_cast( comp.timeFunction()); EXPECT_EQ(step->stepEpoch.toString(), "2000-01-01T00:00:00Z"); } { const auto &comp = mf->components()[IDX_REVERSE_STEP]; ASSERT_NE(comp.timeFunction(), nullptr); ASSERT_EQ(comp.timeFunction()->type, "reverse_step"); const auto step = static_cast( comp.timeFunction()); EXPECT_EQ(step->stepEpoch.toString(), "2000-01-01T00:00:00Z"); } { const auto &comp = mf->components()[IDX_PIECEWISE]; ASSERT_NE(comp.timeFunction(), nullptr); ASSERT_EQ(comp.timeFunction()->type, "piecewise"); const auto piecewise = static_cast( comp.timeFunction()); EXPECT_EQ(piecewise->beforeFirst, "zero"); EXPECT_EQ(piecewise->afterLast, "constant"); EXPECT_EQ(piecewise->model.size(), 4U); EXPECT_EQ(piecewise->model[0].epoch.toString(), "2016-01-01T00:00:00Z"); EXPECT_EQ(piecewise->model[0].scaleFactor, 0.5); } { const auto &comp = mf->components()[IDX_EXPONENTIAL]; ASSERT_NE(comp.timeFunction(), nullptr); ASSERT_EQ(comp.timeFunction()->type, "exponential"); const auto exponential = static_cast( comp.timeFunction()); EXPECT_EQ(exponential->referenceEpoch.toString(), "2000-01-01T00:00:00Z"); EXPECT_EQ(exponential->endEpoch.toString(), "2001-01-01T00:00:00Z"); EXPECT_EQ(exponential->relaxationConstant, 2.0); EXPECT_EQ(exponential->beforeScaleFactor, 0.0); EXPECT_EQ(exponential->initialScaleFactor, 1.0); EXPECT_EQ(exponential->finalScaleFactor, 3.0); } } // --------------------------------------------------------------------------- TEST(defmodel, error_cases) { const auto jFullValid(getFullValidContent()); { json jcopy(jFullValid); jcopy["horizontal_offset_method"] = "unsupported"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["horizontal_offset_unit"] = "unsupported"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["vertical_offset_unit"] = "unsupported"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["components"][IDX_CONSTANT]["spatial_model"] ["interpolation_method"] = "unsupported"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["components"][IDX_CONSTANT]["displacement_type"] = "unsupported"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["model"] = "foo"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["before_first"] = "illegal"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["after_last"] = "illegal"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } { json jcopy(jFullValid); jcopy["components"][0]["time_function"]["type"] = "unknown"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } // Unsupported combination { json jcopy(jFullValid); jcopy["horizontal_offset_method"] = "geocentric"; EXPECT_NO_THROW(MasterFile::parse(jcopy.dump())); } { json jcopy(jFullValid); jcopy["horizontal_offset_unit"] = "degree"; EXPECT_NO_THROW(MasterFile::parse(jcopy.dump())); } { json jcopy(jFullValid); jcopy["horizontal_offset_method"] = "geocentric"; jcopy["horizontal_offset_unit"] = "degree"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } // Unsupported combination { json jcopy(jFullValid); jcopy["components"][IDX_VELOCITY]["spatial_model"] ["interpolation_method"] = "geocentric_bilinear"; EXPECT_NO_THROW(MasterFile::parse(jcopy.dump())); } { json jcopy(jFullValid); jcopy["horizontal_offset_unit"] = "degree"; EXPECT_NO_THROW(MasterFile::parse(jcopy.dump())); } { json jcopy(jFullValid); jcopy["components"][IDX_VELOCITY]["spatial_model"] ["interpolation_method"] = "geocentric_bilinear"; jcopy["horizontal_offset_unit"] = "degree"; EXPECT_THROW(MasterFile::parse(jcopy.dump()), ParsingException); } } // --------------------------------------------------------------------------- TEST(defmodel, ISO8601ToDecimalYear) { EXPECT_EQ(ISO8601ToDecimalYear("2000-01-01T00:00:00Z"), 2000.0); EXPECT_EQ(ISO8601ToDecimalYear("2000-02-29T12:00:00Z"), 2000.0 + ((31 + 28) * 86400. + 12 * 3600) / (366 * 86400)); EXPECT_EQ(ISO8601ToDecimalYear("2000-12-31T23:59:59Z"), 2000.0 + (366 * 86400 - 1.) / (366 * 86400)); EXPECT_EQ(ISO8601ToDecimalYear("2001-01-01T00:00:00Z"), 2001.0); EXPECT_EQ(ISO8601ToDecimalYear("2001-12-31T23:59:59Z"), 2001.0 + (365 * 86400 - 1.) / (365 * 86400)); EXPECT_THROW(ISO8601ToDecimalYear(""), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("0000-01-01T00:00:00Z"), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("2001-02-29T00:00:00Z"), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("2000-13-01T00:00:00Z"), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("2000-01-32T00:00:00Z"), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("2000-01-01T24:00:00Z"), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("2000-01-01T00:60:00Z"), ParsingException); EXPECT_THROW(ISO8601ToDecimalYear("2000-01-01T00:00:61Z"), ParsingException); } // --------------------------------------------------------------------------- TEST(defmodel, evaluate_constant) { const auto jFullValid(getFullValidContent()); const auto mf = MasterFile::parse(jFullValid.dump()); const auto &comp = mf->components()[IDX_CONSTANT]; EXPECT_EQ(comp.timeFunction()->evaluateAt(1999.0), 1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.0), 1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2001.0), 1.0); } // --------------------------------------------------------------------------- TEST(defmodel, evaluate_velocity) { const auto jFullValid(getFullValidContent()); const auto mf = MasterFile::parse(jFullValid.dump()); const auto &comp = mf->components()[IDX_VELOCITY]; EXPECT_EQ(comp.timeFunction()->evaluateAt(1999.0), -1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.0), 0.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2001.0), 1.0); } // --------------------------------------------------------------------------- TEST(defmodel, evaluate_step) { const auto jFullValid(getFullValidContent()); const auto mf = MasterFile::parse(jFullValid.dump()); const auto &comp = mf->components()[IDX_STEP]; EXPECT_EQ(comp.timeFunction()->evaluateAt(1999.99), 0.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.00), 1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.01), 1.0); } // --------------------------------------------------------------------------- TEST(defmodel, evaluate_reverse_step) { const auto jFullValid(getFullValidContent()); const auto mf = MasterFile::parse(jFullValid.dump()); const auto &comp = mf->components()[IDX_REVERSE_STEP]; EXPECT_EQ(comp.timeFunction()->evaluateAt(1999.99), -1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.00), 0.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.01), 0.0); } // --------------------------------------------------------------------------- TEST(defmodel, evaluate_piecewise) { const auto jFullValid(getFullValidContent()); { const auto mf = MasterFile::parse(jFullValid.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.99), 0.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2016.00), 0.5); EXPECT_EQ(comp.timeFunction()->evaluateAt(2016.5), 0.75); EXPECT_NEAR(comp.timeFunction()->evaluateAt(2017 - 1e-9), 1.0, 1e-9); EXPECT_EQ(comp.timeFunction()->evaluateAt(2017.0), 2.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2017.5), 1.5); EXPECT_EQ(comp.timeFunction()->evaluateAt(2018.0), 1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2019.0), 1.0); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["before_first"] = "zero"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.5), 0.0); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["before_first"] = "constant"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.5), 0.5); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["before_first"] = "linear"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.5), 0.25); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["after_last"] = "zero"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2018.5), 0.0); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["after_last"] = "constant"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2018.5), 1.0); } { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["after_last"] = "linear"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2018.5), 0.5); } // No epoch { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["model"] .clear(); const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.5), 0.0); } // Just one epoch { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["model"] = { {{"epoch", "2016-01-01T00:00:00Z"}, {"scale_factor", 0.5}}}; jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["before_first"] = "linear"; jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["after_last"] = "linear"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.5), 0.5); EXPECT_EQ(comp.timeFunction()->evaluateAt(2016.5), 0.5); } // Two identical epochs { json jcopy(jFullValid); jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["model"] = { {{"epoch", "2016-01-01T00:00:00Z"}, {"scale_factor", 0.5}}, {{"epoch", "2016-01-01T00:00:00Z"}, {"scale_factor", 1.0}}}; jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["before_first"] = "linear"; jcopy["components"][IDX_PIECEWISE]["time_function"]["parameters"] ["after_last"] = "linear"; const auto mf = MasterFile::parse(jcopy.dump()); const auto &comp = mf->components()[IDX_PIECEWISE]; EXPECT_EQ(comp.timeFunction()->evaluateAt(2015.5), 0.5); EXPECT_EQ(comp.timeFunction()->evaluateAt(2016.5), 1.0); } } // --------------------------------------------------------------------------- TEST(defmodel, evaluate_exponential) { const auto jFullValid(getFullValidContent()); const auto mf = MasterFile::parse(jFullValid.dump()); const auto &comp = mf->components()[IDX_EXPONENTIAL]; EXPECT_EQ(comp.timeFunction()->evaluateAt(1999.99), 0.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.00), 1.0); EXPECT_EQ(comp.timeFunction()->evaluateAt(2000.50), 1.0 + (3.0 - 1.0) * (1.0 - std::exp(-(2000.50 - 2000.00) / 2.0))); EXPECT_EQ(comp.timeFunction()->evaluateAt(2001.00), 1.0 + (3.0 - 1.0) * (1.0 - std::exp(-(2001.00 - 2000.00) / 2.0))); EXPECT_EQ(comp.timeFunction()->evaluateAt(2002.00), 1.0 + (3.0 - 1.0) * (1.0 - std::exp(-(2001.00 - 2000.00) / 2.0))); } // --------------------------------------------------------------------------- inline double RadToDeg(double d) { return d / DEG_TO_RAD_CONSTANT; } // --------------------------------------------------------------------------- TEST(defmodel, evaluator_horizontal_unit_degree) { json j(getMinValidContent()); j["horizontal_offset_method"] = "addition"; j["horizontal_offset_unit"] = "degree"; constexpr double tFactor = 0.5; constexpr double gridMinX = 160; constexpr double gridMinY = -50; constexpr double gridMaxX = 190; constexpr double gridMaxY = -30; j["components"] = { {{"displacement_type", "horizontal"}, {"uncertainty_type", "none"}, {"extent", {{"type", "bbox"}, {"parameters", {{"bbox", {gridMinX, gridMinY, gridMaxX, gridMaxY}}}}}}, {"spatial_model", { {"type", "GeoTIFF"}, {"interpolation_method", "bilinear"}, {"filename", "bla.tif"}, }}, {"time_function", {{"type", "piecewise"}, {"parameters", {{"before_first", "zero"}, {"after_last", "zero"}, {"model", {{{"epoch", "2010-01-01T00:00:00Z"}, {"scale_factor", tFactor}}, {{"epoch", "2020-01-01T00:00:00Z"}, {"scale_factor", tFactor}}}}}}}}}}; constexpr int iQueriedX = 1; constexpr int iQueriedY = 3; constexpr double longOffsetQueriedX = 0.01; constexpr double longOffsetQueriedXp1 = 0.02; constexpr double latOffsetQueriedY = 0.03; constexpr double latOffsetQueriedYp1 = 0.04; constexpr double zOffsetQueriedXY = 10.; constexpr double zOffsetQueriedXp1Y = 11.; constexpr double zOffsetQueriedXYp1 = 11.; constexpr double zOffsetQueriedXp1Yp1 = 12.; constexpr double gridResX = 2; constexpr double gridResY = 0.5; struct Grid : public GridPrototype { bool getLongLatOffset(int ix, int iy, double &longOffsetRadian, double &latOffsetRadian) const { if (ix == iQueriedX) { longOffsetRadian = DegToRad(longOffsetQueriedX); } else if (ix == iQueriedX + 1) { longOffsetRadian = DegToRad(longOffsetQueriedXp1); } else { return false; } if (iy == iQueriedY) { latOffsetRadian = DegToRad(latOffsetQueriedY); } else if (iy == iQueriedY + 1) { latOffsetRadian = DegToRad(latOffsetQueriedYp1); } else { return false; } return true; } bool getZOffset(int ix, int iy, double &zOffset) const { if (ix == iQueriedX && iy == iQueriedY) { zOffset = zOffsetQueriedXY; } else if (ix == iQueriedX + 1 && iy == iQueriedY) { zOffset = zOffsetQueriedXp1Y; } else if (ix == iQueriedX && iy == iQueriedY + 1) { zOffset = zOffsetQueriedXYp1; } else if (ix == iQueriedX + 1 && iy == iQueriedY + 1) { zOffset = zOffsetQueriedXp1Yp1; } else { return false; } return true; } bool getLongLatZOffset(int ix, int iy, double &longOffsetRadian, double &latOffsetRadian, double &zOffset) const { return getLongLatOffset(ix, iy, longOffsetRadian, latOffsetRadian) && getZOffset(ix, iy, zOffset); } #ifdef DEBUG_DEFMODEL std::string name() const { return std::string(); } #endif }; struct GridSet : public GridSetPrototype { Grid grid{}; GridSet() { grid.minx = DegToRad(gridMinX); grid.miny = DegToRad(gridMinY); grid.resx = DegToRad(gridResX); grid.resy = DegToRad(gridResY); grid.width = 1 + static_cast(0.5 + (gridMaxX - gridMinX) / gridResX); grid.height = 1 + static_cast(0.5 + (gridMaxY - gridMinY) / gridResY); } const Grid *gridAt(double /*x */, double /* y */) { return &grid; } }; struct EvaluatorIface : public EvaluatorIfacePrototype { std::unique_ptr open(const std::string &filename) { if (filename != "bla.tif") return nullptr; return std::unique_ptr(new GridSet()); } bool isGeographicCRS(const std::string & /* crsDef */) { return true; } #ifdef DEBUG_DEFMODEL void log(const std::string & /* msg */) {} #endif }; EvaluatorIface iface; Evaluator eval(MasterFile::parse(j.dump()), iface, 1, 1); double newLong; double newLat; double newZ; constexpr double tValid = 2018; constexpr double EPS = 1e-9; constexpr double zVal = 100; // Query on exact grid intersection { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude + tFactor * longOffsetQueriedX, EPS); EXPECT_NEAR(RadToDeg(newLat), lat + tFactor * latOffsetQueriedY, EPS); EXPECT_EQ(newZ, zVal); } // Query between grid points { constexpr double alphaX = 0.25; constexpr double alphaY = 0.125; const double longitude = gridMinX + iQueriedX * gridResX + alphaX * gridResX; const double lat = gridMinY + iQueriedY * gridResY + alphaY * gridResY; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude + tFactor * (longOffsetQueriedX + alphaX * (longOffsetQueriedXp1 - longOffsetQueriedX)), EPS); EXPECT_NEAR( RadToDeg(newLat), lat + tFactor * (latOffsetQueriedY + alphaY * (latOffsetQueriedYp1 - latOffsetQueriedY)), EPS); EXPECT_EQ(newZ, zVal); } // Longitude < model min { const double longitude = modelMinX - 1e-1; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_FALSE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); } // Longitude > model max { const double longitude = modelMaxX + 1e-1; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_FALSE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); } // Latitude < model min { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = modelMinY - 1e-1; EXPECT_FALSE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); } // Latitude > model max { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = modelMaxY + 1e-1; EXPECT_FALSE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); } // Before timeExtent.first { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_FALSE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, 1000, newLong, newLat, newZ)); } // After timeExtent.last { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_FALSE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, 3000, newLong, newLat, newZ)); } // Longitude < grid min { const double longitude = gridMinX - 1e-1; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude, EPS); EXPECT_NEAR(RadToDeg(newLat), lat, EPS); EXPECT_EQ(newZ, zVal); } // Longitude > grid max { const double longitude = gridMaxX + 1e-1; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude, EPS); EXPECT_NEAR(RadToDeg(newLat), lat, EPS); EXPECT_EQ(newZ, zVal); } // Latitude < grid min { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = gridMinY - 1e-1; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude, EPS); EXPECT_NEAR(RadToDeg(newLat), lat, EPS); EXPECT_EQ(newZ, zVal); } // Latitude > grid max { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = gridMaxY + 1e-1; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude, EPS); EXPECT_NEAR(RadToDeg(newLat), lat, EPS); EXPECT_EQ(newZ, zVal); } // Time function values to zero { const double longitude = gridMinX + iQueriedX * gridResX; const double lat = gridMinY + iQueriedY * gridResY; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, 2000, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude, EPS); EXPECT_NEAR(RadToDeg(newLat), lat, EPS); EXPECT_EQ(newZ, zVal); } // Test vertical j["components"][0]["displacement_type"] = "vertical"; j["vertical_offset_unit"] = "metre"; Evaluator evalVertical( MasterFile::parse(j.dump()), iface, 1, 1); { constexpr double alphaX = 0.25; constexpr double alphaY = 0.125; const double longitude = gridMinX + iQueriedX * gridResX + alphaX * gridResX; const double lat = gridMinY + iQueriedY * gridResY + alphaY * gridResY; EXPECT_TRUE(evalVertical.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude, EPS); EXPECT_NEAR(RadToDeg(newLat), lat, EPS); const double zBottom = zOffsetQueriedXY + alphaX * (zOffsetQueriedXp1Y - zOffsetQueriedXY); const double zTop = zOffsetQueriedXYp1 + alphaX * (zOffsetQueriedXp1Yp1 - zOffsetQueriedXYp1); EXPECT_NEAR( newZ, zVal + tFactor * (zBottom + alphaY * (zTop - zBottom)), EPS); } // Test 3d j["components"][0]["displacement_type"] = "3d"; j["vertical_offset_unit"] = "metre"; Evaluator eval3d(MasterFile::parse(j.dump()), iface, 1, 1); { constexpr double alphaX = 0.25; constexpr double alphaY = 0.125; const double longitude = gridMinX + iQueriedX * gridResX + alphaX * gridResX; const double lat = gridMinY + iQueriedY * gridResY + alphaY * gridResY; EXPECT_TRUE(eval3d.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); EXPECT_NEAR(RadToDeg(newLong), longitude + tFactor * (longOffsetQueriedX + alphaX * (longOffsetQueriedXp1 - longOffsetQueriedX)), EPS); EXPECT_NEAR( RadToDeg(newLat), lat + tFactor * (latOffsetQueriedY + alphaY * (latOffsetQueriedYp1 - latOffsetQueriedY)), EPS); const double zBottom = zOffsetQueriedXY + alphaX * (zOffsetQueriedXp1Y - zOffsetQueriedXY); const double zTop = zOffsetQueriedXYp1 + alphaX * (zOffsetQueriedXp1Yp1 - zOffsetQueriedXYp1); EXPECT_NEAR( newZ, zVal + tFactor * (zBottom + alphaY * (zTop - zBottom)), EPS); } } // --------------------------------------------------------------------------- inline void DeltaLongLatToEastingNorthing(double phi, double dlam, double dphi, double a, double b, double &de, double &dn) { const double sinphi = sin(phi); const double cosphi = cos(phi); const double a2 = a * a; const double b2 = b * b; const double X = a2 * (cosphi * cosphi) + b2 * (sinphi * sinphi); const double sqrtX = sqrt(X); de = dlam * (a2 * cosphi) / sqrtX; dn = dphi * a2 * b2 / (sqrtX * X); } // --------------------------------------------------------------------------- TEST(defmodel, evaluator_horizontal_unit_metre) { json j(getMinValidContent()); j["horizontal_offset_method"] = "addition"; j["horizontal_offset_unit"] = "metre"; j["vertical_offset_unit"] = "metre"; constexpr double tFactor = 0.5; constexpr double gridMinX = 165.8; constexpr double gridMinY = -37.5; constexpr double gridMaxX = 166.2; constexpr double gridMaxY = -37.2; constexpr double gridResX = gridMaxX - gridMinX; constexpr double gridResY = gridMaxY - gridMinY; constexpr int extraPointX = 1; constexpr int extraPointY = 1; j["components"] = { {{"displacement_type", "horizontal"}, {"uncertainty_type", "none"}, {"extent", {{"type", "bbox"}, {"parameters", {{"bbox", {gridMinX - extraPointX * gridResX, gridMinY - extraPointY * gridResY, gridMaxX, gridMaxY}}}}}}, {"spatial_model", { {"type", "GeoTIFF"}, {"interpolation_method", "XXXXXXX"}, {"filename", "bla.tif"}, }}, {"time_function", {{"type", "piecewise"}, {"parameters", {{"before_first", "zero"}, {"after_last", "zero"}, {"model", {{{"epoch", "2010-01-01T00:00:00Z"}, {"scale_factor", tFactor}}, {{"epoch", "2020-01-01T00:00:00Z"}, {"scale_factor", tFactor}}}}}}}}}}; struct Grid : public GridPrototype { bool getEastingNorthingOffset(int ix, int iy, double &eastingOffset, double &northingOffset) const { ix -= extraPointX; iy -= extraPointY; if (ix == -1) ix = 0; if (iy == -1) iy = 0; if (ix == 0 && iy == 0) { eastingOffset = 0.4f; northingOffset = -0.2f; } else if (ix == 1 && iy == 0) { eastingOffset = 0.5f; northingOffset = -0.25f; } else if (ix == 0 && iy == 1) { eastingOffset = 0.8f; northingOffset = -0.4f; } else if (ix == 1 && iy == 1) { eastingOffset = 1.f; northingOffset = -0.3f; } else { return false; } return true; } bool getZOffset(int ix, int iy, double &zOffset) const { ix -= extraPointX; iy -= extraPointY; if (ix == -1) ix = 0; if (iy == -1) iy = 0; if (ix == 0 && iy == 0) { zOffset = 0.84f; } else if (ix == 1 && iy == 0) { zOffset = 0.75f; } else if (ix == 0 && iy == 1) { zOffset = 0.36f; } else if (ix == 1 && iy == 1) { zOffset = 0.f; } else { return false; } return true; } bool getEastingNorthingZOffset(int ix, int iy, double &eastingOffset, double &northingOffset, double &zOffset) const { return getEastingNorthingOffset(ix, iy, eastingOffset, northingOffset) && getZOffset(ix, iy, zOffset); } #ifdef DEBUG_DEFMODEL std::string name() const { return std::string(); } #endif }; struct GridSet : public GridSetPrototype { Grid grid{}; GridSet() { grid.minx = DegToRad(gridMinX - extraPointX * gridResX); grid.miny = DegToRad(gridMinY - extraPointY * gridResY); grid.resx = DegToRad(gridResX); grid.resy = DegToRad(gridResY); grid.width = 2 + extraPointX; grid.height = 2 + extraPointY; } const Grid *gridAt(double /*x */, double /* y */) { return &grid; } }; struct EvaluatorIface : public EvaluatorIfacePrototype { std::unique_ptr open(const std::string &filename) { if (filename != "bla.tif") return nullptr; return std::unique_ptr(new GridSet()); } bool isGeographicCRS(const std::string & /* crsDef */) { return true; } #ifdef DEBUG_DEFMODEL void log(const std::string & /* msg */) {} #endif void geographicToGeocentric(double lam, double phi, double height, double a, double /*b*/, double es, double &X, double &Y, double &Z) { PJ_CONTEXT *ctx = proj_context_create(); PJ *cart = proj_create( ctx, ("+proj=cart +a=" + osgeo::proj::internal::toString(a, 18) + " +es=" + osgeo::proj::internal::toString(es, 18)) .c_str()); PJ_LPZ lpz; lpz.lam = lam; lpz.phi = phi; lpz.z = height; PJ_XYZ xyz = cart->fwd3d(lpz, cart); X = xyz.x; Y = xyz.y; Z = xyz.z; proj_destroy(cart); proj_context_destroy(ctx); } void geocentricToGeographic(double X, double Y, double Z, double a, double /*b*/, double es, double &lam, double &phi, double &height) { PJ_CONTEXT *ctx = proj_context_create(); PJ *cart = proj_create( ctx, ("+proj=cart +a=" + osgeo::proj::internal::toString(a, 18) + " +es=" + osgeo::proj::internal::toString(es, 18)) .c_str()); PJ_XYZ xyz; xyz.x = X; xyz.y = Y; xyz.z = Z; PJ_LPZ lpz = cart->inv3d(xyz, cart); lam = lpz.lam; phi = lpz.phi; height = lpz.z; proj_destroy(cart); proj_context_destroy(ctx); } }; EvaluatorIface iface; constexpr double a = 6378137; constexpr double b = 6356752.314140; constexpr double tValid = 2018; constexpr double zVal = 100; const struct { double longitude; double lat; double expected_de; double expected_dn; double expected_dz; const char *displacement_type; const char *interpolation_method; } testPoints[] = { {gridMinX - extraPointX * gridResX - 1e-11, gridMinY - extraPointY * gridResY - 1e-11, 0.4, -0.2, 0, "horizontal", "bilinear"}, {gridMinX, gridMinY, 0.4, -0.2, 0, "horizontal", "bilinear"}, {gridMaxX, gridMinY, 0.5, -0.25, 0, "horizontal", "bilinear"}, {gridMinX, gridMaxY, 0.8, -0.4, 0, "horizontal", "bilinear"}, {gridMaxX, gridMaxY, 1, -0.3, 0, "horizontal", "bilinear"}, {gridMaxX + 1e-11, gridMaxY + 1e-11, 1, -0.3, 0, "horizontal", "bilinear"}, {165.9, -37.3, 0.70833334, -0.32083334, 0, "horizontal", "bilinear"}, {165.9, -37.3, 0.70833334, -0.32083334, 0.4525, "3d", "bilinear"}, {gridMinX, gridMinY, 0.4, -0.2, 0, "horizontal", "geocentric_bilinear"}, {gridMaxX, gridMinY, 0.5, -0.25, 0, "horizontal", "geocentric_bilinear"}, {gridMinX, gridMaxY, 0.8, -0.4, 0, "horizontal", "geocentric_bilinear"}, {gridMaxX, gridMaxY, 1, -0.3, 0, "horizontal", "geocentric_bilinear"}, {165.9, -37.3, 0.7083692044608846, -0.3209642339711405, 0, "horizontal", "geocentric_bilinear"}, {165.9, -37.3, 0.7083692044608846, -0.3209642339711405, 0.4525, "3d", "geocentric_bilinear"}, }; for (const auto &testPoint : testPoints) { j["components"][0]["displacement_type"] = testPoint.displacement_type; j["components"][0]["spatial_model"]["interpolation_method"] = testPoint.interpolation_method; Evaluator eval( MasterFile::parse(j.dump()), iface, a, b); const double longitude = testPoint.longitude; const double lat = testPoint.lat; double newLong; double newLat; double newZ; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)) << longitude << " " << lat << " " << testPoint.displacement_type << testPoint.interpolation_method; EXPECT_NEAR(newZ - zVal, tFactor * testPoint.expected_dz, 1e-8) << longitude << " " << lat << " " << testPoint.displacement_type << testPoint.interpolation_method; double de; double dn; DeltaLongLatToEastingNorthing(DegToRad(lat), newLong - DegToRad(longitude), newLat - DegToRad(lat), a, b, de, dn); EXPECT_NEAR(de, tFactor * testPoint.expected_de, 1e-8) << longitude << " " << lat << " " << testPoint.displacement_type << testPoint.interpolation_method; EXPECT_NEAR(dn, tFactor * testPoint.expected_dn, 1e-8) << longitude << " " << lat << " " << testPoint.displacement_type << testPoint.interpolation_method; if (longitude == gridMinX && lat == gridMinY) { // Redo the exact same test, to test caching double newLong2; double newLat2; double newZ2; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong2, newLat2, newZ2)); EXPECT_EQ(newLong2, newLong); EXPECT_EQ(newLat2, newLat); EXPECT_EQ(newZ2, newZ); // Shift in longitude EXPECT_TRUE(eval.forward(iface, DegToRad(longitude - gridResX / 2), DegToRad(lat), zVal, tValid, newLong2, newLat2, newZ2)); // Redo test at original position EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong2, newLat2, newZ2)); EXPECT_EQ(newLong2, newLong); EXPECT_EQ(newLat2, newLat); EXPECT_EQ(newZ2, newZ); // Shift in latitude EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat - gridResY / 2), zVal, tValid, newLong2, newLat2, newZ2)); // Redo test at original position EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong2, newLat2, newZ2)); EXPECT_EQ(newLong2, newLong); EXPECT_EQ(newLat2, newLat); EXPECT_EQ(newZ2, newZ); } } // Test inverse() { j["horizontal_offset_method"] = "addition"; j["components"][0]["displacement_type"] = "3d"; j["components"][0]["spatial_model"]["interpolation_method"] = "bilinear"; Evaluator eval( MasterFile::parse(j.dump()), iface, a, b); const double longitude = 165.9; const double lat = -37.3; double newLong; double newLat; double newZ; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); double invLongitude; double invLat; double invZ; EXPECT_TRUE(eval.inverse(iface, newLong, newLat, newZ, tValid, invLongitude, invLat, invZ)); EXPECT_NEAR(RadToDeg(invLongitude), longitude, 1e-10); EXPECT_NEAR(RadToDeg(invLat), lat, 1e-10); EXPECT_NEAR(invZ, zVal, 1e-4); } // Test horizontal_offset_method = geocentric { j["horizontal_offset_method"] = "geocentric"; j["components"][0]["displacement_type"] = "3d"; j["components"][0]["spatial_model"]["interpolation_method"] = "bilinear"; Evaluator eval( MasterFile::parse(j.dump()), iface, a, b); const double longitude = gridMinX; const double lat = gridMinY; double newLong; double newLat; double newZ; EXPECT_TRUE(eval.forward(iface, DegToRad(longitude), DegToRad(lat), zVal, tValid, newLong, newLat, newZ)); double de; double dn; constexpr double expected_de = 0.40000000948081327; constexpr double expected_dn = -0.19999999810542682; DeltaLongLatToEastingNorthing(DegToRad(lat), newLong - DegToRad(longitude), newLat - DegToRad(lat), a, b, de, dn); EXPECT_NEAR(de, tFactor * expected_de, 1e-10); EXPECT_NEAR(dn, tFactor * expected_dn, 1e-9); EXPECT_NEAR(newZ - zVal, tFactor * 0.84, 1e-4); } } // --------------------------------------------------------------------------- TEST(defmodel, evaluator_projected_crs) { json j(getMinValidContent()); j["horizontal_offset_method"] = "addition"; j["horizontal_offset_unit"] = "metre"; j["vertical_offset_unit"] = "metre"; constexpr double gridMinX = 10000; constexpr double gridMinY = 20000; constexpr double gridMaxX = 30000; constexpr double gridMaxY = 40000; constexpr double gridResX = gridMaxX - gridMinX; constexpr double gridResY = gridMaxY - gridMinY; j["extent"]["parameters"] = { {"bbox", {gridMinX, gridMinY, gridMaxX, gridMaxY}}}; j["components"] = { {{"displacement_type", "horizontal"}, {"uncertainty_type", "none"}, {"extent", {{"type", "bbox"}, {"parameters", {{"bbox", {gridMinX, gridMinY, gridMaxX, gridMaxY}}}}}}, {"spatial_model", { {"type", "GeoTIFF"}, {"interpolation_method", "bilinear"}, {"filename", "bla.tif"}, }}, {"time_function", {{"type", "constant"}}}}}; struct Grid : public GridPrototype { bool getEastingNorthingOffset(int ix, int iy, double &eastingOffset, double &northingOffset) const { if (ix == 0 && iy == 0) { eastingOffset = 0.4; northingOffset = -0.2; } else if (ix == 1 && iy == 0) { eastingOffset = 0.5; northingOffset = -0.25; } else if (ix == 0 && iy == 1) { eastingOffset = 0.8; northingOffset = -0.4; } else if (ix == 1 && iy == 1) { eastingOffset = 1.; northingOffset = -0.3; } else { return false; } return true; } #ifdef DEBUG_DEFMODEL std::string name() const { return std::string(); } #endif }; struct GridSet : public GridSetPrototype { Grid grid{}; GridSet() { grid.minx = gridMinX; grid.miny = gridMinY; grid.resx = gridResX; grid.resy = gridResY; grid.width = 2; grid.height = 2; } const Grid *gridAt(double /*x */, double /* y */) { return &grid; } }; struct EvaluatorIface : public EvaluatorIfacePrototype { std::unique_ptr open(const std::string &filename) { if (filename != "bla.tif") return nullptr; return std::unique_ptr(new GridSet()); } bool isGeographicCRS(const std::string & /* crsDef */) { return false; } #ifdef DEBUG_DEFMODEL void log(const std::string & /* msg */) {} #endif }; EvaluatorIface iface; constexpr double a = 6378137; constexpr double b = 6356752.314140; constexpr double tValid = 2018; constexpr double zVal = 100; Evaluator eval(MasterFile::parse(j.dump()), iface, a, b); double newX; double newY; double newZ; EXPECT_TRUE(eval.forward(iface, gridMinX, gridMinY, zVal, tValid, newX, newY, newZ)); EXPECT_NEAR(newX - gridMinX, 0.4, 1e-8); EXPECT_NEAR(newY - gridMinY, -0.2, 1e-8); EXPECT_NEAR(newZ - zVal, 0, 1e-8); { json jcopy(j); jcopy["horizontal_offset_unit"] = "degree"; EXPECT_THROW((Evaluator( MasterFile::parse(jcopy.dump()), iface, a, b)), EvaluatorException); } { json jcopy(j); jcopy["horizontal_offset_method"] = "geocentric"; EXPECT_THROW((Evaluator( MasterFile::parse(jcopy.dump()), iface, a, b)), EvaluatorException); } { json jcopy(j); jcopy["components"][0]["spatial_model"]["interpolation_method"] = "geocentric_bilinear"; EXPECT_THROW((Evaluator( MasterFile::parse(jcopy.dump()), iface, a, b)), EvaluatorException); } } } // namespace #ifdef _MSC_VER #pragma warning(pop) #endif proj-9.8.1/test/unit/test_fork.c000664 001750 001750 00000006616 15166171715 016566 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test behavior of database access across fork() * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2021, Even Rouault * * 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. ****************************************************************************/ #if defined(_WIN32) && defined(PROJ_HAS_PTHREADS) #undef PROJ_HAS_PTHREADS #endif #ifdef PROJ_HAS_PTHREADS #include #include #include #include #include #include "proj.h" int main() { PJ_CONTEXT *ctxt = proj_context_create(); PJ_CONTEXT *ctxt2 = proj_context_create(); /* Cause database initialization */ { PJ *P = proj_create(ctxt, "EPSG:4326"); if (P == NULL) { proj_context_destroy(ctxt); return 1; } proj_destroy(P); } { PJ *P = proj_create(ctxt2, "EPSG:4326"); if (P == NULL) { proj_context_destroy(ctxt); proj_context_destroy(ctxt2); return 1; } proj_destroy(P); } for (int iters = 0; iters < 100; ++iters) { pid_t children[4]; for (int i = 0; i < 4; i++) { children[i] = fork(); if (children[i] < 0) { fprintf(stderr, "fork() failed\n"); return 1; } if (children[i] == 0) { { PJ *P = proj_create(ctxt, "EPSG:3067"); if (P == NULL) _exit(1); proj_destroy(P); } { PJ *P = proj_create(ctxt2, "EPSG:32631"); if (P == NULL) _exit(1); proj_destroy(P); } _exit(0); } } for (int i = 0; i < 4; i++) { int status = 0; waitpid(children[i], &status, 0); if (status != 0) { fprintf(stderr, "Error in child\n"); return 1; } } } proj_context_destroy(ctxt); proj_context_destroy(ctxt2); return 0; } #else int main() { return 0; } #endif proj-9.8.1/test/unit/test_network.cpp000664 001750 001750 00000227441 15166171715 017657 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test networking * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * 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 "gtest_include.h" #include #include #include #include "proj_internal.h" #include #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/internal/io_internal.hpp" #include #include #ifdef CURL_ENABLED #include #endif #ifdef _WIN32 #include #else #include #endif namespace { static const int byte_order_test = 1; #define IS_LSB \ (1 == (reinterpret_cast(&byte_order_test))[0]) static void swap_words(void *dataIn, size_t word_size, size_t word_count) { unsigned char *data = static_cast(dataIn); for (size_t word = 0; word < word_count; word++) { for (size_t i = 0; i < word_size / 2; i++) { unsigned char t; t = data[i]; data[i] = data[word_size - i - 1]; data[word_size - i - 1] = t; } data += word_size; } } // --------------------------------------------------------------------------- #ifdef CURL_ENABLED static bool networkAccessOK = false; static size_t noop_curl_write_func(void *, size_t, size_t nmemb, void *) { return nmemb; } TEST(networking, initial_check) { CURL *hCurlHandle = curl_easy_init(); if (!hCurlHandle) return; curl_easy_setopt(hCurlHandle, CURLOPT_URL, "https://cdn.proj.org/fr_ign_ntf_r93.tif"); curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, "0-1"); curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, noop_curl_write_func); curl_easy_perform(hCurlHandle); long response_code = 0; curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code); curl_easy_cleanup(hCurlHandle); networkAccessOK = (response_code == 206); if (!networkAccessOK) { fprintf(stderr, "network access not working"); } } #endif // --------------------------------------------------------------------------- static void silent_logger(void *, int, const char *) {} // --------------------------------------------------------------------------- TEST(networking, basic) { const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=https://cdn.proj.org/fr_ign_ntf_r93.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; // network access disabled by default auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_log_func(ctx, nullptr, silent_logger); auto P = proj_create(ctx, pipeline); ASSERT_EQ(P, nullptr); proj_context_destroy(ctx); proj_cleanup(); #ifdef CURL_ENABLED // enable through env variable putenv(const_cast("PROJ_NETWORK=ON")); ctx = proj_context_create(); P = proj_create(ctx, pipeline); if (networkAccessOK) { ASSERT_NE(P, nullptr); } proj_destroy(P); proj_context_destroy(ctx); putenv(const_cast("PROJ_NETWORK=")); #endif proj_cleanup(); // still disabled ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_log_func(ctx, nullptr, silent_logger); P = proj_create(ctx, pipeline); ASSERT_EQ(P, nullptr); proj_context_destroy(ctx); proj_cleanup(); // enable through API ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); P = proj_create(ctx, pipeline); #ifdef CURL_ENABLED if (networkAccessOK) { ASSERT_NE(P, nullptr); } else { ASSERT_EQ(P, nullptr); proj_context_destroy(ctx); return; } double longitude = 2; double lat = 49; proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, nullptr, 0, 0, nullptr, 0, 0); EXPECT_NEAR(longitude, 1.9992776848, 1e-10); EXPECT_NEAR(lat, 48.9999322600, 1e-10); proj_destroy(P); #else ASSERT_EQ(P, nullptr); #endif proj_context_destroy(ctx); proj_cleanup(); } // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, curl_invalid_resource) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); proj_log_func(ctx, nullptr, silent_logger); auto P = proj_create( ctx, "+proj=hgridshift +grids=https://i_do_not.exist/my.tif"); proj_context_destroy(ctx); ASSERT_EQ(P, nullptr); } #endif // --------------------------------------------------------------------------- struct Event { virtual ~Event(); std::string type{}; PJ_CONTEXT *ctx = nullptr; }; Event::~Event() = default; struct OpenEvent : public Event { OpenEvent() { type = "OpenEvent"; } std::string url{}; unsigned long long offset = 0; size_t size_to_read = 0; std::vector response{}; std::string errorMsg{}; int file_id = 0; }; struct CloseEvent : public Event { CloseEvent() { type = "CloseEvent"; } int file_id = 0; }; struct GetHeaderValueEvent : public Event { GetHeaderValueEvent() { type = "GetHeaderValueEvent"; } int file_id = 0; std::string key{}; std::string value{}; }; struct ReadRangeEvent : public Event { ReadRangeEvent() { type = "ReadRangeEvent"; } unsigned long long offset = 0; size_t size_to_read = 0; std::vector response{}; std::string errorMsg{}; int file_id = 0; }; struct File {}; struct ExchangeWithCallback { std::vector> events{}; size_t nextEvent = 0; bool error = false; std::map mapIdToHandle{}; bool allConsumedAndNoError() const { return nextEvent == events.size() && !error; } }; static PROJ_NETWORK_HANDLE *open_cbk(PJ_CONTEXT *ctx, const char *url, unsigned long long offset, size_t size_to_read, void *buffer, size_t *out_size_read, size_t error_string_max_size, char *out_error_string, void *user_data) { auto exchange = static_cast(user_data); if (exchange->error) return nullptr; if (exchange->nextEvent >= exchange->events.size()) { fprintf(stderr, "unexpected call to open(%s, %ld, %ld)\n", url, (long)offset, (long)size_to_read); exchange->error = true; return nullptr; } auto openEvent = dynamic_cast(exchange->events[exchange->nextEvent].get()); if (!openEvent) { fprintf(stderr, "unexpected call to open(%s, %ld, %ld). " "Was expecting a %s event\n", url, (long)offset, (long)size_to_read, exchange->events[exchange->nextEvent]->type.c_str()); exchange->error = true; return nullptr; } exchange->nextEvent++; if (openEvent->ctx != ctx || openEvent->url != url || openEvent->offset != offset || openEvent->size_to_read != size_to_read) { fprintf(stderr, "wrong call to open(%s, %ld, %ld). Was expecting " "open(%s, %ld, %ld)\n", url, (long)offset, (long)size_to_read, openEvent->url.c_str(), (long)openEvent->offset, (long)openEvent->size_to_read); exchange->error = true; return nullptr; } if (!openEvent->errorMsg.empty()) { snprintf(out_error_string, error_string_max_size, "%s", openEvent->errorMsg.c_str()); return nullptr; } memcpy(buffer, openEvent->response.data(), openEvent->response.size()); *out_size_read = openEvent->response.size(); auto handle = reinterpret_cast(new File()); exchange->mapIdToHandle[openEvent->file_id] = handle; return handle; } static void close_cbk(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, void *user_data) { auto exchange = static_cast(user_data); if (exchange->error) return; if (exchange->nextEvent >= exchange->events.size()) { fprintf(stderr, "unexpected call to close()\n"); exchange->error = true; return; } auto closeEvent = dynamic_cast(exchange->events[exchange->nextEvent].get()); if (!closeEvent) { fprintf(stderr, "unexpected call to close(). " "Was expecting a %s event\n", exchange->events[exchange->nextEvent]->type.c_str()); exchange->error = true; return; } if (closeEvent->ctx != ctx) { fprintf(stderr, "close() called with bad context\n"); exchange->error = true; return; } if (exchange->mapIdToHandle[closeEvent->file_id] != handle) { fprintf(stderr, "close() called with bad handle\n"); exchange->error = true; return; } exchange->nextEvent++; delete reinterpret_cast(handle); } static const char *get_header_value_cbk(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, const char *header_name, void *user_data) { auto exchange = static_cast(user_data); if (exchange->error) return nullptr; if (exchange->nextEvent >= exchange->events.size()) { fprintf(stderr, "unexpected call to get_header_value()\n"); exchange->error = true; return nullptr; } auto getHeaderValueEvent = dynamic_cast( exchange->events[exchange->nextEvent].get()); if (!getHeaderValueEvent) { fprintf(stderr, "unexpected call to get_header_value(). " "Was expecting a %s event\n", exchange->events[exchange->nextEvent]->type.c_str()); exchange->error = true; return nullptr; } if (getHeaderValueEvent->ctx != ctx) { fprintf(stderr, "get_header_value() called with bad context\n"); exchange->error = true; return nullptr; } if (getHeaderValueEvent->key != header_name) { fprintf(stderr, "wrong call to get_header_value(%s). Was expecting " "get_header_value(%s)\n", header_name, getHeaderValueEvent->key.c_str()); exchange->error = true; return nullptr; } if (exchange->mapIdToHandle[getHeaderValueEvent->file_id] != handle) { fprintf(stderr, "get_header_value() called with bad handle\n"); exchange->error = true; return nullptr; } exchange->nextEvent++; return getHeaderValueEvent->value.c_str(); } static size_t read_range_cbk(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, unsigned long long offset, size_t size_to_read, void *buffer, size_t error_string_max_size, char *out_error_string, void *user_data) { auto exchange = static_cast(user_data); if (exchange->error) return 0; if (exchange->nextEvent >= exchange->events.size()) { fprintf(stderr, "unexpected call to read_range(%ld, %ld)\n", (long)offset, (long)size_to_read); exchange->error = true; return 0; } auto readRangeEvent = dynamic_cast( exchange->events[exchange->nextEvent].get()); if (!readRangeEvent) { fprintf(stderr, "unexpected call to read_range(). " "Was expecting a %s event\n", exchange->events[exchange->nextEvent]->type.c_str()); exchange->error = true; return 0; } if (exchange->mapIdToHandle[readRangeEvent->file_id] != handle) { fprintf(stderr, "read_range() called with bad handle\n"); exchange->error = true; return 0; } if (readRangeEvent->ctx != ctx || readRangeEvent->offset != offset || readRangeEvent->size_to_read != size_to_read) { fprintf(stderr, "wrong call to read_range(%ld, %ld). Was expecting " "read_range(%ld, %ld)\n", (long)offset, (long)size_to_read, (long)readRangeEvent->offset, (long)readRangeEvent->size_to_read); exchange->error = true; return 0; } exchange->nextEvent++; if (!readRangeEvent->errorMsg.empty()) { snprintf(out_error_string, error_string_max_size, "%s", readRangeEvent->errorMsg.c_str()); return 0; } memcpy(buffer, readRangeEvent->response.data(), readRangeEvent->response.size()); return readRangeEvent->response.size(); } TEST(networking, custom) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); ExchangeWithCallback exchange; ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk, get_header_value_cbk, read_range_cbk, &exchange)); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/my.tif"; event->offset = 0; event->size_to_read = 16384; event->response.resize(16384); event->file_id = 1; const char *proj_source_data = getenv("PROJ_SOURCE_DATA"); ASSERT_TRUE(proj_source_data != nullptr); std::string filename(proj_source_data); filename += "/tests/egm96_15_uncompressed_truncated.tif"; FILE *f = fopen(filename.c_str(), "rb"); ASSERT_TRUE(f != nullptr); ASSERT_EQ(fread(&event->response[0], 1, 956, f), 956U); fclose(f); exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } auto P = proj_create( ctx, "+proj=vgridshift +grids=https://foo/my.tif +multiplier=1"); ASSERT_NE(P, nullptr); ASSERT_TRUE(exchange.allConsumedAndNoError()); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/my.tif"; event->offset = 524288; event->size_to_read = 278528; event->response.resize(278528); event->file_id = 2; float f = 1.25; if (!IS_LSB) { swap_words(&f, sizeof(f), 1); } for (size_t i = 0; i < 278528 / sizeof(float); i++) { memcpy(&event->response[i * sizeof(float)], &f, sizeof(float)); } exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { double longitude = 2 / 180. * M_PI; double lat = 49 / 180. * M_PI; double z = 0; ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0), 1U); EXPECT_EQ(z, 1.25); } ASSERT_TRUE(exchange.allConsumedAndNoError()); { std::unique_ptr event(new ReadRangeEvent()); event->ctx = ctx; event->offset = 3670016; event->size_to_read = 278528; event->response.resize(278528); event->file_id = 2; float f = 2.25; if (!IS_LSB) { swap_words(&f, sizeof(f), 1); } for (size_t i = 0; i < 278528 / sizeof(float); i++) { memcpy(&event->response[i * sizeof(float)], &f, sizeof(float)); } exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { double longitude = 2 / 180. * M_PI; double lat = -49 / 180. * M_PI; double z = 0; ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0), 1U); EXPECT_EQ(z, 2.25); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } proj_destroy(P); ASSERT_TRUE(exchange.allConsumedAndNoError()); // Once again ! No network access P = proj_create(ctx, "+proj=vgridshift +grids=https://foo/my.tif +multiplier=1"); ASSERT_NE(P, nullptr); { double longitude = 2 / 180. * M_PI; double lat = 49 / 180. * M_PI; double z = 0; ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0), 1U); EXPECT_EQ(z, 1.25); } proj_destroy(P); ASSERT_TRUE(exchange.allConsumedAndNoError()); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, getfilesize) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); ExchangeWithCallback exchange; ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk, get_header_value_cbk, read_range_cbk, &exchange)); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/getfilesize.tif"; event->offset = 0; event->size_to_read = 16384; event->response.resize(16384); event->file_id = 1; const char *proj_source_data = getenv("PROJ_SOURCE_DATA"); ASSERT_TRUE(proj_source_data != nullptr); std::string filename(proj_source_data); filename += "/tests/test_vgrid_single_strip_truncated.tif"; FILE *f = fopen(filename.c_str(), "rb"); ASSERT_TRUE(f != nullptr); ASSERT_EQ(fread(&event->response[0], 1, 550, f), 550U); fclose(f); exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes 0-16383/4153510"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } auto P = proj_create( ctx, "+proj=vgridshift +grids=https://foo/getfilesize.tif +multiplier=1"); ASSERT_NE(P, nullptr); ASSERT_TRUE(exchange.allConsumedAndNoError()); proj_destroy(P); P = proj_create( ctx, "+proj=vgridshift +grids=https://foo/getfilesize.tif +multiplier=1"); ASSERT_NE(P, nullptr); ASSERT_TRUE(exchange.allConsumedAndNoError()); proj_destroy(P); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, simul_open_error) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_log_func(ctx, nullptr, silent_logger); proj_context_set_enable_network(ctx, true); ExchangeWithCallback exchange; ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk, get_header_value_cbk, read_range_cbk, &exchange)); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/open_error.tif"; event->offset = 0; event->size_to_read = 16384; event->errorMsg = "Cannot open file"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } auto P = proj_create( ctx, "+proj=vgridshift +grids=https://foo/open_error.tif +multiplier=1"); ASSERT_EQ(P, nullptr); ASSERT_TRUE(exchange.allConsumedAndNoError()); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, simul_read_range_error) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); ExchangeWithCallback exchange; ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk, get_header_value_cbk, read_range_cbk, &exchange)); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/read_range_error.tif"; event->offset = 0; event->size_to_read = 16384; event->response.resize(16384); event->file_id = 1; const char *proj_source_data = getenv("PROJ_SOURCE_DATA"); ASSERT_TRUE(proj_source_data != nullptr); std::string filename(proj_source_data); filename += "/tests/egm96_15_uncompressed_truncated.tif"; FILE *f = fopen(filename.c_str(), "rb"); ASSERT_TRUE(f != nullptr); ASSERT_EQ(fread(&event->response[0], 1, 956, f), 956U); fclose(f); exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } auto P = proj_create(ctx, "+proj=vgridshift " "+grids=https://foo/read_range_error.tif " "+multiplier=1"); ASSERT_NE(P, nullptr); ASSERT_TRUE(exchange.allConsumedAndNoError()); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/read_range_error.tif"; event->offset = 524288; event->size_to_read = 278528; event->response.resize(278528); event->file_id = 2; float f = 1.25; if (!IS_LSB) { swap_words(&f, sizeof(f), 1); } for (size_t i = 0; i < 278528 / sizeof(float); i++) { memcpy(&event->response[i * sizeof(float)], &f, sizeof(float)); } exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { double longitude = 2 / 180. * M_PI; double lat = 49 / 180. * M_PI; double z = 0; ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0), 1U); EXPECT_EQ(z, 1.25); } ASSERT_TRUE(exchange.allConsumedAndNoError()); { std::unique_ptr event(new ReadRangeEvent()); event->ctx = ctx; event->offset = 3670016; event->size_to_read = 278528; event->errorMsg = "read range error"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { double longitude = 2 / 180. * M_PI; double lat = -49 / 180. * M_PI; double z = 0; proj_log_func(ctx, nullptr, silent_logger); ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0), 1U); EXPECT_EQ(z, HUGE_VAL); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } proj_destroy(P); ASSERT_TRUE(exchange.allConsumedAndNoError()); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, simul_file_change_while_opened) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); ExchangeWithCallback exchange; ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk, get_header_value_cbk, read_range_cbk, &exchange)); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/file_change_while_opened.tif"; event->offset = 0; event->size_to_read = 16384; event->response.resize(16384); event->file_id = 1; const char *proj_source_data = getenv("PROJ_SOURCE_DATA"); ASSERT_TRUE(proj_source_data != nullptr); std::string filename(proj_source_data); filename += "/tests/egm96_15_uncompressed_truncated.tif"; FILE *f = fopen(filename.c_str(), "rb"); ASSERT_TRUE(f != nullptr); ASSERT_EQ(fread(&event->response[0], 1, 956, f), 956U); fclose(f); exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 1; exchange.events.emplace_back(std::move(event)); } auto P = proj_create(ctx, "+proj=vgridshift " "+grids=https://foo/file_change_while_opened.tif " "+multiplier=1"); ASSERT_NE(P, nullptr); ASSERT_TRUE(exchange.allConsumedAndNoError()); { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/file_change_while_opened.tif"; event->offset = 524288; event->size_to_read = 278528; event->response.resize(278528); event->file_id = 2; float f = 1.25; if (!IS_LSB) { swap_words(&f, sizeof(f), 1); } for (size_t i = 0; i < 278528 / sizeof(float); i++) { memcpy(&event->response[i * sizeof(float)], &f, sizeof(float)); } exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date CHANGED!!!!"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 2; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new OpenEvent()); event->ctx = ctx; event->url = "https://foo/file_change_while_opened.tif"; event->offset = 0; event->size_to_read = 16384; event->response.resize(16384); event->file_id = 3; const char *proj_source_data = getenv("PROJ_SOURCE_DATA"); ASSERT_TRUE(proj_source_data != nullptr); std::string filename(proj_source_data); filename += "/tests/egm96_15_uncompressed_truncated.tif"; FILE *f = fopen(filename.c_str(), "rb"); ASSERT_TRUE(f != nullptr); ASSERT_EQ(fread(&event->response[0], 1, 956, f), 956U); fclose(f); exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Content-Range"; event->value = "bytes=0-16383/10000000"; event->file_id = 3; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "Last-Modified"; event->value = "some_date CHANGED!!!!"; event->file_id = 3; exchange.events.emplace_back(std::move(event)); } { std::unique_ptr event(new GetHeaderValueEvent()); event->ctx = ctx; event->key = "ETag"; event->value = "some_etag"; event->file_id = 3; exchange.events.emplace_back(std::move(event)); } { double longitude = 2 / 180. * M_PI; double lat = 49 / 180. * M_PI; double z = 0; ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0), 1U); EXPECT_EQ(z, 1.25); } ASSERT_TRUE(exchange.allConsumedAndNoError()); { std::unique_ptr event(new CloseEvent()); event->ctx = ctx; event->file_id = 3; exchange.events.emplace_back(std::move(event)); } proj_destroy(P); ASSERT_TRUE(exchange.allConsumedAndNoError()); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, curl_hgridshift) { if (!networkAccessOK) { return; } auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); // NTF to RGF93 v1. Using fr_ign_gr3df97a.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4275", "EPSG:4171", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyz.x = 49; // lat c.xyz.y = 2; // long c.xyz.z = 0; c = proj_trans(P, PJ_FWD, c); proj_assign_context(P, ctx); // (dummy) test context reassignment proj_destroy(P); proj_context_destroy(ctx); EXPECT_NEAR(c.xyz.x, 48.9999322600, 1e-8); EXPECT_NEAR(c.xyz.y, 1.9992776848, 1e-8); EXPECT_NEAR(c.xyz.z, 0, 1e-2); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, curl_vgridshift) { if (!networkAccessOK) { return; } auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); // WGS84 to EGM2008 height. Using egm08_25.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4979", "EPSG:4326+3855", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyz.x = -30; // lat c.xyz.y = 150; // long c.xyz.z = 0; c = proj_trans(P, PJ_FWD, c); proj_assign_context(P, ctx); // (dummy) test context reassignment proj_destroy(P); proj_context_destroy(ctx); EXPECT_NEAR(c.xyz.x, -30, 1e-8); EXPECT_NEAR(c.xyz.y, 150, 1e-8); EXPECT_NEAR(c.xyz.z, -31.89, 1e-2); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, curl_vgridshift_vertcon) { if (!networkAccessOK) { return; } auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); // NGVD29 to NAVD88 height. Using vertcone.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4269+7968", "EPSG:4269+5703", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyz.x = 40; // lat c.xyz.y = -80; // long c.xyz.z = 0; c = proj_trans(P, PJ_FWD, c); proj_destroy(P); proj_context_destroy(ctx); EXPECT_NEAR(c.xyz.x, 40, 1e-8); EXPECT_NEAR(c.xyz.y, -80, 1e-8); EXPECT_NEAR(c.xyz.z, -0.15, 1e-2); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, network_endpoint_env_variable) { putenv(const_cast("PROJ_NETWORK_ENDPOINT=http://0.0.0.0/")); auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); // NAD83 to NAD83(HARN) in West-Virginia. Using wvhpgn.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4269", "EPSG:4152", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyz.x = 40; // lat c.xyz.y = -80; // long c.xyz.z = 0; c = proj_trans(P, PJ_FWD, c); putenv(const_cast("PROJ_NETWORK_ENDPOINT=")); proj_destroy(P); proj_context_destroy(ctx); EXPECT_EQ(c.xyz.x, HUGE_VAL); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, network_endpoint_api_and_not_reachable_gridshift) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); proj_context_set_url_endpoint(ctx, "http://0.0.0.0"); // NAD83 to NAD83(HARN) using // us_noaa_nadcon5_nad83_1986_nad83_harn_conus.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4269", "EPSG:4152", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyzt.x = 40; // lat c.xyzt.y = -80; // long c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "NAD83 to NAD83(HARN) (47)"); proj_destroy(last_op); } proj_errno_reset(P); // Check again. Cf https://github.com/pyproj4/pyproj/issues/705 { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "NAD83 to NAD83(HARN) (47)"); proj_destroy(last_op); } proj_errno_reset(P); // Check also reverse direction { PJ_COORD c2 = proj_trans(P, PJ_INV, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "NAD83 to NAD83(HARN) (47)"); proj_destroy(last_op); } proj_destroy(P); proj_context_destroy(ctx); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, network_endpoint_api_and_not_reachable_xyzgridshift) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); proj_context_set_url_endpoint(ctx, "http://0.0.0.0"); // NTF to RGF93 using fr_ign_gr3df97a.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4275", "EPSG:4171", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyzt.x = 49; // lat c.xyzt.y = 2; // long c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "NTF to RGF93 v1 (1)"); proj_destroy(last_op); } proj_errno_reset(P); // Check again. Cf https://github.com/pyproj4/pyproj/issues/705 { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "NTF to RGF93 v1 (1)"); proj_destroy(last_op); } proj_errno_reset(P); // Check also reverse direction { PJ_COORD c2 = proj_trans(P, PJ_INV, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "NTF to RGF93 v1 (1)"); proj_destroy(last_op); } proj_destroy(P); proj_context_destroy(ctx); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, network_endpoint_api_and_not_reachable_hgridshift) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); proj_context_set_url_endpoint(ctx, "http://0.0.0.0"); // MGI to ETRS89 using at_bev_AT_GIS_GRID_2021_09_28.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:4312", "EPSG:4258", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyzt.x = 48; // lat c.xyzt.y = 15; // long c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "MGI to ETRS89 (8)"); proj_destroy(last_op); } proj_errno_reset(P); // Check again. Cf https://github.com/pyproj4/pyproj/issues/705 { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "MGI to ETRS89 (8)"); proj_destroy(last_op); } proj_errno_reset(P); // Check also reverse direction { PJ_COORD c2 = proj_trans(P, PJ_INV, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "MGI to ETRS89 (8)"); proj_destroy(last_op); } proj_destroy(P); proj_context_destroy(ctx); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, network_endpoint_api_and_not_reachable_vgridshift) { auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); proj_context_set_url_endpoint(ctx, "http://0.0.0.0"); // "POSGAR 2007 to SRVN16 height (1)" using ar_ign_GEOIDE-Ar16.tif auto P = proj_create_crs_to_crs(ctx, "EPSG:5342", "EPSG:9521", nullptr); ASSERT_NE(P, nullptr); PJ_COORD c; c.xyzt.x = -40; // lat c.xyzt.y = -60; // long c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "POSGAR 2007 to SRVN16 height (1)"); proj_destroy(last_op); } proj_errno_reset(P); // Check again. Cf https://github.com/pyproj4/pyproj/issues/705 { PJ_COORD c2 = proj_trans(P, PJ_FWD, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "POSGAR 2007 to SRVN16 height (1)"); proj_destroy(last_op); } proj_errno_reset(P); // Check also reverse direction { PJ_COORD c2 = proj_trans(P, PJ_INV, c); EXPECT_EQ(c2.xyz.x, HUGE_VAL); EXPECT_EQ(proj_errno(P), PROJ_ERR_OTHER_NETWORK_ERROR); PJ *last_op = proj_trans_get_last_used_operation(P); EXPECT_STREQ(proj_pj_info(last_op).description, "POSGAR 2007 to SRVN16 height (1)"); proj_destroy(last_op); } proj_destroy(P); proj_context_destroy(ctx); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED static PROJ_NETWORK_HANDLE *dummy_open_cbk(PJ_CONTEXT *, const char *, unsigned long long, size_t, void *, size_t *, size_t, char *, void *pUserData) { *static_cast(pUserData) = true; return nullptr; } static void dummy_close_cbk(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *, void *pUserData) { *static_cast(pUserData) = true; } static const char *dummy_get_header_value_cbk(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *, const char *, void *pUserData) { *static_cast(pUserData) = true; return nullptr; } static size_t dummy_read_range_cbk(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *, unsigned long long, size_t, void *, size_t, char *, void *pUserData) { *static_cast(pUserData) = true; return 0; } TEST(networking, cache_basic) { if (!networkAccessOK) { return; } proj_cleanup(); const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=https://cdn.proj.org/fr_ign_ntf_r93.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); proj_destroy(P); EXPECT_TRUE(!pj_context_get_grid_cache_filename(ctx).empty()); sqlite3 *hDB = nullptr; sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READONLY, nullptr); ASSERT_NE(hDB, nullptr); sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2(hDB, "SELECT url, offset FROM chunks WHERE id = (" "SELECT chunk_id FROM linked_chunks WHERE id = (" "SELECT head FROM linked_chunks_head_tail))", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); const char *url = reinterpret_cast(sqlite3_column_text(hStmt, 0)); ASSERT_NE(url, nullptr); ASSERT_EQ(std::string(url), "https://cdn.proj.org/fr_ign_ntf_r93.tif"); ASSERT_EQ(sqlite3_column_int64(hStmt, 1), 0); sqlite3_finalize(hStmt); sqlite3_close(hDB); proj_cleanup(); // Check that a second access doesn't trigger any network activity bool networkActivity = false; ASSERT_TRUE(proj_context_set_network_callbacks( ctx, dummy_open_cbk, dummy_close_cbk, dummy_get_header_value_cbk, dummy_read_range_cbk, &networkActivity)); P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); proj_destroy(P); EXPECT_FALSE(networkActivity); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, proj_grid_cache_clear) { if (!networkAccessOK) { return; } const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=https://cdn.proj.org/fr_ign_ntf_r93.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; proj_cleanup(); auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); EXPECT_EQ(pj_context_get_grid_cache_filename(ctx), std::string("tmp_proj_db_cache.db")); proj_grid_cache_clear(ctx); auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); proj_destroy(P); // Check that the file exists { sqlite3 *hDB = nullptr; ASSERT_EQ( sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READONLY, nullptr), SQLITE_OK); sqlite3_close(hDB); } proj_grid_cache_clear(ctx); // Check that the file no longer exists { sqlite3 *hDB = nullptr; ASSERT_NE( sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READONLY, nullptr), SQLITE_OK); sqlite3_close(hDB); } proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, cache_saturation) { if (!networkAccessOK) { return; } const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=https://cdn.proj.org/fr_ign_ntf_r93.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; proj_cleanup(); auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); proj_grid_cache_clear(ctx); // Limit to two chunks putenv(const_cast("PROJ_GRID_CACHE_MAX_SIZE_BYTES=32768")); proj_grid_cache_set_max_size(ctx, 0); putenv(const_cast("PROJ_GRID_CACHE_MAX_SIZE_BYTES=")); auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); double longitude = 2; double lat = 49; proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, nullptr, 0, 0, nullptr, 0, 0); EXPECT_NEAR(longitude, 1.9992776848, 1e-10); EXPECT_NEAR(lat, 48.9999322600, 1e-10); proj_destroy(P); sqlite3 *hDB = nullptr; sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READONLY, nullptr); ASSERT_NE(hDB, nullptr); sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2(hDB, "SELECT COUNT(*) FROM chunk_data UNION ALL " "SELECT COUNT(*) FROM chunks UNION ALL " "SELECT COUNT(*) FROM linked_chunks", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 2); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 2); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 2); sqlite3_finalize(hStmt); sqlite3_close(hDB); proj_grid_cache_clear(ctx); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, cache_ttl) { if (!networkAccessOK) { return; } const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=https://cdn.proj.org/fr_ign_ntf_r93.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; proj_cleanup(); auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); proj_grid_cache_clear(ctx); auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); double longitude = 2; double lat = 49; proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, nullptr, 0, 0, nullptr, 0, 0); EXPECT_NEAR(longitude, 1.9992776848, 1e-10); EXPECT_NEAR(lat, 48.9999322600, 1e-10); proj_destroy(P); sqlite3 *hDB = nullptr; sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READWRITE, nullptr); ASSERT_NE(hDB, nullptr); // Force lastChecked to the Epoch so that data is expired. sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2(hDB, "UPDATE properties SET lastChecked = 0, " "lastModified = 'foo', etag = 'bar'", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); sqlite3_finalize(hStmt); // Put junk in already cached data to check that we will refresh it. hStmt = nullptr; sqlite3_prepare_v2(hDB, "UPDATE chunk_data SET data = zeroblob(16384)", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); sqlite3_finalize(hStmt); sqlite3_close(hDB); proj_cleanup(); // Set a never expire ttl proj_grid_cache_set_ttl(ctx, -1); // We'll get junk data, hence the pipeline initialization fails proj_log_func(ctx, nullptr, silent_logger); P = proj_create(ctx, pipeline); ASSERT_EQ(P, nullptr); proj_destroy(P); proj_cleanup(); // Set a normal ttl proj_grid_cache_set_ttl(ctx, 86400); // Pipeline creation succeeds P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); proj_destroy(P); hDB = nullptr; sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READWRITE, nullptr); ASSERT_NE(hDB, nullptr); hStmt = nullptr; sqlite3_prepare_v2(hDB, "SELECT lastChecked, lastModified, etag FROM properties", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); ASSERT_NE(sqlite3_column_int64(hStmt, 0), 0); ASSERT_NE(sqlite3_column_text(hStmt, 1), nullptr); ASSERT_NE(std::string(reinterpret_cast( sqlite3_column_text(hStmt, 1))), "foo"); ASSERT_NE(sqlite3_column_text(hStmt, 2), nullptr); ASSERT_NE(std::string(reinterpret_cast( sqlite3_column_text(hStmt, 2))), "bar"); sqlite3_finalize(hStmt); sqlite3_close(hDB); proj_grid_cache_clear(ctx); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, cache_lock) { if (!networkAccessOK) { return; } const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=hgridshift +grids=https://cdn.proj.org/fr_ign_ntf_r93.tif " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; proj_cleanup(); auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); proj_grid_cache_clear(ctx); auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); double longitude = 2; double lat = 49; proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, nullptr, 0, 0, nullptr, 0, 0); EXPECT_NEAR(longitude, 1.9992776848, 1e-10); EXPECT_NEAR(lat, 48.9999322600, 1e-10); proj_destroy(P); // Take a lock sqlite3 *hDB = nullptr; sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, SQLITE_OPEN_READWRITE, nullptr); ASSERT_NE(hDB, nullptr); sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2(hDB, "BEGIN EXCLUSIVE", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); sqlite3_finalize(hStmt); proj_cleanup(); time_t start; time(&start); // 2 lock attempts, so we must sleep for each at least 0.5 ms putenv(const_cast("PROJ_LOCK_MAX_ITERS=25")); P = proj_create(ctx, pipeline); putenv(const_cast("PROJ_LOCK_MAX_ITERS=")); ASSERT_NE(P, nullptr); proj_destroy(P); // Check that we have spend more than 1 sec time_t end; time(&end); ASSERT_GE(end - start, 1U); sqlite3_close(hDB); proj_grid_cache_clear(ctx); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(networking, download_whole_files) { if (!networkAccessOK) { return; } proj_cleanup(); unlink("proj_test_tmp/cache.db"); unlink("proj_test_tmp/dk_sdfe_dvr90.tif"); rmdir("proj_test_tmp"); putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); putenv(const_cast("PROJ_USER_WRITABLE_DIRECTORY=./proj_test_tmp")); putenv(const_cast("PROJ_FULL_FILE_CHUNK_SIZE=100000")); proj_context_set_enable_network(nullptr, true); const auto grid_info = proj_grid_info("dk_sdfe_dvr90.tif"); EXPECT_EQ(std::string(grid_info.filename), ""); EXPECT_EQ(std::string(grid_info.gridname), "dk_sdfe_dvr90.tif"); EXPECT_EQ(std::string(grid_info.format), "gtiff"); proj_context_set_enable_network(nullptr, false); auto ctx = proj_context_create(); auto dbContext = ctx->get_cpp_context()->getDatabaseContext(); std::string fullFilename; std::string packageName; std::string url; bool directDownload; bool openLicense; bool gridAvailable = false; EXPECT_FALSE(dbContext->lookForGridInfo( "dk_sdfe_dvr90.tif", false, fullFilename, packageName, url, directDownload, openLicense, gridAvailable)); EXPECT_FALSE(gridAvailable); proj_context_set_enable_network(ctx, true); ASSERT_TRUE(proj_is_download_needed(ctx, "dk_sdfe_dvr90.tif", false)); char out_full_filename[1024]; EXPECT_FALSE(pj_find_file(ctx, "dk_sdfe_dvr90.tif", out_full_filename, sizeof(out_full_filename))); EXPECT_STREQ(out_full_filename, ""); ASSERT_TRUE( proj_download_file(ctx, "dk_sdfe_dvr90.tif", false, nullptr, nullptr)); EXPECT_TRUE(pj_find_file(ctx, "dk_sdfe_dvr90.tif", out_full_filename, sizeof(out_full_filename))); EXPECT_NE(out_full_filename[0], 0); // lookForGridInfo() returns false because the grid is not known in the DB, // but it returns gridAvailable as it is found on the system. EXPECT_FALSE(dbContext->lookForGridInfo( "dk_sdfe_dvr90.tif", false, fullFilename, packageName, url, directDownload, openLicense, gridAvailable)); EXPECT_TRUE(gridAvailable); FILE *f = fopen("proj_test_tmp/dk_sdfe_dvr90.tif", "rb"); ASSERT_NE(f, nullptr); fclose(f); proj_context_set_enable_network(ctx, false); const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=dk_sdfe_dvr90.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); double longitude = 12; double lat = 56; double z = 0; proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0); EXPECT_NEAR(z, 36.5909996032715, 1e-10); proj_destroy(P); proj_context_set_enable_network(ctx, true); ASSERT_FALSE(proj_is_download_needed(ctx, "dk_sdfe_dvr90.tif", false)); { sqlite3 *hDB = nullptr; sqlite3_open_v2("proj_test_tmp/cache.db", &hDB, SQLITE_OPEN_READWRITE, nullptr); ASSERT_NE(hDB, nullptr); // Force lastChecked to the Epoch so that data is expired. sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2( hDB, "UPDATE downloaded_file_properties SET lastChecked = 0", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); sqlite3_finalize(hStmt); sqlite3_close(hDB); } // If we ignore TTL settings, then no network access will be done ASSERT_FALSE(proj_is_download_needed(ctx, "dk_sdfe_dvr90.tif", true)); { sqlite3 *hDB = nullptr; sqlite3_open_v2("proj_test_tmp/cache.db", &hDB, SQLITE_OPEN_READWRITE, nullptr); ASSERT_NE(hDB, nullptr); // Check that the lastChecked timestamp is still 0 sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2(hDB, "SELECT lastChecked FROM downloaded_file_properties", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 0); sqlite3_finalize(hStmt); sqlite3_close(hDB); } // Should recheck from the CDN, update last_checked and do nothing ASSERT_FALSE(proj_is_download_needed(ctx, "dk_sdfe_dvr90.tif", false)); { sqlite3 *hDB = nullptr; sqlite3_open_v2("proj_test_tmp/cache.db", &hDB, SQLITE_OPEN_READWRITE, nullptr); ASSERT_NE(hDB, nullptr); sqlite3_stmt *hStmt = nullptr; // Check that the lastChecked timestamp has been updated sqlite3_prepare_v2(hDB, "SELECT lastChecked FROM downloaded_file_properties", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); ASSERT_NE(sqlite3_column_int64(hStmt, 0), 0); sqlite3_finalize(hStmt); hStmt = nullptr; // Now invalid lastModified. This should trigger a new download sqlite3_prepare_v2( hDB, "UPDATE downloaded_file_properties SET lastChecked = 0, " "lastModified = 'foo'", -1, &hStmt, nullptr); ASSERT_NE(hStmt, nullptr); ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); sqlite3_finalize(hStmt); sqlite3_close(hDB); } ASSERT_TRUE(proj_is_download_needed(ctx, "dk_sdfe_dvr90.tif", false)); // Redo download with a progress callback this time. unlink("proj_test_tmp/dk_sdfe_dvr90.tif"); const auto cbk = [](PJ_CONTEXT *l_ctx, double pct, void *user_data) -> int { auto vect = static_cast> *>( user_data); vect->push_back(std::pair(l_ctx, pct)); return true; }; std::vector> vectPct; ASSERT_TRUE( proj_download_file(ctx, "dk_sdfe_dvr90.tif", false, cbk, &vectPct)); ASSERT_EQ(vectPct.size(), 3U); ASSERT_EQ(vectPct.back().first, ctx); ASSERT_EQ(vectPct.back().second, 1.0); proj_context_destroy(ctx); putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); putenv(const_cast("PROJ_USER_WRITABLE_DIRECTORY=")); putenv(const_cast("PROJ_FULL_FILE_CHUNK_SIZE=")); unlink("proj_test_tmp/cache.db"); unlink("proj_test_tmp/dk_sdfe_dvr90.tif"); rmdir("proj_test_tmp"); } // --------------------------------------------------------------------------- TEST(networking, file_api) { if (!networkAccessOK) { return; } proj_cleanup(); unlink("proj_test_tmp/cache.db"); unlink("proj_test_tmp/dk_sdfe_dvr90.tif"); rmdir("proj_test_tmp"); putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); putenv(const_cast("PROJ_USER_WRITABLE_DIRECTORY=./proj_test_tmp")); putenv(const_cast("PROJ_FULL_FILE_CHUNK_SIZE=30000")); auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); struct UserData { bool in_open = false; bool in_read = false; bool in_write = false; bool in_seek = false; bool in_tell = false; bool in_close = false; bool in_exists = false; bool in_mkdir = false; bool in_unlink = false; bool in_rename = false; }; struct PROJ_FILE_API api; api.version = 1; api.open_cbk = [](PJ_CONTEXT *, const char *filename, PROJ_OPEN_ACCESS access, void *user_data) -> PROJ_FILE_HANDLE * { static_cast(user_data)->in_open = true; return reinterpret_cast( fopen(filename, access == PROJ_OPEN_ACCESS_READ_ONLY ? "rb" : access == PROJ_OPEN_ACCESS_READ_UPDATE ? "r+b" : "w+b")); }; api.read_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, void *buffer, size_t sizeBytes, void *user_data) -> size_t { static_cast(user_data)->in_read = true; return fread(buffer, 1, sizeBytes, reinterpret_cast(handle)); }; api.write_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, const void *buffer, size_t sizeBytes, void *user_data) -> size_t { static_cast(user_data)->in_write = true; return fwrite(buffer, 1, sizeBytes, reinterpret_cast(handle)); }; api.seek_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, long long offset, int whence, void *user_data) -> int { static_cast(user_data)->in_seek = true; return fseek(reinterpret_cast(handle), static_cast(offset), whence) == 0; }; api.tell_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, void *user_data) -> unsigned long long { static_cast(user_data)->in_tell = true; return ftell(reinterpret_cast(handle)); }; api.close_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, void *user_data) -> void { static_cast(user_data)->in_close = true; fclose(reinterpret_cast(handle)); }; api.exists_cbk = [](PJ_CONTEXT *, const char *filename, void *user_data) -> int { static_cast(user_data)->in_exists = true; struct stat buf; return stat(filename, &buf) == 0; }; api.mkdir_cbk = [](PJ_CONTEXT *, const char *filename, void *user_data) -> int { static_cast(user_data)->in_mkdir = true; #ifdef _WIN32 return mkdir(filename) == 0; #else return mkdir(filename, 0755) == 0; #endif }; api.unlink_cbk = [](PJ_CONTEXT *, const char *filename, void *user_data) -> int { static_cast(user_data)->in_unlink = true; return unlink(filename) == 0; }; api.rename_cbk = [](PJ_CONTEXT *, const char *oldPath, const char *newPath, void *user_data) -> int { static_cast(user_data)->in_rename = true; return rename(oldPath, newPath) == 0; }; UserData userData; ASSERT_TRUE(proj_context_set_fileapi(ctx, &api, &userData)); ASSERT_TRUE(proj_is_download_needed(ctx, "dk_sdfe_dvr90.tif", false)); ASSERT_TRUE( proj_download_file(ctx, "dk_sdfe_dvr90.tif", false, nullptr, nullptr)); ASSERT_TRUE(userData.in_open); ASSERT_FALSE(userData.in_read); ASSERT_TRUE(userData.in_write); ASSERT_TRUE(userData.in_close); ASSERT_TRUE(userData.in_exists); ASSERT_TRUE(userData.in_mkdir); ASSERT_TRUE(userData.in_unlink); ASSERT_TRUE(userData.in_rename); proj_context_set_enable_network(ctx, false); const char *pipeline = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=dk_sdfe_dvr90.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; auto P = proj_create(ctx, pipeline); ASSERT_NE(P, nullptr); double longitude = 12; double lat = 56; double z = 0; proj_trans_generic(P, PJ_FWD, &longitude, sizeof(double), 1, &lat, sizeof(double), 1, &z, sizeof(double), 1, nullptr, 0, 0); EXPECT_NEAR(z, 36.5909996032715, 1e-10); proj_destroy(P); ASSERT_TRUE(userData.in_read); ASSERT_TRUE(userData.in_seek); proj_context_destroy(ctx); putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); putenv(const_cast("PROJ_USER_WRITABLE_DIRECTORY=")); putenv(const_cast("PROJ_FULL_FILE_CHUNK_SIZE=")); unlink("proj_test_tmp/cache.db"); unlink("proj_test_tmp/dk_sdfe_dvr90.tif"); rmdir("proj_test_tmp"); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, proj_coordoperation_get_grid_used) { if (!networkAccessOK) { return; } auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); // Test bugfix for // https://github.com/OSGeo/PROJ/issues/3444#issuecomment-1309499342 for (int i = 0; i < 2; ++i) { // This file is not in grid_alternatives, but in the CDN const char *proj_string = "proj=vgridshift grids=nz_linz_nzgd2000-c120100904-grid01.tif"; PJ *P = proj_create(ctx, proj_string); const char *shortName = nullptr; const char *fullName = nullptr; const char *packageName = nullptr; const char *url = nullptr; int directDownload = 0; int openLicense = 0; int available = 0; proj_coordoperation_get_grid_used(ctx, P, 0, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &available); EXPECT_EQ(std::string(shortName), "nz_linz_nzgd2000-c120100904-grid01.tif"); EXPECT_EQ(std::string(fullName), ""); EXPECT_EQ( std::string(url), "https://cdn.proj.org/nz_linz_nzgd2000-c120100904-grid01.tif"); proj_destroy(P); } proj_context_destroy(ctx); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, pyproj_issue_1192) { if (!networkAccessOK) { return; } const auto doTest = [](PJ_CONTEXT *ctxt) { auto factory_context = proj_create_operation_factory_context(ctxt, nullptr); proj_operation_factory_context_set_grid_availability_use( ctxt, factory_context, PROJ_GRID_AVAILABILITY_IGNORED); proj_operation_factory_context_set_spatial_criterion( ctxt, factory_context, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); auto from = proj_create(ctxt, "EPSG:4326"); auto to = proj_create(ctxt, "EPSG:2964"); auto pj_operations = proj_create_operations(ctxt, from, to, factory_context); proj_destroy(from); proj_destroy(to); auto num_operations = proj_list_get_count(pj_operations); for (int i = 0; i < num_operations; ++i) { PJ *P = proj_list_get(ctxt, pj_operations, i); int is_instantiable = proj_coordoperation_is_instantiable(ctxt, P); if (is_instantiable) { EXPECT_TRUE(proj_pj_info(P).id != nullptr); } proj_destroy(P); } proj_operation_factory_context_destroy(factory_context); proj_list_destroy(pj_operations); }; auto ctx = proj_context_create(); proj_grid_cache_set_enable(ctx, false); proj_context_set_enable_network(ctx, true); doTest(ctx); proj_context_set_enable_network(ctx, false); doTest(ctx); proj_context_destroy(ctx); } #endif // --------------------------------------------------------------------------- #ifdef CURL_ENABLED TEST(networking, do_not_attempt_network_access_known_available_network_on) { // Check that proj_create_operations() itself does not trigger network // activity in enable_network == true and // PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE mode when all grids are known. const auto doTest = [](PJ_CONTEXT *ctxt) { auto factory_context = proj_create_operation_factory_context(ctxt, nullptr); proj_operation_factory_context_set_grid_availability_use( ctxt, factory_context, PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE); proj_operation_factory_context_set_spatial_criterion( ctxt, factory_context, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); auto from = proj_create(ctxt, "EPSG:4326"); auto to = proj_create(ctxt, "EPSG:4267"); auto pj_operations = proj_create_operations(ctxt, from, to, factory_context); proj_destroy(from); proj_destroy(to); auto num_operations = proj_list_get_count(pj_operations); EXPECT_GE(num_operations, 10); proj_operation_factory_context_destroy(factory_context); proj_list_destroy(pj_operations); }; auto ctx = proj_context_create(); proj_context_set_enable_network(ctx, true); // Check that we don't trigger any network activity bool networkActivity = false; ASSERT_TRUE(proj_context_set_network_callbacks( ctx, dummy_open_cbk, dummy_close_cbk, dummy_get_header_value_cbk, dummy_read_range_cbk, &networkActivity)); doTest(ctx); EXPECT_FALSE(networkActivity); proj_context_destroy(ctx); } #endif } // namespace proj-9.8.1/test/unit/test_factory.cpp000664 001750 001750 00000671146 15166171715 017642 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include "test_primitives.hpp" #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include #include #ifdef _MSC_VER #include #else #include #endif using namespace osgeo::proj::common; using namespace osgeo::proj::coordinates; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; namespace { // --------------------------------------------------------------------------- TEST(factory, databasecontext_create) { DatabaseContext::create(); #ifndef _WIN32 // For some reason, no exception is thrown on AppVeyor Windows EXPECT_THROW(DatabaseContext::create("/i/do_not/exist"), FactoryException); #endif } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createObject) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createObject("-1"), NoSuchAuthorityCodeException); EXPECT_THROW(factory->createObject("4326"), FactoryException); // area and crs } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createUnitOfMeasure_linear) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createUnitOfMeasure("-1"), NoSuchAuthorityCodeException); auto uom = factory->createUnitOfMeasure("9001"); EXPECT_EQ(uom->name(), "metre"); EXPECT_EQ(uom->type(), UnitOfMeasure::Type::LINEAR); EXPECT_EQ(uom->conversionToSI(), 1.0); EXPECT_EQ(uom->codeSpace(), "EPSG"); EXPECT_EQ(uom->code(), "9001"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createUnitOfMeasure_linear_us_survey_foot) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto uom = factory->createUnitOfMeasure("9003"); EXPECT_EQ(uom->conversionToSI(), 12. / 39.37); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createUnitOfMeasure_angular) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto uom = factory->createUnitOfMeasure("9102"); EXPECT_EQ(uom->name(), "degree"); EXPECT_EQ(uom->type(), UnitOfMeasure::Type::ANGULAR); EXPECT_EQ(uom->conversionToSI(), UnitOfMeasure::DEGREE.conversionToSI()); EXPECT_EQ(uom->codeSpace(), "EPSG"); EXPECT_EQ(uom->code(), "9102"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createUnitOfMeasure_angular_9107) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto uom = factory->createUnitOfMeasure("9107"); EXPECT_EQ(uom->name(), "degree minute second"); EXPECT_EQ(uom->type(), UnitOfMeasure::Type::ANGULAR); EXPECT_EQ(uom->conversionToSI(), UnitOfMeasure::DEGREE.conversionToSI()); EXPECT_EQ(uom->codeSpace(), "EPSG"); EXPECT_EQ(uom->code(), "9107"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createUnitOfMeasure_scale) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto uom = factory->createUnitOfMeasure("1028"); EXPECT_EQ(uom->name(), "parts per billion"); EXPECT_EQ(uom->type(), UnitOfMeasure::Type::SCALE); EXPECT_EQ(uom->conversionToSI(), 1e-9); EXPECT_EQ(uom->codeSpace(), "EPSG"); EXPECT_EQ(uom->code(), "1028"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createUnitOfMeasure_time) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto uom = factory->createUnitOfMeasure("1029"); EXPECT_EQ(uom->name(), "year"); EXPECT_EQ(uom->type(), UnitOfMeasure::Type::TIME); EXPECT_EQ(uom->conversionToSI(), 31556925.445); EXPECT_EQ(uom->codeSpace(), "EPSG"); EXPECT_EQ(uom->code(), "1029"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createPrimeMeridian) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createPrimeMeridian("-1"), NoSuchAuthorityCodeException); EXPECT_TRUE(nn_dynamic_pointer_cast( AuthorityFactory::create(DatabaseContext::create(), "ESRI") ->createObject("108900")) != nullptr); auto pm = factory->createPrimeMeridian("8903"); ASSERT_EQ(pm->identifiers().size(), 1U); EXPECT_EQ(pm->identifiers()[0]->code(), "8903"); EXPECT_EQ(*(pm->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(pm->name()->description()), "Paris"); EXPECT_EQ(pm->longitude(), Angle(2.5969213, UnitOfMeasure::GRAD)); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_identifyBodyFromSemiMajorAxis) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_EQ(factory->identifyBodyFromSemiMajorAxis(6378137, 1e-5), "Earth"); EXPECT_THROW(factory->identifyBodyFromSemiMajorAxis(1, 1e-5), FactoryException); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createEllipsoid) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createEllipsoid("-1"), NoSuchAuthorityCodeException); auto ellipsoid = factory->createEllipsoid("7030"); ASSERT_EQ(ellipsoid->identifiers().size(), 1U); EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030"); EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(ellipsoid->name()->description()), "WGS 84"); EXPECT_TRUE(ellipsoid->inverseFlattening().has_value()); EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137)); EXPECT_EQ(*ellipsoid->inverseFlattening(), Scale(298.257223563)); EXPECT_EQ(ellipsoid->celestialBody(), "Earth"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createEllipsoid_sphere) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ellipsoid = factory->createEllipsoid("7035"); EXPECT_TRUE(ellipsoid->isSphere()); EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6371000)); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createEllipsoid_with_semi_minor_axis) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto ellipsoid = factory->createEllipsoid("7011"); EXPECT_TRUE(ellipsoid->semiMinorAxis().has_value()); EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378249.2)); EXPECT_EQ(*ellipsoid->semiMinorAxis(), Length(6356515.0)); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createExtent) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createExtent("-1"), NoSuchAuthorityCodeException); auto extent = factory->createExtent("1262"); EXPECT_EQ(*(extent->description()), "World."); const auto &geogElts = extent->geographicElements(); ASSERT_EQ(geogElts.size(), 1U); auto bbox = nn_dynamic_pointer_cast(geogElts[0]); ASSERT_TRUE(bbox != nullptr); EXPECT_EQ(bbox->westBoundLongitude(), -180); EXPECT_EQ(bbox->eastBoundLongitude(), 180); EXPECT_EQ(bbox->northBoundLatitude(), 90); EXPECT_EQ(bbox->southBoundLatitude(), -90); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createExtent_no_bbox) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto extent = factory->createExtent("1361"); // Sudan - south. Deprecated EXPECT_EQ(*(extent->description()), "Sudan - south."); const auto &geogElts = extent->geographicElements(); EXPECT_TRUE(geogElts.empty()); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeodeticDatum) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createGeodeticDatum("-1"), NoSuchAuthorityCodeException); auto grf = factory->createGeodeticDatum("6326"); EXPECT_TRUE(nn_dynamic_pointer_cast(grf) == nullptr); ASSERT_EQ(grf->identifiers().size(), 1U); EXPECT_EQ(grf->identifiers()[0]->code(), "6326"); EXPECT_EQ(*(grf->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(grf->name()->description()), "World Geodetic System 1984"); EXPECT_TRUE(grf->ellipsoid()->isEquivalentTo( factory->createEllipsoid("7030").get())); EXPECT_TRUE(grf->primeMeridian()->isEquivalentTo( factory->createPrimeMeridian("8901").get())); ASSERT_EQ(grf->domains().size(), 1U); auto domain = grf->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get())); EXPECT_FALSE(grf->publicationDate().has_value()); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeodeticDatum_with_publication_date) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // North American Datum 1983 auto grf = factory->createGeodeticDatum("6269"); EXPECT_TRUE(nn_dynamic_pointer_cast(grf) == nullptr); EXPECT_TRUE(grf->publicationDate().has_value()); EXPECT_EQ(grf->publicationDate()->toString(), "1986-01-01"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createDynamicGeodeticDatum) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto grf = factory->createGeodeticDatum("1165"); // ITRF 2014 auto dgrf = nn_dynamic_pointer_cast(grf); ASSERT_TRUE(dgrf != nullptr); EXPECT_EQ(dgrf->frameReferenceEpoch().value(), 2010.0); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createVerticalDatum) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createVerticalDatum("-1"), NoSuchAuthorityCodeException); auto vrf = factory->createVerticalDatum("1027"); ASSERT_EQ(vrf->identifiers().size(), 1U); EXPECT_EQ(vrf->identifiers()[0]->code(), "1027"); EXPECT_EQ(*(vrf->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(vrf->name()->description()), "EGM2008 geoid"); auto domain = vrf->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get())); EXPECT_TRUE(vrf->publicationDate().has_value()); EXPECT_EQ(vrf->publicationDate()->toString(), "2008-01-01"); EXPECT_TRUE(!vrf->anchorEpoch().has_value()); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createVerticalDatum_with_anchor_epoch) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // "Canadian Geodetic Vertical Datum of 2013 (CGG2013a) epoch 2010" auto vrf = factory->createVerticalDatum("1256"); EXPECT_TRUE(vrf->anchorEpoch().has_value()); EXPECT_NEAR(vrf->anchorEpoch()->convertToUnit(UnitOfMeasure::YEAR), 2010.0, 1e-6); } #ifdef no_more_dynamic_vertical_datum // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createDynamicVerticalDatum) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto grf = factory->createVerticalDatum("1096"); // Norway Normal Null 2000 auto dvrf = nn_dynamic_pointer_cast(grf); ASSERT_TRUE(dvrf != nullptr); EXPECT_EQ(dvrf->frameReferenceEpoch().value(), 2000.0); } #endif // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createEngineeringDatum) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createEngineeringDatum("-1"), NoSuchAuthorityCodeException); auto datum = factory->createEngineeringDatum("1134"); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "1134"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(datum->name()->description()), "Christmas Island Datum 1985"); auto domain = datum->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("4169").get())); EXPECT_TRUE(datum->publicationDate().has_value()); EXPECT_EQ(datum->publicationDate()->toString(), "1985-01-01"); EXPECT_TRUE(!datum->anchorEpoch().has_value()); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createDatum) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createDatum("-1"), NoSuchAuthorityCodeException); EXPECT_TRUE(factory->createDatum("6326")->isEquivalentTo( factory->createGeodeticDatum("6326").get())); EXPECT_TRUE(factory->createDatum("1027")->isEquivalentTo( factory->createVerticalDatum("1027").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createDatumEnsembleGeodetic) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createDatumEnsemble("-1"), NoSuchAuthorityCodeException); EXPECT_THROW(factory->createDatumEnsemble("6326", "vertical_datum"), NoSuchAuthorityCodeException); auto ensemble = factory->createDatumEnsemble("6326"); EXPECT_EQ(ensemble->nameStr(), "World Geodetic System 1984 ensemble"); ASSERT_EQ(ensemble->identifiers().size(), 1U); EXPECT_EQ(ensemble->identifiers()[0]->code(), "6326"); EXPECT_EQ(*(ensemble->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(ensemble->datums().size(), 8U); EXPECT_EQ(ensemble->positionalAccuracy()->value(), "2.0"); ASSERT_TRUE(!ensemble->domains().empty()); auto domain = ensemble->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get())); { // Without using db auto datum = ensemble->asDatum(nullptr); EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); auto grf = dynamic_cast(datum.get()); ASSERT_TRUE(grf != nullptr); EXPECT_TRUE(grf->isEquivalentTo(factory->createDatum("6326").get())); } { // Using db auto datum = ensemble->asDatum(DatabaseContext::create()); EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); auto grf = dynamic_cast(datum.get()); ASSERT_TRUE(grf != nullptr); EXPECT_TRUE(grf->isEquivalentTo(factory->createDatum("6326").get())); } } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createDatumEnsembleVertical) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createDatumEnsemble("1288", "geodetic_datum"), NoSuchAuthorityCodeException); auto ensemble = factory->createDatumEnsemble("1288"); EXPECT_EQ(ensemble->nameStr(), "British Isles height ensemble"); ASSERT_EQ(ensemble->identifiers().size(), 1U); EXPECT_EQ(ensemble->identifiers()[0]->code(), "1288"); EXPECT_EQ(*(ensemble->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(ensemble->datums().size(), 9U); EXPECT_EQ(ensemble->positionalAccuracy()->value(), "0.4"); ASSERT_TRUE(!ensemble->domains().empty()); auto domain = ensemble->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("4606").get())); { // Without using db auto datum = ensemble->asDatum(nullptr); auto vrf = dynamic_cast(datum.get()); ASSERT_TRUE(vrf != nullptr); EXPECT_TRUE(vrf->isEquivalentTo(factory->createDatum("1288").get())); } { // Using db auto datum = ensemble->asDatum(DatabaseContext::create()); auto vrf = dynamic_cast(datum.get()); ASSERT_TRUE(vrf != nullptr); EXPECT_TRUE(vrf->isEquivalentTo(factory->createDatum("1288").get())); } } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateSystem_ellipsoidal_2_axis) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createCoordinateSystem("-1"), NoSuchAuthorityCodeException); auto cs = factory->createCoordinateSystem("6422"); auto ellipsoidal_cs = nn_dynamic_pointer_cast(cs); ASSERT_TRUE(ellipsoidal_cs != nullptr); ASSERT_EQ(ellipsoidal_cs->identifiers().size(), 1U); EXPECT_EQ(ellipsoidal_cs->identifiers()[0]->code(), "6422"); EXPECT_EQ(*(ellipsoidal_cs->identifiers()[0]->codeSpace()), "EPSG"); const auto &axisList = ellipsoidal_cs->axisList(); EXPECT_EQ(axisList.size(), 2U); EXPECT_EQ(*(axisList[0]->name()->description()), "Geodetic latitude"); EXPECT_EQ(axisList[0]->abbreviation(), "Lat"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(*(axisList[1]->name()->description()), "Geodetic longitude"); EXPECT_EQ(axisList[1]->abbreviation(), "Lon"); EXPECT_EQ(axisList[1]->direction(), AxisDirection::EAST); EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateSystem_ellipsoidal_3_axis) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto cs = factory->createCoordinateSystem("6423"); auto ellipsoidal_cs = nn_dynamic_pointer_cast(cs); ASSERT_TRUE(ellipsoidal_cs != nullptr); ASSERT_EQ(ellipsoidal_cs->identifiers().size(), 1U); EXPECT_EQ(ellipsoidal_cs->identifiers()[0]->code(), "6423"); EXPECT_EQ(*(ellipsoidal_cs->identifiers()[0]->codeSpace()), "EPSG"); const auto &axisList = ellipsoidal_cs->axisList(); EXPECT_EQ(axisList.size(), 3U); EXPECT_EQ(*(axisList[0]->name()->description()), "Geodetic latitude"); EXPECT_EQ(axisList[0]->abbreviation(), "Lat"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(*(axisList[1]->name()->description()), "Geodetic longitude"); EXPECT_EQ(axisList[1]->abbreviation(), "Lon"); EXPECT_EQ(axisList[1]->direction(), AxisDirection::EAST); EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(*(axisList[2]->name()->description()), "Ellipsoidal height"); EXPECT_EQ(axisList[2]->abbreviation(), "h"); EXPECT_EQ(axisList[2]->direction(), AxisDirection::UP); EXPECT_EQ(axisList[2]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateSystem_geocentric) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto cs = factory->createCoordinateSystem("6500"); auto cartesian_cs = nn_dynamic_pointer_cast(cs); ASSERT_TRUE(cartesian_cs != nullptr); ASSERT_EQ(cartesian_cs->identifiers().size(), 1U); EXPECT_EQ(cartesian_cs->identifiers()[0]->code(), "6500"); EXPECT_EQ(*(cartesian_cs->identifiers()[0]->codeSpace()), "EPSG"); const auto &axisList = cartesian_cs->axisList(); EXPECT_EQ(axisList.size(), 3U); EXPECT_EQ(*(axisList[0]->name()->description()), "Geocentric X"); EXPECT_EQ(axisList[0]->abbreviation(), "X"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::GEOCENTRIC_X); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::METRE); EXPECT_EQ(*(axisList[1]->name()->description()), "Geocentric Y"); EXPECT_EQ(axisList[1]->abbreviation(), "Y"); EXPECT_EQ(axisList[1]->direction(), AxisDirection::GEOCENTRIC_Y); EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::METRE); EXPECT_EQ(*(axisList[2]->name()->description()), "Geocentric Z"); EXPECT_EQ(axisList[2]->abbreviation(), "Z"); EXPECT_EQ(axisList[2]->direction(), AxisDirection::GEOCENTRIC_Z); EXPECT_EQ(axisList[2]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateSystem_vertical) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createCoordinateSystem("-1"), NoSuchAuthorityCodeException); auto cs = factory->createCoordinateSystem("6499"); auto vertical_cs = nn_dynamic_pointer_cast(cs); ASSERT_TRUE(vertical_cs != nullptr); ASSERT_EQ(vertical_cs->identifiers().size(), 1U); EXPECT_EQ(vertical_cs->identifiers()[0]->code(), "6499"); EXPECT_EQ(*(vertical_cs->identifiers()[0]->codeSpace()), "EPSG"); const auto &axisList = vertical_cs->axisList(); EXPECT_EQ(axisList.size(), 1U); EXPECT_EQ(*(axisList[0]->name()->description()), "Gravity-related height"); EXPECT_EQ(axisList[0]->abbreviation(), "H"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::UP); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::METRE); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeodeticCRS_geographic2D) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createGeodeticCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createGeodeticCRS("4326"); auto gcrs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(gcrs != nullptr); ASSERT_EQ(gcrs->identifiers().size(), 1U); EXPECT_EQ(gcrs->identifiers()[0]->code(), "4326"); EXPECT_EQ(*(gcrs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(gcrs->name()->description()), "WGS 84"); ASSERT_TRUE(gcrs->datum() == nullptr); ASSERT_TRUE(gcrs->datumEnsemble() != nullptr); EXPECT_TRUE(gcrs->datumEnsemble()->isEquivalentTo( factory->createDatumEnsemble("6326").get())); EXPECT_TRUE(gcrs->coordinateSystem()->isEquivalentTo( factory->createCoordinateSystem("6422").get())); auto domain = crs->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get())); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +datum=WGS84 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeodeticCRS_geographic2D_area_no_bbox) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createGeodeticCRS("4296"); // Sudan - deprecated auto domain = crs->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1361").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeodeticCRS_geographic3D) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createGeodeticCRS("4979"); auto gcrs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(gcrs != nullptr); ASSERT_EQ(gcrs->identifiers().size(), 1U); EXPECT_EQ(gcrs->identifiers()[0]->code(), "4979"); EXPECT_EQ(*(gcrs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(gcrs->name()->description()), "WGS 84"); ASSERT_TRUE(gcrs->datum() == nullptr); ASSERT_TRUE(gcrs->datumEnsemble() != nullptr); EXPECT_TRUE(gcrs->datumEnsemble()->isEquivalentTo( factory->createDatumEnsemble("6326").get())); EXPECT_TRUE(gcrs->coordinateSystem()->isEquivalentTo( factory->createCoordinateSystem("6423").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeodeticCRS_geocentric) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createGeodeticCRS("4978"); ASSERT_TRUE(nn_dynamic_pointer_cast(crs) == nullptr); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "4978"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(crs->name()->description()), "WGS 84"); ASSERT_TRUE(crs->datum() == nullptr); ASSERT_TRUE(crs->datumEnsemble() != nullptr); EXPECT_TRUE(crs->datumEnsemble()->isEquivalentTo( factory->createDatumEnsemble("6326").get())); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( factory->createCoordinateSystem("6500").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createGeographicCRS) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createGeographicCRS("4979"); ASSERT_TRUE(nn_dynamic_pointer_cast(crs) != nullptr); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "4979"); EXPECT_THROW(factory->createGeographicCRS("4978"), FactoryException); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createVerticalCRS) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createVerticalCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createVerticalCRS("3855"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "3855"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(crs->name()->description()), "EGM2008 height"); EXPECT_TRUE( crs->datum()->isEquivalentTo(factory->createDatum("1027").get())); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( factory->createCoordinateSystem("6499").get())); auto domain = crs->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createVerticalCRS_with_datum_ensemble) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createVerticalCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createVerticalCRS("9451"); // BI height ASSERT_TRUE(crs->datum() == nullptr); ASSERT_TRUE(crs->datumEnsemble() != nullptr); EXPECT_TRUE(crs->datumEnsemble()->isEquivalentTo( factory->createDatumEnsemble("1288").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createEngineeringCRS) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createEngineeringCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createEngineeringCRS("6715"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "6715"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(crs->name()->description()), "Christmas Island Grid 1985"); EXPECT_TRUE( crs->datum()->isEquivalentTo(factory->createDatum("1134").get())); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( factory->createCoordinateSystem("4400").get())); auto domain = crs->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("4169").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createConversion) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createConversion("-1"), NoSuchAuthorityCodeException); auto conv = factory->createConversion("16031"); ASSERT_EQ(conv->identifiers().size(), 1U); EXPECT_EQ(conv->identifiers()[0]->code(), "16031"); EXPECT_EQ(*(conv->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(conv->name()->description()), "UTM zone 31N"); auto method = conv->method(); ASSERT_EQ(method->identifiers().size(), 1U); EXPECT_EQ(method->identifiers()[0]->code(), "9807"); EXPECT_EQ(*(method->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(method->name()->description()), "Transverse Mercator"); const auto &values = conv->parameterValues(); ASSERT_EQ(values.size(), 5U); { const auto &opParamvalue = nn_dynamic_pointer_cast(values[0]); ASSERT_TRUE(opParamvalue); const auto ¶mName = *(opParamvalue->parameter()->name()->description()); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8801); EXPECT_EQ(paramName, "Latitude of natural origin"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(measure.value(), 0.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[1]); ASSERT_TRUE(opParamvalue); const auto ¶mName = *(opParamvalue->parameter()->name()->description()); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8802); EXPECT_EQ(paramName, "Longitude of natural origin"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(measure.value(), 3.0); } } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createConversion_from_other_transformation) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7984", false); auto conversion = nn_dynamic_pointer_cast(op); ASSERT_TRUE(conversion != nullptr); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createProjectedCRS) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createProjectedCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createProjectedCRS("32631"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "32631"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(crs->name()->description()), "WGS 84 / UTM zone 31N"); EXPECT_TRUE(crs->baseCRS()->isEquivalentTo( factory->createGeodeticCRS("4326").get())); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( factory->createCoordinateSystem("4400").get())); EXPECT_TRUE(crs->derivingConversion()->isEquivalentTo( factory->createConversion("16031").get())); auto domain = crs->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("2060").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createProjectedCRS_south_pole) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createProjectedCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createProjectedCRS("32761"); auto csList = crs->coordinateSystem()->axisList(); ASSERT_EQ(csList.size(), 2U); EXPECT_TRUE(csList[0]->meridian() != nullptr); EXPECT_EQ(csList[0]->direction(), AxisDirection::NORTH); EXPECT_EQ( csList[0]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE), 0); EXPECT_EQ(csList[1]->direction(), AxisDirection::NORTH); EXPECT_EQ( csList[1]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE), 90); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createProjectedCRS_north_pole) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createProjectedCRS("32661"); auto csList = crs->coordinateSystem()->axisList(); ASSERT_EQ(csList.size(), 2U); EXPECT_TRUE(csList[0]->meridian() != nullptr); EXPECT_EQ(csList[0]->direction(), AxisDirection::SOUTH); EXPECT_EQ( csList[0]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE), 180); EXPECT_EQ(csList[1]->direction(), AxisDirection::SOUTH); EXPECT_EQ( csList[1]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE), 90); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCompoundCRS) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createCompoundCRS("-1"), NoSuchAuthorityCodeException); auto crs = factory->createCompoundCRS("6871"); ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "6871"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(*(crs->name()->description()), "WGS 84 / Pseudo-Mercator + EGM2008 geoid height"); auto components = crs->componentReferenceSystems(); ASSERT_EQ(components.size(), 2U); EXPECT_TRUE(components[0]->isEquivalentTo( factory->createProjectedCRS("3857").get())); EXPECT_TRUE(components[1]->isEquivalentTo( factory->createVerticalCRS("3855").get())); auto domain = crs->domains()[0]; auto extent = domain->domainOfValidity(); ASSERT_TRUE(extent != nullptr); EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get())); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateReferenceSystem) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createCoordinateReferenceSystem("-1"), NoSuchAuthorityCodeException); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("4326"))); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("4979"))); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("4978"))); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("32631"))); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("3855"))); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("6871"))); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_3) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->createCoordinateOperation("-1", false), NoSuchAuthorityCodeException); auto op = factory->createCoordinateOperation("1113", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv " "+proj=longlat +a=6378249.145 +rf=293.4663077 +step +proj=push " "+v_3 +step +proj=cart +a=6378249.145 +rf=293.4663077 +step " "+proj=helmert +x=-143 +y=-90 +z=-294 +step +inv +proj=cart " "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_7_CF) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7676", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=bessel +step +proj=helmert +x=577.88891 " "+y=165.22205 +z=391.18289 +rx=-4.9145 +ry=0.94729 +rz=13.05098 " "+s=7.78664 +convention=coordinate_frame +step +inv +proj=cart " "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_7_PV) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("1074", false); auto wkt = op->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_TRUE(wkt.find("+proj=helmert +x=-275.7224 +y=94.7824 +z=340.8944 " "+rx=-8.001 +ry=-4.42 +rz=-11.821 +s=1 " "+convention=position_vector") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_8_CF) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7702", false); auto expected = " PARAMETER[\"Transformation reference epoch\",2002,\n" " TIMEUNIT[\"year\",31556925.445],\n" " ID[\"EPSG\",1049]],\n"; auto wkt = op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_TRUE(wkt.find(expected) != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_15_CF) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("6276", false); auto expected = "COORDINATEOPERATION[\"ITRF2008 to GDA94 (1)\",\n" " VERSION[\"GA-Aus 2010\"],\n" " SOURCECRS[\n" " GEODCRS[\"ITRF2008\",\n" " DYNAMIC[\n" " FRAMEEPOCH[2005]],\n" " DATUM[\"International Terrestrial Reference Frame " "2008\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5332]]],\n" " TARGETCRS[\n" " GEODCRS[\"GDA94\",\n" " DATUM[\"Geocentric Datum of Australia 1994\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4938]]],\n" " METHOD[\"Time-dependent Coordinate Frame rotation (geocen)\",\n" " ID[\"EPSG\",1056]],\n" " PARAMETER[\"X-axis translation\",-84.68,\n" " LENGTHUNIT[\"millimetre\",0.001],\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",-19.42,\n" " LENGTHUNIT[\"millimetre\",0.001],\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",32.01,\n" " LENGTHUNIT[\"millimetre\",0.001],\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",-0.4254,\n" " ANGLEUNIT[\"milliarc-second\",4.84813681109536E-09],\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",2.2578,\n" " ANGLEUNIT[\"milliarc-second\",4.84813681109536E-09],\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",2.4015,\n" " ANGLEUNIT[\"milliarc-second\",4.84813681109536E-09],\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",9.71,\n" " SCALEUNIT[\"parts per billion\",1E-09],\n" " ID[\"EPSG\",8611]],\n" " PARAMETER[\"Rate of change of X-axis translation\",1.42,\n" " LENGTHUNIT[\"millimetres per year\",3.16887651727315E-11],\n" " ID[\"EPSG\",1040]],\n" " PARAMETER[\"Rate of change of Y-axis translation\",1.34,\n" " LENGTHUNIT[\"millimetres per year\",3.16887651727315E-11],\n" " ID[\"EPSG\",1041]],\n" " PARAMETER[\"Rate of change of Z-axis translation\",0.9,\n" " LENGTHUNIT[\"millimetres per year\",3.16887651727315E-11],\n" " ID[\"EPSG\",1042]],\n" " PARAMETER[\"Rate of change of X-axis rotation\",1.5461,\n" " ANGLEUNIT[\"milliarc-seconds per " "year\",1.53631468932076E-16],\n" " ID[\"EPSG\",1043]],\n" " PARAMETER[\"Rate of change of Y-axis rotation\",1.182,\n" " ANGLEUNIT[\"milliarc-seconds per " "year\",1.53631468932076E-16],\n" " ID[\"EPSG\",1044]],\n" " PARAMETER[\"Rate of change of Z-axis rotation\",1.1551,\n" " ANGLEUNIT[\"milliarc-seconds per " "year\",1.53631468932076E-16],\n" " ID[\"EPSG\",1045]],\n" " PARAMETER[\"Rate of change of Scale difference\",0.109,\n" " SCALEUNIT[\"parts per billion per " "year\",3.16887651727315E-17],\n" " ID[\"EPSG\",1046]],\n" " PARAMETER[\"Parameter reference epoch\",1994,\n" " TIMEUNIT[\"year\",31556925.445],\n" " ID[\"EPSG\",1047]],\n" " OPERATIONACCURACY[0.03],\n" " USAGE[\n" " SCOPE[\"Geodesy.\"],\n" " AREA[\"Australia - onshore and offshore to 200 nautical mile " "EEZ boundary. Includes Lord Howe Island, Ashmore and Cartier " "Islands.\"],\n" " BBOX[-47.2,109.23,-8.88,163.2]],\n" " ID[\"EPSG\",6276],\n" " REMARK[\"RMS residuals 5mm north, 8mm east and 28mm vertical, " "maximum residuals 10mm north, 13mm east and 51mm vertical. Scale " "difference in ppb and scale difference rate in ppb/yr where " "1/billion = 1E-9 or nm/m.\"]]"; EXPECT_EQ( op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_15_PV) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("8069", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=helmert +x=-0.0254 +y=0.0005 +z=0.1548 +rx=-0.0001 +ry=0 " "+rz=-0.00026 +s=-0.01129 +dx=-0.0001 +dy=0.0005 +dz=0.0033 " "+drx=0 +dry=0 +drz=-2e-05 +ds=-0.00012 +t_epoch=2010 " "+convention=position_vector"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_15_PV_rounding_of_drz) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7932", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 +dx=0 +dy=0 " "+dz=0 +drx=0.00011 +dry=0.00057 +drz=-0.00071 +ds=0 " "+t_epoch=1989 +convention=position_vector"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_CF_full_matrix_geog3D) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("10675", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +inv +proj=helmert +exact +x=1138.7432 +y=-2064.4761 " "+z=110.7016 " "+rx=-214.615206 +ry=479.360036 +rz=-164.703951 +s=-402.32073 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=intl " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_molodensky_badekas_PV) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("1066", false); auto so = nn_dynamic_pointer_cast(op); ASSERT_TRUE(so != nullptr); EXPECT_TRUE(so->validateParameters().empty()); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=bessel +step +proj=molobadekas " "+x=593.032 +y=26 +z=478.741 +rx=0.409394387439237 " "+ry=-0.359705195614311 +rz=1.86849100035057 +s=4.0772 " "+px=3903453.148 +py=368135.313 +pz=5012970.306 " "+convention=coordinate_frame +step +inv +proj=cart +ellps=GRS80 " "+step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_grid_transformation_one_parameter) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("1295", false); auto expected = "COORDINATEOPERATION[\"RGNC91-93 to NEA74 Noumea (4)\",\n" " VERSION[\"ESRI-Ncl 0.05m\"],\n" " SOURCECRS[\n" " GEOGCRS[\"RGNC91-93\",\n" " DATUM[\"Reseau Geodesique de Nouvelle Caledonie 91-93\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4749]]],\n" " TARGETCRS[\n" " GEOGCRS[\"NEA74 Noumea\",\n" " DATUM[\"NEA74 Noumea\",\n" " ELLIPSOID[\"International 1924\",6378388,297,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4644]]],\n" " METHOD[\"NTv2\",\n" " ID[\"EPSG\",9615]],\n" " PARAMETERFILE[\"Latitude and longitude difference " "file\",\"RGNC1991_NEA74Noumea.gsb\"],\n" " OPERATIONACCURACY[0.05],\n" " USAGE[\n" " SCOPE[\"Geodesy.\"],\n" " AREA[\"New Caledonia - Grande Terre - Noumea district.\"],\n" " BBOX[-22.37,166.35,-22.19,166.54]],\n" " ID[\"EPSG\",1295],\n" " REMARK[\"Emulation using NTv2 method of tfm NEA74 Noumea to " "RGNC91-93 (3) (code 15943). Note reversal of sign of parameter values " "in grid file.\"]]"; EXPECT_EQ( op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_grid_transformation_two_parameter) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("15864", false); auto expected = " PARAMETERFILE[\"Latitude difference file\",\"alaska.las\"],\n" " PARAMETERFILE[\"Longitude difference file\",\"alaska.los\"],\n"; auto wkt = op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_TRUE(wkt.find(expected) != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_other_transformation) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("1884", false); auto expected = "COORDINATEOPERATION[\"S-JTSK (Ferro) to S-JTSK (1)\",\n" " VERSION[\"EPSG-Cze\"],\n" " SOURCECRS[\n" " GEOGCRS[\"S-JTSK (Ferro)\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network (Ferro)\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Ferro\",-17.6666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4818]]],\n" " TARGETCRS[\n" " GEOGCRS[\"S-JTSK\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4156]]],\n" " METHOD[\"Longitude rotation\",\n" " ID[\"EPSG\",9601]],\n" " PARAMETER[\"Longitude offset\",-17.6666666666667,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8602]],\n" " OPERATIONACCURACY[0.0],\n" " USAGE[\n" " SCOPE[\"Change of prime meridian.\"],\n" " AREA[\"Czechia; Slovakia.\"],\n" " BBOX[47.73,12.09,51.06,22.56]],\n" " ID[\"EPSG\",1884]]"; EXPECT_EQ( op->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_test_uom_9110) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // This tests conversion from unit of measure EPSG:9110 DDD.MMSSsss auto crs = factory->createProjectedCRS("2172"); EXPECT_PRED_FORMAT2( ComparePROJString, crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=sterea +lat_0=53.0019444444444 +lon_0=21.5027777777778 " "+k=0.9998 +x_0=4603000 +y_0=5806000 +ellps=krass +units=m " "+no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_affine_parametric_transform) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("10087", false); // Do not do axis unit change EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +xoff=82357.457 +s11=0.304794369 " "+s12=1.5417425e-05 +yoff=28091.324 +s21=-1.5417425e-05 " "+s22=0.304794369"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_10566_issue_4212) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("10566", true); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=vgridshift +grids=dk_sdfi_gllmsl_2022.tif +multiplier=1"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_concatenated_operation) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("3896", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 2U); EXPECT_TRUE(operations[0]->isEquivalentTo( factory->createCoordinateOperation("3895", false).get())); EXPECT_TRUE(operations[1]->isEquivalentTo( factory->createCoordinateOperation("1618", false).get())); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_concatenated_operation_three_steps) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("8647", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 3U); EXPECT_TRUE(operations[0]->isEquivalentTo( factory->createCoordinateOperation("1313", false).get())); EXPECT_TRUE(operations[1]->isEquivalentTo( factory->createCoordinateOperation("1950", false).get())); EXPECT_TRUE(operations[2]->isEquivalentTo( factory->createCoordinateOperation("1946", false).get())); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_concatenated_operation_inverse_step1) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("8443", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 2U); EXPECT_TRUE(operations[0]->isEquivalentTo( factory->createCoordinateOperation("8364", false)->inverse().get())); EXPECT_TRUE(operations[1]->isEquivalentTo( factory->createCoordinateOperation("8367", false).get())); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_concatenated_operation_inverse_step2) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7811", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 2U); EXPECT_TRUE(operations[0]->isEquivalentTo( factory->createCoordinateOperation("1763", false).get())); EXPECT_TRUE(operations[1]->isEquivalentTo( factory->createCoordinateOperation("15958", false)->inverse().get())); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_concatenated_operation_step1_is_conversion) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7973", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 2U); EXPECT_TRUE(operations[0]->isEquivalentTo( factory->createCoordinateOperation("7813", false).get())); EXPECT_TRUE(operations[1]->isEquivalentTo( factory->createCoordinateOperation("7969", false).get())); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_concatenated_operation_step_2_and_3_are_conversion) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("7987", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 3U); EXPECT_TRUE(operations[0]->isEquivalentTo( factory->createCoordinateOperation("7980", false).get())); EXPECT_TRUE(operations[1]->isEquivalentTo( factory->createCoordinateOperation("7812", false).get())); EXPECT_TRUE(operations[2]->isEquivalentTo( factory->createCoordinateOperation("7813", false).get())); EXPECT_EQ(operations[1]->targetCRS()->nameStr(), "KOC WD depth"); EXPECT_EQ(operations[2]->sourceCRS()->nameStr(), operations[1]->targetCRS()->nameStr()); EXPECT_EQ( concatenated->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=geogoffset +dh=-4.74 " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=unitconvert +z_in=m +z_out=ft"); EXPECT_EQ(concatenated->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +z_in=ft +z_out=m " "+step +proj=axisswap +order=1,2,-3 " "+step +proj=geogoffset +dh=4.74"); } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createCoordinateOperation_concatenated_operation_epsg_9103) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("9103", false); auto concatenated = nn_dynamic_pointer_cast(op); ASSERT_TRUE(concatenated != nullptr); auto operations = concatenated->operations(); ASSERT_EQ(operations.size(), 5U); // we've added an explicit geographic -> geocentric step EXPECT_EQ(operations[0]->nameStr(), "NAD27 to NAD83 (1)"); EXPECT_EQ(operations[1]->nameStr(), "NAD83 to NAD83(2011) (1)"); EXPECT_EQ( operations[2]->nameStr(), "Conversion from NAD83(2011) (geog2D) to NAD83(2011) (geocentric)"); EXPECT_EQ(operations[3]->nameStr(), "Inverse of ITRF2008 to NAD83(2011) (1)"); EXPECT_EQ(operations[4]->nameStr(), "ITRF2008 to ITRF2014 (1)"); } // --------------------------------------------------------------------------- static bool in(const std::string &str, const std::vector &list) { for (const auto &listItem : list) { if (str == listItem) { return true; } } return false; } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_build_all_concatenated) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto setConcatenated = factory->getAuthorityCodes( AuthorityFactory::ObjectType::CONCATENATED_OPERATION); auto setConcatenatedNoDeprecated = factory->getAuthorityCodes( AuthorityFactory::ObjectType::CONCATENATED_OPERATION, false); EXPECT_LT(setConcatenatedNoDeprecated.size(), setConcatenated.size()); for (const auto &code : setConcatenated) { if (in(code, {"8422", "8481", "8482", "8565", "8566", "8572", "11206"})) { EXPECT_THROW(factory->createCoordinateOperation(code, false), FactoryException) << code; } else { factory->createCoordinateOperation(code, false); } } } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_conversion) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("16031", false); auto conversion = nn_dynamic_pointer_cast(op); ASSERT_TRUE(conversion != nullptr); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createCoordinateOperation_point_motion_operation) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("9483", false); auto pmo = nn_dynamic_pointer_cast(op); ASSERT_TRUE(pmo != nullptr); auto expected = "POINTMOTIONOPERATION[\"Canada velocity grid v7\",\n" " VERSION[\"NRC-Can cvg7.0\"],\n" " SOURCECRS[\n" " GEOGCRS[\"NAD83(CSRS)v7\",\n" " DATUM[\"North American Datum of 1983 (CSRS) version 7\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]],\n" " ANCHOREPOCH[2010]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",8254]]],\n" " METHOD[\"Point motion (geog3D domain) using NEU velocity grid " "(NTv2_Vel)\",\n" " ID[\"EPSG\",1141]],\n" " PARAMETERFILE[\"Point motion velocity grid " "file\",\"NAD83v70VG.gvb\"],\n" " OPERATIONACCURACY[0.01],\n" " USAGE[\n" " SCOPE[\"Change of coordinate epoch for points referenced to " "NAD83(CSRS)v7.\"],\n" " AREA[\"Canada - onshore - Alberta; British Columbia (BC); " "Manitoba; New Brunswick (NB); Newfoundland and Labrador; Northwest " "Territories (NWT); Nova Scotia (NS); Nunavut; Ontario; Prince Edward " "Island (PEI); Quebec; Saskatchewan; Yukon.\"],\n" " BBOX[41.67,-141.01,83.17,-52.54]],\n" " ID[\"EPSG\",9483],\n" " REMARK[\"File initially published with name cvg70.cvb, later " "renamed to NAD83v70VG.gvb with no change of content. Replaces Canada " "velocity grid v6 (code 8676). Replaced by Canada velocity grid v8 " "(code 10707). Although the interpolation CRS is given as " "NAD83(CSRS)v7 (also known as NAD83(CSRS) 2010), any version of " "NAD83(CSRS) may be used for grid interpolation without significant " "error.\"]]"; EXPECT_EQ( pmo->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_getAuthorityCodes) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto set = factory->getAuthorityCodes( AuthorityFactory::ObjectType::PRIME_MERIDIAN); ASSERT_TRUE(!set.empty()); factory->createPrimeMeridian(*(set.begin())); } { auto set = factory->getAuthorityCodes(AuthorityFactory::ObjectType::ELLIPSOID); ASSERT_TRUE(!set.empty()); factory->createEllipsoid(*(set.begin())); } { auto setDatum = factory->getAuthorityCodes(AuthorityFactory::ObjectType::DATUM); ASSERT_TRUE(!setDatum.empty()); factory->createDatum(*(setDatum.begin())); auto setGeodeticDatum = factory->getAuthorityCodes( AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME); ASSERT_TRUE(!setGeodeticDatum.empty()); factory->createGeodeticDatum(*(setGeodeticDatum.begin())); auto setDynamicGeodeticDatum = factory->getAuthorityCodes( AuthorityFactory::ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME); ASSERT_TRUE(!setDynamicGeodeticDatum.empty()); auto dgrf = factory->createGeodeticDatum(*(setDynamicGeodeticDatum.begin())); EXPECT_TRUE(dynamic_cast(dgrf.get()) != nullptr); EXPECT_LT(setDynamicGeodeticDatum.size(), setGeodeticDatum.size()); auto setVerticalDatum = factory->getAuthorityCodes( AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME); ASSERT_TRUE(!setVerticalDatum.empty()); factory->createVerticalDatum(*(setVerticalDatum.begin())); auto setDynamicVerticalDatum = factory->getAuthorityCodes( AuthorityFactory::ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME); ASSERT_TRUE(!setDynamicVerticalDatum.empty()); auto dvrf = factory->createVerticalDatum(*(setDynamicVerticalDatum.begin())); EXPECT_TRUE(dynamic_cast(dvrf.get()) != nullptr); EXPECT_LT(setDynamicVerticalDatum.size(), setVerticalDatum.size()); auto setEngineeringDatum = factory->getAuthorityCodes( AuthorityFactory::ObjectType::ENGINEERING_DATUM); ASSERT_TRUE(!setEngineeringDatum.empty()); factory->createEngineeringDatum(*(setEngineeringDatum.begin())); std::set setMerged; for (const auto &v : setGeodeticDatum) { setMerged.insert(v); } for (const auto &v : setVerticalDatum) { setMerged.insert(v); } for (const auto &v : setEngineeringDatum) { setMerged.insert(v); } EXPECT_EQ(setDatum, setMerged); } { auto setCRS = factory->getAuthorityCodes(AuthorityFactory::ObjectType::CRS); ASSERT_TRUE(!setCRS.empty()); factory->createCoordinateReferenceSystem(*(setCRS.begin())); auto setGeodeticCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::GEODETIC_CRS); ASSERT_TRUE(!setGeodeticCRS.empty()); factory->createGeodeticCRS(*(setGeodeticCRS.begin())); auto setGeocentricCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::GEOCENTRIC_CRS); ASSERT_TRUE(!setGeocentricCRS.empty()); factory->createGeodeticCRS(*(setGeocentricCRS.begin())); EXPECT_LT(setGeocentricCRS.size(), setGeodeticCRS.size()); auto setGeographicCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::GEOGRAPHIC_CRS); ASSERT_TRUE(!setGeographicCRS.empty()); factory->createGeographicCRS(*(setGeographicCRS.begin())); EXPECT_LT(setGeographicCRS.size(), setGeodeticCRS.size()); for (const auto &v : setGeographicCRS) { EXPECT_TRUE(setGeodeticCRS.find(v) != setGeodeticCRS.end()); } auto setGeographic2DCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS); ASSERT_TRUE(!setGeographic2DCRS.empty()); factory->createGeographicCRS(*(setGeographic2DCRS.begin())); auto setGeographic3DCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); ASSERT_TRUE(!setGeographic3DCRS.empty()); factory->createGeographicCRS(*(setGeographic3DCRS.begin())); EXPECT_EQ(setGeographic2DCRS.size() + setGeographic3DCRS.size(), setGeographicCRS.size()); EXPECT_EQ(setGeocentricCRS.size() + setGeographicCRS.size(), setGeodeticCRS.size()); auto setVerticalCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::VERTICAL_CRS); ASSERT_TRUE(!setVerticalCRS.empty()); factory->createVerticalCRS(*(setVerticalCRS.begin())); auto setProjectedCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::PROJECTED_CRS); ASSERT_TRUE(!setProjectedCRS.empty()); factory->createProjectedCRS(*(setProjectedCRS.begin())); auto setCompoundCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::COMPOUND_CRS); ASSERT_TRUE(!setCompoundCRS.empty()); factory->createCompoundCRS(*(setCompoundCRS.begin())); auto setEngineeringCRS = factory->getAuthorityCodes( AuthorityFactory::ObjectType::ENGINEERING_CRS); ASSERT_TRUE(!setEngineeringCRS.empty()); factory->createEngineeringCRS(*(setEngineeringCRS.begin())); std::set setMerged; for (const auto &v : setGeodeticCRS) { setMerged.insert(v); } for (const auto &v : setVerticalCRS) { setMerged.insert(v); } for (const auto &v : setProjectedCRS) { setMerged.insert(v); } for (const auto &v : setCompoundCRS) { setMerged.insert(v); } for (const auto &v : setEngineeringCRS) { setMerged.insert(v); } EXPECT_EQ(setCRS, setMerged); } { auto setCO = factory->getAuthorityCodes( AuthorityFactory::ObjectType::COORDINATE_OPERATION); ASSERT_TRUE(!setCO.empty()); factory->createCoordinateOperation(*(setCO.begin()), false); auto setConversion = factory->getAuthorityCodes( AuthorityFactory::ObjectType::CONVERSION); ASSERT_TRUE(!setConversion.empty()); factory->createConversion(*(setConversion.begin())); auto setTransformation = factory->getAuthorityCodes( AuthorityFactory::ObjectType::TRANSFORMATION); ASSERT_TRUE(!setTransformation.empty()); ASSERT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateOperation( *(setTransformation.begin()), false)) != nullptr); auto setConcatenated = factory->getAuthorityCodes( AuthorityFactory::ObjectType::CONCATENATED_OPERATION); ASSERT_TRUE(!setConcatenated.empty()); ASSERT_TRUE(nn_dynamic_pointer_cast( factory->createCoordinateOperation( *(setConcatenated.begin()), false)) != nullptr); std::set setMerged; for (const auto &v : setConversion) { setMerged.insert(v); } for (const auto &v : setTransformation) { setMerged.insert(v); } for (const auto &v : setConcatenated) { setMerged.insert(v); } EXPECT_EQ(setCO.size(), setMerged.size()); std::set setMissing; for (const auto &v : setCO) { if (setMerged.find(v) == setMerged.end()) { setMissing.insert(v); } } EXPECT_EQ(setMissing, std::set()); EXPECT_EQ(setCO, setMerged); } } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_getDescriptionText) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_THROW(factory->getDescriptionText("-1"), NoSuchAuthorityCodeException); EXPECT_EQ(factory->getDescriptionText("10000"), "RGF93 v1 to NGF-IGN69 height (1)"); // Several objects have 4326 code, including an area of use, but return // the CRS one. EXPECT_EQ(factory->getDescriptionText("4326"), "WGS 84"); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_IAU_2015) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "IAU_2015"); { auto crs = factory->createGeographicCRS("19900"); EXPECT_EQ(crs->nameStr(), "Mercury (2015) - Sphere / Ocentric"); const auto ellps = crs->ellipsoid(); EXPECT_TRUE(ellps->isSphere()); EXPECT_NEAR(ellps->semiMajorAxis().value(), 2440530.0, 1e-6); const auto &axisList = crs->coordinateSystem()->axisList(); EXPECT_EQ(axisList.size(), 2U); EXPECT_EQ(*(axisList[0]->name()->description()), "Geodetic latitude"); EXPECT_EQ(axisList[0]->abbreviation(), "Lat"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(*(axisList[1]->name()->description()), "Geodetic longitude"); EXPECT_EQ(axisList[1]->abbreviation(), "Lon"); EXPECT_EQ(axisList[1]->direction(), AxisDirection::EAST); EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE); } { auto crs = factory->createGeographicCRS("19901"); EXPECT_EQ(crs->nameStr(), "Mercury (2015) / Ographic"); const auto ellps = crs->ellipsoid(); EXPECT_TRUE(!ellps->isSphere()); EXPECT_NEAR(ellps->semiMajorAxis().value(), 2440530.0, 1e-6); EXPECT_NEAR(ellps->computeSemiMinorAxis().value(), 2438260.0, 1e-6); const auto &axisList = crs->coordinateSystem()->axisList(); EXPECT_EQ(axisList.size(), 2U); EXPECT_EQ(*(axisList[0]->name()->description()), "Geodetic latitude"); EXPECT_EQ(axisList[0]->abbreviation(), "Lat"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(*(axisList[1]->name()->description()), "Geodetic longitude"); EXPECT_EQ(axisList[1]->abbreviation(), "Lon"); EXPECT_EQ(axisList[1]->direction(), AxisDirection::WEST); // WEST ! EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE); } { auto crs = factory->createGeodeticCRS("19902"); EXPECT_EQ(crs->nameStr(), "Mercury (2015) / Ocentric"); EXPECT_TRUE(dynamic_cast(crs.get()) == nullptr); const auto ellps = crs->ellipsoid(); EXPECT_TRUE(!ellps->isSphere()); EXPECT_NEAR(ellps->semiMajorAxis().value(), 2440530.0, 1e-6); EXPECT_NEAR(ellps->computeSemiMinorAxis().value(), 2438260.0, 1e-6); const auto &cs = crs->coordinateSystem(); EXPECT_TRUE(dynamic_cast(cs.get()) != nullptr); const auto &axisList = cs->axisList(); EXPECT_EQ(axisList.size(), 2U); EXPECT_EQ(*(axisList[0]->name()->description()), "Planetocentric latitude"); EXPECT_EQ(axisList[0]->abbreviation(), "U"); EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE); EXPECT_EQ(*(axisList[1]->name()->description()), "Planetocentric longitude"); EXPECT_EQ(axisList[1]->abbreviation(), "V"); EXPECT_EQ(axisList[1]->direction(), AxisDirection::EAST); EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE); } } // --------------------------------------------------------------------------- class FactoryWithTmpDatabase : public ::testing::Test { protected: void SetUp() override { sqlite3_open(":memory:", &m_ctxt); } void TearDown() override { sqlite3_free_table(m_papszResult); sqlite3_close(m_ctxt); } void createStructure() { auto referenceDb = DatabaseContext::create(); const auto dbStructure = referenceDb->getDatabaseStructure(); for (const auto &sql : dbStructure) { ASSERT_TRUE(execute(sql)) << last_error(); } ASSERT_TRUE(execute("PRAGMA foreign_keys = 1;")) << last_error(); } void populateWithFakeEPSG() { ASSERT_TRUE(execute("INSERT INTO unit_of_measure " "VALUES('EPSG','9001','metre','length',1.0,NULL," "0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO unit_of_measure " "VALUES('EPSG','9102','degree','angle',1." "74532925199432781271e-02,NULL,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO unit_of_measure VALUES('EPSG','9122','degree " "(supplier to " "define representation)','angle',1.74532925199432781271e-02,NULL," "0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO extent " "VALUES('EPSG','1262','World','World.',-90.0,90.0,-180." "0,180.0,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO scope VALUES('EPSG','1024','Not known.',0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO prime_meridian " "VALUES('EPSG','8901','Greenwich',0.0,'EPSG','9102',0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO celestial_body VALUES('PROJ','EARTH','Earth'," "6378137.0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO ellipsoid VALUES('EPSG','7030','WGS 84',''," "'PROJ','EARTH',6378137.0,'EPSG','9001',298.257223563," "NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO geodetic_datum " "VALUES('EPSG','6326','World Geodetic System 1984',''," "'EPSG','7030','EPSG','8901',NULL,NULL,NULL," "'my anchor',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'geodetic_datum_6326_usage','geodetic_datum'," "'EPSG','6326','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE( execute("INSERT INTO vertical_datum VALUES('EPSG','1027','EGM2008 " "geoid',NULL,NULL,NULL,NULL,'my anchor',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'vertical_datum_1027_usage','vertical_datum'," "'EPSG','1027','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO coordinate_system " "VALUES('EPSG','6422','ellipsoidal',2);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO axis VALUES('EPSG','106','Geodetic " "latitude','Lat','north','EPSG','6422',1,'EPSG','9122');")) << last_error(); ASSERT_TRUE( execute("INSERT INTO axis VALUES('EPSG','107','Geodetic " "longitude','Lon','east','EPSG','6422',2,'EPSG','9122');")) << last_error(); ASSERT_TRUE( execute("INSERT INTO geodetic_crs VALUES('EPSG','4326','WGS " "84',NULL,'geographic " "2D','EPSG','6422','EPSG','6326',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'geodetic_crs4326_usage','geodetic_crs'," "'EPSG','4326','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO coordinate_system " "VALUES('EPSG','6499','vertical',1);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO axis VALUES('EPSG','114','Gravity-related " "height','H','up','EPSG','6499',1,'EPSG','9001');")) << last_error(); ASSERT_TRUE( execute("INSERT INTO vertical_crs VALUES('EPSG','3855','EGM2008 " "height',NULL,'EPSG','6499','EPSG','1027',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'vertical_crs3855_usage','vertical_crs'," "'EPSG','3855','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO unit_of_measure " "VALUES('EPSG','9201','unity','scale',1.0," "NULL,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO extent VALUES('EPSG','1933','World - N hemisphere - " "0°E to 6°E','',0.0,84.0,0.0,6.0,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO conversion VALUES('EPSG','16031','UTM zone " "31N',NULL,'EPSG','9807','Transverse " "Mercator','EPSG','8801','Latitude " "of " "natural origin',0.0,'EPSG','9102','EPSG','8802','Longitude of " "natural " "origin',3.0,'EPSG','9102','EPSG','8805','Scale factor at natural " "origin',0.9996,'EPSG','9201','EPSG','8806','False " "easting',500000.0,'EPSG','9001','EPSG','8807','False " "northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL," "NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'conversion16031_usage','conversion'," "'EPSG','16031','EPSG','1933','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO extent VALUES('EPSG','2060','World - N hemisphere - " "0°E to 6°E - by country','',0.0,84.0,0.0,6.0,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO coordinate_system " "VALUES('EPSG','4400','Cartesian',2);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO axis " "VALUES('EPSG','1','Easting','E','east','EPSG','4400'," "1,'EPSG','9001');")) << last_error(); ASSERT_TRUE( execute("INSERT INTO axis " "VALUES('EPSG','2','Northing','N','north','EPSG','4400'" ",2,'EPSG','9001');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('EPSG','32631','WGS 84 / UTM zone " "31N',NULL,'EPSG','4400','EPSG','4326'," "'EPSG','16031',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'projected_crs32631_usage','projected_crs'," "'EPSG','32631','EPSG','2060','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO compound_crs VALUES('EPSG','MY_COMPOUND','WGS 84 + " "EGM2008 geoid height',NULL,'EPSG','4326','EPSG','3855',0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('EPSG'," "'compound_crsMY_COMPOUND_usage','compound_crs'," "'EPSG','MY_COMPOUND','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO helmert_transformation " "VALUES('EPSG','DUMMY_HELMERT','dummy_helmert',NULL,'EPSG','9603','" "Geocentric translations (geog2D domain)','EPSG','4326'," "'EPSG','4326',44.0,-143." "0,-90.0,-294.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('EPSG'," "'helmert_transformation_DUMMY_HELMERT_usage'," "'helmert_transformation'," "'EPSG','DUMMY_HELMERT','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO grid_transformation " "VALUES('EPSG','DUMMY_GRID_TRANSFORMATION'," "'dummy_grid_transformation',NULL," "'EPSG','9615'" ",'NTv2','EPSG','4326','EPSG','4326',1.0,'EPSG','" "8656','Latitude and longitude difference " "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL," "0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('EPSG'," "'grid_transformation_DUMMY_GRID_TRANSFORMATION_usage'," "'grid_transformation'," "'EPSG','DUMMY_GRID_TRANSFORMATION'," "'EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO unit_of_measure VALUES('EPSG','9110','sexagesimal " "DMS','angle',NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('EPSG','DUMMY_OTHER_TRANSFORMATION'," "'dummy_other_transformation',NULL," "'EPSG','9601','Longitude rotation'," "'EPSG','4326','EPSG','4326',0.0,'EPSG'" ",'8602','Longitude " "offset',-17.4,'EPSG','9110',NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('EPSG'," "'other_transformation_DUMMY_OTHER_TRANSFORMATION_usage'," "'other_transformation'," "'EPSG','DUMMY_OTHER_TRANSFORMATION'," "'EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation " "VALUES('EPSG','DUMMY_CONCATENATED'," "'dummy_concatenated',NULL," "'EPSG','4326','EPSG'" ",'4326',NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'concatenated_operation_DUMMY_CONCATENATED_usage'," "'concatenated_operation'," "'EPSG','DUMMY_CONCATENATED'," "'EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','DUMMY_CONCATENATED',1," "'EPSG','DUMMY_OTHER_TRANSFORMATION',NULL);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','DUMMY_CONCATENATED',2," "'EPSG','DUMMY_OTHER_TRANSFORMATION',NULL);")) << last_error(); } void createSourceTargetPivotCRS() { const auto vals = std::vector{"SOURCE", "TARGET", "PIVOT"}; for (const auto &val : vals) { ASSERT_TRUE(execute("INSERT INTO geodetic_datum " "VALUES('FOO','" + val + "','" + val + "',''," "'EPSG','7030','EPSG','8901'," "NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('FOO'," "'geodetic_datum_" + val + "_usage'," "'geodetic_datum'," "'FOO','" + val + "'," "'EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO geodetic_crs " "VALUES('NS_" + val + "','" + val + "','" + val + "',NULL,'geographic 2D','EPSG','6422'," "'FOO','" + val + "',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('FOO'," "'geodetic_crs_" + val + "_usage'," "'geodetic_crs'," "'NS_" + val + "','" + val + "','EPSG','1262','EPSG','1024');")) << last_error(); } } void createTransformationForPivotTesting(const std::string &src, const std::string &dst) { ASSERT_TRUE(execute( "INSERT INTO helmert_transformation " "VALUES('OTHER','" + src + "_" + dst + "','Transformation from " + src + " to " + dst + "',NULL,'EPSG','9603','" "Geocentric translations (geog2D domain)','NS_" + src + "','" + src + "','NS_" + dst + "','" + dst + "',1.0,0,0,0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'helmert_transformation" + src + '_' + dst + "_usage'," "'helmert_transformation'," "'OTHER','" + src + "_" + dst + "'," "'EPSG','1262','EPSG','1024');")) << last_error(); } void checkSourceToOther() { { auto factoryOTHER = AuthorityFactory::create( DatabaseContext::create(m_ctxt), "OTHER"); auto res = factoryOTHER->createFromCRSCodesWithIntermediates( "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, false, false, {}); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE(res.empty() || nn_dynamic_pointer_cast(res[0])); res = factoryOTHER->createFromCRSCodesWithIntermediates( "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, false, false, {std::make_pair(std::string("NS_PIVOT"), std::string("PIVOT"))}); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE(res.empty() || nn_dynamic_pointer_cast(res[0])); res = factoryOTHER->createFromCRSCodesWithIntermediates( "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, false, false, {std::make_pair(std::string("NS_PIVOT"), std::string("NOT_EXISTING"))}); EXPECT_EQ(res.size(), 0U); res = factoryOTHER->createFromCRSCodesWithIntermediates( "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, false, false, {std::make_pair(std::string("BAD_NS"), std::string("PIVOT"))}); EXPECT_EQ(res.size(), 0U); res = factoryOTHER->createFromCRSCodesWithIntermediates( "NS_TARGET", "TARGET", "NS_SOURCE", "SOURCE", false, false, false, false, {}); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE(res.empty() || nn_dynamic_pointer_cast(res[0])); } { auto factory = AuthorityFactory::create( DatabaseContext::create(m_ctxt), std::string()); auto res = factory->createFromCRSCodesWithIntermediates( "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, false, false, {}); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE(res.empty() || nn_dynamic_pointer_cast(res[0])); auto srcCRS = AuthorityFactory::create( DatabaseContext::create(m_ctxt), "NS_SOURCE") ->createCoordinateReferenceSystem("SOURCE"); auto targetCRS = AuthorityFactory::create( DatabaseContext::create(m_ctxt), "NS_TARGET") ->createCoordinateReferenceSystem("TARGET"); { auto ctxt = CoordinateOperationContext::create(factory, nullptr, 0); res = CoordinateOperationFactory::create()->createOperations( srcCRS, targetCRS, ctxt); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE( res.empty() || nn_dynamic_pointer_cast(res[0])); } { auto ctxt = CoordinateOperationContext::create(factory, nullptr, 0); ctxt->setIntermediateCRS({std::make_pair( std::string("NS_PIVOT"), std::string("PIVOT"))}); res = CoordinateOperationFactory::create()->createOperations( srcCRS, targetCRS, ctxt); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE( res.empty() || nn_dynamic_pointer_cast(res[0])); } { auto ctxt = CoordinateOperationContext::create(factory, nullptr, 0); ctxt->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::NEVER); res = CoordinateOperationFactory::create()->createOperations( srcCRS, targetCRS, ctxt); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE(res.empty() || nn_dynamic_pointer_cast(res[0])); } { auto ctxt = CoordinateOperationContext::create(factory, nullptr, 0); ctxt->setIntermediateCRS({std::make_pair( std::string("NS_PIVOT"), std::string("NOT_EXISTING"))}); res = CoordinateOperationFactory::create()->createOperations( srcCRS, targetCRS, ctxt); EXPECT_EQ(res.size(), 1U); EXPECT_TRUE(res.empty() || nn_dynamic_pointer_cast(res[0])); } } } bool execute(const std::string &sql) { return sqlite3_exec(m_ctxt, sql.c_str(), nullptr, nullptr, nullptr) == SQLITE_OK; } std::string last_error() { const char *msg = sqlite3_errmsg(m_ctxt); return msg ? msg : std::string(); } int m_nRows = 0; int m_nCols = 0; char **m_papszResult = nullptr; sqlite3 *m_ctxt = nullptr; }; // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_test_with_fake_EPSG_database) { createStructure(); populateWithFakeEPSG(); auto factory = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "EPSG"); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("9001")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("1262")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("8901")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("7030")) != nullptr); auto grf = nn_dynamic_pointer_cast( factory->createObject("6326")); ASSERT_TRUE(grf != nullptr); EXPECT_EQ(*grf->anchorDefinition(), "my anchor"); auto vrf = nn_dynamic_pointer_cast( factory->createObject("1027")); ASSERT_TRUE(vrf != nullptr); EXPECT_EQ(*vrf->anchorDefinition(), "my anchor"); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("4326")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("3855")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("16031")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("32631")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("MY_COMPOUND")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("DUMMY_HELMERT")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast(factory->createObject( "DUMMY_GRID_TRANSFORMATION")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast(factory->createObject( "DUMMY_OTHER_TRANSFORMATION")) != nullptr); EXPECT_TRUE(nn_dynamic_pointer_cast( factory->createObject("DUMMY_CONCATENATED")) != nullptr); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_createFromCoordinateReferenceSystemCodes) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); EXPECT_TRUE( factory->createFromCoordinateReferenceSystemCodes("-1", "-1").empty()); { auto res = factory->createFromCoordinateReferenceSystemCodes("4326", "32631"); ASSERT_EQ(res.size(), 1U); EXPECT_TRUE(res[0]->sourceCRS() != nullptr); EXPECT_TRUE(res[0]->targetCRS() != nullptr); EXPECT_TRUE( res[0]->isEquivalentTo(factory->createConversion("16031").get())); } { auto res = factory->createFromCoordinateReferenceSystemCodes("4209", "4326"); EXPECT_TRUE(!res.empty()); for (const auto &conv : res) { EXPECT_TRUE(conv->sourceCRS()->getEPSGCode() == 4209); EXPECT_TRUE(conv->targetCRS()->getEPSGCode() == 4326); EXPECT_FALSE(conv->isDeprecated()); } } { auto list = factory->createFromCoordinateReferenceSystemCodes("4179", "4258"); ASSERT_EQ(list.size(), 3U); // Romania has a larger area than Poland (given our approx formula) EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m EXPECT_EQ(list[1]->getEPSGCode(), 15993); // Romania - 10m EXPECT_EQ(list[2]->getEPSGCode(), 1644); // Poland - 1m } { // Test removal of superseded transform auto list = factory->createFromCoordinateReferenceSystemCodes( "EPSG", "4179", "EPSG", "4258", false, false, false, true); ASSERT_EQ(list.size(), 2U); // Romania has a larger area than Poland (given our approx formula) EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m EXPECT_EQ(list[1]->getEPSGCode(), 1644); // Poland - 1m } } // --------------------------------------------------------------------------- TEST( factory, AuthorityFactory_createFromCoordinateReferenceSystemCodes_anonymous_authority) { auto factory = AuthorityFactory::create(DatabaseContext::create(), std::string()); { auto res = factory->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "EPSG", "32631", false, false, false, false); ASSERT_EQ(res.size(), 1U); } { auto res = factory->createFromCoordinateReferenceSystemCodes( "EPSG", "4209", "EPSG", "4326", false, false, false, false); EXPECT_TRUE(!res.empty()); for (const auto &conv : res) { EXPECT_TRUE(conv->sourceCRS()->getEPSGCode() == 4209); EXPECT_TRUE(conv->targetCRS()->getEPSGCode() == 4326); EXPECT_FALSE(conv->isDeprecated()); } } } TEST(factory, AuthorityFactory_getAvailableGeoidmodels) { const std::string OSGM15{"OSGM15"}; const std::string GEOID12B{"GEOID12B"}; const std::string GEOID18{"GEOID18"}; auto checkNavd88 = [&](const std::list &res) { EXPECT_TRUE(res.end() != std::find(res.begin(), res.end(), GEOID12B)); EXPECT_TRUE(res.end() != std::find(res.begin(), res.end(), GEOID18)); EXPECT_FALSE(res.end() != std::find(res.begin(), res.end(), OSGM15)); }; auto checkOdn = [&](const std::list &res) { EXPECT_FALSE(res.end() != std::find(res.begin(), res.end(), GEOID12B)); EXPECT_FALSE(res.end() != std::find(res.begin(), res.end(), GEOID18)); EXPECT_TRUE(res.end() != std::find(res.begin(), res.end(), OSGM15)); }; auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); { auto res = factory->getGeoidModels("4326"); ASSERT_TRUE(res.empty()); } { auto res = factory->getGeoidModels("5703"); // "NAVD88 height" checkNavd88(res); } { auto res = factory->getGeoidModels("6360"); // "NAVD88 height (ftUS)" checkNavd88(res); } { auto res = factory->getGeoidModels("8228"); // "NAVD88 height (ft)" checkNavd88(res); } { auto res = factory->getGeoidModels("6357"); // "NAVD88 depth" checkNavd88(res); } { auto res = factory->getGeoidModels("6358"); // "NAVD88 depth (ftUS)" checkNavd88(res); } { auto res = factory->getGeoidModels("5701"); // "ODN height" checkOdn(res); } { auto res = factory->getGeoidModels("5732"); // "Belfast height" checkOdn(res); } } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_test_inversion_first_and_last_steps_of_concat_op) { createStructure(); populateWithFakeEPSG(); // Completely dummy, to test proper inversion of first and last // steps in ConcatenatedOperation, when it is needed ASSERT_TRUE(execute("INSERT INTO geodetic_datum " "VALUES('EPSG','OTHER_DATUM','Other datum',''," "'EPSG','7030','EPSG','8901',NULL,NULL,NULL," "'my anchor',NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO geodetic_crs VALUES('EPSG','OTHER_GEOG_CRS'," "'OTHER_GEOG_CRS',NULL,'geographic 2D','EPSG','6422'," "'EPSG','OTHER_DATUM',NULL,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('EPSG','4326_TO_OTHER_GEOG_CRS'," "'4326_to_other_geog_crs',NULL," "'EPSG','9601','Longitude rotation'," "'EPSG','4326','EPSG','OTHER_GEOG_CRS',0.0,'EPSG'" ",'8602','Longitude " "offset',-17.4,'EPSG','9110',NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('EPSG','OTHER_GEOG_CRS_TO_4326'," "'other_geog_crs_to_4326',NULL," "'EPSG','9601','Longitude rotation'," "'EPSG','OTHER_GEOG_CRS','EPSG','4326',0.0,'EPSG'" ",'8602','Longitude " "offset',17.4,'EPSG','9110',NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation " "VALUES('EPSG','DUMMY_CONCATENATED_2'," "'dummy_concatenated_2',NULL," "'EPSG','4326','EPSG'" ",'4326',NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','DUMMY_CONCATENATED_2',1," "'EPSG','OTHER_GEOG_CRS_TO_4326',NULL);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','DUMMY_CONCATENATED_2',2," "'EPSG','4326_TO_OTHER_GEOG_CRS',NULL);")) << last_error(); auto factoryEPSG = AuthorityFactory::create(DatabaseContext::create(m_ctxt), std::string("EPSG")); EXPECT_TRUE(nn_dynamic_pointer_cast( factoryEPSG->createObject("DUMMY_CONCATENATED_2")) != nullptr); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_test_with_fake_EPSG_and_OTHER_database) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE( execute("INSERT INTO geodetic_crs VALUES('OTHER','OTHER_4326','WGS " "84',NULL,'geographic " "2D','EPSG','6422','EPSG','6326',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'geodetic_crs_OTHER_4326_usage','geodetic_crs'," "'OTHER','OTHER_4326','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('OTHER','OTHER_32631','my WGS 84 / UTM zone " "31N',NULL,'EPSG','4400','OTHER','OTHER_4326'," "'EPSG','16031',NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'projected_crs_OTHER_32631_usage','projected_crs'," "'OTHER','OTHER_32631','EPSG','2060','EPSG','1024');")) << last_error(); auto factoryGeneral = AuthorityFactory::create( DatabaseContext::create(m_ctxt), std::string()); { auto res = factoryGeneral->createFromCoordinateReferenceSystemCodes( "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false, false, false); ASSERT_EQ(res.size(), 1U); } auto factoryEPSG = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "EPSG"); { auto res = factoryEPSG->createFromCoordinateReferenceSystemCodes( "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false, false, false); ASSERT_EQ(res.size(), 1U); } auto factoryOTHER = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER"); { auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes( "OTHER_4326", "OTHER_32631"); ASSERT_EQ(res.size(), 0U); // the conversion is in the EPSG space } ASSERT_TRUE(execute( "INSERT INTO grid_transformation " "VALUES('OTHER','OTHER_GRID_TRANSFORMATION'," "'other_grid_transformation_2',NULL," "'EPSG','9615'" ",'NTv2','EPSG','4326','OTHER','OTHER_4326',1.0,'EPSG','" "8656','Latitude and longitude difference " "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO usage VALUES('OTHER'," "'grid_transformation_OTHER_GRID_TRANSFORMATION_usage'," "'grid_transformation'," "'OTHER','OTHER_GRID_TRANSFORMATION','EPSG','1262','EPSG','1024');")) << last_error(); { auto res = factoryGeneral->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false, false); ASSERT_EQ(res.size(), 1U); } { auto res = factoryEPSG->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false, false); ASSERT_EQ(res.size(), 0U); } { auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false, false); ASSERT_EQ(res.size(), 1U); } } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_test_sorting_of_coordinate_operations) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute( "INSERT INTO grid_transformation " "VALUES('OTHER','TRANSFORMATION_10M','TRANSFORMATION_10M',NULL," "'EPSG','9615'" ",'NTv2','EPSG','4326','EPSG','4326',10.0,'EPSG','" "8656','Latitude and longitude difference " "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('OTHER'," "'grid_transformation_TTRANSFORMATION_10M_usage'," "'grid_transformation'," "'OTHER','TRANSFORMATION_10M','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE( execute("INSERT INTO grid_transformation " "VALUES('OTHER','TRANSFORMATION_1M_SMALL_EXTENT','" "TRANSFORMATION_1M_SMALL_EXTENT',NULL,'EPSG','9615'" ",'NTv2','EPSG','4326','EPSG','4326',1.0,'EPSG','" "8656','Latitude and longitude difference " "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('OTHER'," "'grid_transformation_TRANSFORMATION_1M_SMALL_EXTENT_usage'," "'grid_transformation'," "'OTHER','TRANSFORMATION_1M_SMALL_EXTENT'," "'EPSG','2060','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO grid_transformation " "VALUES('OTHER','TRANSFORMATION_1M','TRANSFORMATION_1M',NULL," "'EPSG','9615'" ",'NTv2','EPSG','4326','EPSG','4326',1.0,'EPSG','" "8656','Latitude and longitude difference " "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('OTHER'," "'grid_transformation_TRANSFORMATION_1M_usage'," "'grid_transformation'," "'OTHER','TRANSFORMATION_1M','EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO grid_transformation " "VALUES('OTHER','TRANSFORMATION_0.5M_DEPRECATED','" "TRANSFORMATION_0.5M_DEPRECATED',NULL,'EPSG','9615'" ",'NTv2','EPSG','4326','EPSG','4326',1.0,'EPSG','" "8656','Latitude and longitude difference " "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('OTHER'," "'grid_transformation_TRANSFORMATION_0.5M_DEPRECATED_usage'," "'grid_transformation'," "'OTHER','TRANSFORMATION_0.5M_DEPRECATED'," "'EPSG','1262','EPSG','1024');")) << last_error(); auto factoryOTHER = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER"); auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "EPSG", "4326", false, false, false, false); ASSERT_EQ(res.size(), 3U); EXPECT_EQ(*(res[0]->name()->description()), "TRANSFORMATION_1M"); EXPECT_EQ(*(res[1]->name()->description()), "TRANSFORMATION_10M"); EXPECT_EQ(*(res[2]->name()->description()), "TRANSFORMATION_1M_SMALL_EXTENT"); } // --------------------------------------------------------------------------- TEST_F( FactoryWithTmpDatabase, AuthorityFactory_createFromCRSCodesWithIntermediates_source_equals_target) { createStructure(); populateWithFakeEPSG(); auto factory = AuthorityFactory::create(DatabaseContext::create(m_ctxt), std::string()); auto res = factory->createFromCRSCodesWithIntermediates( "EPSG", "4326", "EPSG", "4326", false, false, false, false, {}); EXPECT_EQ(res.size(), 0U); } // --------------------------------------------------------------------------- TEST_F( FactoryWithTmpDatabase, AuthorityFactory_createFromCRSCodesWithIntermediates_case_source_pivot_target_pivot) { createStructure(); populateWithFakeEPSG(); createSourceTargetPivotCRS(); createTransformationForPivotTesting("SOURCE", "PIVOT"); createTransformationForPivotTesting("TARGET", "PIVOT"); checkSourceToOther(); } // --------------------------------------------------------------------------- TEST_F( FactoryWithTmpDatabase, AuthorityFactory_createFromCRSCodesWithIntermediates_case_source_pivot_pivot_target) { createStructure(); populateWithFakeEPSG(); createSourceTargetPivotCRS(); createTransformationForPivotTesting("SOURCE", "PIVOT"); createTransformationForPivotTesting("PIVOT", "TARGET"); checkSourceToOther(); } // --------------------------------------------------------------------------- TEST_F( FactoryWithTmpDatabase, AuthorityFactory_createFromCRSCodesWithIntermediates_case_pivot_source_pivot_target) { createStructure(); populateWithFakeEPSG(); createSourceTargetPivotCRS(); createTransformationForPivotTesting("PIVOT", "SOURCE"); createTransformationForPivotTesting("PIVOT", "TARGET"); checkSourceToOther(); } // --------------------------------------------------------------------------- TEST_F( FactoryWithTmpDatabase, AuthorityFactory_createFromCRSCodesWithIntermediates_case_pivot_source_target_pivot) { createStructure(); populateWithFakeEPSG(); createSourceTargetPivotCRS(); createTransformationForPivotTesting("PIVOT", "SOURCE"); createTransformationForPivotTesting("TARGET", "PIVOT"); checkSourceToOther(); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_proj_based_transformation) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('OTHER','FOO','My PROJ string based op',NULL,'PROJ'," "'PROJString','+proj=pipeline +ellps=WGS84 +step +proj=longlat'," "'EPSG','4326','EPSG','4326',0.0,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'other_transformation_FOO_usage'," "'other_transformation'," "'OTHER','FOO'," "'EPSG','1262','EPSG','1024');")) << last_error(); auto factoryOTHER = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER"); auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "EPSG", "4326", false, false, false, false); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res[0]->nameStr(), "My PROJ string based op"); EXPECT_EQ(res[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +ellps=WGS84 +step +proj=longlat"); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_wkt_based_transformation) { createStructure(); populateWithFakeEPSG(); auto wkt = "COORDINATEOPERATION[\"My WKT string based op\",\n" " SOURCECRS[\n" " GEODCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north],\n" " AXIS[\"geodetic longitude (Lon)\",east],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " TARGETCRS[\n" " GEODCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north],\n" " AXIS[\"geodetic longitude (Lon)\",east],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " METHOD[\"Geocentric translations (geog2D domain)\"],\n" " PARAMETER[\"X-axis translation\",1,UNIT[\"metre\",1]],\n" " PARAMETER[\"Y-axis translation\",2,UNIT[\"metre\",1]],\n" " PARAMETER[\"Z-axis translation\",3,UNIT[\"metre\",1]]]"; ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('OTHER','FOO','My WKT string based op',NULL," "'PROJ','WKT','" + std::string(wkt) + "'," "'EPSG','4326','EPSG','4326',0.0,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'other_transformation_FOO_usage'," "'other_transformation'," "'OTHER','FOO'," "'EPSG','1262','EPSG','1024');")) << last_error(); auto factoryOTHER = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER"); auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "EPSG", "4326", false, false, false, false); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res[0]->nameStr(), "My WKT string based op"); EXPECT_EQ(res[0]->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 +step +proj=helmert +x=1 +y=2 " "+z=3 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step " "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_wkt_based_transformation_not_wkt) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('OTHER','FOO','My WKT string based op',NULL," "'PROJ','WKT','" + std::string("invalid_wkt") + "'," "'EPSG','4326','EPSG','4326',0.0,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'other_transformation_FOO_usage'," "'other_transformation'," "'OTHER','FOO'," "'EPSG','1262','EPSG','1024');")) << last_error(); auto factoryOTHER = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER"); EXPECT_THROW( factoryOTHER->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "EPSG", "4326", false, false, false, false), FactoryException); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, AuthorityFactory_wkt_based_transformation_not_co_wkt) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('OTHER','FOO','My WKT string based op',NULL," "'PROJ','WKT','" + std::string("LOCAL_CS[\"foo\"]") + "'," "'EPSG','4326','EPSG','4326',0.0,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER'," "'other_transformation_FOO_usage'," "'other_transformation'," "'OTHER','FOO'," "'EPSG','1262','EPSG','1024');")) << last_error(); auto factoryOTHER = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER"); EXPECT_THROW( factoryOTHER->createFromCoordinateReferenceSystemCodes( "EPSG", "4326", "EPSG", "4326", false, false, false, false), FactoryException); } // --------------------------------------------------------------------------- TEST(factory, AuthorityFactory_EPSG_4326_approximate_equivalent_to_builtin) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = nn_dynamic_pointer_cast( factory->createCoordinateReferenceSystem("4326")); EXPECT_TRUE(crs->isEquivalentTo(GeographicCRS::EPSG_4326.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, getAuthorities) { createStructure(); populateWithFakeEPSG(); auto res = DatabaseContext::create(m_ctxt)->getAuthorities(); EXPECT_EQ(res.size(), 2U); EXPECT_TRUE(res.find("EPSG") != res.end()); EXPECT_TRUE(res.find("PROJ") != res.end()); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, lookForGridInfo) { createStructure(); ASSERT_TRUE(execute("INSERT INTO grid_alternatives(original_grid_name," "proj_grid_name, " "old_proj_grid_name, " "proj_grid_format, " "proj_method, " "inverse_direction, " "package_name, " "url, direct_download, open_license, directory) " "VALUES (" "'NOT-YET-IN-GRID-TRANSFORMATION-PROJ_fake_grid', " "'PROJ_fake_grid', " "'old_PROJ_fake_grid', " "'NTv2', " "'hgridshift', " "0, " "NULL, " "'url', 1, 1, NULL);")) << last_error(); std::string fullFilename; std::string packageName; std::string url; bool directDownload = false; bool openLicense = false; bool gridAvailable = false; EXPECT_TRUE(DatabaseContext::create(m_ctxt)->lookForGridInfo( "PROJ_fake_grid", false, fullFilename, packageName, url, directDownload, openLicense, gridAvailable)); EXPECT_TRUE(fullFilename.empty()); EXPECT_TRUE(packageName.empty()); EXPECT_EQ(url, "url"); EXPECT_EQ(directDownload, true); EXPECT_EQ(openLicense, true); EXPECT_EQ(gridAvailable, false); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, lookForGridInfo_from_old_name_with_new_grid_available) { createStructure(); ASSERT_TRUE(execute("INSERT INTO grid_alternatives(original_grid_name," "proj_grid_name, " "old_proj_grid_name, " "proj_grid_format, " "proj_method, " "inverse_direction, " "package_name, " "url, direct_download, open_license, directory) " "VALUES (" "'NOT-YET-IN-GRID-TRANSFORMATION-original_grid_name', " "'tests/egm96_15_uncompressed_truncated.tif', " "'old_name.gtx', " "'NTv2', " "'hgridshift', " "0, " "NULL, " "'url', 1, 1, NULL);")) << last_error(); std::string fullFilename; std::string packageName; std::string url; bool directDownload = false; bool openLicense = false; bool gridAvailable = false; EXPECT_TRUE(DatabaseContext::create(m_ctxt)->lookForGridInfo( "old_name.gtx", false, fullFilename, packageName, url, directDownload, openLicense, gridAvailable)); EXPECT_TRUE( fullFilename.find("tests/egm96_15_uncompressed_truncated.tif") != std::string::npos) << fullFilename; EXPECT_EQ(gridAvailable, true); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, custom_geodetic_crs) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute("INSERT INTO geodetic_crs VALUES('TEST_NS','TEST','my " "name TEST',NULL,'geographic 2D'," "NULL,NULL,NULL,NULL,'+proj=longlat +a=2 " "+rf=300',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO geodetic_crs VALUES" "('TEST_NS','TEST_BOUND'," "'my name TEST',NULL,'geographic 2D'," "NULL,NULL,NULL,NULL,'+proj=longlat +a=2 " "+rf=300 +towgs84=1,2,3',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO geodetic_crs VALUES('TEST_NS','TEST_GC'," "'my name',NULL,'geocentric',NULL,NULL," "NULL,NULL,'+proj=geocent +a=2 +rf=300',0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO geodetic_crs " "VALUES('TEST_NS','TEST_REF_ANOTHER','my name TEST_REF_ANOTHER'," "NULL," "'geographic 2D',NULL,NULL,NULL,NULL,'TEST_NS:TEST',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO geodetic_crs " "VALUES('TEST_NS','TEST_WRONG','my name',NULL," "'geographic 2D',NULL,NULL,NULL,NULL," "'+proj=merc',0);")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO geodetic_crs " "VALUES('TEST_NS','TEST_RECURSIVE','my name',NULL,'geographic 2D'," "NULL,NULL,NULL,NULL,'TEST_NS:TEST_RECURSIVE',0);")) << last_error(); auto factory = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "TEST_NS"); { auto crs = factory->createGeodeticCRS("TEST"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs) != nullptr); EXPECT_EQ(*(crs->name()->description()), "my name TEST"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2)); EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300)); EXPECT_TRUE(crs->canonicalBoundCRS() == nullptr); } { auto crs = factory->createGeodeticCRS("TEST_BOUND"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs) != nullptr); EXPECT_EQ(*(crs->name()->description()), "my name TEST"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2)); EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300)); EXPECT_TRUE(crs->canonicalBoundCRS() != nullptr); } { auto crs = factory->createGeodeticCRS("TEST_GC"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs) == nullptr); EXPECT_EQ(*(crs->name()->description()), "my name"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2)); EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300)); } { auto crs = factory->createGeodeticCRS("TEST_REF_ANOTHER"); EXPECT_TRUE(nn_dynamic_pointer_cast(crs) != nullptr); EXPECT_EQ(*(crs->name()->description()), "my name TEST_REF_ANOTHER"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2)); EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300)); } EXPECT_THROW(factory->createGeodeticCRS("TEST_WRONG"), FactoryException); EXPECT_THROW(factory->createGeodeticCRS("TEST_RECURSIVE"), FactoryException); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, custom_projected_crs) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('TEST_NS','TEST'," "'custom_projected_crs',NULL,NULL," "NULL,NULL,NULL,NULL,NULL," "'+proj=mbt_s +unused_flag',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('TEST_NS','TEST_BOUND'," "'custom_projected_crs2',NULL," "NULL,NULL,NULL,NULL,NULL,NULL," "'+proj=mbt_s +unused_flag +towgs84=1,2,3',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('TEST_NS','TEST_WRONG'," "'custom_projected_crs3',NULL," "NULL,NULL,NULL,NULL,NULL,NULL," "'+proj=longlat',0);")) << last_error(); // Unknown ellipsoid ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('TEST_NS','TEST_MERC','merc',NULL,NULL," "NULL,NULL,NULL,NULL,NULL," "'+proj=merc +x_0=0 +R=1',0);")) << last_error(); // Well-known ellipsoid ASSERT_TRUE(execute("INSERT INTO projected_crs " "VALUES('TEST_NS','TEST_MERC2','merc2',NULL,NULL," "NULL,NULL,NULL,NULL,NULL," "'+proj=merc +x_0=0 +ellps=GRS80',0);")) << last_error(); // WKT1_GDAL ASSERT_TRUE( execute("INSERT INTO projected_crs " "VALUES('TEST_NS','TEST_WKT1_GDAL','WKT1_GDAL',NULL,NULL," "NULL,NULL,NULL,NULL,NULL," "'" "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"Unknown_based_on_WGS84_ellipsoid\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]" "',0);")) << last_error(); auto factory = AuthorityFactory::create(DatabaseContext::create(m_ctxt), "TEST_NS"); { auto crs = factory->createProjectedCRS("TEST"); EXPECT_EQ(*(crs->name()->description()), "custom_projected_crs"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get()); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=mbt_s +datum=WGS84 +units=m +no_defs +type=crs"); EXPECT_TRUE(crs->canonicalBoundCRS() == nullptr); } { auto crs = factory->createProjectedCRS("TEST_BOUND"); EXPECT_EQ(*(crs->name()->description()), "custom_projected_crs2"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get()); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=mbt_s +datum=WGS84 +units=m +no_defs +type=crs"); EXPECT_TRUE(crs->canonicalBoundCRS() != nullptr); } EXPECT_THROW(factory->createProjectedCRS("TEST_WRONG"), FactoryException); { auto obj = PROJStringParser().createFromPROJString( "+proj=merc +a=1 +b=1 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front().first->nameStr(), "merc"); } } { auto obj = PROJStringParser().createFromPROJString( "+proj=merc +ellps=GRS80 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front().first->nameStr(), "merc2"); } } { auto obj = PROJStringParser().createFromPROJString( "+proj=merc +a=6378137 +rf=298.257222101 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front().first->nameStr(), "merc2"); } } { auto obj = PROJStringParser().createFromPROJString( "+proj=merc +ellps=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front().first->nameStr(), "WKT1_GDAL"); } } { const auto list = factory->getCRSInfoList(); bool found = false; for (const auto &info : list) { if (info.authName == "TEST_NS" && info.code == "TEST_BOUND") { found = true; break; } } EXPECT_TRUE(found); } } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, CoordinateMetadata) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute("INSERT INTO coordinate_metadata " "VALUES('TEST_NS','TEST','my desc','EPSG',4326," "NULL,2020.1,0);")) << last_error(); const std::string wkt = "GEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " USAGE[\n" " SCOPE[\"Horizontal component of 3D system.\"],\n" " AREA[\"World.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",4326]]"; ASSERT_TRUE(execute("INSERT INTO coordinate_metadata " "VALUES('TEST_NS','TEST2','my desc',NULL,NULL," "'" + wkt + "',2021.1,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO coordinate_metadata " "VALUES('TEST_NS','TEST_NO_EPOCH','my desc'," "'EPSG',4326,NULL,NULL,0);")) << last_error(); auto dbContext = DatabaseContext::create(m_ctxt); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto crs_4326 = factoryEPSG->createCoordinateReferenceSystem("4326"); auto factory = AuthorityFactory::create(dbContext, "TEST_NS"); { auto cm = factory->createCoordinateMetadata("TEST"); EXPECT_TRUE(cm->crs()->isEquivalentTo(crs_4326.get())); EXPECT_TRUE(cm->coordinateEpoch().has_value()); EXPECT_NEAR(cm->coordinateEpochAsDecimalYear(), 2020.1, 1e-10); } { auto cm = factory->createCoordinateMetadata("TEST2"); EXPECT_TRUE(cm->crs()->isEquivalentTo( crs_4326.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(cm->coordinateEpoch().has_value()); EXPECT_NEAR(cm->coordinateEpochAsDecimalYear(), 2021.1, 1e-10); } { auto cm = factory->createCoordinateMetadata("TEST_NO_EPOCH"); EXPECT_TRUE(cm->crs()->isEquivalentTo(crs_4326.get())); EXPECT_FALSE(cm->coordinateEpoch().has_value()); } { auto obj = createFromUserInput( "urn:ogc:def:coordinateMetadata:TEST_NS::TEST", dbContext, true); auto cm = dynamic_cast(obj.get()); ASSERT_TRUE(cm != nullptr); EXPECT_TRUE(cm->crs()->isEquivalentTo(crs_4326.get())); EXPECT_TRUE(cm->coordinateEpoch().has_value()); EXPECT_NEAR(cm->coordinateEpochAsDecimalYear(), 2020.1, 1e-10); } } // --------------------------------------------------------------------------- TEST(factory, attachExtraDatabases_none) { auto ctxt = DatabaseContext::create(std::string(), {}); auto factory = AuthorityFactory::create(ctxt, "EPSG"); auto crs = factory->createGeodeticCRS("4979"); auto gcrs = nn_dynamic_pointer_cast(crs); EXPECT_TRUE(gcrs != nullptr); } // --------------------------------------------------------------------------- TEST(factory, attachExtraDatabases_auxiliary) { const std::string auxDbName( "file:attachExtraDatabases_auxiliary.db?mode=memory&cache=shared"); sqlite3 *dbAux = nullptr; sqlite3_open_v2( auxDbName.c_str(), &dbAux, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr); ASSERT_TRUE(dbAux != nullptr); ASSERT_TRUE(sqlite3_exec(dbAux, "BEGIN", nullptr, nullptr, nullptr) == SQLITE_OK); std::vector tableStructureBefore; { auto ctxt = DatabaseContext::create(); tableStructureBefore = ctxt->getDatabaseStructure(); for (const auto &sql : tableStructureBefore) { if (sql.find("CREATE TRIGGER") == std::string::npos) { ASSERT_TRUE(sqlite3_exec(dbAux, sql.c_str(), nullptr, nullptr, nullptr) == SQLITE_OK); } } } ASSERT_TRUE(sqlite3_exec( dbAux, "INSERT INTO geodetic_crs VALUES('OTHER','OTHER_4326','WGS " "84',NULL,'geographic 2D','EPSG','6422','EPSG','6326'," "NULL,0);", nullptr, nullptr, nullptr) == SQLITE_OK); ASSERT_TRUE(sqlite3_exec(dbAux, "COMMIT", nullptr, nullptr, nullptr) == SQLITE_OK); { auto ctxt = DatabaseContext::create(std::string(), {auxDbName}); // Look for object located in main DB { auto factory = AuthorityFactory::create(ctxt, "EPSG"); auto crs = factory->createGeodeticCRS("4326"); auto gcrs = nn_dynamic_pointer_cast(crs); EXPECT_TRUE(gcrs != nullptr); } // Look for object located in auxiliary DB { auto factory = AuthorityFactory::create(ctxt, "OTHER"); auto crs = factory->createGeodeticCRS("OTHER_4326"); auto gcrs = nn_dynamic_pointer_cast(crs); EXPECT_TRUE(gcrs != nullptr); } const auto dbStructure = ctxt->getDatabaseStructure(); EXPECT_EQ(dbStructure, tableStructureBefore); } { auto ctxt = DatabaseContext::create(std::string(), {auxDbName, ":memory:"}); // Look for object located in main DB { auto factory = AuthorityFactory::create(ctxt, "EPSG"); auto crs = factory->createGeodeticCRS("4326"); auto gcrs = nn_dynamic_pointer_cast(crs); EXPECT_TRUE(gcrs != nullptr); } // Look for object located in auxiliary DB { auto factory = AuthorityFactory::create(ctxt, "OTHER"); auto crs = factory->createGeodeticCRS("OTHER_4326"); auto gcrs = nn_dynamic_pointer_cast(crs); EXPECT_TRUE(gcrs != nullptr); } } { auto ctxt = DatabaseContext::create(std::string(), {":memory:"}); // Look for object located in main DB { auto factory = AuthorityFactory::create(ctxt, "EPSG"); auto crs = factory->createGeodeticCRS("4326"); auto gcrs = nn_dynamic_pointer_cast(crs); EXPECT_TRUE(gcrs != nullptr); } // Look for object located in auxiliary DB { auto factory = AuthorityFactory::create(ctxt, "OTHER"); EXPECT_THROW(factory->createGeodeticCRS("OTHER_4326"), FactoryException); } } sqlite3_close(dbAux); } // --------------------------------------------------------------------------- TEST(factory, attachExtraDatabases_auxiliary_error) { EXPECT_THROW(DatabaseContext::create(std::string(), {"i_dont_exist_db"}), FactoryException); } // --------------------------------------------------------------------------- TEST(factory, getOfficialNameFromAlias) { auto ctxt = DatabaseContext::create(std::string(), {}); auto factory = AuthorityFactory::create(ctxt, std::string()); std::string outTableName; std::string outAuthName; std::string outCode; { auto officialName = factory->getOfficialNameFromAlias( "GCS_WGS_1984", std::string(), std::string(), false, outTableName, outAuthName, outCode); EXPECT_EQ(officialName, "WGS 84"); EXPECT_EQ(outTableName, "geodetic_crs"); EXPECT_EQ(outAuthName, "EPSG"); EXPECT_EQ(outCode, "4326"); } { auto officialName = factory->getOfficialNameFromAlias( "GCS_WGS_1984", "geodetic_crs", "ESRI", false, outTableName, outAuthName, outCode); EXPECT_EQ(officialName, "WGS 84"); EXPECT_EQ(outTableName, "geodetic_crs"); EXPECT_EQ(outAuthName, "EPSG"); EXPECT_EQ(outCode, "4326"); } { auto officialName = factory->getOfficialNameFromAlias( "no match", std::string(), std::string(), false, outTableName, outAuthName, outCode); EXPECT_EQ(officialName, ""); } { auto officialName = factory->getOfficialNameFromAlias( "System_Jednotne_Trigonometricke_Site_Katastralni_Ferro", "geodetic_datum", std::string(), true, outTableName, outAuthName, outCode); EXPECT_EQ( officialName, "System of the Unified Trigonometrical Cadastral Network (Ferro)"); } } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, createOperations_exact_transform_not_whole_area) { createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('OTHER','PARTIAL_AREA_PERFECT_ACCURACY'," "'PARTIAL_AREA_PERFECT_ACCURACY',NULL,'PROJ'," "'PROJString','+proj=helmert +x=1'," "'EPSG','4326','EPSG','4326',0.0,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER', " "'1','other_transformation','OTHER','PARTIAL_AREA_" "PERFECT_ACCURACY','EPSG','1933','EPSG','1024')")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('OTHER','WHOLE_AREA_APPROX_ACCURACY'," "'WHOLE_AREA_APPROX_ACCURACY',NULL,'PROJ'," "'PROJString','+proj=helmert +x=2'," "'EPSG','4326','EPSG','4326',1.0,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('OTHER', " "'2','other_transformation','OTHER','WHOLE_AREA_APPROX_" "ACCURACY','EPSG','1262','EPSG','1024')")) << last_error(); auto dbContext = DatabaseContext::create(m_ctxt); auto authFactory = AuthorityFactory::create(dbContext, std::string("OTHER")); auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = CoordinateOperationFactory::create()->createOperations( AuthorityFactory::create(dbContext, "EPSG") ->createCoordinateReferenceSystem("4326"), AuthorityFactory::create(dbContext, "EPSG") ->createCoordinateReferenceSystem("4326"), ctxt); ASSERT_EQ(list.size(), 2U); EXPECT_EQ(list[0]->nameStr(), "WHOLE_AREA_APPROX_ACCURACY"); EXPECT_EQ(list[1]->nameStr(), "PARTIAL_AREA_PERFECT_ACCURACY"); } // --------------------------------------------------------------------------- TEST_F(FactoryWithTmpDatabase, check_fixup_direction_concatenated_inverse_map_projection) { // This tests https://github.com/OSGeo/PROJ/issues/2817 createStructure(); populateWithFakeEPSG(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('EPSG','NOOP_TRANSFORMATION_32631'," "'NOOP_TRANSFORMATION_32631',NULL," "'PROJ','PROJString','+proj=noop'," "'EPSG','32631','EPSG','32631',0.0," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE( execute("INSERT INTO usage VALUES('EPSG'," "'other_transformation_NOOP_TRANSFORMATION_32631_usage'," "'other_transformation'," "'EPSG','NOOP_TRANSFORMATION_32631'," "'EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute( "INSERT INTO other_transformation " "VALUES('EPSG','NOOP_TRANSFORMATION_4326'," "'NOOP_TRANSFORMATION_4326',NULL," "'PROJ','PROJString','+proj=noop'," "'EPSG','4326','EPSG','4326',0.0," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'other_transformation_NOOP_TRANSFORMATION_4326_usage'," "'other_transformation'," "'EPSG','NOOP_TRANSFORMATION_4326'," "'EPSG','1262','EPSG','1024');")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation " "VALUES('EPSG','TEST_CONCATENATED','name',NULL," "'EPSG','4326','EPSG'" ",'4326',NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO usage VALUES('EPSG'," "'concatenated_operation_TEST_CONCATENATED_usage'," "'concatenated_operation'," "'EPSG','TEST_CONCATENATED'," "'EPSG','1262','EPSG','1024');")) << last_error(); // Forward map projection ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',1," "'EPSG','16031',NULL);")) << last_error(); // Noop projected ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',2," "'EPSG','NOOP_TRANSFORMATION_32631',NULL);")) << last_error(); // Inverse map projection ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',3," "'EPSG','16031',NULL);")) << last_error(); // Noop geographic ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',4," "'EPSG','NOOP_TRANSFORMATION_4326',NULL);")) << last_error(); // Forward map projection ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',5," "'EPSG','16031',NULL);")) << last_error(); // Noop projected ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',6," "'EPSG','NOOP_TRANSFORMATION_32631',NULL);")) << last_error(); // Inverse map projection ASSERT_TRUE(execute("INSERT INTO concatenated_operation_step " "VALUES('EPSG','TEST_CONCATENATED',7," "'EPSG','16031',NULL);")) << last_error(); auto dbContext = DatabaseContext::create(m_ctxt); auto authFactory = AuthorityFactory::create(dbContext, std::string("EPSG")); const auto op = authFactory->createCoordinateOperation("TEST_CONCATENATED", false); auto wkt = op->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_EQ(wkt, "+proj=noop"); } // --------------------------------------------------------------------------- TEST(factory, createObjectsFromName) { auto ctxt = DatabaseContext::create(); auto factory = AuthorityFactory::create(ctxt, std::string()); auto factoryEPSG = AuthorityFactory::create(ctxt, "EPSG"); EXPECT_EQ(factory->createObjectsFromName("").size(), 0U); // ellipsoid + datum + 3 geodeticCRS EXPECT_EQ(factory->createObjectsFromName("WGS 84", {}, false).size(), 5U); EXPECT_EQ(factory->createObjectsFromName("WGS 84", {}, true, 10).size(), 10U); EXPECT_EQ(factory ->createObjectsFromName( "WGS 84", {AuthorityFactory::ObjectType::CRS}, false) .size(), 3U); EXPECT_EQ( factory ->createObjectsFromName( "WGS 84", {AuthorityFactory::ObjectType::GEOCENTRIC_CRS}, false) .size(), 1U); { auto res = factoryEPSG->createObjectsFromName( "WGS84", {AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS}, true); // EPSG:4326 and the 6 WGS84 realizations // and EPSG:7881 'Tritan St. Helena'' whose alias is // 'WGS 84 Tritan St. Helena' EXPECT_EQ(res.size(), 10U); if (!res.empty()) { EXPECT_EQ(res.front()->getEPSGCode(), 4326); } } // Exact name, but just not the official case ==> should match with exact // match EXPECT_EQ(factory->createObjectsFromName("WGS 84 / utm zone 31n", {}, false) .size(), 1U); // Exact name, but with other CRS that have an aliases to it ==> should // match only the CRS with the given name, not those other CRS. EXPECT_EQ(factory->createObjectsFromName("ETRS89 / UTM zone 32N", {}, false) .size(), 1U); // Prime meridian EXPECT_EQ(factoryEPSG->createObjectsFromName("Paris", {}, false, 2).size(), 1U); // Ellipsoid EXPECT_EQ( factoryEPSG->createObjectsFromName("Clarke 1880 (IGN)", {}, false, 2) .size(), 1U); // Geodetic datum EXPECT_EQ( factoryEPSG->createObjectsFromName("Hungarian Datum 1909", {}, false, 2) .size(), 1U); // Vertical datum EXPECT_EQ(factoryEPSG->createObjectsFromName("EGM2008 geoid", {}, false, 2) .size(), 1U); // Engineering datum EXPECT_EQ( factoryEPSG ->createObjectsFromName("Christmas Island Datum 1985", {}, false, 2) .size(), 1U); // Geodetic CRS EXPECT_EQ(factoryEPSG ->createObjectsFromName( "Unknown datum based upon the Airy 1830 ellipsoid", {}, false, 2) .size(), 1U); // Projected CRS EXPECT_EQ(factoryEPSG ->createObjectsFromName( "Anguilla 1957 / British West Indies Grid", {}, false, 2) .size(), 1U); // Vertical CRS EXPECT_EQ(factoryEPSG->createObjectsFromName("EGM2008 height", {}, false, 2) .size(), 1U); // Compound CRS EXPECT_EQ(factoryEPSG ->createObjectsFromName( "KKJ / Finland Uniform Coordinate System + N60 height", {}, false, 2) .size(), 1U); // Engineering CRS EXPECT_EQ( factoryEPSG ->createObjectsFromName("Christmas Island Grid 1985", {}, false, 2) .size(), 1U); // Conversion EXPECT_EQ( factoryEPSG->createObjectsFromName("Belgian Lambert 2008", {}, false, 2) .size(), 1U); // Helmert transform EXPECT_EQ( factoryEPSG->createObjectsFromName("MGI to ETRS89 (4)", {}, false, 2) .size(), 1U); // Grid transform EXPECT_EQ(factoryEPSG ->createObjectsFromName("Guam 1963 to NAD83(HARN) (1)", {}, false, 2) .size(), 1U); // Other transform EXPECT_EQ(factoryEPSG ->createObjectsFromName( "Monte Mario (Rome) to Monte Mario (1)", {}, false, 2) .size(), 1U); // Concatenated operation EXPECT_EQ( factoryEPSG ->createObjectsFromName("MGI (Ferro) to WGS 84 (2)", {}, false, 2) .size(), 1U); // Deprecated object EXPECT_EQ(factoryEPSG ->createObjectsFromName( "NAD27(CGQ77) / SCoPQ zone 2 (deprecated)", {}, false, 2) .size(), 1U); // Deprecated object (but without explicit deprecated) EXPECT_EQ( factoryEPSG ->createObjectsFromName("NAD27(CGQ77) / SCoPQ zone 2", {}, false, 2) .size(), 1U); // Dynamic Geodetic datum EXPECT_EQ(factoryEPSG ->createObjectsFromName( "International Terrestrial Reference Frame 2008", {AuthorityFactory::ObjectType:: DYNAMIC_GEODETIC_REFERENCE_FRAME}, false, 2) .size(), 1U); #ifdef no_more_dynamic_vertical_datum // Dynamic Vertical datum EXPECT_EQ( factoryEPSG ->createObjectsFromName("Norway Normal Null 2000", {AuthorityFactory::ObjectType:: DYNAMIC_VERTICAL_REFERENCE_FRAME}, false, 2) .size(), 1U); #endif { auto res = factory->createObjectsFromName( "World Geodetic System 1984 ensemble", {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, false); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front()->getEPSGCode(), 6326); EXPECT_TRUE(dynamic_cast(res.front().get()) != nullptr); } } { auto res = factory->createObjectsFromName( "World Geodetic System 1984 ensemble", {}, false); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front()->getEPSGCode(), 6326); EXPECT_TRUE(dynamic_cast(res.front().get()) != nullptr); } } const auto types = std::vector{ AuthorityFactory::ObjectType::PRIME_MERIDIAN, AuthorityFactory::ObjectType::ELLIPSOID, AuthorityFactory::ObjectType::DATUM, AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, AuthorityFactory::ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME, AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, AuthorityFactory::ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME, AuthorityFactory::ObjectType::ENGINEERING_DATUM, AuthorityFactory::ObjectType::CRS, AuthorityFactory::ObjectType::GEODETIC_CRS, AuthorityFactory::ObjectType::GEOCENTRIC_CRS, AuthorityFactory::ObjectType::GEOGRAPHIC_CRS, AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS, AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS, AuthorityFactory::ObjectType::PROJECTED_CRS, AuthorityFactory::ObjectType::VERTICAL_CRS, AuthorityFactory::ObjectType::COMPOUND_CRS, AuthorityFactory::ObjectType::ENGINEERING_CRS, AuthorityFactory::ObjectType::COORDINATE_OPERATION, AuthorityFactory::ObjectType::CONVERSION, AuthorityFactory::ObjectType::TRANSFORMATION, AuthorityFactory::ObjectType::CONCATENATED_OPERATION, AuthorityFactory::ObjectType::DATUM_ENSEMBLE, }; for (const auto type : types) { factory->createObjectsFromName("i_dont_exist", {type}, false, 1); } factory->createObjectsFromName("i_dont_exist", types, false, 1); { auto res = factoryEPSG->createObjectsFromName( "ETRS89", {AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS}, false, 1); EXPECT_EQ(res.size(), 1U); if (!res.empty()) { EXPECT_EQ(res.front()->getEPSGCode(), 4258); } } } // --------------------------------------------------------------------------- TEST(factory, getMetadata) { auto ctxt = DatabaseContext::create(); EXPECT_EQ(ctxt->getMetadata("i_do_not_exist"), nullptr); const char *IGNF_VERSION = ctxt->getMetadata("IGNF.VERSION"); ASSERT_TRUE(IGNF_VERSION != nullptr); EXPECT_EQ(std::string(IGNF_VERSION), "3.1.0"); } // --------------------------------------------------------------------------- TEST(factory, listAreaOfUseFromName) { auto ctxt = DatabaseContext::create(); auto factory = AuthorityFactory::create(ctxt, std::string()); auto factoryEPSG = AuthorityFactory::create(ctxt, "EPSG"); { auto res = factory->listAreaOfUseFromName("Denmark - onshore", false); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first, "EPSG"); EXPECT_EQ(res.front().second, "3237"); } { auto res = factory->listAreaOfUseFromName("Denmark", true); EXPECT_GT(res.size(), 1U); } { auto res = factory->listAreaOfUseFromName("no where land", false); ASSERT_EQ(res.size(), 0U); } } // --------------------------------------------------------------------------- TEST(factory, getCRSInfoList) { auto ctxt = DatabaseContext::create(); { auto factory = AuthorityFactory::create(ctxt, std::string()); auto list = factory->getCRSInfoList(); EXPECT_GT(list.size(), 1U); bool foundEPSG = false; bool foundIGNF = false; bool found4326 = false; bool foundIAU_2015_19902 = false; for (const auto &info : list) { foundEPSG |= info.authName == "EPSG"; foundIGNF |= info.authName == "IGNF"; if (info.authName == "EPSG" && info.code == "4326") { found4326 = true; } else if (info.authName == "IAU_2015" && info.code == "19902") { foundIAU_2015_19902 = true; EXPECT_EQ(info.type, AuthorityFactory::ObjectType::GEODETIC_CRS); } } EXPECT_TRUE(foundEPSG); EXPECT_TRUE(foundIGNF); EXPECT_TRUE(found4326); EXPECT_TRUE(foundIAU_2015_19902); } { auto factory = AuthorityFactory::create(ctxt, "EPSG"); auto list = factory->getCRSInfoList(); EXPECT_GT(list.size(), 1U); bool found4326 = false; bool found4978 = false; bool found4979 = false; bool found32631 = false; bool found3855 = false; bool found6871 = false; for (const auto &info : list) { EXPECT_EQ(info.authName, "EPSG"); if (info.code == "4326") { EXPECT_EQ(info.name, "WGS 84"); EXPECT_EQ(info.type, AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS); EXPECT_EQ(info.deprecated, false); EXPECT_EQ(info.bbox_valid, true); EXPECT_EQ(info.west_lon_degree, -180.0); EXPECT_EQ(info.south_lat_degree, -90.0); EXPECT_EQ(info.east_lon_degree, 180.0); EXPECT_EQ(info.north_lat_degree, 90.0); EXPECT_TRUE(std::string(info.areaName).find("World") == 0) << std::string(info.areaName); EXPECT_TRUE(info.projectionMethodName.empty()); found4326 = true; } else if (info.code == "4296") { // Soudan - deprecated EXPECT_EQ(info.bbox_valid, false); EXPECT_EQ(info.west_lon_degree, 0.0); EXPECT_EQ(info.south_lat_degree, 0.0); EXPECT_EQ(info.east_lon_degree, 0.0); EXPECT_EQ(info.north_lat_degree, 0.0); } else if (info.code == "4978") { EXPECT_EQ(info.name, "WGS 84"); EXPECT_EQ(info.type, AuthorityFactory::ObjectType::GEOCENTRIC_CRS); found4978 = true; } else if (info.code == "4979") { EXPECT_EQ(info.name, "WGS 84"); EXPECT_EQ(info.type, AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); found4979 = true; } else if (info.code == "32631") { EXPECT_EQ(info.name, "WGS 84 / UTM zone 31N"); EXPECT_EQ(info.type, AuthorityFactory::ObjectType::PROJECTED_CRS); EXPECT_EQ(info.deprecated, false); EXPECT_EQ(info.bbox_valid, true); EXPECT_EQ(info.west_lon_degree, 0.0); EXPECT_EQ(info.south_lat_degree, 0.0); EXPECT_EQ(info.east_lon_degree, 6.0); EXPECT_EQ(info.north_lat_degree, 84.0); EXPECT_TRUE(info.areaName.find("Between 0\xC2\xB0" "E and 6\xC2\xB0" "E, northern hemisphere") == 0) << info.areaName; EXPECT_EQ(info.projectionMethodName, "Transverse Mercator"); found32631 = true; } else if (info.code == "3855") { EXPECT_EQ(info.name, "EGM2008 height"); EXPECT_EQ(info.type, AuthorityFactory::ObjectType::VERTICAL_CRS); found3855 = true; } else if (info.code == "6871") { EXPECT_EQ(info.name, "WGS 84 / Pseudo-Mercator + EGM2008 geoid height"); EXPECT_EQ(info.type, AuthorityFactory::ObjectType::COMPOUND_CRS); found6871 = true; } } EXPECT_TRUE(found4326); EXPECT_TRUE(found4978); EXPECT_TRUE(found4979); EXPECT_TRUE(found32631); EXPECT_TRUE(found3855); EXPECT_TRUE(found6871); } } // --------------------------------------------------------------------------- TEST(factory, getUnitList) { auto ctxt = DatabaseContext::create(); { auto factory = AuthorityFactory::create(ctxt, std::string()); auto list = factory->getUnitList(); EXPECT_GT(list.size(), 1U); bool foundEPSG = false; bool foundPROJ = false; bool found1027 = false; bool found1028 = false; bool found1032 = false; bool found1036 = false; bool found9001 = false; bool found9101 = false; for (const auto &info : list) { foundEPSG |= info.authName == "EPSG"; foundPROJ |= info.authName == "PROJ"; if (info.authName == "EPSG" && info.code == "1027") { EXPECT_EQ(info.name, "millimetres per year"); EXPECT_EQ(info.category, "linear_per_time"); found1027 = true; } else if (info.authName == "EPSG" && info.code == "1028") { EXPECT_EQ(info.name, "parts per billion"); EXPECT_EQ(info.category, "scale"); found1028 = true; } else if (info.authName == "EPSG" && info.code == "1032") { EXPECT_EQ(info.name, "milliarc-seconds per year"); EXPECT_EQ(info.category, "angular_per_time"); found1032 = true; } else if (info.authName == "EPSG" && info.code == "1036") { EXPECT_EQ(info.name, "unity per second"); EXPECT_EQ(info.category, "scale_per_time"); found1036 = true; } else if (info.authName == "EPSG" && info.code == "9001") { EXPECT_EQ(info.name, "metre"); EXPECT_EQ(info.category, "linear"); EXPECT_EQ(info.convFactor, 1.0); EXPECT_EQ(info.projShortName, "m"); EXPECT_FALSE(info.deprecated); found9001 = true; } else if (info.authName == "EPSG" && info.code == "9101") { EXPECT_EQ(info.name, "radian"); EXPECT_EQ(info.category, "angular"); EXPECT_FALSE(info.deprecated); found9101 = true; } } EXPECT_TRUE(foundEPSG); EXPECT_TRUE(foundPROJ); EXPECT_TRUE(found1027); EXPECT_TRUE(found1028); EXPECT_TRUE(found1032); EXPECT_TRUE(found1036); EXPECT_TRUE(found9001); EXPECT_TRUE(found9101); } { auto factory = AuthorityFactory::create(ctxt, "EPSG"); auto list = factory->getUnitList(); EXPECT_GT(list.size(), 1U); for (const auto &info : list) { EXPECT_EQ(info.authName, "EPSG"); } } } // --------------------------------------------------------------------------- TEST(factory, getCelestialBodyList) { auto ctxt = DatabaseContext::create(); { auto factory = AuthorityFactory::create(ctxt, std::string()); auto list = factory->getCelestialBodyList(); EXPECT_GT(list.size(), 1U); bool foundPROJ = false; bool foundESRI = false; bool foundEarth = false; for (const auto &info : list) { foundESRI |= info.authName == "ESRI"; foundPROJ |= info.authName == "PROJ"; if (info.authName == "PROJ") { EXPECT_EQ(info.name, "Earth"); foundEarth = true; } } EXPECT_TRUE(foundESRI); EXPECT_TRUE(foundPROJ); EXPECT_TRUE(foundEarth); } { auto factory = AuthorityFactory::create(ctxt, "ESRI"); auto list = factory->getCelestialBodyList(); EXPECT_GT(list.size(), 1U); for (const auto &info : list) { EXPECT_EQ(info.authName, "ESRI"); } } } // --------------------------------------------------------------------------- TEST(factory, objectInsertion) { // Cannot nest startInsertStatementsSession { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); EXPECT_THROW(ctxt->startInsertStatementsSession(), FactoryException); } { auto ctxt = DatabaseContext::create(); // Tolerated without explicit stop ctxt->startInsertStatementsSession(); } { auto ctxt = DatabaseContext::create(); // Tolerated ctxt->stopInsertStatementsSession(); } // getInsertStatementsFor() must be preceded with // startInsertStatementsSession() { auto ctxt = DatabaseContext::create(); EXPECT_THROW(ctxt->getInsertStatementsFor(GeographicCRS::EPSG_4326, "EPSG", "4326", true), FactoryException); } { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); // Nothing to do EXPECT_TRUE(ctxt->getInsertStatementsFor(GeographicCRS::EPSG_4326, "EPSG", "4326", true) .empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), GeographicCRS::EPSG_4326->datum(), GeographicCRS::EPSG_4326->datumEnsemble(), GeographicCRS::EPSG_4326->coordinateSystem()); EXPECT_EQ(ctxt->suggestsCodeFor(crs, "HOBU", true), "1"); EXPECT_EQ(ctxt->suggestsCodeFor(crs, "HOBU", false), "MY_EPSG_4326"); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "1234", true); EXPECT_EQ(ctxt->suggestsCodeFor(crs, "HOBU", true), "1235"); ASSERT_EQ(sql.size(), 2U); EXPECT_EQ(sql[0], "INSERT INTO geodetic_crs VALUES('HOBU','1234','my " "EPSG:4326','','geographic " "2D','EPSG','6422','EPSG','6326',NULL,0);"); EXPECT_EQ( sql[1], "INSERT INTO usage " "VALUES('HOBU','USAGE_GEODETIC_CRS_1234','geodetic_crs','HOBU','" "1234','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "1234", true).empty()); ctxt->stopInsertStatementsSession(); AuthorityFactory::create(ctxt, std::string("EPSG")) ->createGeographicCRS("4326"); EXPECT_THROW(AuthorityFactory::create(ctxt, std::string("HOBU")) ->createGeographicCRS("1234"), NoSuchAuthorityCodeException); } // Geographic 3D CRS, with known usage { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto usages = AuthorityFactory::create(ctxt, std::string("EPSG")) ->createGeographicCRS("4979") ->domains(); auto array(ArrayOfBaseObject::create()); for (const auto &usage : usages) { array->add(usage); } auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4979"); props.set(ObjectUsage::OBJECT_DOMAIN_KEY, nn_static_pointer_cast(array)); const auto crs = GeographicCRS::create(props, GeographicCRS::EPSG_4979->datum(), GeographicCRS::EPSG_4979->datumEnsemble(), GeographicCRS::EPSG_4979->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "4979", false); ASSERT_EQ(sql.size(), 2U); EXPECT_EQ(sql[0], "INSERT INTO geodetic_crs VALUES('HOBU','4979','my " "EPSG:4979','','geographic " "3D','EPSG','6423','EPSG','6326',NULL,0);"); EXPECT_EQ( sql[1], "INSERT INTO usage " "VALUES('HOBU','USAGE_GEODETIC_CRS_4979','geodetic_crs','HOBU','" "4979','EPSG','1262','EPSG','1176');"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "4979", false).empty()); ctxt->stopInsertStatementsSession(); } // BoundCRS of Geocentric CRS, with new usage { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4978"); auto array(ArrayOfBaseObject::create()); const auto extent = Extent::createFromBBOX(1, 2, 3, 4); optional scope; scope = "my scope"; array->add(ObjectDomain::create(scope, extent)); props.set(ObjectUsage::OBJECT_DOMAIN_KEY, nn_static_pointer_cast(array)); const auto crs = GeodeticCRS::create( props, NN_NO_CHECK(GeodeticCRS::EPSG_4978->datum()), NN_NO_CHECK(nn_dynamic_pointer_cast( GeodeticCRS::EPSG_4978->coordinateSystem()))); const auto boundCRS = BoundCRS::createFromTOWGS84( crs, std::vector{1, 2, 3, 4, 5, 6, 7}); const auto sql = ctxt->getInsertStatementsFor(boundCRS, "HOBU", "4978", false); ASSERT_EQ(sql.size(), 4U); EXPECT_EQ( sql[0], "INSERT INTO geodetic_crs VALUES('HOBU','4978','my " "EPSG:4978','','geocentric','EPSG','6500','EPSG','6326',NULL,0);"); EXPECT_EQ(sql[1], "INSERT INTO scope VALUES('HOBU','SCOPE_geodetic_crs_4978'," "'my scope',0);"); EXPECT_EQ(sql[2], "INSERT INTO extent VALUES('HOBU','EXTENT_geodetic_crs_4978'," "'unknown','unknown',2,4,1,3,0);"); EXPECT_EQ( sql[3], "INSERT INTO usage VALUES('HOBU','USAGE_GEODETIC_CRS_4978'," "'geodetic_crs','HOBU','4978','HOBU'," "'EXTENT_geodetic_crs_4978','HOBU','SCOPE_geodetic_crs_4978');"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(boundCRS, "HOBU", "4978", false) .empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS with unknown datum, numeric code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto datum = GeodeticReferenceFrame::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "my datum") .set("ANCHOR_EPOCH", "2023"), Ellipsoid::WGS84, optional("my anchor"), PrimeMeridian::GREENWICH); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), datum, GeographicCRS::EPSG_4326->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true); ASSERT_EQ(sql.size(), 4U); EXPECT_EQ(sql[0], "INSERT INTO geodetic_datum VALUES('HOBU','1','my " "datum','','EPSG','7030','EPSG','8901',NULL,NULL,NULL," "'my anchor',2023.000,0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true).empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS with unknown datum, alpha code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my datum"), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), datum, GeographicCRS::EPSG_4326->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "MY_EPSG_4326", false); EXPECT_EQ(ctxt->suggestsCodeFor(crs, "HOBU", false), "MY_EPSG_4326_2"); ASSERT_EQ(sql.size(), 4U); EXPECT_EQ(sql[0], "INSERT INTO geodetic_datum " "VALUES('HOBU','GEODETIC_DATUM_MY_EPSG_4326','my " "datum','','EPSG','7030','EPSG','8901',NULL,NULL,NULL,NULL," "NULL,0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "MY_EPSG_4326", false) .empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS with unknown ellipsoid, numeric code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto ellipsoid = Ellipsoid::createFlattenedSphere( PropertyMap().set(IdentifiedObject::NAME_KEY, "my ellipsoid"), Length(6378137), Scale(295)); const auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my datum"), ellipsoid, optional(), PrimeMeridian::GREENWICH); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), datum, GeographicCRS::EPSG_4326->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true); ASSERT_EQ(sql.size(), 5U); EXPECT_EQ(sql[0], "INSERT INTO ellipsoid VALUES('HOBU','1','my " "ellipsoid','','IAU_2015','399',6378137,'EPSG','9001'" ",295,NULL,0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true).empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS with unknown ellipsoid, alpha code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto ellipsoid = Ellipsoid::createTwoAxis( PropertyMap().set(IdentifiedObject::NAME_KEY, "my ellipsoid"), Length(6378137), Length(6378136)); const auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my datum"), ellipsoid, optional(), PrimeMeridian::GREENWICH); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), datum, GeographicCRS::EPSG_4326->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", false); ASSERT_EQ(sql.size(), 5U); EXPECT_EQ(sql[0], "INSERT INTO ellipsoid " "VALUES('HOBU','ELLPS_GEODETIC_DATUM_XXXX','my " "ellipsoid','','IAU_2015','399',6378137," "'EPSG','9001'," "NULL,6378136,0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", false).empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS with unknown prime meridian, numeric code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto pm = PrimeMeridian::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "My meridian"), Angle(10)); const auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my datum"), Ellipsoid::WGS84, optional(), pm); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), datum, GeographicCRS::EPSG_4326->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true); ASSERT_EQ(sql.size(), 5U); EXPECT_EQ(sql[0], "INSERT INTO prime_meridian VALUES('HOBU','1','My " "meridian',10,'EPSG','9122',0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true).empty()); ctxt->stopInsertStatementsSession(); } // Geographic 2D CRS with unknown prime meridian, alpha code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto pm = PrimeMeridian::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "My meridian"), Angle(10)); const auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my datum"), Ellipsoid::WGS84, optional(), pm); const auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my EPSG:4326"), datum, GeographicCRS::EPSG_4326->coordinateSystem()); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", false); ASSERT_EQ(sql.size(), 5U); EXPECT_EQ(sql[0], "INSERT INTO prime_meridian " "VALUES('HOBU','PM_GEODETIC_DATUM_XXXX','My " "meridian',10,'EPSG','9122',0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", false).empty()); ctxt->stopInsertStatementsSession(); } // Projected CRS, numeric code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto crs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my projected CRS"), GeographicCRS::EPSG_4807, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true); ASSERT_EQ(sql.size(), 4U); EXPECT_EQ(sql[0], "INSERT INTO conversion VALUES('HOBU','1'," "'UTM zone 31N',''," "'EPSG','9807','Transverse Mercator'," "'EPSG','8801','Latitude of natural origin',0,'EPSG','9122'," "'EPSG','8802','Longitude of natural origin',3,'EPSG','9122'," "'EPSG','8805','Scale factor at natural origin',0.9996," "'EPSG','9201'," "'EPSG','8806','False easting',500000,'EPSG','9001'," "'EPSG','8807','False northing',0,'EPSG','9001'," "NULL,NULL,NULL,NULL,NULL,NULL," "NULL,NULL,NULL,NULL,NULL,NULL,0);"); EXPECT_EQ(sql[1], "INSERT INTO usage " "VALUES('HOBU','USAGE_CONVERSION_1','conversion','HOBU','1','" "PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');"); EXPECT_EQ( sql[2], "INSERT INTO projected_crs VALUES('HOBU','XXXX','my projected " "CRS','','EPSG','4400','EPSG','4807','HOBU','1',NULL,0);"); EXPECT_EQ( sql[3], "INSERT INTO usage " "VALUES('HOBU','USAGE_PROJECTED_CRS_XXXX','projected_crs','HOBU','" "XXXX','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true).empty()); ctxt->stopInsertStatementsSession(); } // Vertical CRS, known vertical datum, numeric code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create(propertiesVDatum); PropertyMap propertiesCRS; propertiesCRS.set(IdentifiedObject::NAME_KEY, "my height"); const auto uom = UnitOfMeasure("my unit", 3.0, UnitOfMeasure::Type::LINEAR); const auto crs = VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(uom)); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true); ASSERT_EQ(sql.size(), 5U); EXPECT_EQ(sql[0], "INSERT INTO coordinate_system VALUES" "('HOBU','CS_VERTICAL_CRS_XXXX','vertical',1);"); EXPECT_EQ(sql[1], "INSERT INTO unit_of_measure VALUES" "('HOBU','MY_UNIT','my unit','length',3,NULL,0);"); EXPECT_EQ(sql[2], "INSERT INTO axis VALUES('HOBU'," "'CS_VERTICAL_CRS_XXXX_AXIS_1','Gravity-related height','H'," "'up','HOBU','CS_VERTICAL_CRS_XXXX',1,'HOBU','MY_UNIT');"); EXPECT_EQ(sql[3], "INSERT INTO vertical_crs VALUES('HOBU','XXXX','my height'," "'','HOBU','CS_VERTICAL_CRS_XXXX','EPSG','5101',0);"); EXPECT_EQ(sql[4], "INSERT INTO usage VALUES('HOBU','USAGE_VERTICAL_CRS_XXXX'," "'vertical_crs','HOBU','XXXX','PROJ','EXTENT_UNKNOWN'," "'PROJ','SCOPE_UNKNOWN');"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", true).empty()); ctxt->stopInsertStatementsSession(); } // Vertical CRS, unknown vertical datum, alpha code { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); PropertyMap propertiesVDatum; propertiesVDatum.set(IdentifiedObject::NAME_KEY, "my datum"); auto vdatum = VerticalReferenceFrame::create( propertiesVDatum, optional("my anchor")); PropertyMap propertiesCRS; propertiesCRS.set(IdentifiedObject::NAME_KEY, "my height"); const auto crs = VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", false); ASSERT_EQ(sql.size(), 4U); EXPECT_EQ(sql[0], "INSERT INTO vertical_datum VALUES('HOBU'," "'VERTICAL_DATUM_XXXX','my datum','',NULL,NULL,NULL," "'my anchor',NULL,0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "XXXX", false).empty()); ctxt->stopInsertStatementsSession(); } // Same as above with ANCHOR_EPOCH { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); PropertyMap propertiesVDatum; propertiesVDatum.set(IdentifiedObject::NAME_KEY, "my datum"); propertiesVDatum.set("ANCHOR_EPOCH", "2023"); auto vdatum = VerticalReferenceFrame::create( propertiesVDatum, optional("my anchor")); PropertyMap propertiesCRS; propertiesCRS.set(IdentifiedObject::NAME_KEY, "my height"); const auto crs = VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); const auto sql = ctxt->getInsertStatementsFor(crs, "HOBU", "YYYY", false); ASSERT_EQ(sql.size(), 4U); EXPECT_EQ(sql[0], "INSERT INTO vertical_datum VALUES('HOBU'," "'VERTICAL_DATUM_YYYY','my datum','',NULL,NULL,NULL," "'my anchor',2023.000,0);"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE( ctxt->getInsertStatementsFor(crs, "HOBU", "YYYY", false).empty()); ctxt->stopInsertStatementsSession(); } // Compound CRS { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto wkt = "COMPD_CS[\"unknown\"," "PROJCS[\"NAD_1983_2011_StatePlane_South_Carolina_FIPS_3900_USFT\"," "GEOGCS[\"NAD83(2011)\"," "DATUM[\"NAD83_National_Spatial_Reference_System_2011\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101004," "AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"1116\"]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433," "AUTHORITY[\"EPSG\",\"9122\"]]]," "PROJECTION[\"Lambert_Conformal_Conic_2SP\"]," "PARAMETER[\"latitude_of_origin\",31.8333333333333]," "PARAMETER[\"central_meridian\",-81]," "PARAMETER[\"standard_parallel_1\",32.5]," "PARAMETER[\"standard_parallel_2\",34.8333333333333]," "PARAMETER[\"false_easting\",1999996]," "PARAMETER[\"false_northing\",0]," "UNIT[\"US survey foot\",0.304800609601219," "AUTHORITY[\"EPSG\",\"9003\"]]," "AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]," "VERT_CS[\"NAVD88 height (ftUS)\"," "VERT_DATUM[\"North American Vertical Datum 1988\",2005," "AUTHORITY[\"EPSG\",\"5103\"]]," "UNIT[\"US survey foot\",0.304800609601219," "AUTHORITY[\"EPSG\",\"9003\"]]," "AXIS[\"Up\",UP],AUTHORITY[\"EPSG\",\"6360\"]]]"; const auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(wkt)); ASSERT_TRUE(crs != nullptr); const auto sql = ctxt->getInsertStatementsFor(NN_NO_CHECK(crs), "HOBU", "XXXX", false); ASSERT_EQ(sql.size(), 6U); EXPECT_EQ(sql[4], "INSERT INTO compound_crs VALUES('HOBU','XXXX','unknown'," "'','HOBU','COMPONENT_XXXX_1','EPSG','6360',0);"); EXPECT_EQ(sql[5], "INSERT INTO usage VALUES('HOBU','USAGE_COMPOUND_CRS_XXXX'," "'compound_crs','HOBU','XXXX','PROJ','EXTENT_UNKNOWN'," "'PROJ','SCOPE_UNKNOWN');"); const auto identified = crs->identify(AuthorityFactory::create(ctxt, std::string())); ASSERT_EQ(identified.size(), 1U); EXPECT_EQ( *(identified.front().first->identifiers().front()->codeSpace()), "HOBU"); EXPECT_TRUE(identified.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); EXPECT_EQ(identified.front().second, 100); EXPECT_TRUE(ctxt->getInsertStatementsFor(NN_NO_CHECK(crs), "HOBU", "XXXX", false) .empty()); ctxt->stopInsertStatementsSession(); } // DynamicGeodeticReferenceFrame { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto datum = AuthorityFactory::create(ctxt, "EPSG") ->createDatum("1165"); // ITRF2014 const auto sql = ctxt->getInsertStatementsFor(datum, "HOBU", "XXXX", false, {"HOBU"}); EXPECT_TRUE(!sql.empty()); const auto datumNew = AuthorityFactory::create(ctxt, "HOBU")->createDatum("XXXX"); EXPECT_TRUE(datumNew->isEquivalentTo( datum.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // DynamicVerticalReferenceFrame { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto datum = AuthorityFactory::create(ctxt, "EPSG") ->createDatum("1096"); // Norway Normal Null 2000 const auto sql = ctxt->getInsertStatementsFor(datum, "HOBU", "XXXX", false, {"HOBU"}); EXPECT_TRUE(!sql.empty()); const auto datumNew = AuthorityFactory::create(ctxt, "HOBU")->createDatum("XXXX"); EXPECT_TRUE(datumNew->isEquivalentTo( datum.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // geodetic DatumEnsemble, and add members inline { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto ensemble = AuthorityFactory::create(ctxt, "EPSG") ->createDatumEnsemble("6326"); // WGS84 const auto sql = ctxt->getInsertStatementsFor(ensemble, "HOBU", "XXXX", false, {"HOBU"}); EXPECT_TRUE(!sql.empty()); const auto ensembleNew = AuthorityFactory::create(ctxt, "HOBU")->createDatumEnsemble("XXXX"); EXPECT_TRUE(ensembleNew->isEquivalentTo( ensemble.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // geodetic DatumEnsemble, and reference members { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto ensemble = AuthorityFactory::create(ctxt, "EPSG") ->createDatumEnsemble("6326"); // WGS84 const auto sql = ctxt->getInsertStatementsFor(ensemble, "HOBU", "XXXX", false); EXPECT_TRUE(!sql.empty()); const auto ensembleNew = AuthorityFactory::create(ctxt, "HOBU")->createDatumEnsemble("XXXX"); EXPECT_TRUE(ensembleNew->isEquivalentTo( ensemble.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // vertical DatumEnsemble { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); // British Isles height ensemble const auto ensemble = AuthorityFactory::create(ctxt, "EPSG")->createDatumEnsemble("1288"); const auto sql = ctxt->getInsertStatementsFor(ensemble, "HOBU", "XXXX", false, {"HOBU"}); EXPECT_TRUE(!sql.empty()); const auto ensembleNew = AuthorityFactory::create(ctxt, "HOBU")->createDatumEnsemble("XXXX"); EXPECT_TRUE(ensembleNew->isEquivalentTo( ensemble.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // non-EPSG projection method { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto crs = nn_dynamic_pointer_cast( PROJStringParser().createFromPROJString( "+proj=sinu +lon_0=195 +x_0=0 +y_0=0 +R=3396000 +units=m " "+no_defs +type=crs")); ASSERT_TRUE(crs != nullptr); const auto statements = ctxt->getInsertStatementsFor( NN_NO_CHECK(crs), "HOBU", "XXXX", false); bool found = false; for (const auto &sql : statements) { if (sql.find("INSERT INTO conversion") != std::string::npos) { found = true; const char *expected = "VALUES('HOBU','CONVERSION_XXXX'," "'unknown','','PROJ','sinu','Sinusoidal',"; EXPECT_TRUE(sql.find(expected) != std::string::npos) << sql; } } EXPECT_TRUE(found); const auto crsNew = AuthorityFactory::create(ctxt, "HOBU")->createProjectedCRS("XXXX"); EXPECT_TRUE(crsNew->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // Missing projection method and parameter id, and parameters not in // their nominal order { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto wkt = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; const auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(wkt)); ASSERT_TRUE(crs != nullptr); const auto statements = ctxt->getInsertStatementsFor( NN_NO_CHECK(crs), "HOBU", "XXXX", false); bool found = false; const char *expected = "INSERT INTO conversion VALUES('HOBU','CONVERSION_XXXX'," "'UTM zone 31N','','EPSG','9807','Transverse Mercator'," "'EPSG','8801','Latitude of natural origin',0,'EPSG','9102'," "'EPSG','8802','Longitude of natural origin',3,'EPSG','9102'," "'EPSG','8805','Scale factor at natural origin',0.9996," "'EPSG','9201'," "'EPSG','8806','False easting',500000,'EPSG','9001'," "'EPSG','8807','False northing',0,'EPSG','9001'," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," "NULL,0)"; for (const auto &sql : statements) { if (sql.find("INSERT INTO conversion") != std::string::npos) { found = true; EXPECT_TRUE(sql.find(expected) != std::string::npos) << sql; } } EXPECT_TRUE(found); const auto crsNew = AuthorityFactory::create(ctxt, "HOBU")->createProjectedCRS("XXXX"); EXPECT_TRUE(crsNew->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT)); ctxt->stopInsertStatementsSession(); } // Error: unknown projection method. { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto wkt = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"unknown\"]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; const auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(wkt)); ASSERT_TRUE(crs != nullptr); EXPECT_THROW(ctxt->getInsertStatementsFor(NN_NO_CHECK(crs), "HOBU", "XXXX", false), std::exception); } // Error: unknown projection parameter. { auto ctxt = DatabaseContext::create(); ctxt->startInsertStatementsSession(); const auto wkt = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"unknown\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; const auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(wkt)); ASSERT_TRUE(crs != nullptr); EXPECT_THROW(ctxt->getInsertStatementsFor(NN_NO_CHECK(crs), "HOBU", "XXXX", false), std::exception); } } // --------------------------------------------------------------------------- TEST(factory, ogc_timecrs) { auto ctxt = DatabaseContext::create(); auto factory = AuthorityFactory::create(ctxt, Identifier::OGC); factory->createCoordinateReferenceSystem("AnsiDate"); factory->createCoordinateReferenceSystem("JulianDate"); factory->createCoordinateReferenceSystem("UnixTime"); } // --------------------------------------------------------------------------- TEST(factory, ogc_crs) { auto ctxt = DatabaseContext::create(); auto factory = AuthorityFactory::create(ctxt, Identifier::OGC); factory->createCoordinateReferenceSystem("CRS84"); factory->createCoordinateReferenceSystem("84"); factory->createCoordinateReferenceSystem("CRS27"); factory->createCoordinateReferenceSystem("CRS83"); } // --------------------------------------------------------------------------- TEST(factory, getPointMotionOperationsFor) { auto ctxt = DatabaseContext::create(); auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // "NAD83(CSRS)v7" auto crs = factory->createGeodeticCRS("8255"); auto opList = factory->getPointMotionOperationsFor(crs, false); ASSERT_TRUE(!opList.empty()); EXPECT_EQ(opList.front()->identifiers().front()->code(), "9483"); } // --------------------------------------------------------------------------- TEST(factory, toWGS84AutocorrectWrongValues) { auto ctxt = DatabaseContext::create(); { double tx = 1; double ty = 2; double tz = 3; double rx = 0; double ry = 0; double rz = 0; double scale_difference = 0; EXPECT_FALSE(ctxt->toWGS84AutocorrectWrongValues(tx, ty, tz, rx, ry, rz, scale_difference)); EXPECT_EQ(tx, 1); EXPECT_EQ(ty, 2); EXPECT_EQ(tz, 3); EXPECT_EQ(rx, 0); EXPECT_EQ(ry, 0); EXPECT_EQ(rz, 0); EXPECT_EQ(scale_difference, 0); } { // Incorrect parameters for EPSG:15929: WGS84 -> Belgian Lambert 72 // Cf https://github.com/OSGeo/PROJ/issues/4170 double tx = -106.8686; double ty = 52.2978; double tz = -103.7239; double rx = -0.3366; double ry = 0.457; double rz = -1.8422; double scale_difference = -1.2747; EXPECT_TRUE(ctxt->toWGS84AutocorrectWrongValues(tx, ty, tz, rx, ry, rz, scale_difference)); EXPECT_EQ(tx, -106.8686); EXPECT_EQ(ty, 52.2978); EXPECT_EQ(tz, -103.7239); EXPECT_EQ(rx, 0.3366); EXPECT_EQ(ry, -0.457); EXPECT_EQ(rz, 1.8422); EXPECT_EQ(scale_difference, -1.2747); } { // Almost incorrect parameters EPSG:15929: WGS84 -> Belgian Lambert 72 double tx = -106; double ty = 52.2978; double tz = -103.7239; double rx = -0.3366; double ry = 0.457; double rz = -1.8422; double scale_difference = -1.2747; EXPECT_FALSE(ctxt->toWGS84AutocorrectWrongValues(tx, ty, tz, rx, ry, rz, scale_difference)); EXPECT_EQ(tx, -106); EXPECT_EQ(ty, 52.2978); EXPECT_EQ(tz, -103.7239); EXPECT_EQ(rx, -0.3366); EXPECT_EQ(ry, 0.457); EXPECT_EQ(rz, -1.8422); EXPECT_EQ(scale_difference, -1.2747); } { // Correct Position Vector transformation ('EPSG','15869','DHDN to WGS // 84 (3)) double tx = 612.4; double ty = 77.0; double tz = 440.2; double rx = -0.054; double ry = 0.057; double rz = -2.797; double scale_difference = 2.55; EXPECT_FALSE(ctxt->toWGS84AutocorrectWrongValues(tx, ty, tz, rx, ry, rz, scale_difference)); EXPECT_EQ(tx, 612.4); EXPECT_EQ(ty, 77.0); EXPECT_EQ(tz, 440.2); EXPECT_EQ(rx, -0.054); EXPECT_EQ(ry, 0.057); EXPECT_EQ(rz, -2.797); EXPECT_EQ(scale_difference, 2.55); } { // Correct parameters for EPSG:15929: WGS84 -> Belgian Lambert 72 // (Coordinate Frame rotation) Cf // https://github.com/OSGeo/PROJ/issues/4170 double tx = -106.8686; double ty = 52.2978; double tz = -103.7239; double rx = 0.3366; double ry = -0.457; double rz = 1.8422; double scale_difference = -1.2747; EXPECT_FALSE(ctxt->toWGS84AutocorrectWrongValues(tx, ty, tz, rx, ry, rz, scale_difference)); EXPECT_EQ(tx, -106.8686); EXPECT_EQ(ty, 52.2978); EXPECT_EQ(tz, -103.7239); EXPECT_EQ(rx, 0.3366); EXPECT_EQ(ry, -0.457); EXPECT_EQ(rz, 1.8422); EXPECT_EQ(scale_difference, -1.2747); } } } // namespace proj-9.8.1/test/unit/proj_angular_io_test.cpp000664 001750 001750 00000010156 15166171715 021331 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test that in- and output units behave correctly, especially * angular units that need special treatment. * Author: Kristian Evers * ****************************************************************************** * Copyright (c) 2019, Kristian Evers * * 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 "proj.h" #include "gtest_include.h" namespace { TEST(AngularUnits, Basic) { auto ctx = proj_context_create(); auto P = proj_create(ctx, "proj=latlong"); EXPECT_TRUE(proj_angular_input(P, PJ_FWD)); EXPECT_TRUE(proj_angular_output(P, PJ_FWD)); EXPECT_TRUE(proj_angular_input(P, PJ_INV)); EXPECT_TRUE(proj_angular_output(P, PJ_INV)); proj_destroy(P); proj_context_destroy(ctx); } TEST(AngularUnits, Pipelines) { auto ctx = proj_context_create(); auto P = proj_create(ctx, "proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=latlong +step +proj=axisswap +order=2,1"); EXPECT_TRUE(proj_angular_input(P, PJ_FWD)); EXPECT_TRUE(proj_angular_output(P, PJ_FWD)); EXPECT_TRUE(proj_angular_input(P, PJ_INV)); EXPECT_TRUE(proj_angular_output(P, PJ_INV)); proj_destroy(P); proj_context_destroy(ctx); } TEST(AngularUnits, Pipelines2) { auto ctx = proj_context_create(); auto P = proj_create( ctx, "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 " "+y_0=0 +ellps=WGS84 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=m +z_in=m +xy_out=us-ft +z_out=us-ft"); EXPECT_FALSE(proj_angular_input(P, PJ_FWD)); EXPECT_FALSE(proj_angular_output(P, PJ_FWD)); proj_destroy(P); proj_context_destroy(ctx); } TEST(AngularUnits, Pipelines3) { auto ctx = proj_context_create(); auto P = proj_create( ctx, "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 " "+y_0=0 +ellps=WGS84 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=m +z_in=m +xy_out=us-ft +z_out=us-ft"); EXPECT_TRUE(proj_angular_input(P, PJ_FWD)); EXPECT_FALSE(proj_angular_output(P, PJ_FWD)); proj_destroy(P); proj_context_destroy(ctx); } TEST(AngularUnits, Degrees) { auto ctx = proj_context_create(); auto P = proj_create(ctx, "+proj=pipeline " "+step +inv +proj=utm +zone=32 +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg "); EXPECT_FALSE(proj_degree_input(P, PJ_FWD)); EXPECT_TRUE(proj_degree_input(P, PJ_INV)); EXPECT_TRUE(proj_degree_output(P, PJ_FWD)); EXPECT_FALSE(proj_degree_output(P, PJ_INV)); proj_destroy(P); proj_context_destroy(ctx); } } // namespace proj-9.8.1/test/unit/gie_self_tests.cpp000664 001750 001750 00000175633 15166171715 020133 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2017, Thomas Knudsen * Copyright (c) 2017, SDFE * * 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 "gtest_include.h" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // clang-format on #include #include namespace { // --------------------------------------------------------------------------- TEST(gie, cart_selftest) { PJ_CONTEXT *ctx; PJ *P; PJ_COORD a, b, obs[2]; PJ_COORD coord[3]; size_t n, sz; double dist, h, t; const char *const args[3] = {"proj=utm", "zone=32", "ellps=GRS80"}; char arg[50] = {"+proj=utm; +zone=32; +ellps=GRS80"}; /* An utm projection on the GRS80 ellipsoid */ P = proj_create(PJ_DEFAULT_CTX, arg); ASSERT_TRUE(P != nullptr); /* Clean up */ proj_destroy(P); /* Same projection, now using argc/argv style initialization */ P = proj_create_argv(PJ_DEFAULT_CTX, 3, const_cast(args)); ASSERT_TRUE(P != nullptr); /* zero initialize everything, then set (longitude, latitude) to (12, 55) */ a = proj_coord(0, 0, 0, 0); /* a.lp: The coordinate part of a, interpreted as a classic LP pair */ a.lp.lam = proj_torad(12); a.lp.phi = proj_torad(55); /* Forward projection */ b = proj_trans(P, PJ_FWD, a); /* Inverse projection */ a = proj_trans(P, PJ_INV, b); /* Null projection */ a = proj_trans(P, PJ_IDENT, a); /* Forward again, to get two linear items for comparison */ a = proj_trans(P, PJ_FWD, a); dist = proj_xy_dist(a, b); ASSERT_LE(dist, 2e-9); /* Clear any previous error */ proj_errno_reset(P); /* Clean up */ proj_destroy(P); /* Now do some 3D transformations */ P = proj_create(PJ_DEFAULT_CTX, "+proj=cart +ellps=GRS80"); ASSERT_TRUE(P != nullptr); /* zero initialize everything, then set (longitude, latitude, height) to * (12, 55, 100) */ a = b = proj_coord(0, 0, 0, 0); a.lpz.lam = proj_torad(12); a.lpz.phi = proj_torad(55); a.lpz.z = 100; /* Forward projection: 3D-Cartesian-to-Ellipsoidal */ b = proj_trans(P, PJ_FWD, a); /* Check roundtrip precision for 10000 iterations each way */ dist = proj_roundtrip(P, PJ_FWD, 10000, &a); dist += proj_roundtrip(P, PJ_INV, 10000, &b); ASSERT_LE(dist, 4e-9); /* Test at the North Pole */ a = b = proj_coord(0, 0, 0, 0); a.lpz.lam = proj_torad(0); a.lpz.phi = proj_torad(90); a.lpz.z = 100; /* Forward projection: Ellipsoidal-to-3D-Cartesian */ dist = proj_roundtrip(P, PJ_FWD, 1, &a); ASSERT_LE(dist, 1e-9); /* Test at the South Pole */ a = b = proj_coord(0, 0, 0, 0); a.lpz.lam = proj_torad(0); a.lpz.phi = proj_torad(-90); a.lpz.z = 100; b = a; /* Forward projection: Ellipsoidal-to-3D-Cartesian */ dist = proj_roundtrip(P, PJ_FWD, 1, &a); ASSERT_LE(dist, 4e-9); /* Inverse projection: 3D-Cartesian-to-Ellipsoidal */ b = proj_trans(P, PJ_INV, b); /* Move p to another context */ ctx = proj_context_create(); ASSERT_NE(ctx, pj_get_default_ctx()); proj_context_set(P, ctx); ASSERT_EQ(ctx, P->ctx); b = proj_trans(P, PJ_FWD, b); /* Move it back to the default context */ proj_context_set(P, nullptr); ASSERT_EQ(pj_get_default_ctx(), P->ctx); proj_context_destroy(ctx); /* We go on with the work - now back on the default context */ b = proj_trans(P, PJ_INV, b); proj_destroy(P); /* Testing proj_trans_generic () */ /* An utm projection on the GRS80 ellipsoid */ P = proj_create(PJ_DEFAULT_CTX, "+proj=utm +zone=32 +ellps=GRS80"); ASSERT_TRUE(P != nullptr); obs[0] = proj_coord(proj_torad(12), proj_torad(55), 45, 0); obs[1] = proj_coord(proj_torad(12), proj_torad(56), 50, 0); sz = sizeof(PJ_COORD); /* Forward projection */ a = proj_trans(P, PJ_FWD, obs[0]); b = proj_trans(P, PJ_FWD, obs[1]); n = proj_trans_generic(P, PJ_FWD, &(obs[0].lpz.lam), sz, 2, &(obs[0].lpz.phi), sz, 2, &(obs[0].lpz.z), sz, 2, nullptr, sz, 0); ASSERT_EQ(n, 2U); ASSERT_EQ(a.lpz.lam, obs[0].lpz.lam); ASSERT_EQ(a.lpz.phi, obs[0].lpz.phi); ASSERT_EQ(a.lpz.z, obs[0].lpz.z); ASSERT_EQ(b.lpz.lam, obs[1].lpz.lam); ASSERT_EQ(b.lpz.phi, obs[1].lpz.phi); ASSERT_EQ(b.lpz.z, obs[1].lpz.z); /* now test the case of constant z */ obs[0] = proj_coord(proj_torad(12), proj_torad(55), 45, 0); obs[1] = proj_coord(proj_torad(12), proj_torad(56), 50, 0); h = 27; t = 33; n = proj_trans_generic(P, PJ_FWD, &(obs[0].lpz.lam), sz, 2, &(obs[0].lpz.phi), sz, 2, &h, 0, 1, &t, 0, 1); ASSERT_EQ(n, 2U); ASSERT_EQ(a.lpz.lam, obs[0].lpz.lam); ASSERT_EQ(a.lpz.phi, obs[0].lpz.phi); ASSERT_EQ(45, obs[0].lpz.z); ASSERT_EQ(b.lpz.lam, obs[1].lpz.lam); ASSERT_EQ(b.lpz.phi, obs[1].lpz.phi); ASSERT_EQ(50, obs[1].lpz.z); ASSERT_NE(50, h); /* test proj_trans_array () */ coord[0] = proj_coord(proj_torad(12), proj_torad(55), 45, 0); coord[1] = proj_coord(proj_torad(12), proj_torad(56), 50, 0); ASSERT_EQ(proj_trans_array(P, PJ_FWD, 2, coord), 0); ASSERT_EQ(a.lpz.lam, coord[0].lpz.lam); ASSERT_EQ(a.lpz.phi, coord[0].lpz.phi); ASSERT_EQ(a.lpz.z, coord[0].lpz.z); ASSERT_EQ(b.lpz.lam, coord[1].lpz.lam); ASSERT_EQ(b.lpz.phi, coord[1].lpz.phi); ASSERT_EQ(b.lpz.z, coord[1].lpz.z); /* test proj_trans_array () with two failed points for the same reason */ coord[0] = proj_coord(proj_torad(12), proj_torad(95), 45, 0); // invalid latitude coord[1] = proj_coord(proj_torad(12), proj_torad(56), 50, 0); coord[2] = proj_coord(proj_torad(12), proj_torad(95), 45, 0); // invalid latitude ASSERT_EQ(proj_trans_array(P, PJ_FWD, 3, coord), PROJ_ERR_COORD_TRANSFM_INVALID_COORD); ASSERT_EQ(HUGE_VAL, coord[0].lpz.lam); ASSERT_EQ(HUGE_VAL, coord[0].lpz.phi); ASSERT_EQ(HUGE_VAL, coord[0].lpz.z); ASSERT_EQ(b.lpz.lam, coord[1].lpz.lam); ASSERT_EQ(b.lpz.phi, coord[1].lpz.phi); ASSERT_EQ(b.lpz.z, coord[1].lpz.z); ASSERT_EQ(HUGE_VAL, coord[2].lpz.lam); ASSERT_EQ(HUGE_VAL, coord[2].lpz.phi); ASSERT_EQ(HUGE_VAL, coord[2].lpz.z); /* test proj_trans_array () with two failed points for different reasons */ coord[0] = proj_coord(proj_torad(12), proj_torad(95), 45, 0); // invalid latitude coord[1] = proj_coord(proj_torad(105), proj_torad(0), 45, 0); // in the equatorial axis, at 90° of the central meridian ASSERT_EQ(proj_trans_array(P, PJ_FWD, 2, coord), PROJ_ERR_COORD_TRANSFM); /* Clean up after proj_trans_* tests */ proj_destroy(P); } // --------------------------------------------------------------------------- class gieTest : public ::testing::Test { static void DummyLogFunction(void *, int, const char *) {} protected: void SetUp() override { m_ctxt = proj_context_create(); proj_log_func(m_ctxt, nullptr, DummyLogFunction); } void TearDown() override { proj_context_destroy(m_ctxt); } PJ_CONTEXT *m_ctxt = nullptr; }; // --------------------------------------------------------------------------- TEST_F(gieTest, proj_create_crs_to_crs) { /* test proj_create_crs_to_crs() */ auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "epsg:25832", "epsg:25833", nullptr); ASSERT_TRUE(P != nullptr); PJ_COORD a, b; a.xyzt.x = 700000.0; a.xyzt.y = 6000000.0; a.xyzt.z = 0; a.xyzt.t = HUGE_VAL; b.xy.x = 307788.8761171057; b.xy.y = 5999669.3036037628; a = proj_trans(P, PJ_FWD, a); EXPECT_NEAR(a.xy.x, b.xy.x, 1e-8); EXPECT_NEAR(a.xy.y, b.xy.y, 1e-8); auto src = proj_get_source_crs(PJ_DEFAULT_CTX, P); ASSERT_TRUE(src != nullptr); EXPECT_EQ(proj_get_name(src), std::string("ETRS89 / UTM zone 32N")); proj_destroy(src); proj_destroy(P); /* we can also allow PROJ strings as a usable PJ */ P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "proj=utm +zone=32 +datum=WGS84", "proj=utm +zone=33 +datum=WGS84", nullptr); ASSERT_TRUE(P != nullptr); proj_destroy(P); EXPECT_TRUE(proj_create_crs_to_crs(m_ctxt, "invalid", "EPSG:25833", NULL) == nullptr); EXPECT_TRUE(proj_create_crs_to_crs(m_ctxt, "EPSG:25832", "invalid", NULL) == nullptr); } // --------------------------------------------------------------------------- TEST_F(gieTest, proj_create_crs_to_crs_EPSG_4326) { auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "EPSG:4326", "EPSG:32631", nullptr); ASSERT_TRUE(P != nullptr); PJ_COORD a, b; // Lat, long degrees a.xyzt.x = 0.0; a.xyzt.y = 3.0; a.xyzt.z = 0; a.xyzt.t = HUGE_VAL; b.xy.x = 500000.0; b.xy.y = 0.0; a = proj_trans(P, PJ_FWD, a); EXPECT_NEAR(a.xy.x, b.xy.x, 1e-9); EXPECT_NEAR(a.xy.y, b.xy.y, 1e-9); proj_destroy(P); } // --------------------------------------------------------------------------- TEST_F(gieTest, proj_create_crs_to_crs_proj_longlat) { auto P = proj_create_crs_to_crs( PJ_DEFAULT_CTX, "+proj=longlat +datum=WGS84", "EPSG:32631", nullptr); ASSERT_TRUE(P != nullptr); PJ_COORD a, b; // Long, lat degrees a.xyzt.x = 3.0; a.xyzt.y = 0; a.xyzt.z = 0; a.xyzt.t = HUGE_VAL; b.xy.x = 500000.0; b.xy.y = 0.0; a = proj_trans(P, PJ_FWD, a); EXPECT_NEAR(a.xy.x, b.xy.x, 1e-9); EXPECT_NEAR(a.xy.y, b.xy.y, 1e-9); proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, info_functions) { PJ_INFO info; PJ_PROJ_INFO pj_info; PJ_GRID_INFO grid_info; PJ_INIT_INFO init_info; std::vector buf(40); PJ *P; char arg[50] = {"+proj=utm; +zone=32; +ellps=GRS80"}; /* ********************************************************************** */ /* Test info functions */ /* ********************************************************************** */ /* proj_info() */ /* this one is difficult to test, since the output changes with the setup */ putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); info = proj_info(); putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); if (info.version[0] != '\0') { char tmpstr[64]; snprintf(tmpstr, sizeof(tmpstr), "%d.%d.%d", info.major, info.minor, info.patch); ASSERT_EQ(std::string(info.version), std::string(tmpstr)); } ASSERT_NE(std::string(info.release), ""); if (getenv("HOME") || getenv("PROJ_LIB") || getenv("PROJ_DATA")) { ASSERT_NE(std::string(info.searchpath), std::string()); } ASSERT_TRUE(std::string(info.searchpath).find("/proj") != std::string::npos); /* proj_pj_info() */ { P = proj_create(PJ_DEFAULT_CTX, "+proj=august"); /* august has no inverse */ auto has_inverse = proj_pj_info(P).has_inverse; proj_destroy(P); ASSERT_FALSE(has_inverse); } P = proj_create(PJ_DEFAULT_CTX, arg); pj_info = proj_pj_info(P); ASSERT_TRUE(pj_info.has_inverse); pj_shrink(arg); ASSERT_EQ(std::string(pj_info.definition), arg); ASSERT_EQ(std::string(pj_info.id), "utm"); proj_destroy(P); #ifdef TIFF_ENABLED /* proj_grid_info() */ grid_info = proj_grid_info("tests/test_hgrid.tif"); ASSERT_NE(std::string(grid_info.filename), ""); ASSERT_EQ(std::string(grid_info.gridname), "tests/test_hgrid.tif"); ASSERT_EQ(std::string(grid_info.format), "gtiff"); EXPECT_EQ(grid_info.n_lon, 4); EXPECT_EQ(grid_info.n_lat, 4); EXPECT_NEAR(grid_info.cs_lon, 0.017453292519943295, 1e-15); EXPECT_NEAR(grid_info.cs_lat, 0.017453292519943295, 1e-15); EXPECT_NEAR(grid_info.lowerleft.lam, 0.069813170079773182, 1e-15); EXPECT_NEAR(grid_info.lowerleft.phi, 0.90757121103705141, 1e-15); EXPECT_NEAR(grid_info.upperright.lam, 0.12217304763960307, 1e-15); EXPECT_NEAR(grid_info.upperright.phi, 0.95993108859688125, 1e-15); #endif grid_info = proj_grid_info("nonexistinggrid"); ASSERT_EQ(std::string(grid_info.filename), ""); // File exists, but is not a grid grid_info = proj_grid_info("proj.db"); ASSERT_EQ(std::string(grid_info.filename), ""); /* proj_init_info() */ init_info = proj_init_info("unknowninit"); ASSERT_EQ(std::string(init_info.filename), ""); init_info = proj_init_info("epsg"); /* Need to allow for "Unknown" until all commonly distributed EPSG-files * comes with a metadata section */ ASSERT_TRUE(std::string(init_info.origin) == "EPSG" || std::string(init_info.origin) == "Unknown") << std::string(init_info.origin); ASSERT_EQ(std::string(init_info.name), "epsg"); /* test proj_rtodms() and proj_dmstor() */ ASSERT_EQ(std::string("180dN"), proj_rtodms2(&buf[0], buf.size(), M_PI, 'N', 'S')); ASSERT_EQ(proj_dmstor(&buf[0], NULL), M_PI); ASSERT_EQ(std::string("114d35'29.612\"S"), proj_rtodms2(&buf[0], buf.size(), -2.0, 'N', 'S')); // buffer of just one byte ASSERT_EQ(std::string(""), proj_rtodms2(&buf[0], 1, -2.0, 'N', 'S')); // last character truncated ASSERT_EQ(std::string("114d35'29.612\""), proj_rtodms2(&buf[0], 15, -2.0, 'N', 'S')); // just enough bytes to store the string and the terminating nul character ASSERT_EQ(std::string("114d35'29.612\"S"), proj_rtodms2(&buf[0], 16, -2.0, 'N', 'S')); // buffer of just one byte ASSERT_EQ(std::string(""), proj_rtodms2(&buf[0], 1, -2.0, 0, 0)); // last character truncated ASSERT_EQ(std::string("-114d35'29.612"), proj_rtodms2(&buf[0], 15, -2.0, 0, 0)); // just enough bytes to store the string and the terminating nul character ASSERT_EQ(std::string("-114d35'29.612\""), proj_rtodms2(&buf[0], 16, -2.0, 0, 0)); /* we can't expect perfect numerical accuracy so testing with a tolerance */ ASSERT_NEAR(-2.0, proj_dmstor(&buf[0], NULL), 1e-7); /* test UTF-8 degree sign on DMS input */ ASSERT_NEAR(0.34512432, proj_dmstor("19°46'27\"E", NULL), 1e-7); /* test ISO 8859-1, cp1252, et al. degree sign on DMS input */ ASSERT_NEAR(0.34512432, proj_dmstor("19" "\260" "46'27\"E", NULL), 1e-7); } // --------------------------------------------------------------------------- TEST(gie, proj_factors) { PJ *P = proj_create(PJ_DEFAULT_CTX, "+proj=merc +ellps=WGS84"); PJ_COORD a = proj_coord(0, 0, 0, 0); a.lp.lam = proj_torad(12); a.lp.phi = proj_torad(55); PJ_FACTORS factors = proj_factors(P, a); ASSERT_FALSE(proj_errno(P)); /* factors not created correctly */ /* check a few key characteristics of the Mercator projection */ EXPECT_NEAR(factors.angular_distortion, 0.0, 1e-7) << factors.angular_distortion; /* angular distortion should be 0 */ /* Meridian/parallel angle should be 90 deg */ EXPECT_NEAR(factors.meridian_parallel_angle, M_PI_2, 1e-7) << factors.meridian_parallel_angle; EXPECT_EQ(factors.meridian_convergence, 0.0); /* meridian convergence should be 0 */ proj_destroy(P); // Test with a projected CRS { P = proj_create(PJ_DEFAULT_CTX, "EPSG:3395"); const auto factors2 = proj_factors(P, a); EXPECT_EQ(factors.angular_distortion, factors2.angular_distortion); EXPECT_EQ(factors.meridian_parallel_angle, factors2.meridian_parallel_angle); EXPECT_EQ(factors.meridian_convergence, factors2.meridian_convergence); EXPECT_EQ(factors.tissot_semimajor, factors2.tissot_semimajor); proj_destroy(P); } // Test with a projected CRS with feet unit { PJ_COORD c; c.lp.lam = proj_torad(-110); c.lp.phi = proj_torad(30); P = proj_create(PJ_DEFAULT_CTX, "+proj=tmerc +lat_0=31 +lon_0=-110.166666666667 " "+k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +units=ft"); factors = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, 0.99990319, 1e-8) << factors.meridional_scale; EXPECT_NEAR(factors.parallel_scale, 0.99990319, 1e-8) << factors.parallel_scale; EXPECT_NEAR(factors.angular_distortion, 0, 1e-7) << factors.angular_distortion; EXPECT_NEAR(factors.meridian_parallel_angle, M_PI_2, 1e-7) << factors.meridian_parallel_angle; proj_destroy(P); P = proj_create(PJ_DEFAULT_CTX, "EPSG:2222"); const auto factors2 = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, factors2.meridional_scale, 1e-10); EXPECT_NEAR(factors.parallel_scale, factors2.parallel_scale, 1e-10); EXPECT_NEAR(factors.angular_distortion, factors2.angular_distortion, 1e-10); EXPECT_NEAR(factors.meridian_parallel_angle, factors2.meridian_parallel_angle, 1e-109); proj_destroy(P); } // Test with a projected CRS with northing, easting axis order { PJ_COORD c; c.lp.lam = proj_torad(9); c.lp.phi = proj_torad(0); P = proj_create(PJ_DEFAULT_CTX, "+proj=utm +zone=32 +ellps=GRS80"); factors = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, 0.9996, 1e-8) << factors.meridional_scale; EXPECT_NEAR(factors.parallel_scale, 0.9996, 1e-8) << factors.parallel_scale; EXPECT_NEAR(factors.angular_distortion, 0, 1e-7) << factors.angular_distortion; EXPECT_NEAR(factors.meridian_parallel_angle, M_PI_2, 1e-7) << factors.meridian_parallel_angle; EXPECT_NEAR(factors.areal_scale, 0.99920016, 1e-7) << factors.areal_scale; proj_destroy(P); P = proj_create(PJ_DEFAULT_CTX, "EPSG:3044"); const auto factors2 = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, factors2.meridional_scale, 1e-10); EXPECT_NEAR(factors.parallel_scale, factors2.parallel_scale, 1e-10); EXPECT_NEAR(factors.angular_distortion, factors2.angular_distortion, 1e-10); EXPECT_NEAR(factors.meridian_parallel_angle, factors2.meridian_parallel_angle, 1e-109); EXPECT_NEAR(factors.areal_scale, factors2.areal_scale, 1e-109); proj_destroy(P); } // Test with a projected CRS whose datum has a non-Greenwich prime meridian { P = proj_create(PJ_DEFAULT_CTX, "EPSG:27571"); PJ_COORD c; c.lp.lam = proj_torad(0.0689738); c.lp.phi = proj_torad(49.508567); const auto factors2 = proj_factors(P, c); EXPECT_NEAR(factors2.meridional_scale, 1 - 12.26478760 * 1e-5, 1e-8); proj_destroy(P); } // Test with a compound CRS of a projected CRS { P = proj_create(PJ_DEFAULT_CTX, "EPSG:5972"); PJ_COORD c; c.lp.lam = proj_torad(10.729030600); c.lp.phi = proj_torad(59.916494500); { const auto factors2 = proj_factors(P, c); EXPECT_NEAR(factors2.meridional_scale, 1 - 28.54587730 * 1e-5, 1e-8); } // Try again to test caching of internal objects { const auto factors2 = proj_factors(P, c); EXPECT_NEAR(factors2.meridional_scale, 1 - 28.54587730 * 1e-5, 1e-8); } proj_destroy(P); } // Test with a geographic CRS --> error { P = proj_create(PJ_DEFAULT_CTX, "EPSG:4326"); const auto factors2 = proj_factors(P, a); EXPECT_NE(proj_errno(P), 0); proj_errno_reset(P); EXPECT_EQ(factors2.meridian_parallel_angle, 0); proj_destroy(P); } // Test with a dummy derived projected CRS, identical to above projected CRS { PJ_COORD c; c.lp.lam = proj_torad(9); c.lp.phi = proj_torad(0); const char *wkt = "DERIVEDPROJCRS[\"unknown\",\n" " BASEPROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"Unknown based on GRS 1980 ellipsoid\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7019]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 32N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",9,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16032]]],\n" " DERIVINGCONVERSION[\"Scale change\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",1,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",1,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; P = proj_create(PJ_DEFAULT_CTX, wkt); factors = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, 0.9996, 1e-8) << factors.meridional_scale; EXPECT_NEAR(factors.parallel_scale, 0.9996, 1e-8) << factors.parallel_scale; EXPECT_NEAR(factors.angular_distortion, 0, 1e-7) << factors.angular_distortion; EXPECT_NEAR(factors.meridian_parallel_angle, M_PI_2, 1e-7) << factors.meridian_parallel_angle; EXPECT_NEAR(factors.areal_scale, 0.99920016, 1e-7) << factors.areal_scale; proj_destroy(P); } // Test with a derived projected CRS, with slight scale change { PJ_COORD c; c.lp.lam = proj_torad(9); c.lp.phi = proj_torad(0); const char *wkt = "DERIVEDPROJCRS[\"unknown\",\n" " BASEPROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"Unknown based on GRS 1980 ellipsoid\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7019]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 32N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",9,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16032]]],\n" " DERIVINGCONVERSION[\"Scale change\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",1.0001,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",1.0001,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; P = proj_create(PJ_DEFAULT_CTX, wkt); factors = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, 0.99969996, 1e-8) << factors.meridional_scale; EXPECT_NEAR(factors.parallel_scale, 0.99969996, 1e-8) << factors.parallel_scale; EXPECT_NEAR(factors.angular_distortion, 0, 1e-7) << factors.angular_distortion; EXPECT_NEAR(factors.meridian_parallel_angle, M_PI_2, 1e-7) << factors.meridian_parallel_angle; EXPECT_NEAR(factors.areal_scale, 0.99940001, 1e-7) << factors.areal_scale; proj_destroy(P); } // Same as above but with foot unit { PJ_COORD c; c.lp.lam = proj_torad(9); c.lp.phi = proj_torad(0); const char *wkt = "DERIVEDPROJCRS[\"unknown\",\n" " BASEPROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"Unknown based on GRS 1980 ellipsoid\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7019]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"UTM zone 32N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",9,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16032]]],\n" " DERIVINGCONVERSION[\"Scale change\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8623]],\n" // 3.2811679790026242 = 1.0 / 0.3048 * 1.0001 " PARAMETER[\"A1\",3.2811679790026242,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8640]],\n" // 3.2811679790026242 = 1.0 / 0.3048 * 1.0001 " PARAMETER[\"B2\",3.2811679790026242,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"foot\",0.3048]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"foot\",0.3048]]]"; P = proj_create(PJ_DEFAULT_CTX, wkt); factors = proj_factors(P, c); EXPECT_NEAR(factors.meridional_scale, 0.99969996, 1e-8) << factors.meridional_scale; EXPECT_NEAR(factors.parallel_scale, 0.99969996, 1e-8) << factors.parallel_scale; EXPECT_NEAR(factors.angular_distortion, 0, 1e-7) << factors.angular_distortion; EXPECT_NEAR(factors.meridian_parallel_angle, M_PI_2, 1e-7) << factors.meridian_parallel_angle; EXPECT_NEAR(factors.areal_scale, 0.99940001, 1e-7) << factors.areal_scale; proj_destroy(P); } } // --------------------------------------------------------------------------- TEST(gie, list_functions) { const PJ_OPERATIONS *oper_list; const PJ_ELLPS *ellps_list; const PJ_PRIME_MERIDIANS *pm_list; /* Check that proj_list_* functions work by looping through them */ size_t n = 0; for (oper_list = proj_list_operations(); oper_list->id; ++oper_list) n++; ASSERT_NE(n, 0U); n = 0; for (ellps_list = proj_list_ellps(); ellps_list->id; ++ellps_list) n++; ASSERT_NE(n, 0U); n = 0; for (pm_list = proj_list_prime_meridians(); pm_list->id; ++pm_list) n++; /* hard-coded prime meridians are not updated; expect a fixed size */ EXPECT_EQ(n, 14U); } // --------------------------------------------------------------------------- TEST(gie, io_predicates) { /* check io-predicates */ /* angular in on fwd, linear out */ auto P = proj_create(PJ_DEFAULT_CTX, "+proj=cart +ellps=GRS80"); ASSERT_TRUE(P != nullptr); ASSERT_TRUE(proj_angular_input(P, PJ_FWD)); ASSERT_FALSE(proj_angular_input(P, PJ_INV)); ASSERT_FALSE(proj_angular_output(P, PJ_FWD)); ASSERT_TRUE(proj_angular_output(P, PJ_INV)); P->inverted = 1; ASSERT_FALSE(proj_angular_input(P, PJ_FWD)); ASSERT_TRUE(proj_angular_input(P, PJ_INV)); ASSERT_TRUE(proj_angular_output(P, PJ_FWD)); ASSERT_FALSE(proj_angular_output(P, PJ_INV)); proj_destroy(P); /* angular in and out */ P = proj_create(PJ_DEFAULT_CTX, "+proj=molodensky +a=6378160 +rf=298.25 " "+da=-23 +df=-8.120449e-8 +dx=-134 +dy=-48 +dz=149 " "+abridged "); ASSERT_TRUE(P != nullptr); ASSERT_TRUE(proj_angular_input(P, PJ_FWD)); ASSERT_TRUE(proj_angular_input(P, PJ_INV)); ASSERT_TRUE(proj_angular_output(P, PJ_FWD)); ASSERT_TRUE(proj_angular_output(P, PJ_INV)); P->inverted = 1; ASSERT_TRUE(proj_angular_input(P, PJ_FWD)); ASSERT_TRUE(proj_angular_input(P, PJ_INV)); ASSERT_TRUE(proj_angular_output(P, PJ_FWD)); ASSERT_TRUE(proj_angular_output(P, PJ_INV)); proj_destroy(P); /* linear in and out */ P = proj_create(PJ_DEFAULT_CTX, " +proj=helmert" " +x=0.0127 +y=0.0065 +z=-0.0209 +s=0.00195" " +rx=-0.00039 +ry=0.00080 +rz=-0.00114" " +dx=-0.0029 +dy=-0.0002 +dz=-0.0006 +ds=0.00001" " +drx=-0.00011 +dry=-0.00019 +drz=0.00007" " +t_epoch=1988.0 +convention=coordinate_frame"); ASSERT_TRUE(P != nullptr); ASSERT_FALSE(proj_angular_input(P, PJ_FWD)); ASSERT_FALSE(proj_angular_input(P, PJ_INV)); ASSERT_FALSE(proj_angular_output(P, PJ_FWD)); ASSERT_FALSE(proj_angular_output(P, PJ_INV)); P->inverted = 1; ASSERT_FALSE(proj_angular_input(P, PJ_FWD)); ASSERT_FALSE(proj_angular_input(P, PJ_INV)); ASSERT_FALSE(proj_angular_output(P, PJ_FWD)); ASSERT_FALSE(proj_angular_output(P, PJ_INV)); /* pj_init_ctx should default to GRS80 */ ASSERT_EQ(P->a, 6378137.0); ASSERT_EQ(P->f, 1.0 / 298.257222101); proj_destroy(P); /* Test that pj_fwd* and pj_inv* returns NaNs when receiving NaN input */ P = proj_create(PJ_DEFAULT_CTX, "+proj=merc +ellps=WGS84"); ASSERT_TRUE(P != nullptr); auto a = proj_coord(NAN, NAN, NAN, NAN); a = proj_trans(P, PJ_FWD, a); ASSERT_TRUE((std::isnan(a.v[0]) && std::isnan(a.v[1]) && std::isnan(a.v[2]) && std::isnan(a.v[3]))); a = proj_coord(NAN, NAN, NAN, NAN); a = proj_trans(P, PJ_INV, a); ASSERT_TRUE((std::isnan(a.v[0]) && std::isnan(a.v[1]) && std::isnan(a.v[2]) && std::isnan(a.v[3]))); proj_destroy(P); } // --------------------------------------------------------------------------- static void test_time(const char *args, double tol, double t_in, double t_exp) { PJ_COORD in, out; PJ *P = proj_create(PJ_DEFAULT_CTX, args); ASSERT_TRUE(P != nullptr); in = proj_coord(0.0, 0.0, 0.0, t_in); out = proj_trans(P, PJ_FWD, in); EXPECT_NEAR(out.xyzt.t, t_exp, tol); out = proj_trans(P, PJ_INV, out); EXPECT_NEAR(out.xyzt.t, t_in, tol); proj_destroy(P); proj_log_level(nullptr, PJ_LOG_NONE); } // --------------------------------------------------------------------------- TEST(gie, unitconvert_selftest) { char args1[] = "+proj=unitconvert +t_in=decimalyear +t_out=decimalyear"; double in1 = 2004.25; char args2[] = "+proj=unitconvert +t_in=gps_week +t_out=gps_week"; double in2 = 1782.0; char args3[] = "+proj=unitconvert +t_in=mjd +t_out=mjd"; double in3 = 57390.0; char args4[] = "+proj=unitconvert +t_in=gps_week +t_out=decimalyear"; double in4 = 1877.71428, exp4 = 2016.0; char args5[] = "+proj=unitconvert +t_in=yyyymmdd +t_out=yyyymmdd"; double in5 = 20170131; test_time(args1, 1e-6, in1, in1); test_time(args2, 1e-6, in2, in2); test_time(args3, 1e-6, in3, in3); test_time(args4, 1e-6, in4, exp4); test_time(args5, 1e-6, in5, in5); } static void test_date(const char *args, double tol, double t_in, double t_exp) { PJ_COORD in, out; PJ *P = proj_create(PJ_DEFAULT_CTX, args); ASSERT_TRUE(P != nullptr); in = proj_coord(0.0, 0.0, 0.0, t_in); out = proj_trans(P, PJ_FWD, in); EXPECT_NEAR(out.xyzt.t, t_exp, tol); proj_destroy(P); proj_log_level(nullptr, PJ_LOG_NONE); } TEST(gie, unitconvert_selftest_date) { char args[] = "+proj=unitconvert +t_in=decimalyear +t_out=yyyymmdd"; test_date(args, 1e-6, 2022.0027, 20220102); test_date(args, 1e-6, 1990.0, 19900101); test_date(args, 1e-6, 2004.1612, 20040229); test_date(args, 1e-6, 1899.999, 19000101); strcpy(&args[18], "+t_in=yyyymmdd +t_out=decimalyear"); test_date(args, 1e-6, 20220102, 2022.0027397); test_date(args, 1e-6, 19900101, 1990.0); test_date(args, 1e-6, 20040229, 2004.1612022); test_date(args, 1e-6, 18991231, 1899.9972603); } static const char tc32_utm32[] = { " +proj=horner" " +ellps=intl" " +range=500000" " +fwd_origin=877605.269066,6125810.306769" " +inv_origin=877605.760036,6125811.281773" " +deg=4" " +fwd_v=6.1258112678e+06,9.9999971567e-01,1.5372750011e-10,5.9300860915e-" "15,2.2609497633e-19,4.3188227445e-05,2.8225130416e-10,7.8740007114e-16,-1." "7453997279e-19,1.6877465415e-10,-1.1234649773e-14,-1.7042333358e-18,-7." "9303467953e-15,-5.2906832535e-19,3.9984284847e-19" " +fwd_u=8.7760574982e+05,9.9999752475e-01,2.8817299305e-10,5.5641310680e-" "15,-1.5544700949e-18,-4.1357045890e-05,4.2106213519e-11,2.8525551629e-14,-" "1.9107771273e-18,3.3615590093e-10,2.4380247154e-14,-2.0241230315e-18,1." "2429019719e-15,5.3886155968e-19,-1.0167505000e-18" " +inv_v=6.1258103208e+06,1.0000002826e+00,-1.5372762184e-10,-5." "9304261011e-15,-2.2612705361e-19,-4.3188331419e-05,-2.8225549995e-10,-7." "8529116371e-16,1.7476576773e-19,-1.6875687989e-10,1.1236475299e-14,1." "7042518057e-18,7.9300735257e-15,5.2881862699e-19,-3.9990736798e-19" " +inv_u=8.7760527928e+05,1.0000024735e+00,-2.8817540032e-10,-5." "5627059451e-15,1.5543637570e-18,4.1357152105e-05,-4.2114813612e-11,-2." "8523713454e-14,1.9109017837e-18,-3.3616407783e-10,-2.4382678126e-14,2." "0245020199e-18,-1.2441377565e-15,-5.3885232238e-19,1.0167203661e-18"}; static const char sb_utm32[] = { " +proj=horner" " +ellps=intl" " +range=500000" " +tolerance=0.0005" " +fwd_origin=4.94690026817276e+05,6.13342113183056e+06" " +inv_origin=6.19480258923588e+05,6.13258568148837e+06" " +deg=3" " +fwd_c=6.13258562111350e+06,6.19480105709997e+05,9.99378966275206e-01,-2." "82153291753490e-02,-2.27089979140026e-10,-1.77019590701470e-09,1." "08522286274070e-14,2.11430298751604e-15" " +inv_c=6.13342118787027e+06,4.94690181709311e+05,9.99824464710368e-01,2." "82279070814774e-02,7.66123542220864e-11,1.78425334628927e-09,-1." "05584823306400e-14,-3.32554258683744e-15"}; // --------------------------------------------------------------------------- TEST(gie, horner_selftest) { PJ *P; PJ_COORD a, b, c; double dist; /* Real polynonia relating the technical coordinate system TC32 to "System * 45 Bornholm" */ P = proj_create(PJ_DEFAULT_CTX, tc32_utm32); ASSERT_TRUE(P != nullptr); a = b = proj_coord(0, 0, 0, 0); a.uv.v = 6125305.4245; a.uv.u = 878354.8539; c = a; /* Check roundtrip precision for 1 iteration each way, starting in forward * direction */ dist = proj_roundtrip(P, PJ_FWD, 1, &c); EXPECT_LE(dist, 0.01); proj_destroy(P); /* The complex polynomial transformation between the "System Storebaelt" and * utm32/ed50 */ P = proj_create(PJ_DEFAULT_CTX, sb_utm32); ASSERT_TRUE(P != nullptr); /* Test value: utm32_ed50(620000, 6130000) = sb_ed50(495136.8544, * 6130821.2945) */ a = b = c = proj_coord(0, 0, 0, 0); a.uv.v = 6130821.2945; a.uv.u = 495136.8544; c.uv.v = 6130000.0000; c.uv.u = 620000.0000; /* Forward projection */ b = proj_trans(P, PJ_FWD, a); dist = proj_xy_dist(b, c); EXPECT_LE(dist, 0.001); /* Inverse projection */ b = proj_trans(P, PJ_INV, c); dist = proj_xy_dist(b, a); EXPECT_LE(dist, 0.001); /* Check roundtrip precision for 1 iteration each way */ dist = proj_roundtrip(P, PJ_FWD, 1, &a); EXPECT_LE(dist, 0.01); proj_destroy(P); } static const char tc32_utm32_fwd_only[] = { " +proj=horner" " +ellps=intl" " +range=10000000" " +fwd_origin=877605.269066,6125810.306769" " +deg=4" " +fwd_v=6.1258112678e+06,9.9999971567e-01,1.5372750011e-10,5.9300860915e-" "15,2.2609497633e-19,4.3188227445e-05,2.8225130416e-10,7.8740007114e-16,-1." "7453997279e-19,1.6877465415e-10,-1.1234649773e-14,-1.7042333358e-18,-7." "9303467953e-15,-5.2906832535e-19,3.9984284847e-19" " +fwd_u=8.7760574982e+05,9.9999752475e-01,2.8817299305e-10,5.5641310680e-" "15,-1.5544700949e-18,-4.1357045890e-05,4.2106213519e-11,2.8525551629e-14,-" "1.9107771273e-18,3.3615590093e-10,2.4380247154e-14,-2.0241230315e-18,1." "2429019719e-15,5.3886155968e-19,-1.0167505000e-18"}; static const char sb_utm32_fwd_only[] = { " +proj=horner" " +ellps=intl" " +range=10000000" " +fwd_origin=4.94690026817276e+05,6.13342113183056e+06" " +deg=3" " +fwd_c=6.13258562111350e+06,6.19480105709997e+05,9.99378966275206e-01,-2." "82153291753490e-02,-2.27089979140026e-10,-1.77019590701470e-09,1." "08522286274070e-14,2.11430298751604e-15"}; static const char hatt_to_ggrs[] = { " +proj=horner" " +ellps=bessel" " +fwd_origin=0.0, 0.0" " +deg=2" " +range=10000000" " +fwd_u=370552.68, 0.9997155, -1.08e-09, 0.0175123, 2.04e-09, 1.63e-09" " +fwd_v=4511927.23, 0.9996979, 5.60e-10, -0.0174755, -1.65e-09, " "-6.50e-10"}; TEST(gie, horner_only_fwd_selftest) { { PJ *P = proj_create(PJ_DEFAULT_CTX, tc32_utm32_fwd_only); ASSERT_TRUE(P != nullptr); PJ_COORD a = proj_coord(0, 0, 0, 0); a.uv.v = 6125305.4245; a.uv.u = 878354.8539; /* Check roundtrip precision for 1 iteration each way, starting in * forward direction */ double dist = proj_roundtrip(P, PJ_FWD, 1, &a); EXPECT_LE(dist, 0.01); proj_destroy(P); } { PJ_COORD a; a = proj_coord(0, 0, 0, 0); a.xy.x = -10157.950; a.xy.y = -21121.093; PJ_COORD c; c = proj_coord(0, 0, 0, 0); c.enu.e = 360028.794; c.enu.n = 4490989.862; PJ *P = proj_create(PJ_DEFAULT_CTX, hatt_to_ggrs); ASSERT_TRUE(P != nullptr); /* Forward projection */ PJ_COORD b = proj_trans(P, PJ_FWD, a); double dist = proj_xy_dist(b, c); EXPECT_LE(dist, 0.001); /* Inverse projection */ b = proj_trans(P, PJ_INV, c); dist = proj_xy_dist(b, a); EXPECT_LE(dist, 0.001); /* Check roundtrip precision for 1 iteration each way, starting in * forward direction */ dist = proj_roundtrip(P, PJ_FWD, 1, &a); EXPECT_LE(dist, 0.01); proj_destroy(P); } { PJ *P = proj_create(PJ_DEFAULT_CTX, sb_utm32_fwd_only); ASSERT_TRUE(P != nullptr); PJ_COORD a = proj_coord(0, 0, 0, 0); PJ_COORD b = proj_coord(0, 0, 0, 0); PJ_COORD c = proj_coord(0, 0, 0, 0); a.uv.v = 6130821.2945; a.uv.u = 495136.8544; c.uv.v = 6130000.0000; c.uv.u = 620000.0000; /* Forward projection */ b = proj_trans(P, PJ_FWD, a); double dist = proj_xy_dist(b, c); EXPECT_LE(dist, 0.001); /* Inverse projection */ b = proj_trans(P, PJ_INV, c); dist = proj_xy_dist(b, a); EXPECT_LE(dist, 0.001); /* Check roundtrip precision for 1 iteration each way */ dist = proj_roundtrip(P, PJ_FWD, 1, &a); EXPECT_LE(dist, 0.01); proj_destroy(P); } } // --------------------------------------------------------------------------- TEST(gie, proj_create_crs_to_crs_PULKOVO42_ETRS89) { auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "EPSG:4179", "EPSG:4258", nullptr); ASSERT_TRUE(P != nullptr); PJ_COORD c; EXPECT_EQ(std::string(proj_pj_info(P).definition), "unavailable until proj_trans is called"); EXPECT_EQ(proj_get_name(P), nullptr); EXPECT_EQ(P->fwd, nullptr); EXPECT_EQ(P->fwd3d, nullptr); EXPECT_EQ(P->fwd4d, nullptr); // get source CRS even if the P object is in a dummy state auto src_crs = proj_get_source_crs(PJ_DEFAULT_CTX, P); EXPECT_TRUE(src_crs != nullptr); EXPECT_EQ(proj_get_name(src_crs), std::string("Pulkovo 1942(58)")); proj_destroy(src_crs); // get target CRS even if the P object is in a dummy state auto target_crs = proj_get_target_crs(PJ_DEFAULT_CTX, P); EXPECT_TRUE(target_crs != nullptr); EXPECT_EQ(proj_get_name(target_crs), std::string("ETRS89")); proj_destroy(target_crs); // Romania c.xyzt.x = 45; // Lat c.xyzt.y = 25; // Long c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 44.999701238, 1e-9); EXPECT_NEAR(c.xy.y, 24.998474948, 1e-9); EXPECT_EQ(std::string(proj_pj_info(P).definition), "proj=pipeline step proj=axisswap order=2,1 " "step proj=unitconvert xy_in=deg xy_out=rad " "step proj=push v_3 " "step proj=cart " "ellps=krass step proj=helmert x=2.3287 y=-147.0425 z=-92.0802 " "rx=0.3092483 ry=-0.32482185 rz=-0.49729934 s=5.68906266 " "convention=coordinate_frame step inv proj=cart ellps=GRS80 " "step proj=pop v_3 " "step proj=unitconvert xy_in=rad xy_out=deg step proj=axisswap " "order=2,1"); c = proj_trans(P, PJ_INV, c); EXPECT_NEAR(c.xy.x, 45, 1e-8); EXPECT_NEAR(c.xy.y, 25, 1e-8); c.xyzt.x = 45; // Lat c.xyzt.y = 25; // Long c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; proj_trans_generic(P, PJ_FWD, &(c.xyz.x), sizeof(double), 1, &(c.xyz.y), sizeof(double), 1, &(c.xyz.z), sizeof(double), 1, nullptr, 0, 0); EXPECT_NEAR(c.xy.x, 44.999701238, 1e-9); EXPECT_NEAR(c.xy.y, 24.998474948, 1e-9); // Poland c.xyz.x = 52; // Lat c.xyz.y = 20; // Long c.xyz.z = 0; c = proj_trans(P, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 51.999714150, 1e-9); EXPECT_NEAR(c.xy.y, 19.998187811, 1e-9); EXPECT_EQ(std::string(proj_pj_info(P).definition), "proj=pipeline step proj=axisswap order=2,1 " "step proj=unitconvert xy_in=deg xy_out=rad " "step proj=push v_3 " "step proj=cart " "ellps=krass step proj=helmert x=33.4 y=-146.6 z=-76.3 rx=-0.359 " "ry=-0.053 rz=0.844 s=-0.84 convention=position_vector step inv " "proj=cart ellps=GRS80 step proj=pop v_3 " "step proj=unitconvert xy_in=rad " "xy_out=deg step proj=axisswap order=2,1"); proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, proj_create_crs_to_crs_WGS84_EGM08_to_WGS84) { auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "EPSG:4326+3855", "EPSG:4979", nullptr); ASSERT_TRUE(P != nullptr); EXPECT_EQ(std::string(proj_pj_info(P).description), "Transformation from EGM2008 height to WGS 84 (ballpark vertical " "transformation, without ellipsoid height to vertical height " "correction)"); proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, proj_create_crs_to_crs_outside_area_of_use) { // See https://github.com/OSGeo/proj.4/issues/1329 auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "EPSG:4275", "EPSG:4807", nullptr); ASSERT_TRUE(P != nullptr); PJ_COORD c; EXPECT_EQ(P->fwd, nullptr); // Test point outside area of use of both candidate coordinate operations c.xyzt.x = 58; // Lat in deg c.xyzt.y = 5; // Long in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 64.44444444444444, 1e-9); // Lat in grad EXPECT_NEAR(c.xy.y, 2.958634259259258, 1e-9); // Long in grad proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, proj_create_crs_to_crs_with_area_large) { // Test bugfix for https://github.com/OSGeo/gdal/issues/3695 auto area = proj_area_create(); proj_area_set_bbox(area, -14.1324, 49.5614, 3.76488, 62.1463); auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "EPSG:4277", "EPSG:4326", area); proj_area_destroy(area); ASSERT_TRUE(P != nullptr); PJ_COORD c; c.xyzt.x = 50; // Lat in deg c.xyzt.y = -2; // Long in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 50.00065628, 1e-8); EXPECT_NEAR(c.xy.y, -2.00133989, 1e-8); proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, proj_create_crs_to_crs_with_longitude_outside_minus_180_180) { // Test bugfix for https://github.com/OSGeo/PROJ/issues/3594 auto P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, "EPSG:4277", "EPSG:4326", nullptr); ASSERT_TRUE(P != nullptr); PJ_COORD c; c.xyzt.x = 50; // Lat in deg c.xyzt.y = -2 + 360; // Long in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 50.00065628, 1e-8); EXPECT_NEAR(c.xy.y, -2.00133989, 1e-8); c.xyzt.x = 50; // Lat in deg c.xyzt.y = -2 - 360; // Long in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_FWD, c); EXPECT_NEAR(c.xy.x, 50.00065628, 1e-8); EXPECT_NEAR(c.xy.y, -2.00133989, 1e-8); c.xyzt.x = 50.00065628; // Lat in deg c.xyzt.y = -2.00133989 + 360; // Long in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_INV, c); EXPECT_NEAR(c.xy.x, 50, 1e-8); EXPECT_NEAR(c.xy.y, -2, 1e-8); c.xyzt.x = 50.00065628; // Lat in deg c.xyzt.y = -2.00133989 - 360; // Long in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(P, PJ_INV, c); EXPECT_NEAR(c.xy.x, 50, 1e-8); EXPECT_NEAR(c.xy.y, -2, 1e-8); auto Pnormalized = proj_normalize_for_visualization(PJ_DEFAULT_CTX, P); c.xyzt.x = -2 + 360; // Long in deg c.xyzt.y = 50; // Lat in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(Pnormalized, PJ_FWD, c); EXPECT_NEAR(c.xy.x, -2.00133989, 1e-8); EXPECT_NEAR(c.xy.y, 50.00065628, 1e-8); c.xyzt.x = -2.00133989 + 360; // Long in deg c.xyzt.y = 50.00065628; // Lat in deg c.xyzt.z = 0; c.xyzt.t = HUGE_VAL; c = proj_trans(Pnormalized, PJ_INV, c); EXPECT_NEAR(c.xy.x, -2, 1e-8); EXPECT_NEAR(c.xy.y, 50, 1e-8); proj_destroy(Pnormalized); proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, proj_trans_generic) { // GDA2020 to WGS84 (G1762) auto P = proj_create( PJ_DEFAULT_CTX, "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 +dx=0 " "+dy=0 +dz=0 +drx=-0.00150379 +dry=-0.00118346 +drz=-0.00120716 " "+ds=0 +t_epoch=2020 +convention=coordinate_frame " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); double lat = -60; double longitude = 120; proj_trans_generic(P, PJ_FWD, &lat, sizeof(double), 1, &longitude, sizeof(double), 1, nullptr, 0, 0, nullptr, 0, 0); // Should be a no-op when the time is unknown (or equal to 2020) EXPECT_NEAR(lat, -60, 1e-9); EXPECT_NEAR(longitude, 120, 1e-9); proj_destroy(P); } // --------------------------------------------------------------------------- TEST(gie, proj_trans_with_a_crs) { auto P = proj_create(PJ_DEFAULT_CTX, "EPSG:4326"); PJ_COORD input; input.xyzt.x = 0; input.xyzt.y = 0; input.xyzt.z = 0; input.xyzt.t = 0; auto output = proj_trans(P, PJ_FWD, input); EXPECT_EQ(proj_errno(P), PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); proj_destroy(P); EXPECT_EQ(HUGE_VAL, output.xyzt.x); } // --------------------------------------------------------------------------- TEST(gie, proj_create_crs_to_crs_from_pj_force_over) { PJ_CONTEXT *ctx; ctx = proj_context_create(); ASSERT_TRUE(ctx != nullptr); auto epsg27700 = proj_create(ctx, "EPSG:27700"); ASSERT_TRUE(epsg27700 != nullptr); auto epsg4326 = proj_create(ctx, "EPSG:4326"); ASSERT_TRUE(epsg4326 != nullptr); auto epsg3857 = proj_create(ctx, "EPSG:3857"); ASSERT_TRUE(epsg3857 != nullptr); { const char *const options[] = {"FORCE_OVER=YES", nullptr}; auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg3857, nullptr, options); ASSERT_TRUE(P != nullptr); ASSERT_TRUE(P->over); PJ_COORD input; PJ_COORD input_over; // Test a point along the equator. // The same point, but in two different representations. input.xyzt.x = 0; // Lat in deg input.xyzt.y = 140; // Long in deg input.xyzt.z = 0; input.xyzt.t = HUGE_VAL; input_over.xyzt.x = 0; // Lat in deg input_over.xyzt.y = -220; // Long in deg input_over.xyzt.z = 0; input_over.xyzt.t = HUGE_VAL; auto output = proj_trans(P, PJ_FWD, input); auto output_over = proj_trans(P, PJ_FWD, input_over); auto P_clone = proj_clone(ctx, P); auto output_clone_over = proj_trans(P_clone, PJ_FWD, input_over); proj_destroy(P_clone); auto input_inv = proj_trans(P, PJ_INV, output); auto input_over_inv = proj_trans(P, PJ_INV, output_over); // Web Mercator x's between 0 and 180 longitude come out positive. // But when forcing the over flag, the -220 calculation makes it flip. EXPECT_GT(output.xyz.x, 0); EXPECT_LT(output_over.xyz.x, 0); EXPECT_NEAR(output.xyz.x, 15584728.711058298, 1e-8); EXPECT_NEAR(output_over.xyz.x, -24490287.974520184, 1e-8); EXPECT_EQ(output_clone_over.xyz.x, output_over.xyz.x); // The distance from 140 to 180 and -220 to -180 should be pretty much // the same. auto dx_o = fabs(output.xyz.x - 20037508.342789244); auto dx_over = fabs(output_over.xyz.x + 20037508.342789244); auto dx = fabs(dx_o - dx_over); EXPECT_NEAR(dx, 0, 1e-8); // Check the inverse operations get us back close to our original input // values. EXPECT_NEAR(input.xyz.x, input_inv.xyz.x, 1e-8); EXPECT_NEAR(input.xyz.y, input_inv.xyz.y, 1e-8); EXPECT_NEAR(input_over.xyz.x, input_over_inv.xyz.x, 1e-8); EXPECT_NEAR(input_over.xyz.y, input_over_inv.xyz.y, 1e-8); auto Pnormalized = proj_normalize_for_visualization(ctx, P); ASSERT_TRUE(Pnormalized->over); PJ_COORD input_over_normalized; input_over_normalized.xyzt.x = -220; // Long in deg input_over_normalized.xyzt.y = 0; // Lat in deg input_over_normalized.xyzt.z = 0; input_over_normalized.xyzt.t = HUGE_VAL; auto output_over_normalized = proj_trans(Pnormalized, PJ_FWD, input_over_normalized); EXPECT_NEAR(output_over_normalized.xyz.x, -24490287.974520184, 1e-8); proj_destroy(Pnormalized); proj_destroy(P); } { // Try again with force over set to anything but YES to verify it didn't // do anything. const char *const options[] = {"FORCE_OVER=NO", nullptr}; auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg3857, nullptr, options); ASSERT_TRUE(P != nullptr); ASSERT_FALSE(P->over); PJ_COORD input; PJ_COORD input_notOver; input.xyzt.x = 0; // Lat in deg input.xyzt.y = 140; // Long in deg input.xyzt.z = 0; input.xyzt.t = HUGE_VAL; input_notOver.xyzt.x = 0; // Lat in deg input_notOver.xyzt.y = -220; // Long in deg input_notOver.xyzt.z = 0; input_notOver.xyzt.t = HUGE_VAL; auto output = proj_trans(P, PJ_FWD, input); auto output_notOver = proj_trans(P, PJ_FWD, input_notOver); EXPECT_GT(output.xyz.x, 0); EXPECT_GT(output_notOver.xyz.x, 0); EXPECT_NEAR(output.xyz.x, 15584728.711058298, 1e-8); EXPECT_NEAR(output_notOver.xyz.x, 15584728.711058298, 1e-8); proj_destroy(P); } { // Try again with no options to verify it didn't do anything. auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg3857, nullptr, nullptr); ASSERT_TRUE(P != nullptr); ASSERT_FALSE(P->over); PJ_COORD input; PJ_COORD input_notOver; input.xyzt.x = 0; // Lat in deg input.xyzt.y = 140; // Long in deg input.xyzt.z = 0; input.xyzt.t = HUGE_VAL; input_notOver.xyzt.x = 0; // Lat in deg input_notOver.xyzt.y = -220; // Long in deg input_notOver.xyzt.z = 0; input_notOver.xyzt.t = HUGE_VAL; auto output = proj_trans(P, PJ_FWD, input); auto output_notOver = proj_trans(P, PJ_FWD, input_notOver); EXPECT_GT(output.xyz.x, 0); EXPECT_GT(output_notOver.xyz.x, 0); EXPECT_NEAR(output.xyz.x, 15584728.711058298, 1e-8); EXPECT_NEAR(output_notOver.xyz.x, 15584728.711058298, 1e-8); proj_destroy(P); } { // EPSG:4326 -> EPSG:27700 has more than one coordinate operation // candidate. const char *const options[] = {"FORCE_OVER=YES", nullptr}; auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg27700, nullptr, options); ASSERT_TRUE(P != nullptr); ASSERT_TRUE(P->over); PJ_COORD input; PJ_COORD input_over; input.xyzt.x = 0; // Lat in deg input.xyzt.y = 140; // Long in deg input.xyzt.z = 0; input.xyzt.t = HUGE_VAL; input_over.xyzt.x = 0; // Lat in deg input_over.xyzt.y = -220; // Long in deg input_over.xyzt.z = 0; input_over.xyzt.t = HUGE_VAL; auto output = proj_trans(P, PJ_FWD, input); auto output_over = proj_trans(P, PJ_FWD, input_over); // Doesn't actually change the result for this tmerc transformation. EXPECT_NEAR(output.xyz.x, 4980122.749364435, 1e-8); EXPECT_NEAR(output.xyz.y, 14467212.882603768, 1e-8); EXPECT_NEAR(output_over.xyz.x, 4980122.749364435, 1e-8); EXPECT_NEAR(output_over.xyz.y, 14467212.882603768, 1e-8); auto Pnormalized = proj_normalize_for_visualization(ctx, P); ASSERT_TRUE(Pnormalized->over); for (const auto &op : Pnormalized->alternativeCoordinateOperations) { ASSERT_TRUE(op.pj->over); } PJ_COORD input_over_normalized; input_over_normalized.xyzt.x = -220; // Long in deg input_over_normalized.xyzt.y = 0; // Lat in deg input_over_normalized.xyzt.z = 0; input_over_normalized.xyzt.t = HUGE_VAL; auto output_over_normalized = proj_trans(Pnormalized, PJ_FWD, input_over_normalized); EXPECT_NEAR(output_over_normalized.xyz.x, 4980122.749364435, 1e-8); EXPECT_NEAR(output_over_normalized.xyz.y, 14467212.882603768, 1e-8); proj_destroy(Pnormalized); proj_destroy(P); } { // Negative test for 27700. const char *const options[] = {"FORCE_OVER=NO", nullptr}; auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg27700, nullptr, options); ASSERT_TRUE(P != nullptr); ASSERT_FALSE(P->over); PJ_COORD input; PJ_COORD input_over; input.xyzt.x = 0; // Lat in deg input.xyzt.y = 140; // Long in deg input.xyzt.z = 0; input.xyzt.t = HUGE_VAL; input_over.xyzt.x = 0; // Lat in deg input_over.xyzt.y = -220; // Long in deg input_over.xyzt.z = 0; input_over.xyzt.t = HUGE_VAL; auto output = proj_trans(P, PJ_FWD, input); auto output_over = proj_trans(P, PJ_FWD, input_over); EXPECT_NEAR(output.xyz.x, 4980122.749364435, 1e-8); EXPECT_NEAR(output.xyz.y, 14467212.882603768, 1e-8); EXPECT_NEAR(output_over.xyz.x, 4980122.749364435, 1e-8); EXPECT_NEAR(output_over.xyz.y, 14467212.882603768, 1e-8); proj_destroy(P); } proj_destroy(epsg27700); proj_destroy(epsg4326); proj_destroy(epsg3857); proj_context_destroy(ctx); } } // namespace proj-9.8.1/test/unit/test_util.cpp000664 001750 001750 00000005403 15166171715 017133 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include "proj/util.hpp" #include using namespace osgeo::proj::util; // --------------------------------------------------------------------------- TEST(util, NameFactory) { LocalNameNNPtr localname(NameFactory::createLocalName(nullptr, "foo")); auto ns = localname->scope(); EXPECT_EQ(ns->isGlobal(), true); EXPECT_EQ(ns->name()->toFullyQualifiedName()->toString(), "global"); EXPECT_EQ(localname->toFullyQualifiedName()->toString(), "foo"); } // --------------------------------------------------------------------------- TEST(util, NameFactory2) { PropertyMap map; map.set("separator", "/"); NameSpaceNNPtr nsIn(NameFactory::createNameSpace( NameFactory::createLocalName(nullptr, std::string("bar")), map)); LocalNameNNPtr localname( NameFactory::createLocalName(nsIn, std::string("foo"))); auto ns = localname->scope(); EXPECT_EQ(ns->isGlobal(), false); auto fullyqualifiedNS = ns->name()->toFullyQualifiedName(); EXPECT_EQ(fullyqualifiedNS->toString(), "bar"); EXPECT_EQ(fullyqualifiedNS->scope()->isGlobal(), true); EXPECT_EQ(fullyqualifiedNS->scope()->name()->scope()->isGlobal(), true); EXPECT_EQ(localname->toFullyQualifiedName()->toString(), "bar/foo"); } proj-9.8.1/test/unit/proj_context_test.cpp000664 001750 001750 00000015330 15166171715 020674 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test functions in proj_context namespae * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * 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 #ifdef _MSC_VER #include #else #include #endif #include "proj.h" #include "proj_internal.h" #include "gtest_include.h" namespace { static bool createTmpFile(const std::string &filename) { FILE *f = fopen(filename.c_str(), "wt"); if (!f) return false; fprintf( f, " +proj=pipeline +step +proj=utm +zone=31 +ellps=GRS80\n"); fclose(f); return true; } // --------------------------------------------------------------------------- static std::string createTempDict(std::string &dirname, const char *filename) { const char *temp_dir = getenv("TEMP"); if (!temp_dir) { temp_dir = getenv("TMP"); } #ifndef WIN32 if (!temp_dir) { temp_dir = "/tmp"; } #endif if (!temp_dir) return std::string(); dirname = temp_dir; std::string tmpFilename; tmpFilename = temp_dir; tmpFilename += DIR_CHAR; tmpFilename += filename; return createTmpFile(tmpFilename) ? tmpFilename : std::string(); } // --------------------------------------------------------------------------- static int MyUnlink(const std::string &filename) { #ifdef _MSC_VER return _unlink(filename.c_str()); #else return unlink(filename.c_str()); #endif } // --------------------------------------------------------------------------- TEST(proj_context, proj_context_set_file_finder) { std::string dirname; auto filename = createTempDict(dirname, "temp_proj_dic1"); if (filename.empty()) return; auto ctx = proj_context_create(); struct FinderData { PJ_CONTEXT *got_ctx = nullptr; std::string dirname{}; std::string tmpFilename{}; }; const auto finder = [](PJ_CONTEXT *got_ctx, const char *file, void *user_data) -> const char * { auto finderData = static_cast(user_data); finderData->got_ctx = got_ctx; finderData->tmpFilename = finderData->dirname; finderData->tmpFilename += DIR_CHAR; finderData->tmpFilename += file; return finderData->tmpFilename.c_str(); }; FinderData finderData; finderData.dirname = dirname; proj_context_set_file_finder(ctx, finder, &finderData); auto P = proj_create(ctx, "+init=temp_proj_dic1:MY_PIPELINE"); EXPECT_NE(P, nullptr); proj_destroy(P); EXPECT_EQ(finderData.got_ctx, ctx); proj_context_destroy(ctx); } // --------------------------------------------------------------------------- TEST(proj_context, proj_context_set_search_paths) { std::string dirname; auto filename = createTempDict(dirname, "temp_proj_dic2"); if (filename.empty()) return; auto ctx = proj_context_create(); const char *path = dirname.c_str(); proj_context_set_search_paths(ctx, 1, &path); auto P = proj_create(ctx, "+init=temp_proj_dic2:MY_PIPELINE"); EXPECT_NE(P, nullptr); proj_destroy(P); proj_context_destroy(ctx); MyUnlink(filename); } // --------------------------------------------------------------------------- TEST(proj_context, proj_context_set_user_writable_directory) { auto ctx = proj_context_create(); auto default_path = std::string(proj_context_get_user_writable_directory(ctx, false)); EXPECT_TRUE(!default_path.empty()); auto new_path = default_path + DIR_CHAR + "temp_proj_dic4"; proj_context_set_user_writable_directory(ctx, new_path.c_str(), true); EXPECT_STREQ(proj_context_get_user_writable_directory(ctx, false), new_path.c_str()); proj_context_set_user_writable_directory(ctx, nullptr, false); EXPECT_STREQ(proj_context_get_user_writable_directory(ctx, false), default_path.c_str()); proj_context_destroy(ctx); MyUnlink(new_path); } // --------------------------------------------------------------------------- TEST(proj_context, read_grid_from_user_writable_directory) { auto ctx = proj_context_create(); auto path = std::string(proj_context_get_user_writable_directory(ctx, true)); EXPECT_TRUE(!path.empty()); auto filename = path + DIR_CHAR + "temp_proj_dic3"; EXPECT_TRUE(createTmpFile(filename)); { // Check that with PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES (set by // calling script), we cannot find the file auto P = proj_create(ctx, "+init=temp_proj_dic3:MY_PIPELINE"); EXPECT_EQ(P, nullptr); proj_destroy(P); } { // Cancel the effect of PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY putenv(const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); auto P = proj_create(ctx, "+init=temp_proj_dic3:MY_PIPELINE"); EXPECT_NE(P, nullptr); putenv( const_cast("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); proj_destroy(P); } proj_context_destroy(ctx); MyUnlink(filename); } // --------------------------------------------------------------------------- TEST(proj_context, proj_context_set_ca_bundle_path) { std::string dirname("/tmp/dummmy/path/cacert.pem"); auto ctx = proj_context_create(); proj_context_set_ca_bundle_path(ctx, dirname.c_str()); ASSERT_EQ(ctx->ca_bundle_path, dirname); proj_context_destroy(ctx); } } // namespace proj-9.8.1/test/unit/test_coordinates.cpp000664 001750 001750 00000022511 15166171715 020467 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2023, Even Rouault * * 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 "gtest_include.h" // to be able to use internal::replaceAll #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include #include #include using namespace osgeo::proj::common; using namespace osgeo::proj::coordinates; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::metadata; using namespace osgeo::proj::util; namespace { struct ObjectKeeper { PJ *m_obj = nullptr; explicit ObjectKeeper(PJ *obj) : m_obj(obj) {} ~ObjectKeeper() { proj_destroy(m_obj); } ObjectKeeper(const ObjectKeeper &) = delete; ObjectKeeper &operator=(const ObjectKeeper &) = delete; }; struct PjContextKeeper { PJ_CONTEXT *m_ctxt = nullptr; explicit PjContextKeeper(PJ_CONTEXT *ctxt) : m_ctxt(ctxt) {} ~PjContextKeeper() { proj_context_destroy(m_ctxt); } PjContextKeeper(const PjContextKeeper &) = delete; PjContextKeeper &operator=(const PjContextKeeper &) = delete; }; } // namespace // --------------------------------------------------------------------------- static VerticalCRSNNPtr createVerticalCRS() { PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create(propertiesVDatum); PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5701) .set(IdentifiedObject::NAME_KEY, "ODN height"); return VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(coordinateMetadata, static_crs) { auto coordinateMetadata = CoordinateMetadata::create(GeographicCRS::EPSG_4326); EXPECT_TRUE(coordinateMetadata->crs()->isEquivalentTo( GeographicCRS::EPSG_4326.get())); EXPECT_FALSE(coordinateMetadata->coordinateEpoch().has_value()); // We tolerate coordinate epochs for EPSG:4326 EXPECT_NO_THROW( CoordinateMetadata::create(GeographicCRS::EPSG_4326, 2025.0)); // A coordinate epoch should NOT be provided EXPECT_THROW(CoordinateMetadata::create(createVerticalCRS(), 2025.0), Exception); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); auto wkt = coordinateMetadata->exportToWKT(f.get()); auto obj = WKTParser().createFromWKT(wkt); auto coordinateMetadataFromWkt = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(coordinateMetadataFromWkt != nullptr); EXPECT_TRUE(coordinateMetadataFromWkt->crs()->isEquivalentTo( GeographicCRS::EPSG_4326.get())); EXPECT_FALSE(coordinateMetadataFromWkt->coordinateEpoch().has_value()); auto ctxt = proj_context_create(); PjContextKeeper ctxtKeeper(ctxt); auto pjObj = proj_create(ctxt, wkt.c_str()); ObjectKeeper objKeeper(pjObj); ASSERT_TRUE(pjObj != nullptr); EXPECT_EQ(proj_get_type(pjObj), PJ_TYPE_COORDINATE_METADATA); EXPECT_TRUE(std::isnan(proj_coordinate_metadata_get_epoch(ctxt, pjObj))); auto pjObj2 = proj_get_source_crs(ctxt, pjObj); ObjectKeeper objKeeper2(pjObj2); EXPECT_TRUE(pjObj2 != nullptr); auto projjson = coordinateMetadata->exportToJSON(JSONFormatter::create(nullptr).get()); auto obj2 = createFromUserInput(projjson, nullptr); auto coordinateMetadataFromJson = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(coordinateMetadataFromJson != nullptr); EXPECT_TRUE(coordinateMetadataFromJson->crs()->isEquivalentTo( GeographicCRS::EPSG_4326.get())); EXPECT_FALSE(coordinateMetadataFromJson->coordinateEpoch().has_value()); } // --------------------------------------------------------------------------- TEST(coordinateMetadata, dynamic_crs) { auto drf = DynamicGeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), Ellipsoid::WGS84, optional("My anchor"), PrimeMeridian::GREENWICH, Measure(2018.5, UnitOfMeasure::YEAR), optional("My model")); auto crs = GeographicCRS::create( PropertyMap(), drf, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto coordinateMetadata = CoordinateMetadata::create(crs, 2023.5); EXPECT_TRUE(coordinateMetadata->crs()->isEquivalentTo(crs.get())); EXPECT_TRUE(coordinateMetadata->coordinateEpoch().has_value()); EXPECT_NEAR(coordinateMetadata->coordinateEpochAsDecimalYear(), 2023.5, 1e-10); // A coordinate epoch should be provided EXPECT_THROW(CoordinateMetadata::create(crs), Exception); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); auto wkt = coordinateMetadata->exportToWKT(f.get()); auto obj = WKTParser().createFromWKT(wkt); auto coordinateMetadataFromWkt = nn_dynamic_pointer_cast(obj); EXPECT_TRUE(coordinateMetadataFromWkt->crs()->isEquivalentTo(crs.get())); EXPECT_TRUE(coordinateMetadataFromWkt->coordinateEpoch().has_value()); EXPECT_NEAR(coordinateMetadataFromWkt->coordinateEpochAsDecimalYear(), 2023.5, 1e-10); auto ctxt = proj_context_create(); PjContextKeeper ctxtKeeper(ctxt); auto pjObj = proj_create(ctxt, wkt.c_str()); ObjectKeeper objKeeper(pjObj); ASSERT_TRUE(pjObj != nullptr); EXPECT_EQ(proj_get_type(pjObj), PJ_TYPE_COORDINATE_METADATA); EXPECT_NEAR(proj_coordinate_metadata_get_epoch(ctxt, pjObj), 2023.5, 1e-10); auto projjson = coordinateMetadata->exportToJSON(JSONFormatter::create(nullptr).get()); auto obj2 = createFromUserInput(projjson, nullptr); auto coordinateMetadataFromJson = nn_dynamic_pointer_cast(obj2); EXPECT_TRUE(coordinateMetadataFromJson->crs()->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(coordinateMetadataFromJson->coordinateEpoch().has_value()); EXPECT_NEAR(coordinateMetadataFromJson->coordinateEpochAsDecimalYear(), 2023.5, 1e-10); } // --------------------------------------------------------------------------- TEST(coordinateMetadata, crs_with_point_motion_operation_and_promote_to_3D) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); { // "NAD83(CSRS)v7" auto crs = factory->createCoordinateReferenceSystem("8255"); EXPECT_THROW(CoordinateMetadata::create(crs, 2023.5), Exception); EXPECT_NO_THROW(CoordinateMetadata::create(crs, 2023.5, dbContext)); auto cm = CoordinateMetadata::create(crs, 2023.5, dbContext) ->promoteTo3D(std::string(), dbContext); EXPECT_TRUE(cm->crs()->isEquivalentTo( crs->promoteTo3D(std::string(), dbContext).get())); EXPECT_TRUE(cm->coordinateEpoch().has_value()); EXPECT_NEAR(cm->coordinateEpochAsDecimalYear(), 2023.5, 1e-10); } { auto crs = factory->createCoordinateReferenceSystem("4267"); EXPECT_THROW(CoordinateMetadata::create(crs, 2023.5, dbContext), Exception); auto cm = CoordinateMetadata::create(crs)->promoteTo3D(std::string(), dbContext); EXPECT_TRUE(cm->crs()->isEquivalentTo( crs->promoteTo3D(std::string(), dbContext).get())); EXPECT_TRUE(!cm->coordinateEpoch().has_value()); } } proj-9.8.1/test/unit/test_common.cpp000664 001750 001750 00000021477 15166171715 017457 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include using namespace osgeo::proj::common; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; // --------------------------------------------------------------------------- TEST(common, unit_of_measure) { EXPECT_EQ(UnitOfMeasure::METRE.name(), "metre"); EXPECT_EQ(UnitOfMeasure::METRE.conversionToSI(), 1.0); EXPECT_EQ(UnitOfMeasure::METRE.type(), UnitOfMeasure::Type::LINEAR); EXPECT_EQ(UnitOfMeasure::DEGREE.name(), "degree"); EXPECT_EQ(UnitOfMeasure::DEGREE.conversionToSI(), 0.017453292519943295); EXPECT_EQ(UnitOfMeasure::DEGREE.type(), UnitOfMeasure::Type::ANGULAR); EXPECT_EQ(UnitOfMeasure::RADIAN.name(), "radian"); EXPECT_EQ(UnitOfMeasure::RADIAN.conversionToSI(), 1.0); EXPECT_EQ(UnitOfMeasure::RADIAN.type(), UnitOfMeasure::Type::ANGULAR); EXPECT_EQ(Length(2.0, UnitOfMeasure("km", 1000.0)) .convertToUnit(UnitOfMeasure::METRE), 2000.0); EXPECT_EQ( Angle(2.0, UnitOfMeasure::DEGREE).convertToUnit(UnitOfMeasure::RADIAN), 2 * 0.017453292519943295); EXPECT_EQ(Angle(2.5969213, UnitOfMeasure::GRAD) .convertToUnit(UnitOfMeasure::DEGREE), 2.5969213 / 100.0 * 90.0); } // --------------------------------------------------------------------------- TEST(common, measure) { EXPECT_TRUE(Measure(0.0) == Measure(0.0)); EXPECT_TRUE(Measure(1.0) == Measure(1.0)); EXPECT_FALSE(Measure(1.0) == Measure(2.0)); EXPECT_FALSE(Measure(1.0) == Measure(0.0)); EXPECT_FALSE(Measure(0.0) == Measure(1.0)); EXPECT_TRUE(Measure(std::numeric_limits::infinity()) == Measure(std::numeric_limits::infinity())); EXPECT_TRUE(Measure(-std::numeric_limits::infinity()) == Measure(-std::numeric_limits::infinity())); EXPECT_FALSE(Measure(std::numeric_limits::infinity()) == Measure(-std::numeric_limits::infinity())); EXPECT_FALSE(Measure(std::numeric_limits::infinity()) == Measure(1.0)); EXPECT_FALSE(Measure(1.0) == Measure(std::numeric_limits::infinity())); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_empty) { PropertyMap properties; auto obj = OperationParameter::create(properties); EXPECT_TRUE(obj->name()->code().empty()); EXPECT_TRUE(obj->identifiers().empty()); EXPECT_TRUE(obj->aliases().empty()); EXPECT_TRUE(obj->remarks().empty()); EXPECT_TRUE(!obj->isDeprecated()); EXPECT_TRUE(obj->alias().empty()); } // --------------------------------------------------------------------------- TEST(common, identifiedobject) { PropertyMap properties; properties.set(IdentifiedObject::NAME_KEY, "name"); properties.set(IdentifiedObject::IDENTIFIERS_KEY, Identifier::create("identifier_code")); properties.set(IdentifiedObject::ALIAS_KEY, "alias"); properties.set(IdentifiedObject::REMARKS_KEY, "remarks"); properties.set(IdentifiedObject::DEPRECATED_KEY, true); auto obj = OperationParameter::create(properties); EXPECT_EQ(*(obj->name()->description()), "name"); ASSERT_EQ(obj->identifiers().size(), 1U); EXPECT_EQ(obj->identifiers()[0]->code(), "identifier_code"); ASSERT_EQ(obj->aliases().size(), 1U); EXPECT_EQ(obj->aliases()[0]->toString(), "alias"); EXPECT_EQ(obj->remarks(), "remarks"); EXPECT_TRUE(obj->isDeprecated()); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_name_invalid_type_integer) { PropertyMap properties; properties.set(IdentifiedObject::NAME_KEY, 123); ASSERT_THROW(OperationParameter::create(properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_name_invalid_type_citation) { PropertyMap properties; properties.set(IdentifiedObject::NAME_KEY, nn_make_shared("invalid")); ASSERT_THROW(OperationParameter::create(properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_identifier_invalid_type) { PropertyMap properties; properties.set(IdentifiedObject::IDENTIFIERS_KEY, "string not allowed"); ASSERT_THROW(OperationParameter::create(properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_identifier_array_of_identifier) { PropertyMap properties; auto array = ArrayOfBaseObject::create(); array->add(Identifier::create("identifier_code1")); array->add(Identifier::create("identifier_code2")); properties.set(IdentifiedObject::IDENTIFIERS_KEY, array); auto obj = OperationParameter::create(properties); ASSERT_EQ(obj->identifiers().size(), 2U); EXPECT_EQ(obj->identifiers()[0]->code(), "identifier_code1"); EXPECT_EQ(obj->identifiers()[1]->code(), "identifier_code2"); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_identifier_array_of_invalid_type) { PropertyMap properties; auto array = ArrayOfBaseObject::create(); array->add(nn_make_shared("unexpected type")); properties.set(IdentifiedObject::IDENTIFIERS_KEY, array); ASSERT_THROW(OperationParameter::create(properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_alias_array_of_string) { PropertyMap properties; properties.set(IdentifiedObject::ALIAS_KEY, std::vector{"alias1", "alias2"}); auto obj = OperationParameter::create(properties); ASSERT_EQ(obj->aliases().size(), 2U); EXPECT_EQ(obj->aliases()[0]->toString(), "alias1"); EXPECT_EQ(obj->aliases()[1]->toString(), "alias2"); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_alias_invalid_type) { PropertyMap properties; properties.set(IdentifiedObject::ALIAS_KEY, nn_make_shared("unexpected type")); ASSERT_THROW(OperationParameter::create(properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(common, identifiedobject_alias_array_of_invalid_type) { PropertyMap properties; auto array = ArrayOfBaseObject::create(); array->add(nn_make_shared("unexpected type")); properties.set(IdentifiedObject::ALIAS_KEY, array); ASSERT_THROW(OperationParameter::create(properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(common, DataEpoch) { DataEpoch epochSrc(Measure(2010.5, UnitOfMeasure::YEAR)); DataEpoch epoch(epochSrc); EXPECT_EQ(epoch.coordinateEpoch().value(), 2010.5); EXPECT_EQ(epoch.coordinateEpoch().unit(), UnitOfMeasure::YEAR); } proj-9.8.1/test/unit/test_tinshift.cpp000664 001750 001750 00000023176 15166171715 020015 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test TIN shift * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2020, Even Rouault * * 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 "gtest_include.h" #define PROJ_COMPILATION #define TINSHIFT_NAMESPACE TestTINShift #include "transformations/tinshift.hpp" using namespace TINSHIFT_NAMESPACE; namespace { static json getMinValidContent() { json j; j["file_type"] = "triangulation_file"; j["format_version"] = "1.0"; j["input_crs"] = "EPSG:2393"; j["output_crs"] = "EPSG:3067"; j["transformed_components"] = {"horizontal"}; j["vertices_columns"] = {"source_x", "source_y", "target_x", "target_y"}; j["triangles_columns"] = {"idx_vertex1", "idx_vertex2", "idx_vertex3"}; j["vertices"] = {{0, 0, 101, 101}, {0, 1, 100, 101}, {1, 1, 100, 100}}; j["triangles"] = {{0, 1, 2}}; return j; } // --------------------------------------------------------------------------- TEST(tinshift, basic) { EXPECT_THROW(TINShiftFile::parse("foo"), ParsingException); EXPECT_THROW(TINShiftFile::parse("null"), ParsingException); EXPECT_THROW(TINShiftFile::parse("{}"), ParsingException); const auto jMinValid(getMinValidContent()); { auto f = TINShiftFile::parse(jMinValid.dump()); EXPECT_EQ(f->fileType(), "triangulation_file"); EXPECT_EQ(f->formatVersion(), "1.0"); EXPECT_EQ(f->inputCRS(), "EPSG:2393"); EXPECT_EQ(f->outputCRS(), "EPSG:3067"); EXPECT_EQ(f->fallbackStrategy(), FALLBACK_NONE); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_FALSE(eval.forward(-0.1, 0.0, 1000.0, x_out, y_out, z_out)); EXPECT_TRUE(eval.forward(0.0, 0.0, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 101.0); EXPECT_EQ(y_out, 101.0); EXPECT_EQ(z_out, 1000.0); EXPECT_TRUE(eval.forward(0.0, 1.0, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 100.0); EXPECT_EQ(y_out, 101.0); EXPECT_EQ(z_out, 1000.0); EXPECT_TRUE(eval.forward(1.0, 1.0, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 100.0); EXPECT_EQ(y_out, 100.0); EXPECT_EQ(z_out, 1000.0); EXPECT_TRUE(eval.forward(0.0, 0.5, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 100.5); EXPECT_EQ(y_out, 101.0); EXPECT_EQ(z_out, 1000.0); EXPECT_TRUE(eval.forward(0.5, 0.5, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 100.5); EXPECT_EQ(y_out, 100.5); EXPECT_EQ(z_out, 1000.0); EXPECT_TRUE(eval.forward(0.5, 0.75, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 100.25); EXPECT_EQ(y_out, 100.5); EXPECT_EQ(z_out, 1000.0); EXPECT_TRUE(eval.inverse(100.25, 100.5, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.5); EXPECT_EQ(y_out, 0.75); EXPECT_EQ(z_out, 1000.0); } // Vertical only, through source_z / target_z { auto j(jMinValid); j["transformed_components"] = {"vertical"}; j["vertices_columns"] = {"source_x", "source_y", "source_z", "target_z"}; j["triangles_columns"] = {"idx_vertex1", "idx_vertex2", "idx_vertex3"}; j["vertices"] = { {0, 0, 10.5, 10.6}, {0, 1, 15.0, 15.2}, {1, 1, 17.5, 18.0}}; j["triangles"] = {{0, 1, 2}}; auto f = TINShiftFile::parse(j.dump()); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_TRUE(eval.forward(0.0, 0.0, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.0); EXPECT_EQ(y_out, 0.0); EXPECT_EQ(z_out, 1000.1); EXPECT_TRUE(eval.forward(0.5, 0.75, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.5); EXPECT_EQ(y_out, 0.75); EXPECT_EQ(z_out, 1000.325); EXPECT_TRUE(eval.inverse(0.5, 0.75, 1000.325, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.5); EXPECT_EQ(y_out, 0.75); EXPECT_EQ(z_out, 1000.0); } // Vertical only, through offset_z { auto j(jMinValid); j["transformed_components"] = {"vertical"}; j["vertices_columns"] = {"source_x", "source_y", "offset_z"}; j["triangles_columns"] = {"idx_vertex1", "idx_vertex2", "idx_vertex3"}; j["vertices"] = {{0, 0, 0.1}, {0, 1, 0.2}, {1, 1, 0.5}}; j["triangles"] = {{0, 1, 2}}; auto f = TINShiftFile::parse(j.dump()); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_TRUE(eval.forward(0.0, 0.0, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.0); EXPECT_EQ(y_out, 0.0); EXPECT_EQ(z_out, 1000.1); EXPECT_TRUE(eval.forward(0.5, 0.75, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.5); EXPECT_EQ(y_out, 0.75); EXPECT_EQ(z_out, 1000.325); EXPECT_TRUE(eval.inverse(0.5, 0.75, 1000.325, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.5); EXPECT_EQ(y_out, 0.75); EXPECT_EQ(z_out, 1000.0); } // Horizontal + vertical { auto j(jMinValid); j["transformed_components"] = {"horizontal", "vertical"}; j["vertices_columns"] = {"source_x", "source_y", "target_x", "target_y", "offset_z"}; j["triangles_columns"] = {"idx_vertex1", "idx_vertex2", "idx_vertex3"}; j["vertices"] = {{0, 0, 101, 101, 0.1}, {0, 1, 100, 101, 0.2}, {1, 1, 100, 100, 0.5}}; j["triangles"] = {{0, 1, 2}}; auto f = TINShiftFile::parse(j.dump()); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_TRUE(eval.forward(0.0, 0.0, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 101.0); EXPECT_EQ(y_out, 101.0); EXPECT_EQ(z_out, 1000.1); EXPECT_TRUE(eval.forward(0.5, 0.75, 1000.0, x_out, y_out, z_out)); EXPECT_EQ(x_out, 100.25); EXPECT_EQ(y_out, 100.5); EXPECT_EQ(z_out, 1000.325); EXPECT_TRUE(eval.inverse(100.25, 100.5, 1000.325, x_out, y_out, z_out)); EXPECT_EQ(x_out, 0.5); EXPECT_EQ(y_out, 0.75); EXPECT_EQ(z_out, 1000.0); } // invalid fallback_strategy field with 1.0 version { auto j(jMinValid); j["fallback_strategy"] = "none"; EXPECT_THROW(TINShiftFile::parse(j.dump()), ParsingException); } // invalid fallback_strategy field with 1.1 version { auto j(jMinValid); j["format_version"] = "1.1"; j["fallback_strategy"] = "invalid"; EXPECT_THROW(TINShiftFile::parse(j.dump()), ParsingException); } // fail with no triangles and fallback nearest_side { auto j(jMinValid); j["format_version"] = "1.1"; j["fallback_strategy"] = "nearest_side"; j["triangles"] = json::array(); // empty auto f = TINShiftFile::parse(j.dump()); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_FALSE(eval.forward(1.0, 1.0, 1.0, x_out, y_out, z_out)); } // fail with only degenerate triangles (one size is zero length) and // fallback nearest_side { auto j(jMinValid); j["format_version"] = "1.1"; j["fallback_strategy"] = "nearest_side"; j["vertices"] = {{0, 0, 101, 101}, {0, 1, 100, 101}, {0, 1, 100, 100}}; auto f = TINShiftFile::parse(j.dump()); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_FALSE(eval.forward(1.0, 1.0, 1.0, x_out, y_out, z_out)); } // fail with only degenerate triangles (two angles are 0°) and fallback // nearest_side { auto j(jMinValid); j["format_version"] = "1.1"; j["fallback_strategy"] = "nearest_side"; j["vertices"] = { {0, 0, 101, 101}, {0, 0.5, 100, 101}, {0, 1, 100, 100}}; auto f = TINShiftFile::parse(j.dump()); auto eval = Evaluator(std::move(f)); double x_out = 0; double y_out = 0; double z_out = 0; EXPECT_FALSE(eval.forward(1.0, 1.0, 1.0, x_out, y_out, z_out)); } } } // namespace proj-9.8.1/test/unit/test_operation.cpp000664 001750 001750 00001101707 15166171715 020163 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" // to be able to use internal::replaceAll #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj_constants.h" #include #include using namespace osgeo::proj::common; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::internal; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; namespace { struct UnrelatedObject : public IComparable { UnrelatedObject() = default; bool _isEquivalentTo(const IComparable *, Criterion, const DatabaseContextPtr &) const override { assert(false); return false; } }; static nn> createUnrelatedObject() { return nn_make_shared(); } } // namespace // --------------------------------------------------------------------------- TEST(operation, method) { auto method = OperationMethod::create( PropertyMap(), std::vector{}); EXPECT_TRUE(method->isEquivalentTo(method.get())); EXPECT_FALSE(method->isEquivalentTo(createUnrelatedObject().get())); auto otherMethod = OperationMethod::create( PropertyMap(), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}); EXPECT_TRUE(otherMethod->isEquivalentTo(otherMethod.get())); EXPECT_FALSE(method->isEquivalentTo(otherMethod.get())); auto otherMethod2 = OperationMethod::create( PropertyMap(), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))}); EXPECT_FALSE(otherMethod->isEquivalentTo(otherMethod2.get())); EXPECT_FALSE(otherMethod->isEquivalentTo( otherMethod2.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, method_parameter_different_order) { auto method1 = OperationMethod::create( PropertyMap(), std::vector{ OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "paramName")), OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "paramName2"))}); auto method2 = OperationMethod::create( PropertyMap(), std::vector{ OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "paramName2")), OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "paramName"))}); auto method3 = OperationMethod::create( PropertyMap(), std::vector{ OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "paramName3")), OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "paramName"))}); EXPECT_FALSE(method1->isEquivalentTo(method2.get())); EXPECT_TRUE(method1->isEquivalentTo(method2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(method1->isEquivalentTo(method3.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, ParameterValue) { auto valStr1 = ParameterValue::create("str1"); auto valStr2 = ParameterValue::create("str2"); EXPECT_TRUE(valStr1->isEquivalentTo(valStr1.get())); EXPECT_FALSE(valStr1->isEquivalentTo(createUnrelatedObject().get())); EXPECT_FALSE(valStr1->isEquivalentTo(valStr2.get())); auto valMeasure1 = ParameterValue::create(Angle(-90.0)); auto valMeasure1Eps = ParameterValue::create(Angle(-90.0 - 1e-11)); auto valMeasure2 = ParameterValue::create(Angle(-89.0)); EXPECT_TRUE(valMeasure1->isEquivalentTo(valMeasure1.get())); EXPECT_TRUE(valMeasure1->isEquivalentTo( valMeasure1.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(valMeasure1->isEquivalentTo(valMeasure1Eps.get())); EXPECT_TRUE(valMeasure1->isEquivalentTo( valMeasure1Eps.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(valMeasure1->isEquivalentTo(valStr1.get())); EXPECT_FALSE(valMeasure1->isEquivalentTo(valMeasure2.get())); EXPECT_FALSE(valMeasure1->isEquivalentTo( valMeasure2.get(), IComparable::Criterion::EQUIVALENT)); auto valInt1 = ParameterValue::create(1); auto valInt2 = ParameterValue::create(2); EXPECT_TRUE(valInt1->isEquivalentTo(valInt1.get())); EXPECT_FALSE(valInt1->isEquivalentTo(valInt2.get())); auto valTrue = ParameterValue::create(true); auto valFalse = ParameterValue::create(false); EXPECT_TRUE(valTrue->isEquivalentTo(valTrue.get())); EXPECT_FALSE(valTrue->isEquivalentTo(valFalse.get())); } // --------------------------------------------------------------------------- TEST(operation, OperationParameter) { auto op1 = OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")); auto op2 = OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2")); EXPECT_TRUE(op1->isEquivalentTo(op1.get())); EXPECT_FALSE(op1->isEquivalentTo(createUnrelatedObject().get())); EXPECT_FALSE(op1->isEquivalentTo(op2.get())); } // --------------------------------------------------------------------------- TEST(operation, OperationParameterValue) { auto op1 = OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")); auto op2 = OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2")); auto valStr1 = ParameterValue::create("str1"); auto valStr2 = ParameterValue::create("str2"); auto opv11 = OperationParameterValue::create(op1, valStr1); EXPECT_TRUE(opv11->isEquivalentTo(opv11.get())); EXPECT_FALSE(opv11->isEquivalentTo(createUnrelatedObject().get())); auto opv12 = OperationParameterValue::create(op1, valStr2); EXPECT_FALSE(opv11->isEquivalentTo(opv12.get())); auto opv21 = OperationParameterValue::create(op2, valStr1); EXPECT_FALSE(opv11->isEquivalentTo(opv12.get())); } // --------------------------------------------------------------------------- TEST(operation, SingleOperation) { auto sop1 = Transformation::create( PropertyMap(), nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), static_cast(GeographicCRS::EPSG_4979.as_nullable()), PropertyMap(), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector{ PositionalAccuracy::create("0.1")}); EXPECT_TRUE(sop1->isEquivalentTo(sop1.get())); EXPECT_FALSE(sop1->isEquivalentTo(createUnrelatedObject().get())); EXPECT_TRUE( sop1->isEquivalentTo(sop1->CoordinateOperation::shallowClone().get())); EXPECT_TRUE(sop1->inverse()->isEquivalentTo( sop1->inverse()->CoordinateOperation::shallowClone().get())); auto sop2 = Transformation::create( PropertyMap(), nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), static_cast(GeographicCRS::EPSG_4979.as_nullable()), PropertyMap(), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector{ PositionalAccuracy::create("0.1")}); EXPECT_FALSE(sop1->isEquivalentTo(sop2.get())); auto sop3 = Transformation::create( PropertyMap(), nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), static_cast(GeographicCRS::EPSG_4979.as_nullable()), PropertyMap(), std::vector{ OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")), OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))}, std::vector{ ParameterValue::createFilename("foo.bin"), ParameterValue::createFilename("foo2.bin")}, std::vector{ PositionalAccuracy::create("0.1")}); EXPECT_FALSE(sop1->isEquivalentTo(sop3.get())); auto sop4 = Transformation::create( PropertyMap(), nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), static_cast(GeographicCRS::EPSG_4979.as_nullable()), PropertyMap(), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo2.bin")}, std::vector{ PositionalAccuracy::create("0.1")}); EXPECT_FALSE(sop1->isEquivalentTo(sop4.get())); } // --------------------------------------------------------------------------- TEST(operation, SingleOperation_different_order) { auto sop1 = Transformation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "ignored1"), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, nullptr, PropertyMap(), std::vector{ OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")), OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))}, std::vector{ ParameterValue::createFilename("foo.bin"), ParameterValue::createFilename("foo2.bin")}, {}); auto sop2 = Transformation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "ignored2"), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, nullptr, PropertyMap(), std::vector{ OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2")), OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo2.bin"), ParameterValue::createFilename("foo.bin")}, {}); auto sop3 = Transformation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "ignored3"), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, nullptr, PropertyMap(), std::vector{ OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")), OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))}, std::vector{ ParameterValue::createFilename("foo2.bin"), ParameterValue::createFilename("foo.bin")}, {}); EXPECT_FALSE(sop1->isEquivalentTo(sop2.get())); EXPECT_TRUE( sop1->isEquivalentTo(sop2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( sop1->isEquivalentTo(sop3.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, transformation_to_wkt) { PropertyMap propertiesTransformation; propertiesTransformation .set(Identifier::CODESPACE_KEY, "codeSpaceTransformation") .set(Identifier::CODE_KEY, "codeTransformation") .set(IdentifiedObject::NAME_KEY, "transformationName") .set(IdentifiedObject::REMARKS_KEY, "my remarks"); auto transf = Transformation::create( propertiesTransformation, nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), static_cast(GeographicCRS::EPSG_4979.as_nullable()), PropertyMap() .set(Identifier::CODESPACE_KEY, "codeSpaceOperationMethod") .set(Identifier::CODE_KEY, "codeOperationMethod") .set(IdentifiedObject::NAME_KEY, "operationMethodName"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector{ PositionalAccuracy::create("0.1")}); std::string src_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get()); } std::string dst_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get()); } std::string interpolation_wkt; { auto formatter = WKTFormatter::create(); formatter->setOutputId(false); interpolation_wkt = GeographicCRS::EPSG_4979->exportToWKT(formatter.get()); } auto expected = "COORDINATEOPERATION[\"transformationName\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "],\n" " METHOD[\"operationMethodName\",\n" " ID[\"codeSpaceOperationMethod\",\"codeOperationMethod\"]],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n" " INTERPOLATIONCRS[" + interpolation_wkt + "],\n" " OPERATIONACCURACY[0.1],\n" " ID[\"codeSpaceTransformation\",\"codeTransformation\"],\n" " REMARK[\"my remarks\"]]"; EXPECT_EQ( replaceAll(replaceAll(transf->exportToWKT(WKTFormatter::create().get()), " ", ""), "\n", ""), replaceAll(replaceAll(expected, " ", ""), "\n", "")); EXPECT_THROW( transf->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); EXPECT_TRUE(transf->isEquivalentTo(transf.get())); EXPECT_FALSE(transf->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(operation, concatenated_operation) { PropertyMap propertiesTransformation; propertiesTransformation .set(Identifier::CODESPACE_KEY, "codeSpaceTransformation") .set(Identifier::CODE_KEY, "codeTransformation") .set(IdentifiedObject::NAME_KEY, "transformationName") .set(IdentifiedObject::REMARKS_KEY, "my remarks"); auto transf_1 = Transformation::create( propertiesTransformation, nn_static_pointer_cast(GeographicCRS::EPSG_4326), nn_static_pointer_cast(GeographicCRS::EPSG_4807), nullptr, PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector()); auto transf_2 = Transformation::create( propertiesTransformation, nn_static_pointer_cast(GeographicCRS::EPSG_4807), nn_static_pointer_cast(GeographicCRS::EPSG_4979), nullptr, PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector()); auto concat = ConcatenatedOperation::create( PropertyMap() .set(Identifier::CODESPACE_KEY, "codeSpace") .set(Identifier::CODE_KEY, "code") .set(IdentifiedObject::NAME_KEY, "name") .set(IdentifiedObject::REMARKS_KEY, "my remarks"), std::vector{transf_1, transf_2}, std::vector{ PositionalAccuracy::create("0.1")}); std::string src_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get()); } std::string dst_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); dst_wkt = GeographicCRS::EPSG_4979->exportToWKT(formatter.get()); } std::string step1_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); step1_wkt = transf_1->exportToWKT(formatter.get()); } std::string step2_wkt; { auto formatter = WKTFormatter::create(WKTFormatter::Convention::WKT2_2019); step2_wkt = transf_2->exportToWKT(formatter.get()); } auto expected = "CONCATENATEDOPERATION[\"name\",\n" " SOURCECRS[" + src_wkt + "],\n" " TARGETCRS[" + dst_wkt + "],\n" " STEP[" + step1_wkt + "],\n" " STEP[" + step2_wkt + "],\n" " OPERATIONACCURACY[0.1],\n" " ID[\"codeSpace\",\"code\"],\n" " REMARK[\"my remarks\"]]"; EXPECT_EQ(replaceAll(replaceAll(concat->exportToWKT( WKTFormatter::create( WKTFormatter::Convention::WKT2_2019) .get()), " ", ""), "\n", ""), replaceAll(replaceAll(expected, " ", ""), "\n", "")); EXPECT_THROW(concat->exportToWKT(WKTFormatter::create().get()), FormattingException); EXPECT_THROW(ConcatenatedOperation::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "name"), std::vector{transf_1, transf_1}, std::vector()), InvalidOperation); auto inv = concat->inverse(); EXPECT_EQ(inv->nameStr(), "Inverse of name"); EXPECT_EQ(inv->sourceCRS()->nameStr(), concat->targetCRS()->nameStr()); EXPECT_EQ(inv->targetCRS()->nameStr(), concat->sourceCRS()->nameStr()); auto inv_as_concat = nn_dynamic_pointer_cast(inv); ASSERT_TRUE(inv_as_concat != nullptr); ASSERT_EQ(inv_as_concat->operations().size(), 2U); EXPECT_EQ(inv_as_concat->operations()[0]->nameStr(), "Inverse of transformationName"); EXPECT_EQ(inv_as_concat->operations()[1]->nameStr(), "Inverse of transformationName"); EXPECT_TRUE(concat->isEquivalentTo(concat.get())); EXPECT_FALSE(concat->isEquivalentTo(createUnrelatedObject().get())); EXPECT_TRUE(concat->isEquivalentTo( concat->CoordinateOperation::shallowClone().get())); EXPECT_FALSE( ConcatenatedOperation::create(PropertyMap(), std::vector{ transf_1, transf_1->inverse()}, std::vector()) ->isEquivalentTo(ConcatenatedOperation::create( PropertyMap(), std::vector{ transf_1->inverse(), transf_1}, std::vector()) .get())); EXPECT_FALSE( ConcatenatedOperation::create(PropertyMap(), std::vector{ transf_1, transf_1->inverse()}, std::vector()) ->isEquivalentTo(ConcatenatedOperation::create( PropertyMap(), std::vector{ transf_1, transf_1->inverse(), transf_1}, std::vector()) .get())); } // --------------------------------------------------------------------------- TEST(operation, transformation_createGeocentricTranslations) { auto transf = Transformation::createGeocentricTranslations( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, std::vector()); EXPECT_TRUE(transf->validateParameters().empty()); auto params = transf->getTOWGS84Parameters(true); auto expected = std::vector{1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0}; EXPECT_EQ(params, expected); auto inv_transf = transf->inverse(); auto inv_transf_as_transf = nn_dynamic_pointer_cast(inv_transf); ASSERT_TRUE(inv_transf_as_transf != nullptr); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf_as_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf_as_transf->sourceCRS()->nameStr()); auto expected_inv = std::vector{-1.0, -2.0, -3.0, 0.0, 0.0, 0.0, 0.0}; EXPECT_EQ(inv_transf_as_transf->getTOWGS84Parameters(true), expected_inv); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=1 +y=2 " "+z=3 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step " "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- static GeodeticCRSNNPtr createGeocentricDatumWGS84() { PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 4328) .set(IdentifiedObject::NAME_KEY, "WGS 84"); return GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- static GeodeticCRSNNPtr createGeocentricKM() { PropertyMap propertiesCRS; propertiesCRS.set(IdentifiedObject::NAME_KEY, "Based on WGS 84"); return GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric( UnitOfMeasure("kilometre", 1000.0, UnitOfMeasure::Type::LINEAR))); } // --------------------------------------------------------------------------- TEST(operation, transformation_createGeocentricTranslations_between_geocentricCRS) { auto transf1 = Transformation::createGeocentricTranslations( PropertyMap(), createGeocentricDatumWGS84(), createGeocentricKM(), 1.0, 2.0, 3.0, std::vector()); EXPECT_EQ(transf1->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=helmert +x=1 +y=2 +z=3 +step " "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km"); auto transf2 = Transformation::createGeocentricTranslations( PropertyMap(), createGeocentricKM(), createGeocentricDatumWGS84(), 1.0, 2.0, 3.0, std::vector()); EXPECT_EQ(transf2->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=km +z_in=km " "+xy_out=m +z_out=m +step +proj=helmert +x=1 +y=2 +z=3"); auto transf3 = Transformation::createGeocentricTranslations( PropertyMap(), createGeocentricKM(), createGeocentricKM(), 1.0, 2.0, 3.0, std::vector()); EXPECT_EQ(transf3->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=unitconvert +xy_in=km +z_in=km " "+xy_out=m +z_out=m +step +proj=helmert +x=1 +y=2 +z=3 +step " "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km"); } // --------------------------------------------------------------------------- TEST(operation, transformation_createGeocentricTranslations_null) { auto transf = Transformation::createGeocentricTranslations( PropertyMap(), createGeocentricDatumWGS84(), createGeocentricDatumWGS84(), 0.0, 0.0, 0.0, std::vector()); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, transformation_createGeocentricTranslations_neg_zero) { auto transf = Transformation::createGeocentricTranslations( PropertyMap(), createGeocentricDatumWGS84(), createGeocentricDatumWGS84(), 1.0, -0.0, 0.0, std::vector()); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=helmert +x=-1 +y=0 +z=0"); } // --------------------------------------------------------------------------- TEST(operation, transformation_createPositionVector) { auto transf = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector{ PositionalAccuracy::create("100")}); EXPECT_TRUE(transf->validateParameters().empty()); ASSERT_EQ(transf->coordinateOperationAccuracies().size(), 1U); auto expected = std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; EXPECT_EQ(transf->getTOWGS84Parameters(true), expected); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=1 +y=2 " "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step " "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); auto inv_transf = transf->inverse(); ASSERT_EQ(inv_transf->coordinateOperationAccuracies().size(), 1U); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf->sourceCRS()->nameStr()); #ifdef USE_APPROXIMATE_HELMERT_INVERSE auto inv_transf_as_transf = nn_dynamic_pointer_cast(inv_transf); ASSERT_TRUE(inv_transf_as_transf != nullptr); #else EXPECT_EQ( inv_transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step " "+proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=1 +y=2 +z=3 +rx=4 " "+ry=5 +rz=6 +s=7 +convention=position_vector +step +inv +proj=cart " "+ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad " "+xy_out=deg +step +proj=axisswap +order=2,1"); // In WKT, use approximate formula auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE( wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("Position Vector transformation (geog2D domain)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("ID[\"EPSG\",9606]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis rotation\",-4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis rotation\",-5") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis rotation\",-6") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos) << wkt; #endif } // --------------------------------------------------------------------------- TEST(operation, transformation_createCoordinateFrameRotation) { auto transf = Transformation::createCoordinateFrameRotation( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, -4.0, -5.0, -6.0, 7.0, std::vector()); EXPECT_TRUE(transf->validateParameters().empty()); auto params = transf->getTOWGS84Parameters(true); auto expected = std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; EXPECT_EQ(params, expected); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=1 +y=2 " "+z=3 +rx=-4 +ry=-5 +rz=-6 +s=7 +convention=coordinate_frame " "+step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); auto inv_transf = transf->inverse(); ASSERT_EQ(inv_transf->coordinateOperationAccuracies().size(), 0U); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf->sourceCRS()->nameStr()); #ifdef USE_APPROXIMATE_HELMERT_INVERSE auto inv_transf_as_transf = nn_dynamic_pointer_cast(inv_transf); ASSERT_TRUE(inv_transf_as_transf != nullptr); #else EXPECT_EQ( inv_transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step " "+proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=1 +y=2 +z=3 " "+rx=-4 +ry=-5 +rz=-6 +s=7 +convention=coordinate_frame +step +inv " "+proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"); // In WKT, use approximate formula auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE( wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("Coordinate Frame rotation (geog2D domain)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("ID[\"EPSG\",9607]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis rotation\",4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis rotation\",5") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis rotation\",6") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos) << wkt; #endif } // --------------------------------------------------------------------------- TEST(operation, transformation_createTimeDependentPositionVector) { auto transf = Transformation::createTimeDependentPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 2018.5, std::vector()); EXPECT_TRUE(transf->validateParameters().empty()); EXPECT_TRUE(transf->requiresPerCoordinateInputTime()); auto inv_transf = transf->inverse(); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf->sourceCRS()->nameStr()); EXPECT_TRUE(inv_transf->requiresPerCoordinateInputTime()); auto projString = inv_transf->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_TRUE(projString.find("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 " "+rz=6 +s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 " "+dry=0.5 +drz=0.6 +ds=0.7 +t_epoch=2018.5 " "+convention=position_vector") != std::string::npos) << projString; // In WKT, use approximate formula auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE( wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("Time-dependent Position Vector tfm (geog2D)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("ID[\"EPSG\",1054]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis rotation\",-4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis rotation\",-5") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis rotation\",-6") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of X-axis translation\",-0.1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis translation\",-0.2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis translation\",-0.3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of X-axis rotation\",-0.4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis rotation\",-0.5") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis rotation\",-0.6") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Scale difference\",-0.7") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Parameter reference epoch\",2018.5") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(operation, transformation_createTimeDependentCoordinateFrameRotation) { auto transf = Transformation::createTimeDependentCoordinateFrameRotation( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 2018.5, std::vector()); EXPECT_TRUE(transf->validateParameters().empty()); auto inv_transf = transf->inverse(); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf->sourceCRS()->nameStr()); auto projString = inv_transf->exportToPROJString(PROJStringFormatter::create().get()); EXPECT_TRUE(projString.find("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 " "+rz=6 +s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 " "+dry=0.5 +drz=0.6 +ds=0.7 +t_epoch=2018.5 " "+convention=coordinate_frame") != std::string::npos) << projString; // In WKT, use approximate formula auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE( wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("Time-dependent Coordinate Frame rotation (geog2D)") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("ID[\"EPSG\",1057]]") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"X-axis rotation\",-4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Y-axis rotation\",-5") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Z-axis rotation\",-6") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of X-axis translation\",-0.1") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis translation\",-0.2") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis translation\",-0.3") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of X-axis rotation\",-0.4") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis rotation\",-0.5") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis rotation\",-0.6") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Rate of change of Scale difference\",-0.7") != std::string::npos) << wkt; EXPECT_TRUE(wkt.find("\"Parameter reference epoch\",2018.5") != std::string::npos) << wkt; } // --------------------------------------------------------------------------- TEST(operation, transformation_successive_helmert_noop) { auto transf_1 = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector()); auto transf_2 = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, -1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, std::vector()); auto concat = ConcatenatedOperation::create( PropertyMap(), std::vector{transf_1, transf_2}, std::vector{}); EXPECT_EQ(concat->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, transformation_successive_helmert_non_trivial_1) { auto transf_1 = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector()); auto transf_2 = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, -1.0, -2.0, -3.0, -4.0, -5.0, -6.0, 7.0, std::vector()); auto concat = ConcatenatedOperation::create( PropertyMap(), std::vector{transf_1, transf_2}, std::vector{}); EXPECT_NE(concat->exportToPROJString(PROJStringFormatter::create().get()), ""); } // --------------------------------------------------------------------------- TEST(operation, transformation_successive_helmert_non_trivial_2) { auto transf_1 = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector()); auto transf_2 = Transformation::createCoordinateFrameRotation( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, -1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, std::vector()); auto concat = ConcatenatedOperation::create( PropertyMap(), std::vector{transf_1, transf_2}, std::vector{}); EXPECT_NE(concat->exportToPROJString(PROJStringFormatter::create().get()), ""); } // --------------------------------------------------------------------------- TEST(operation, transformation_createMolodensky) { auto transf = Transformation::createMolodensky( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0, 2.0, 3.0, 4.0, 5.0, std::vector()); EXPECT_TRUE(transf->validateParameters().empty()); auto wkt = transf->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(replaceAll(replaceAll(wkt, " ", ""), "\n", "") .find("METHOD[\"Molodensky\",ID[\"EPSG\",9604]]") != std::string::npos) << wkt; auto inv_transf = transf->inverse(); auto inv_transf_as_transf = nn_dynamic_pointer_cast(inv_transf); ASSERT_TRUE(inv_transf_as_transf != nullptr); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf_as_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf_as_transf->sourceCRS()->nameStr()); auto projString = inv_transf_as_transf->exportToPROJString( PROJStringFormatter::create().get()); EXPECT_EQ(projString, "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=molodensky +ellps=GRS80 +dx=-1 +dy=-2 " "+dz=-3 +da=-4 +df=-5 +step +proj=unitconvert " "+xy_in=rad +xy_out=deg +step +proj=axisswap " "+order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_createAbridgedMolodensky) { auto transf = Transformation::createAbridgedMolodensky( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0, 2.0, 3.0, 4.0, 5.0, std::vector()); auto wkt = transf->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(replaceAll(replaceAll(wkt, " ", ""), "\n", "") .find(replaceAll( "METHOD[\"Abridged Molodensky\",ID[\"EPSG\",9605]]", " ", "")) != std::string::npos) << wkt; auto inv_transf = transf->inverse(); auto inv_transf_as_transf = nn_dynamic_pointer_cast(inv_transf); ASSERT_TRUE(inv_transf_as_transf != nullptr); EXPECT_EQ(transf->sourceCRS()->nameStr(), inv_transf_as_transf->targetCRS()->nameStr()); EXPECT_EQ(transf->targetCRS()->nameStr(), inv_transf_as_transf->sourceCRS()->nameStr()); auto projString = inv_transf_as_transf->exportToPROJString( PROJStringFormatter::create().get()); EXPECT_EQ(projString, "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=molodensky +ellps=GRS80 +dx=-1 +dy=-2 " "+dz=-3 +da=-4 +df=-5 +abridged +step " "+proj=unitconvert +xy_in=rad +xy_out=deg +step " "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, transformation_inverse) { auto transf = Transformation::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "my transformation") .set(Identifier::CODESPACE_KEY, "my codeSpace") .set(Identifier::CODE_KEY, "my code"), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, nullptr, PropertyMap() .set(IdentifiedObject::NAME_KEY, "my operation") .set(Identifier::CODESPACE_KEY, "my codeSpace") .set(Identifier::CODE_KEY, "my code"), std::vector{OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))}, std::vector{ ParameterValue::createFilename("foo.bin")}, std::vector{ PositionalAccuracy::create("0.1")}); auto inv = transf->inverse(); EXPECT_EQ(inv->inverse(), transf); EXPECT_EQ( inv->exportToWKT(WKTFormatter::create().get()), "COORDINATEOPERATION[\"Inverse of my transformation\",\n" " SOURCECRS[\n" " GEODCRS[\"NAD83\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " TARGETCRS[\n" " GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n" " METHOD[\"Inverse of my operation\",\n" " ID[\"INVERSE(my codeSpace)\",\"my code\"]],\n" " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n" " OPERATIONACCURACY[0.1],\n" " ID[\"INVERSE(my codeSpace)\",\"my code\"]]"); EXPECT_THROW(inv->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); } // --------------------------------------------------------------------------- static VerticalCRSNNPtr createVerticalCRS() { PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create(propertiesVDatum); PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5701) .set(IdentifiedObject::NAME_KEY, "ODN height"); return VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(operation, transformation_createTOWGS84) { EXPECT_THROW(Transformation::createTOWGS84(GeographicCRS::EPSG_4326, std::vector()), InvalidOperation); EXPECT_THROW(Transformation::createTOWGS84(createVerticalCRS(), std::vector(7, 0)), InvalidOperation); } // --------------------------------------------------------------------------- TEST(operation, createAxisOrderReversal) { { auto conv = Conversion::createAxisOrderReversal(false); EXPECT_TRUE(conv->validateParameters().empty()); } { auto conv = Conversion::createAxisOrderReversal(true); EXPECT_TRUE(conv->validateParameters().empty()); } auto latLongDeg = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto longLatDeg = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::DEGREE)); { auto op = CoordinateOperationFactory::create()->createOperation( latLongDeg, longLatDeg); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=axisswap +order=2,1"); } { auto longLatRad = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::RADIAN)); auto op = CoordinateOperationFactory::create()->createOperation( longLatRad, latLongDeg); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } } // --------------------------------------------------------------------------- TEST(operation, utm_export) { auto conv = Conversion::createUTM(PropertyMap(), 1, false); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=1 +south"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"UTM zone 1S\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",-177,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",10000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]],\n" " ID[\"EPSG\",16101]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Transverse_Mercator\"],\n" "PARAMETER[\"latitude_of_origin\",0],\n" "PARAMETER[\"central_meridian\",-177],\n" "PARAMETER[\"scale_factor\",0.9996],\n" "PARAMETER[\"false_easting\",500000],\n" "PARAMETER[\"false_northing\",10000000]"); } // --------------------------------------------------------------------------- TEST(operation, tmerc_export) { auto conv = Conversion::createTransverseMercator( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tmerc +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5"); { auto formatter = PROJStringFormatter::create(); formatter->setUseApproxTMerc(true); EXPECT_EQ(conv->exportToPROJString(formatter.get()), "+proj=tmerc +approx +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5"); } EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Transverse Mercator\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Transverse_Mercator\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, gstmerc_export) { auto conv = Conversion::createGaussSchreiberTransverseMercator( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=gstmerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Gauss Schreiber Transverse Mercator\",\n" " METHOD[\"Gauss Schreiber Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Gauss_Schreiber_Transverse_Mercator\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, tmerc_south_oriented_export) { auto conv = Conversion::createTransverseMercatorSouthOriented( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); // False easting/northing != 0 not supported EXPECT_THROW(conv->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Transverse Mercator (South Orientated)\",\n" " METHOD[\"Transverse Mercator (South Orientated)\",\n" " ID[\"EPSG\",9808]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Transverse_Mercator_South_Orientated\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); auto wkt = "PROJCRS[\"Hartebeesthoek94 / Lo29\"," " BASEGEODCRS[\"Hartebeesthoek94\"," " DATUM[\"Hartebeesthoek94\"," " ELLIPSOID[\"WGS " "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1.0]]]]," " CONVERSION[\"South African Survey Grid zone 29\"," " METHOD[\"Transverse Mercator (South " "Orientated)\",ID[\"EPSG\",9808]]," " PARAMETER[\"Latitude of natural " "origin\",0,ANGLEUNIT[\"degree\",0.01745329252]]," " PARAMETER[\"Longitude of natural " "origin\",29,ANGLEUNIT[\"degree\",0.01745329252]]," " PARAMETER[\"Scale factor at natural " "origin\",1,SCALEUNIT[\"unity\",1.0]]," " PARAMETER[\"False easting\",0,LENGTHUNIT[\"metre\",1.0]]," " PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1.0]]]," " CS[cartesian,2]," " AXIS[\"westing (Y)\",west,ORDER[1]]," " AXIS[\"southing (X)\",south,ORDER[2]]," " LENGTHUNIT[\"metre\",1.0]," " ID[\"EPSG\",2053]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tmerc +axis=wsu +lat_0=0 +lon_0=29 +k=1 +x_0=0 +y_0=0 " "+ellps=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, tmerc_south_oriented_export_esri_102470) { // South Orientated TMerc presented as regular TMerc but with // Scale_Factor=-1 auto wkt = "PROJCS[\"Cape_Lo15\",GEOGCS[\"GCS_Cape\",DATUM[\"D_Cape\"," "SPHEROID[\"Clarke_1880_Arc\",6378249.145,293.4663077]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",15.0]," "PARAMETER[\"Scale_Factor\",-1.0]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tmerc +axis=wsu +lat_0=0 +lon_0=15 +k=1 +x_0=0 +y_0=0 " "+a=6378249.145 +rf=293.4663077 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, tped_export) { auto conv = Conversion::createTwoPointEquidistant( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tpeqd +lat_1=1 +lon_1=2 +lat_2=3 +lon_2=4 +x_0=5 +y_0=6"); auto formatter = WKTFormatter::create(); formatter->simulCurNodeHasId(); EXPECT_EQ(conv->exportToWKT(formatter.get()), "CONVERSION[\"Two Point Equidistant\",\n" " METHOD[\"Two Point Equidistant\"],\n" " PARAMETER[\"Latitude of 1st point\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of 1st point\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Latitude of 2nd point\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of 2nd point\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Two_Point_Equidistant\"],\n" "PARAMETER[\"Latitude_Of_1st_Point\",1],\n" "PARAMETER[\"Longitude_Of_1st_Point\",2],\n" "PARAMETER[\"Latitude_Of_2nd_Point\",3],\n" "PARAMETER[\"Longitude_Of_2nd_Point\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, tmg_export) { auto conv = Conversion::createTunisiaMiningGrid( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_THROW(conv->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Tunisia Mining Grid\",\n" " METHOD[\"Tunisia Mining Grid\",\n" " ID[\"EPSG\",9816]],\n" " PARAMETER[\"Latitude of false origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Easting at false origin\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Tunisia_Mining_Grid\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, aea_export) { auto conv = Conversion::createAlbersEqualArea(PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=aea +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Albers Equal Area\",\n" " METHOD[\"Albers Equal Area\",\n" " ID[\"EPSG\",9822]],\n" " PARAMETER[\"Latitude of false origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Albers_Conic_Equal_Area\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"standard_parallel_1\",3],\n" "PARAMETER[\"standard_parallel_2\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, azimuthal_equidistant_export) { auto conv = Conversion::createAzimuthalEquidistant( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=aeqd +lat_0=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Azimuthal Equidistant\",\n" " METHOD[\"Azimuthal Equidistant\",\n" " ID[\"EPSG\",1125]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Azimuthal_Equidistant\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, guam_projection_export) { auto conv = Conversion::createGuamProjection( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=aeqd +guam +lat_0=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Guam Projection\",\n" " METHOD[\"Guam Projection\",\n" " ID[\"EPSG\",9831]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_THROW( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(operation, bonne_export) { auto conv = Conversion::createBonne(PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=bonne +lat_1=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Bonne\",\n" " METHOD[\"Bonne\",\n" " ID[\"EPSG\",9827]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Bonne\"],\n" "PARAMETER[\"standard_parallel_1\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); auto obj = WKTParser().createFromWKT( "PROJCS[\"unnamed\"," "GEOGCS[\"unnamed ellipse\"," " DATUM[\"unknown\"," " SPHEROID[\"unnamed\",6378137,298.257223563]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"degree\",0.0174532925199433]]," "PROJECTION[\"Bonne\"]," "PARAMETER[\"standard_parallel_1\",1]," "PARAMETER[\"central_meridian\",2]," "PARAMETER[\"false_easting\",3]," "PARAMETER[\"false_northing\",4]," "UNIT[\"metre\",1]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=bonne +lat_1=1 +lon_0=2 +x_0=3 +y_0=4 +ellps=WGS84 " "+units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, lambert_cylindrical_equal_area_spherical_export) { auto conv = Conversion::createLambertCylindricalEqualAreaSpherical( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=cea +R_A +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Cylindrical Equal Area (Spherical)\",\n" " METHOD[\"Lambert Cylindrical Equal Area (Spherical)\",\n" " ID[\"EPSG\",9834]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Cylindrical_Equal_Area\"],\n" "PARAMETER[\"standard_parallel_1\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, lambert_cylindrical_equal_area_export) { auto conv = Conversion::createLambertCylindricalEqualArea( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=cea +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Cylindrical Equal Area\",\n" " METHOD[\"Lambert Cylindrical Equal Area\",\n" " ID[\"EPSG\",9835]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Cylindrical_Equal_Area\"],\n" "PARAMETER[\"standard_parallel_1\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, lcc1sp_export) { auto conv = Conversion::createLambertConicConformal_1SP( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=lcc +lat_1=1 +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Conic Conformal (1SP)\",\n" " METHOD[\"Lambert Conic Conformal (1SP)\",\n" " ID[\"EPSG\",9801]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Lambert_Conformal_Conic_1SP\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, lcc1sp_variant_b_export) { auto conv = Conversion::createLambertConicConformal_1SP_VariantB( PropertyMap(), Angle(1), Scale(2), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=lcc +lat_1=1 +k_0=2 +lat_0=3 +lon_0=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Conic Conformal (1SP variant B)\",\n" " METHOD[\"Lambert Conic Conformal (1SP variant B)\",\n" " ID[\"EPSG\",1102]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Scale factor at natural origin\",2,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"Latitude of false origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Easting at false origin\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]]"); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_export) { auto conv = Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=lcc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Conic Conformal (2SP)\",\n" " METHOD[\"Lambert Conic Conformal (2SP)\",\n" " ID[\"EPSG\",9802]],\n" " PARAMETER[\"Latitude of false origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Lambert_Conformal_Conic_2SP\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"standard_parallel_1\",3],\n" "PARAMETER[\"standard_parallel_2\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_isEquivalentTo_parallels_switched) { auto conv1 = Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6)); auto conv2 = Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(1), Angle(2), Angle(4), Angle(3), Length(5), Length(6)); EXPECT_TRUE( conv1->isEquivalentTo(conv2.get(), IComparable::Criterion::EQUIVALENT)); auto conv3 = Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(3), Length(5), Length(6)); EXPECT_FALSE( conv1->isEquivalentTo(conv3.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_michigan_export) { auto conv = Conversion::createLambertConicConformal_2SP_Michigan( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6), Scale(7)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ( conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=lcc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6 +k_0=7"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Conic Conformal (2SP Michigan)\",\n" " METHOD[\"Lambert Conic Conformal (2SP Michigan)\",\n" " ID[\"EPSG\",1051]],\n" " PARAMETER[\"Latitude of false origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]],\n" " PARAMETER[\"Ellipsoid scaling factor\",7,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",1038]]]"); EXPECT_THROW( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_belgium_export) { auto conv = Conversion::createLambertConicConformal_2SP_Belgium( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=lcc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Conic Conformal (2SP Belgium)\",\n" " METHOD[\"Lambert Conic Conformal (2SP Belgium)\",\n" " ID[\"EPSG\",9803]],\n" " PARAMETER[\"Latitude of false origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Lambert_Conformal_Conic_2SP_Belgium\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"standard_parallel_1\",3],\n" "PARAMETER[\"standard_parallel_2\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, cassini_soldner_export) { auto conv = Conversion::createCassiniSoldner( PropertyMap(), Angle(1), Angle(2), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=cass +lat_0=1 +lon_0=2 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Cassini-Soldner\",\n" " METHOD[\"Cassini-Soldner\",\n" " ID[\"EPSG\",9806]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Cassini_Soldner\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, equidistant_conic_export) { auto conv = Conversion::createEquidistantConic(PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=eqdc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Equidistant Conic\",\n" " METHOD[\"Equidistant Conic\",\n" " ID[\"EPSG\",1119]],\n" " PARAMETER[\"Latitude of false origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8821]],\n" " PARAMETER[\"Longitude of false origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8822]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8824]],\n" " PARAMETER[\"Easting at false origin\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8826]],\n" " PARAMETER[\"Northing at false origin\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8827]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Equidistant_Conic\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"standard_parallel_1\",3],\n" "PARAMETER[\"standard_parallel_2\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, eckert_export) { std::vector numbers{"", "1", "2", "3", "4", "5", "6"}; std::vector latinNumbers{"", "I", "II", "III", "IV", "V", "VI"}; for (int i = 1; i <= 6; i++) { auto conv = (i == 1) ? Conversion::createEckertI(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 2) ? Conversion::createEckertII(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 3) ? Conversion::createEckertIII(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 4) ? Conversion::createEckertIV(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 5) ? Conversion::createEckertV(PropertyMap(), Angle(1), Length(2), Length(3)) : Conversion::createEckertVI(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=eck" + numbers[i] + " +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Eckert " + latinNumbers[i] + "\",\n" " METHOD[\"Eckert " + latinNumbers[i] + "\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ(conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) .get()), "PROJECTION[\"Eckert_" + latinNumbers[i] + "\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } } // --------------------------------------------------------------------------- TEST(operation, createEquidistantCylindrical) { auto conv = Conversion::createEquidistantCylindrical( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=eqc +lat_ts=1 +lat_0=0 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Equidistant Cylindrical\",\n" " METHOD[\"Equidistant Cylindrical\",\n" " ID[\"EPSG\",1028]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Equirectangular\"],\n" "PARAMETER[\"standard_parallel_1\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); EXPECT_TRUE(conv->validateParameters().empty()); } // --------------------------------------------------------------------------- TEST(operation, createEquidistantCylindricalSpherical) { auto conv = Conversion::createEquidistantCylindricalSpherical( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=eqc +lat_ts=1 +lat_0=0 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Equidistant Cylindrical (Spherical)\",\n" " METHOD[\"Equidistant Cylindrical (Spherical)\",\n" " ID[\"EPSG\",1029]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Equirectangular\"],\n" "PARAMETER[\"standard_parallel_1\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); EXPECT_TRUE(conv->validateParameters().empty()); } // --------------------------------------------------------------------------- TEST(operation, equidistant_cylindrical_lat_0) { auto obj = PROJStringParser().createFromPROJString( "+proj=eqc +ellps=sphere +lat_0=-2 +lat_ts=1 +lon_0=-10 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt = crs->exportToWKT(WKTFormatter::create().get()); EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of natural origin\",-2") != std::string::npos) << wkt; auto projString = crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()); EXPECT_EQ(projString, "+proj=eqc +lat_ts=1 +lat_0=-2 +lon_0=-10 +x_0=0 +y_0=0 " "+ellps=sphere +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, gall_export) { auto conv = Conversion::createGall(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=gall +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Gall Stereographic\",\n" " METHOD[\"Gall Stereographic\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Gall_Stereographic\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, goode_homolosine_export) { auto conv = Conversion::createGoodeHomolosine(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=goode +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Goode Homolosine\",\n" " METHOD[\"Goode Homolosine\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Goode_Homolosine\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, interrupted_goode_homolosine_export) { auto conv = Conversion::createInterruptedGoodeHomolosine( PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=igh +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Interrupted Goode Homolosine\",\n" " METHOD[\"Interrupted Goode Homolosine\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Interrupted_Goode_Homolosine\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, geostationary_satellite_sweep_x_export) { auto conv = Conversion::createGeostationarySatelliteSweepX( PropertyMap(), Angle(1), Length(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=geos +sweep=x +lon_0=1 +h=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Geostationary Satellite (Sweep X)\",\n" " METHOD[\"Geostationary Satellite (Sweep X)\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Satellite Height\",2,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); auto crs = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, conv, CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(wkt1.find("PROJECTION[\"Geostationary_Satellite\"]") != std::string::npos) << wkt1; EXPECT_TRUE(wkt1.find("EXTENSION[\"PROJ4\",\"+proj=geos +sweep=x +lon_0=1 " "+h=2 +x_0=3 +y_0=4 +datum=WGS84 +units=m " "+no_defs\"]]") != std::string::npos) << wkt1; } // --------------------------------------------------------------------------- TEST(operation, geostationary_satellite_sweep_y_export) { auto conv = Conversion::createGeostationarySatelliteSweepY( PropertyMap(), Angle(1), Length(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=geos +lon_0=1 +h=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Geostationary Satellite (Sweep Y)\",\n" " METHOD[\"Geostationary Satellite (Sweep Y)\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Satellite Height\",2,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Geostationary_Satellite\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"satellite_height\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, gnomonic_export) { auto conv = Conversion::createGnomonic(PropertyMap(), Angle(1), Angle(2), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=gnom +lat_0=1 +lon_0=2 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Gnomonic\",\n" " METHOD[\"Gnomonic\"],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Gnomonic\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, hotine_oblique_mercator_variant_A_export) { auto conv = Conversion::createHotineObliqueMercatorVariantA( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Scale(5), Length(6), Length(7)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=omerc +no_uoff +lat_0=1 +lonc=2 +alpha=3 +gamma=4 +k=5 " "+x_0=6 +y_0=7"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Hotine Oblique Mercator (variant A)\",\n" " METHOD[\"Hotine Oblique Mercator (variant A)\",\n" " ID[\"EPSG\",9812]],\n" " PARAMETER[\"Latitude of projection centre\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection centre\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth at projection centre\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Angle from Rectified to Skew Grid\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8814]],\n" " PARAMETER[\"Scale factor at projection centre\",5,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"False easting\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",7,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Hotine_Oblique_Mercator\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"azimuth\",3],\n" "PARAMETER[\"rectified_grid_angle\",4],\n" "PARAMETER[\"scale_factor\",5],\n" "PARAMETER[\"false_easting\",6],\n" "PARAMETER[\"false_northing\",7]"); } // --------------------------------------------------------------------------- TEST(operation, hotine_oblique_mercator_variant_A_export_swiss_mercator) { auto conv = Conversion::createHotineObliqueMercatorVariantA( PropertyMap(), Angle(1), Angle(2), Angle(90), Angle(90), Scale(5), Length(6), Length(7)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=somerc +lat_0=1 +lon_0=2 +k_0=5 " "+x_0=6 +y_0=7"); } // --------------------------------------------------------------------------- TEST(operation, hotine_oblique_mercator_variant_B_export) { auto conv = Conversion::createHotineObliqueMercatorVariantB( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Scale(5), Length(6), Length(7)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=omerc +lat_0=1 +lonc=2 +alpha=3 +gamma=4 +k=5 " "+x_0=6 +y_0=7"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Hotine Oblique Mercator (variant B)\",\n" " METHOD[\"Hotine Oblique Mercator (variant B)\",\n" " ID[\"EPSG\",9815]],\n" " PARAMETER[\"Latitude of projection centre\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection centre\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth at projection centre\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Angle from Rectified to Skew Grid\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8814]],\n" " PARAMETER[\"Scale factor at projection centre\",5,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"Easting at projection centre\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8816]],\n" " PARAMETER[\"Northing at projection centre\",7,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8817]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Hotine_Oblique_Mercator_Azimuth_Center\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"azimuth\",3],\n" "PARAMETER[\"rectified_grid_angle\",4],\n" "PARAMETER[\"scale_factor\",5],\n" "PARAMETER[\"false_easting\",6],\n" "PARAMETER[\"false_northing\",7]"); } // --------------------------------------------------------------------------- TEST(operation, hotine_oblique_mercator_variant_B_export_swiss_mercator) { auto conv = Conversion::createHotineObliqueMercatorVariantB( PropertyMap(), Angle(1), Angle(2), Angle(90), Angle(90), Scale(5), Length(6), Length(7)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=somerc +lat_0=1 +lon_0=2 +k_0=5 " "+x_0=6 +y_0=7"); } // --------------------------------------------------------------------------- TEST(operation, hotine_oblique_mercator_two_point_natural_origin_export) { auto conv = Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Angle(5), Scale(6), Length(7), Length(8)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=omerc +lat_0=1 +lat_1=2 +lon_1=3 +lat_2=4 +lon_2=5 +k=6 " "+x_0=7 +y_0=8"); auto formatter = WKTFormatter::create(); formatter->simulCurNodeHasId(); EXPECT_EQ( conv->exportToWKT(formatter.get()), "CONVERSION[\"Hotine Oblique Mercator Two Point Natural Origin\",\n" " METHOD[\"Hotine Oblique Mercator Two Point Natural Origin\"],\n" " PARAMETER[\"Latitude of projection centre\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Latitude of 1st point\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of 1st point\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Latitude of 2nd point\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of 2nd point\",5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Scale factor at projection centre\",6,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"Easting at projection centre\",7,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8816]],\n" " PARAMETER[\"Northing at projection centre\",8,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8817]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Hotine_Oblique_Mercator_Two_Point_Natural_Origin\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"latitude_of_point_1\",2],\n" "PARAMETER[\"longitude_of_point_1\",3],\n" "PARAMETER[\"latitude_of_point_2\",4],\n" "PARAMETER[\"longitude_of_point_2\",5],\n" "PARAMETER[\"scale_factor\",6],\n" "PARAMETER[\"false_easting\",7],\n" "PARAMETER[\"false_northing\",8]"); } // --------------------------------------------------------------------------- TEST(operation, laborde_oblique_mercator_export) { auto conv = Conversion::createLabordeObliqueMercator( PropertyMap(), Angle(1), Angle(2), Angle(3), Scale(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=labrd +lat_0=1 +lon_0=2 +azi=3 +k=4 +x_0=5 +y_0=6"); auto formatter = WKTFormatter::create(); formatter->simulCurNodeHasId(); EXPECT_EQ(conv->exportToWKT(formatter.get()), "CONVERSION[\"Laborde Oblique Mercator\",\n" " METHOD[\"Laborde Oblique Mercator\",\n" " ID[\"EPSG\",9813]],\n" " PARAMETER[\"Latitude of projection centre\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection centre\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth at projection centre\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Scale factor at projection centre\",4,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Laborde_Oblique_Mercator\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"azimuth\",3],\n" "PARAMETER[\"scale_factor\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, imw_polyconic_export) { auto conv = Conversion::createInternationalMapWorldPolyconic( PropertyMap(), Angle(1), Angle(3), Angle(4), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=imw_p +lon_0=1 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"International Map of the World Polyconic\",\n" " METHOD[\"International Map of the World Polyconic\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Latitude of 1st point\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Latitude of 2nd point\",4,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"International_Map_of_the_World_Polyconic\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"Latitude_Of_1st_Point\",3],\n" "PARAMETER[\"Latitude_Of_2nd_Point\",4],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, krovak_north_oriented_export) { auto conv = Conversion::createKrovakNorthOriented( PropertyMap(), Angle(49.5), Angle(42.5), Angle(30.2881397527778), Angle(78.5), Scale(0.9999), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=krovak +lat_0=49.5 +lon_0=42.5 +alpha=30.2881397527778 " "+k=0.9999 +x_0=5 +y_0=6"); EXPECT_EQ( conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Krovak (North Orientated)\",\n" " METHOD[\"Krovak (North Orientated)\",\n" " ID[\"EPSG\",1041]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",42.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Krovak\"],\n" "PARAMETER[\"latitude_of_center\",49.5],\n" "PARAMETER[\"longitude_of_center\",42.5],\n" "PARAMETER[\"azimuth\",30.2881397527778],\n" "PARAMETER[\"pseudo_standard_parallel_1\",78.5],\n" "PARAMETER[\"scale_factor\",0.9999],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, krovak_export) { auto conv = Conversion::createKrovak( PropertyMap(), Angle(49.5), Angle(42.5), Angle(30.2881397527778), Angle(78.5), Scale(0.9999), Length(5), Length(6)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=krovak +axis=swu +lat_0=49.5 +lon_0=42.5 " "+alpha=30.2881397527778 +k=0.9999 +x_0=5 " "+y_0=6"); EXPECT_EQ( conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Krovak\",\n" " METHOD[\"Krovak\",\n" " ID[\"EPSG\",9819]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",42.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Krovak\"],\n" "PARAMETER[\"latitude_of_center\",49.5],\n" "PARAMETER[\"longitude_of_center\",42.5],\n" "PARAMETER[\"azimuth\",30.2881397527778],\n" "PARAMETER[\"pseudo_standard_parallel_1\",78.5],\n" "PARAMETER[\"scale_factor\",0.9999],\n" "PARAMETER[\"false_easting\",5],\n" "PARAMETER[\"false_northing\",6]"); } // --------------------------------------------------------------------------- TEST(operation, lambert_azimuthal_equal_area_export) { auto conv = Conversion::createLambertAzimuthalEqualArea( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=laea +lat_0=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Lambert Azimuthal Equal Area\",\n" " METHOD[\"Lambert Azimuthal Equal Area\",\n" " ID[\"EPSG\",9820]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Lambert_Azimuthal_Equal_Area\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, miller_cylindrical_export) { auto conv = Conversion::createMillerCylindrical(PropertyMap(), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=mill +R_A +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Miller Cylindrical\",\n" " METHOD[\"Miller Cylindrical\"],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Miller_Cylindrical\"],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_export) { auto conv = Conversion::createMercatorVariantA( PropertyMap(), Angle(0), Angle(1), Scale(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +lon_0=1 +k=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Mercator (variant A)\",\n" " METHOD[\"Mercator (variant A)\",\n" " ID[\"EPSG\",9804]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",2,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Mercator_1SP\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"scale_factor\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_export_latitude_origin_non_zero) { auto conv = Conversion::createMercatorVariantA( PropertyMap(), Angle(10), Angle(1), Scale(2), Length(3), Length(4)); EXPECT_THROW(conv->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); } // --------------------------------------------------------------------------- TEST(operation, wkt1_import_mercator_variant_A) { auto wkt = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",1],\n" " PARAMETER[\"scale_factor\",2],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conversion = crs->derivingConversion(); auto convRef = Conversion::createMercatorVariantA( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0), Angle(1), Scale(2), Length(3), Length(4)); EXPECT_EQ(conversion->exportToWKT(WKTFormatter::create().get()), convRef->exportToWKT(WKTFormatter::create().get())); } // --------------------------------------------------------------------------- TEST(operation, wkt1_import_mercator_variant_A_that_is_variant_B) { // Addresses https://trac.osgeo.org/gdal/ticket/3026 auto wkt = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",-1],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conversion = crs->derivingConversion(); auto convRef = Conversion::createMercatorVariantB( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(-1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conversion->isEquivalentTo(convRef.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_B_export) { auto conv = Conversion::createMercatorVariantB( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Mercator (variant B)\",\n" " METHOD[\"Mercator (variant B)\",\n" " ID[\"EPSG\",9805]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Mercator_2SP\"],\n" "PARAMETER[\"standard_parallel_1\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, odd_mercator_1sp_with_non_null_latitude) { auto obj = WKTParser().createFromWKT( "PROJCS[\"unnamed\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",30],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"scale_factor\",0.99],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW(crs->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); } // --------------------------------------------------------------------------- TEST(operation, odd_mercator_2sp_with_latitude_of_origin) { auto obj = WKTParser().createFromWKT( "PROJCS[\"unnamed\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Mercator_2SP\"],\n" " PARAMETER[\"standard_parallel_1\",30],\n" " PARAMETER[\"latitude_of_origin\",40],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW(crs->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); } // --------------------------------------------------------------------------- TEST(operation, webmerc_export) { auto conv = Conversion::createPopularVisualisationPseudoMercator( PropertyMap(), Angle(0), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=webmerc +lat_0=0 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Popular Visualisation Pseudo Mercator\",\n" " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" " ID[\"EPSG\",1024]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); auto projCRS = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Pseudo-Mercator"), GeographicCRS::EPSG_4326, conv, CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); EXPECT_EQ( projCRS->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJCS[\"Pseudo-Mercator\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " EXTENSION[\"PROJ4\",\"+proj=merc " "+a=6378137 +b=6378137 +lat_ts=0 +lon_0=2 " "+x_0=3 +y_0=4 +k=1 +units=m " "+nadgrids=@null +wktext +no_defs\"]]"); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, projCRS); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=webmerc " "+lat_0=0 +lon_0=2 +x_0=3 +y_0=4 +ellps=WGS84"); EXPECT_EQ( projCRS->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=2 +x_0=3 " "+y_0=4 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, webmerc_import_from_GDAL_wkt1) { auto projCRS = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Pseudo-Mercator"), GeographicCRS::EPSG_4326, Conversion::createPopularVisualisationPseudoMercator( PropertyMap(), Angle(0), Angle(0), Length(0), Length(0)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto wkt1 = projCRS->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); auto obj = WKTParser().createFromWKT(wkt1); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto convGot = crs->derivingConversion(); EXPECT_EQ(convGot->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"unnamed\",\n" " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" " ID[\"EPSG\",1024]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); } // --------------------------------------------------------------------------- TEST(operation, webmerc_import_from_GDAL_wkt1_with_EPSG_code) { auto projCRS = ProjectedCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "Pseudo-Mercator") .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, "3857"), GeographicCRS::EPSG_4326, Conversion::createPopularVisualisationPseudoMercator( PropertyMap(), Angle(0), Angle(0), Length(0), Length(0)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto wkt1 = projCRS->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_TRUE(wkt1.find("3857") != std::string::npos) << wkt1; auto obj = WKTParser().createFromWKT(wkt1); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->identifiers().size(), 1U); } // --------------------------------------------------------------------------- TEST(operation, webmerc_import_from_GDAL_wkt1_EPSG_3785_deprecated) { auto wkt1 = "PROJCS[\"Popular Visualisation CRS / Mercator (deprecated)\"," " GEOGCS[\"Popular Visualisation CRS\"," " DATUM[\"Popular_Visualisation_Datum\"," " SPHEROID[\"Popular Visualisation Sphere\",6378137,0," " AUTHORITY[\"EPSG\",\"7059\"]]," " TOWGS84[0,0,0,0,0,0,0]," " AUTHORITY[\"EPSG\",\"6055\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4055\"]]," " PROJECTION[\"Mercator_1SP\"]," " PARAMETER[\"central_meridian\",0]," " PARAMETER[\"scale_factor\",1]," " PARAMETER[\"false_easting\",0]," " PARAMETER[\"false_northing\",0]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"X\",EAST]," " AXIS[\"Y\",NORTH]," " EXTENSION[\"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\"]]"; auto obj = WKTParser().createFromWKT(wkt1); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 " "+y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"); auto convGot = crs->derivingConversion(); EXPECT_EQ(convGot->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"unnamed\",\n" " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" " ID[\"EPSG\",1024]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); } // --------------------------------------------------------------------------- TEST(operation, webmerc_import_from_WKT2_EPSG_3785_deprecated) { auto wkt2 = "PROJCRS[\"Popular Visualisation CRS / Mercator\",\n" " BASEGEODCRS[\"Popular Visualisation CRS\",\n" " DATUM[\"Popular Visualisation Datum\",\n" " ELLIPSOID[\"Popular Visualisation Sphere\",6378137,0,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"Popular Visualisation Mercator\",\n" " METHOD[\"Mercator (1SP) (Spherical)\",\n" " ID[\"EPSG\",9841]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting (X)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"; auto obj = WKTParser().createFromWKT(wkt2); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 " "+y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()), wkt2); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) .get()), "PROJCS[\"Popular Visualisation CRS / Mercator\",\n" " GEOGCS[\"Popular Visualisation CRS\",\n" " DATUM[\"Popular_Visualisation_Datum\",\n" " SPHEROID[\"Popular Visualisation Sphere\",6378137,0],\n" " TOWGS84[0,0,0,0,0,0,0]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0 " "+lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext " "+no_defs\"]]"); } // --------------------------------------------------------------------------- TEST(operation, webmerc_import_from_broken_esri_WGS_84_Pseudo_Mercator) { // Likely the result of a broken export of GDAL morphToESRI() auto wkt1 = "PROJCS[\"WGS_84_Pseudo_Mercator\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137,298.257223563]],PRIMEM[\"Greenwich\",0]," "UNIT[\"Degree\",0.017453292519943295]]," "PROJECTION[\"Mercator\"],PARAMETER[\"central_meridian\",0]," "PARAMETER[\"false_easting\",0]," "PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]," "PARAMETER[\"standard_parallel_1\",0.0]]"; auto obj = WKTParser().setStrict(false).createFromWKT(wkt1); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto convGot = crs->derivingConversion(); EXPECT_EQ(convGot->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"unnamed\",\n" " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" " ID[\"EPSG\",1024]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); } // --------------------------------------------------------------------------- TEST(operation, mercator_spherical_export) { auto conv = Conversion::createMercatorSpherical( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +R_C +lat_0=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Mercator (Spherical)\",\n" " METHOD[\"Mercator (Spherical)\",\n" " ID[\"EPSG\",1026]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); } // --------------------------------------------------------------------------- TEST(operation, mercator_spherical_import) { auto wkt2 = "PROJCRS[\"unknown\",\n" " BASEGEOGCRS[\"unknown\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6326]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]]],\n" " CONVERSION[\"unknown\",\n" " METHOD[\"Mercator (Spherical)\",\n" " ID[\"EPSG\",1026]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj = WKTParser().createFromWKT(wkt2); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc +R_C +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 " "+units=m +no_defs +type=crs"); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), wkt2); } // --------------------------------------------------------------------------- TEST(operation, mollweide_export) { auto conv = Conversion::createMollweide(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=moll +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Mollweide\",\n" " METHOD[\"Mollweide\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Mollweide\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, nzmg_export) { auto conv = Conversion::createNewZealandMappingGrid( PropertyMap(), Angle(1), Angle(2), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=nzmg +lat_0=1 +lon_0=2 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"New Zealand Map Grid\",\n" " METHOD[\"New Zealand Map Grid\",\n" " ID[\"EPSG\",9811]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"New_Zealand_Map_Grid\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, oblique_stereographic_export) { auto conv = Conversion::createObliqueStereographic( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=sterea +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Oblique Stereographic\",\n" " METHOD[\"Oblique Stereographic\",\n" " ID[\"EPSG\",9809]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Oblique_Stereographic\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, orthographic_export) { auto conv = Conversion::createOrthographic(PropertyMap(), Angle(1), Angle(2), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=ortho +lat_0=1 +lon_0=2 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Orthographic\",\n" " METHOD[\"Orthographic\",\n" " ID[\"EPSG\",9840]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Orthographic\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, local_orthographic_export) { auto conv = Conversion::createLocalOrthographic( PropertyMap(), Angle(1), Angle(2), Angle(3), Scale(1.25), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=ortho +lat_0=1 +lon_0=2 +alpha=3 +k=1.25 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Local Orthographic\",\n" " METHOD[\"Local Orthographic\",\n" " ID[\"EPSG\",1130]],\n" " PARAMETER[\"Latitude of projection centre\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of projection centre\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8812]],\n" " PARAMETER[\"Azimuth at projection centre\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8813]],\n" " PARAMETER[\"Scale factor at projection centre\",1.25,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8815]],\n" " PARAMETER[\"Easting at projection centre\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8816]],\n" " PARAMETER[\"Northing at projection centre\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8817]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Local Orthographic\"],\n" "PARAMETER[\"latitude_of_center\",1],\n" "PARAMETER[\"longitude_of_center\",2],\n" "PARAMETER[\"azimuth\",3],\n" "PARAMETER[\"scale_factor\",1.25],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, american_polyconic_export) { auto conv = Conversion::createAmericanPolyconic( PropertyMap(), Angle(1), Angle(2), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=poly +lat_0=1 +lon_0=2 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"American Polyconic\",\n" " METHOD[\"American Polyconic\",\n" " ID[\"EPSG\",9818]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Polyconic\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, polar_stereographic_variant_A_export) { auto conv = Conversion::createPolarStereographicVariantA( PropertyMap(), Angle(90), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=stere +lat_0=90 +lon_0=2 +k=3 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Polar Stereographic (variant A)\",\n" " METHOD[\"Polar Stereographic (variant A)\",\n" " ID[\"EPSG\",9810]],\n" " PARAMETER[\"Latitude of natural origin\",90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Polar_Stereographic\"],\n" "PARAMETER[\"latitude_of_origin\",90],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, polar_stereographic_variant_B_export_positive_lat) { auto conv = Conversion::createPolarStereographicVariantB( PropertyMap(), Angle(70), Angle(2), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=stere +lat_0=90 +lat_ts=70 +lon_0=2 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Polar Stereographic (variant B)\",\n" " METHOD[\"Polar Stereographic (variant B)\",\n" " ID[\"EPSG\",9829]],\n" " PARAMETER[\"Latitude of standard parallel\",70,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8832]],\n" " PARAMETER[\"Longitude of origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Polar_Stereographic\"],\n" "PARAMETER[\"latitude_of_origin\",70],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, polar_stereographic_variant_B_export_negative_lat) { auto conv = Conversion::createPolarStereographicVariantB( PropertyMap(), Angle(-70), Angle(2), Length(4), Length(5)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=stere +lat_0=-90 +lat_ts=-70 +lon_0=2 +x_0=4 +y_0=5"); } // --------------------------------------------------------------------------- TEST(operation, wkt1_import_polar_stereographic_variantA) { auto wkt = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",-90],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",3],\n" " PARAMETER[\"false_easting\",4],\n" " PARAMETER[\"false_northing\",5],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conversion = crs->derivingConversion(); auto convRef = Conversion::createPolarStereographicVariantA( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(-90), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_EQ(conversion->exportToWKT(WKTFormatter::create().get()), convRef->exportToWKT(WKTFormatter::create().get())); } // --------------------------------------------------------------------------- TEST(operation, wkt1_import_polar_stereographic_variantB) { auto wkt = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",-70],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",4],\n" " PARAMETER[\"false_northing\",5],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conversion = crs->derivingConversion(); auto convRef = Conversion::createPolarStereographicVariantB( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(-70), Angle(2), Length(4), Length(5)); EXPECT_EQ(conversion->exportToWKT(WKTFormatter::create().get()), convRef->exportToWKT(WKTFormatter::create().get())); } // --------------------------------------------------------------------------- TEST(operation, wkt1_import_polar_stereographic_ambiguous) { auto wkt = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Polar_Stereographic\"],\n" " PARAMETER[\"latitude_of_origin\",-70],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",3],\n" " PARAMETER[\"false_easting\",4],\n" " PARAMETER[\"false_northing\",5],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conversion = crs->derivingConversion(); EXPECT_EQ(conversion->method()->nameStr(), "Polar_Stereographic"); } // --------------------------------------------------------------------------- TEST(operation, wkt1_import_equivalent_parameters) { auto wkt = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Hotine Oblique Mercator Two Point Natural " "Origin\"],\n" " PARAMETER[\"latitude_of_origin\",1],\n" " PARAMETER[\"Latitude_Of_1st_Point\",2],\n" " PARAMETER[\"Longitude_Of_1st_Point\",3],\n" " PARAMETER[\"Latitude_Of_2nd_Point\",4],\n" " PARAMETER[\"Longitude_Of 2nd_Point\",5],\n" " PARAMETER[\"scale_factor\",6],\n" " PARAMETER[\"false_easting\",7],\n" " PARAMETER[\"false_northing\",8],\n" " UNIT[\"metre\",1]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto conversion = crs->derivingConversion(); auto convRef = Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Angle(5), Scale(6), Length(7), Length(8)); EXPECT_EQ( conversion->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), convRef->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get())); } // --------------------------------------------------------------------------- TEST(operation, robinson_export) { auto conv = Conversion::createRobinson(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=robin +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Robinson\",\n" " METHOD[\"Robinson\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Robinson\"],\n" "PARAMETER[\"longitude_of_center\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, sinusoidal_export) { auto conv = Conversion::createSinusoidal(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=sinu +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Sinusoidal\",\n" " METHOD[\"Sinusoidal\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Sinusoidal\"],\n" "PARAMETER[\"longitude_of_center\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, stereographic_export) { auto conv = Conversion::createStereographic( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=stere +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Stereographic\",\n" " METHOD[\"Stereographic\"],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Stereographic\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"scale_factor\",3],\n" "PARAMETER[\"false_easting\",4],\n" "PARAMETER[\"false_northing\",5]"); } // --------------------------------------------------------------------------- TEST(operation, vandergrinten_export) { auto conv = Conversion::createVanDerGrinten(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=vandg +R_A +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Van Der Grinten\",\n" " METHOD[\"Van Der Grinten\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"VanDerGrinten\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } // --------------------------------------------------------------------------- TEST(operation, wagner_export) { std::vector numbers{"", "1", "2", "3", "4", "5", "6", "7"}; std::vector latinNumbers{"", "I", "II", "III", "IV", "V", "VI", "VII"}; for (int i = 1; i <= 7; i++) { if (i == 3) continue; auto conv = (i == 1) ? Conversion::createWagnerI(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 2) ? Conversion::createWagnerII(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 4) ? Conversion::createWagnerIV(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 5) ? Conversion::createWagnerV(PropertyMap(), Angle(1), Length(2), Length(3)) : (i == 6) ? Conversion::createWagnerVI(PropertyMap(), Angle(1), Length(2), Length(3)) : Conversion::createWagnerVII(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=wag" + numbers[i] + " +lon_0=1 +x_0=2 +y_0=3"); auto formatter = WKTFormatter::create(); formatter->simulCurNodeHasId(); EXPECT_EQ(conv->exportToWKT(formatter.get()), "CONVERSION[\"Wagner " + latinNumbers[i] + "\",\n" " METHOD[\"Wagner " + latinNumbers[i] + "\"],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ(conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) .get()), "PROJECTION[\"Wagner_" + latinNumbers[i] + "\"],\n" "PARAMETER[\"central_meridian\",1],\n" "PARAMETER[\"false_easting\",2],\n" "PARAMETER[\"false_northing\",3]"); } } // --------------------------------------------------------------------------- TEST(operation, wagnerIII_export) { auto conv = Conversion::createWagnerIII(PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=wag3 +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4"); auto formatter = WKTFormatter::create(); formatter->simulCurNodeHasId(); EXPECT_EQ(conv->exportToWKT(formatter.get()), "CONVERSION[\"Wagner III\",\n" " METHOD[\"Wagner III\"],\n" " PARAMETER[\"Latitude of true scale\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Wagner_III\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, qsc_export) { auto conv = Conversion::createQuadrilateralizedSphericalCube( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=qsc +lat_0=1 +lon_0=2 +x_0=3 +y_0=4"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Quadrilateralized Spherical Cube\",\n" " METHOD[\"Quadrilateralized Spherical Cube\"],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Quadrilateralized_Spherical_Cube\"],\n" "PARAMETER[\"latitude_of_origin\",1],\n" "PARAMETER[\"central_meridian\",2],\n" "PARAMETER[\"false_easting\",3],\n" "PARAMETER[\"false_northing\",4]"); } // --------------------------------------------------------------------------- TEST(operation, sch_export) { auto conv = Conversion::createSphericalCrossTrackHeight( PropertyMap(), Angle(1), Angle(2), Angle(3), Length(4)); EXPECT_TRUE(conv->validateParameters().empty()); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=sch +plat_0=1 +plon_0=2 +phdg_0=3 +h_0=4"); auto formatter = WKTFormatter::create(); formatter->simulCurNodeHasId(); EXPECT_EQ(conv->exportToWKT(formatter.get()), "CONVERSION[\"Spherical Cross-Track Height\",\n" " METHOD[\"Spherical Cross-Track Height\"],\n" " PARAMETER[\"Peg point latitude\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Peg point longitude\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Peg point heading\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"Peg point height\",4,\n" " LENGTHUNIT[\"metre\",1]]]"); EXPECT_EQ( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), "PROJECTION[\"Spherical_Cross_Track_Height\"],\n" "PARAMETER[\"peg_point_latitude\",1],\n" "PARAMETER[\"peg_point_longitude\",2],\n" "PARAMETER[\"peg_point_heading\",3],\n" "PARAMETER[\"peg_point_height\",4]"); } // --------------------------------------------------------------------------- TEST(operation, conversion_inverse) { auto conv = Conversion::createTransverseMercator( PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5)); auto inv = conv->inverse(); EXPECT_EQ(inv->inverse(), conv); EXPECT_EQ(inv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Inverse of Transverse Mercator\",\n" " METHOD[\"Inverse of Transverse Mercator\",\n" " ID[\"INVERSE(EPSG)\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",3,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); EXPECT_EQ(inv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k=3 " "+x_0=4 +y_0=5"); EXPECT_TRUE(inv->isEquivalentTo(inv.get())); EXPECT_FALSE(inv->isEquivalentTo(createUnrelatedObject().get())); EXPECT_TRUE( conv->isEquivalentTo(conv->CoordinateOperation::shallowClone().get())); EXPECT_TRUE( inv->isEquivalentTo(inv->CoordinateOperation::shallowClone().get())); } // --------------------------------------------------------------------------- TEST(operation, eqearth_export) { auto conv = Conversion::createEqualEarth(PropertyMap(), Angle(1), Length(2), Length(3)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=eqearth +lon_0=1 +x_0=2 +y_0=3"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Equal Earth\",\n" " METHOD[\"Equal Earth\",\n" " ID[\"EPSG\",1078]],\n" " PARAMETER[\"Longitude of natural origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",2,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); } // --------------------------------------------------------------------------- TEST(operation, vertical_perspective_export) { auto conv = Conversion::createVerticalPerspective( PropertyMap(), Angle(1), Angle(2), Length(3), Length(4), Length(5), Length(6)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=nsper +lat_0=1 +lon_0=2 +h=4 +x_0=5 +y_0=6"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Vertical Perspective\",\n" " METHOD[\"Vertical Perspective\",\n" " ID[\"EPSG\",9838]],\n" " PARAMETER[\"Latitude of topocentric origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8834]],\n" " PARAMETER[\"Longitude of topocentric origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8835]],\n" " PARAMETER[\"Ellipsoidal height of topocentric origin\",3,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8836]],\n" " PARAMETER[\"Viewpoint height\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8840]],\n" " PARAMETER[\"False easting\",5,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",6,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]]"); } // --------------------------------------------------------------------------- TEST( operation, vertical_perspective_export_no_topocentric_height_and_false_easting_northing) { auto conv = Conversion::createVerticalPerspective( PropertyMap(), Angle(1), Angle(2), Length(0), Length(4), Length(0), Length(0)); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=nsper +lat_0=1 +lon_0=2 +h=4 +x_0=0 +y_0=0"); // Check that False esting and False northing are not exported, when they // are 0. EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"Vertical Perspective\",\n" " METHOD[\"Vertical Perspective\",\n" " ID[\"EPSG\",9838]],\n" " PARAMETER[\"Latitude of topocentric origin\",1,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8834]],\n" " PARAMETER[\"Longitude of topocentric origin\",2,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8835]],\n" " PARAMETER[\"Ellipsoidal height of topocentric origin\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8836]],\n" " PARAMETER[\"Viewpoint height\",4,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8840]]]"); } // --------------------------------------------------------------------------- TEST(operation, laborde_oblique_mercator) { // Content of EPSG:29701 "Tananarive (Paris) / Laborde Grid" auto projString = "+proj=labrd +lat_0=-18.9 +lon_0=44.1 +azi=18.9 " "+k=0.9995 +x_0=400000 +y_0=800000 +ellps=intl +pm=paris " "+units=m +no_defs +type=crs"; auto obj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), projString); } // --------------------------------------------------------------------------- TEST(operation, adams_ws2_export) { auto dbContext = DatabaseContext::create(); // ESRI:54098 WGS_1984_Adams_Square_II auto crs = AuthorityFactory::create(dbContext, "ESRI") ->createProjectedCRS("54098"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=spilhaus +lat_0=0 +lon_0=0 +azi=0 +k_0=1.4142135623731 " "+rot=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, spilhaus_esri_export) { auto dbContext = DatabaseContext::create(); // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square auto crs = AuthorityFactory::create(dbContext, "ESRI") ->createProjectedCRS("54099"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=spilhaus +lat_0=-49.56371678 +lon_0=66.94970198 " "+azi=40.17823482 +k_0=1.4142135623731 +rot=45 " "+x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, hyperbolic_cassini_soldner) { auto dbContext = DatabaseContext::create(); auto crs = AuthorityFactory::create(dbContext, "EPSG")->createProjectedCRS("3139"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=cass +hyperbolic +lat_0=-16.25 +lon_0=179.333333333333 " "+x_0=251727.9155424 +y_0=334519.953768 " "+a=6378306.3696 +b=6356571.996 +units=link +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(operation, PROJ_based) { auto conv = SingleOperation::createPROJBased(PropertyMap(), "+proj=merc", nullptr, nullptr); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=merc"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"PROJ-based coordinate operation\",\n" " METHOD[\"PROJ-based operation method: +proj=merc\"]]"); EXPECT_EQ(conv->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=merc"); auto str = "+proj=pipeline +step +proj=unitconvert +xy_in=grad +xy_out=rad " "+step +proj=axisswap +order=2,1 +step +proj=longlat " "+ellps=clrk80ign +pm=paris +step +proj=axisswap +order=2,1"; EXPECT_EQ( SingleOperation::createPROJBased(PropertyMap(), str, nullptr, nullptr) ->exportToPROJString(PROJStringFormatter::create().get()), str); EXPECT_THROW(SingleOperation::createPROJBased( PropertyMap(), "+proj=pipeline +step +proj=pipeline", nullptr, nullptr) ->exportToPROJString(PROJStringFormatter::create().get()), UnsupportedOperationException); } // --------------------------------------------------------------------------- TEST(operation, PROJ_based_empty) { auto conv = SingleOperation::createPROJBased(PropertyMap(), "", nullptr, nullptr); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()), "CONVERSION[\"PROJ-based coordinate operation\",\n" " METHOD[\"PROJ-based operation method: \"]]"); EXPECT_THROW( conv->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); EXPECT_EQ(conv->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, PROJ_based_with_global_parameters) { auto conv = SingleOperation::createPROJBased( PropertyMap(), "+proj=pipeline +ellps=WGS84 +step +proj=longlat", nullptr, nullptr); EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +ellps=WGS84 +step +proj=longlat"); } // --------------------------------------------------------------------------- TEST(operation, geographic_topocentric) { auto wkt = "PROJCRS[\"EPSG topocentric example A\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4979]],\n" " CONVERSION[\"EPSG topocentric example A\",\n" " METHOD[\"Geographic/topocentric conversions\",\n" " ID[\"EPSG\",9837]],\n" " PARAMETER[\"Latitude of topocentric origin\",55,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8834]],\n" " PARAMETER[\"Longitude of topocentric origin\",5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8835]],\n" " PARAMETER[\"Ellipsoidal height of topocentric origin\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8836]]],\n" " CS[Cartesian,3],\n" " AXIS[\"topocentric East (U)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"topocentric North (V)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"topocentric height (W)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Example only (fictitious).\"],\n" " AREA[\"Description of the extent of the CRS.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",5819]]"; auto obj = WKTParser().createFromWKT(wkt); auto dst = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(dst != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4979, NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=WGS84 " "+step +proj=topocentric +lat_0=55 +lon_0=5 +h_0=0 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, geocentric_topocentric) { auto wkt = "PROJCRS[\"EPSG topocentric example B\",\n" " BASEGEODCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4978]],\n" " CONVERSION[\"EPSG topocentric example B\",\n" " METHOD[\"Geocentric/topocentric conversions\",\n" " ID[\"EPSG\",9836]],\n" " PARAMETER[\"Geocentric X of topocentric origin\",3771793.97,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8837]],\n" " PARAMETER[\"Geocentric Y of topocentric origin\",140253.34,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8838]],\n" " PARAMETER[\"Geocentric Z of topocentric origin\",5124304.35,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8839]]],\n" " CS[Cartesian,3],\n" " AXIS[\"topocentric East (U)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"topocentric North (V)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"topocentric height (W)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Example only (fictitious).\"],\n" " AREA[\"Description of the extent of the CRS.\"],\n" " BBOX[-90,-180,90,180]],\n" " ID[\"EPSG\",5820]]"; auto dbContext = DatabaseContext::create(); // Need a database so that EPSG:4978 is resolved auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto dst = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(dst != nullptr); auto f(NS_PROJ::io::WKTFormatter::create( NS_PROJ::io::WKTFormatter::Convention::WKT2_2019)); auto op = CoordinateOperationFactory::create()->createOperation( GeodeticCRS::EPSG_4978, NN_CHECK_ASSERT(dst)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=topocentric +X_0=3771793.97 +Y_0=140253.34 " "+Z_0=5124304.35 +ellps=WGS84"); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_to_variant_B) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), Scale(0.9), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto conv = projCRS->derivingConversion(); auto sameConv = conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); ASSERT_TRUE(sameConv); EXPECT_TRUE(sameConv->isEquivalentTo(conv.get())); auto targetConv = conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); ASSERT_TRUE(targetConv); auto lat_1 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_EQ(lat_1, 25.917499691810534) << lat_1; EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE), 1); EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE), 3); EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE), 4); EXPECT_FALSE( conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::STRICT)); EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_to_variant_B_scale_1) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), Scale(1.0), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_MERCATOR_VARIANT_B); ASSERT_TRUE(targetConv); auto lat_1 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_EQ(lat_1, 0.0) << lat_1; } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_to_variant_B_no_crs) { auto targetConv = Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), Scale(1.0), Length(3), Length(4)) ->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_to_variant_B_invalid_scale) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), Scale(0.0), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_MERCATOR_VARIANT_B); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- static GeographicCRSNNPtr geographicCRSInvalidEccentricity() { return GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::create( PropertyMap(), Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137), Scale(0.1)), optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_A_to_variant_B_invalid_eccentricity) { auto projCRS = ProjectedCRS::create( PropertyMap(), geographicCRSInvalidEccentricity(), Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1), Scale(1.0), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_MERCATOR_VARIANT_B); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_B_to_variant_A) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createMercatorVariantB(PropertyMap(), Angle(25.917499691810534), Angle(1), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_MERCATOR_VARIANT_A); ASSERT_TRUE(targetConv); EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE), 0); EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE), 1); auto k_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, UnitOfMeasure::SCALE_UNITY); EXPECT_EQ(k_0, 0.9) << k_0; EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE), 3); EXPECT_EQ(targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE), 4); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_B_to_variant_A_invalid_std1) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createMercatorVariantB(PropertyMap(), Angle(100), Angle(1), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_MERCATOR_VARIANT_A); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, mercator_variant_B_to_variant_A_invalid_eccentricity) { auto projCRS = ProjectedCRS::create( PropertyMap(), geographicCRSInvalidEccentricity(), Conversion::createMercatorVariantB(PropertyMap(), Angle(0), Angle(1), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_MERCATOR_VARIANT_A); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp) { // equivalent to EPSG:2154 auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80 Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(49), Angle(44), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto conv = projCRS->derivingConversion(); auto targetConv = conv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); ASSERT_TRUE(targetConv); { auto lat_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_0, 46.519430223986866, 1e-12) << lat_0; auto lon_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0; auto k_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, UnitOfMeasure::SCALE_UNITY); EXPECT_NEAR(k_0, 0.9990510286374692, 1e-15) << k_0; auto x_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE); EXPECT_NEAR(x_0, 700000, 1e-15) << x_0; auto y_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE); EXPECT_NEAR(y_0, 6602157.8388103368, 1e-7) << y_0; } auto _2sp_from_1sp = targetConv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); ASSERT_TRUE(_2sp_from_1sp); { auto lat_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_0, 46.5, 1e-15) << lat_0; auto lon_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lon_0, 3, 1e-15) << lon_0; auto lat_1 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_1, 49, 1e-15) << lat_1; auto lat_2 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_2, 44, 1e-15) << lat_2; auto x_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, UnitOfMeasure::METRE); EXPECT_NEAR(x_0, 700000, 1e-15) << x_0; auto y_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, UnitOfMeasure::METRE); EXPECT_NEAR(y_0, 6600000, 1e-15) << y_0; } EXPECT_FALSE( conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::STRICT)); EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_phi0_eq_phi1_eq_phi2) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80 Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(46.5), Angle(46.5), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto conv = projCRS->derivingConversion(); auto targetConv = conv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); ASSERT_TRUE(targetConv); { auto lat_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_0, 46.5, 1e-15) << lat_0; auto lon_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0; auto k_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, UnitOfMeasure::SCALE_UNITY); EXPECT_NEAR(k_0, 1.0, 1e-15) << k_0; auto x_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE); EXPECT_NEAR(x_0, 700000, 1e-15) << x_0; auto y_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE); EXPECT_NEAR(y_0, 6600000, 1e-15) << y_0; } auto _2sp_from_1sp = targetConv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); ASSERT_TRUE(_2sp_from_1sp); { auto lat_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_0, 46.5, 1e-15) << lat_0; auto lon_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lon_0, 3, 1e-15) << lon_0; auto lat_1 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_1, 46.5, 1e-15) << lat_1; auto lat_2 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_2, 46.5, 1e-15) << lat_2; auto x_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, UnitOfMeasure::METRE); EXPECT_NEAR(x_0, 700000, 1e-15) << x_0; auto y_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, UnitOfMeasure::METRE); EXPECT_NEAR(y_0, 6600000, 1e-15) << y_0; } EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_phi0_diff_phi1_and_phi1_eq_phi2) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80 Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.123), Angle(3), Angle(46.4567), Angle(46.4567), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto conv = projCRS->derivingConversion(); auto targetConv = conv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); ASSERT_TRUE(targetConv); { auto lat_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_0, 46.4567, 1e-14) << lat_0; auto lon_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0; auto k_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, UnitOfMeasure::SCALE_UNITY); EXPECT_NEAR(k_0, 1.0, 1e-15) << k_0; auto x_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE); EXPECT_NEAR(x_0, 700000, 1e-15) << x_0; auto y_0 = targetConv->parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE); EXPECT_NEAR(y_0, 6637093.292952879, 1e-8) << y_0; } auto _2sp_from_1sp = targetConv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); ASSERT_TRUE(_2sp_from_1sp); { auto lat_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_0, 46.4567, 1e-14) << lat_0; auto lon_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE); EXPECT_NEAR(lon_0, 3, 1e-15) << lon_0; auto lat_1 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_1, 46.4567, 1e-14) << lat_1; auto lat_2 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, UnitOfMeasure::DEGREE); EXPECT_NEAR(lat_2, 46.4567, 1e-14) << lat_2; auto x_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, UnitOfMeasure::METRE); EXPECT_NEAR(x_0, 700000, 1e-15) << x_0; auto y_0 = _2sp_from_1sp->parameterValueNumeric( EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, UnitOfMeasure::METRE); EXPECT_NEAR(y_0, 6637093.292952879, 1e-8) << y_0; } EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(_2sp_from_1sp->isEquivalentTo( targetConv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(targetConv->isEquivalentTo(_2sp_from_1sp.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(conv->isEquivalentTo(_2sp_from_1sp.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, lcc1sp_to_lcc2sp_invalid_eccentricity) { auto projCRS = ProjectedCRS::create( PropertyMap(), geographicCRSInvalidEccentricity(), Conversion::createLambertConicConformal_1SP(PropertyMap(), Angle(40), Angle(1), Scale(0.99), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc1sp_to_lcc2sp_invalid_scale) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_1SP( PropertyMap(), Angle(40), Angle(1), Scale(0), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc1sp_to_lcc2sp_invalid_lat0) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_1SP(PropertyMap(), Angle(100), Angle(1), Scale(0.99), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc1sp_to_lcc2sp_null_lat0) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_1SP(PropertyMap(), Angle(0), Angle(1), Scale(0.99), Length(3), Length(4)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_invalid_lat0) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(100), Angle(3), Angle(44), Angle(49), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_invalid_lat1) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(100), Angle(49), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_invalid_lat2) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(44), Angle(100), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_invalid_lat1_opposite_lat2) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(-49), Angle(49), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_invalid_lat1_and_lat2_close_to_zero) { auto projCRS = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(.0000000000000001), Angle(.0000000000000002), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, lcc2sp_to_lcc1sp_invalid_eccentricity) { auto projCRS = ProjectedCRS::create( PropertyMap(), geographicCRSInvalidEccentricity(), Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(46.5), Angle(3), Angle(44), Angle(49), Length(700000), Length(6600000)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto targetConv = projCRS->derivingConversion()->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); EXPECT_FALSE(targetConv != nullptr); } // --------------------------------------------------------------------------- TEST(operation, three_param_equivalent_to_seven_param) { auto three_param = Transformation::createGeocentricTranslations( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, {}); auto seven_param_pv = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0, {}); auto seven_param_cf = Transformation::createCoordinateFrameRotation( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0, {}); auto seven_param_non_eq = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 1.0, 0.0, 0.0, 0.0, {}); EXPECT_TRUE(three_param->isEquivalentTo( seven_param_pv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(three_param->isEquivalentTo( seven_param_cf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(seven_param_cf->isEquivalentTo( three_param.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(seven_param_pv->isEquivalentTo( three_param.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(three_param->isEquivalentTo( seven_param_non_eq.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, position_vector_equivalent_coordinate_frame) { auto pv = Transformation::createPositionVector( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, {}); auto cf = Transformation::createCoordinateFrameRotation( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, -4 + 1e-11, -5.0, -6.0, 7.0, {}); auto cf_non_eq = Transformation::createCoordinateFrameRotation( PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, {}); EXPECT_TRUE( pv->isEquivalentTo(cf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( cf->isEquivalentTo(pv.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(pv->isEquivalentTo(cf_non_eq.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, conversion_missing_parameter) { auto wkt1 = "PROJCS[\"NAD83(CSRS98) / UTM zone 20N (deprecated)\"," " GEOGCS[\"NAD83(CSRS98)\"," " DATUM[\"NAD83_Canadian_Spatial_Reference_System\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6140\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9108\"]]," " AUTHORITY[\"EPSG\",\"4140\"]]," " PROJECTION[\"Transverse_Mercator\"]," " PARAMETER[\"latitude_of_origin\",0]," " PARAMETER[\"central_meridian\",-63]," " PARAMETER[\"scale_factor\",0.9996]," " PARAMETER[\"false_easting\",500000]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]," " AUTHORITY[\"EPSG\",\"2038\"]]"; auto obj1 = WKTParser().createFromWKT(wkt1); auto crs1 = nn_dynamic_pointer_cast(obj1); ASSERT_TRUE(crs1 != nullptr); // Difference with wkt1: latitude_of_origin missing, but false_northing // added to 0 auto wkt2 = "PROJCS[\"NAD83(CSRS98) / UTM zone 20N (deprecated)\"," " GEOGCS[\"NAD83(CSRS98)\"," " DATUM[\"NAD83_Canadian_Spatial_Reference_System\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6140\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9108\"]]," " AUTHORITY[\"EPSG\",\"4140\"]]," " PROJECTION[\"Transverse_Mercator\"]," " PARAMETER[\"central_meridian\",-63]," " PARAMETER[\"scale_factor\",0.9996]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",0]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]," " AUTHORITY[\"EPSG\",\"2038\"]]"; auto obj2 = WKTParser().createFromWKT(wkt2); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); // Difference with wkt1: false_northing added to 0 auto wkt3 = "PROJCS[\"NAD83(CSRS98) / UTM zone 20N (deprecated)\"," " GEOGCS[\"NAD83(CSRS98)\"," " DATUM[\"NAD83_Canadian_Spatial_Reference_System\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6140\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9108\"]]," " AUTHORITY[\"EPSG\",\"4140\"]]," " PROJECTION[\"Transverse_Mercator\"]," " PARAMETER[\"latitude_of_origin\",0]," " PARAMETER[\"central_meridian\",-63]," " PARAMETER[\"scale_factor\",0.9996]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",0]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]," " AUTHORITY[\"EPSG\",\"2038\"]]"; auto obj3 = WKTParser().createFromWKT(wkt3); auto crs3 = nn_dynamic_pointer_cast(obj3); ASSERT_TRUE(crs3 != nullptr); // Difference with wkt1: UNKNOWN added to non-zero auto wkt4 = "PROJCS[\"NAD83(CSRS98) / UTM zone 20N (deprecated)\"," " GEOGCS[\"NAD83(CSRS98)\"," " DATUM[\"NAD83_Canadian_Spatial_Reference_System\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6140\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9108\"]]," " AUTHORITY[\"EPSG\",\"4140\"]]," " PROJECTION[\"Transverse_Mercator\"]," " PARAMETER[\"latitude_of_origin\",0]," " PARAMETER[\"central_meridian\",-63]," " PARAMETER[\"scale_factor\",0.9996]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",0]," " PARAMETER[\"UNKNOWN\",13]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]," " AUTHORITY[\"EPSG\",\"2038\"]]"; auto obj4 = WKTParser().createFromWKT(wkt4); auto crs4 = nn_dynamic_pointer_cast(obj4); ASSERT_TRUE(crs4 != nullptr); // Difference with wkt1: latitude_of_origin missing, but false_northing // added to non-zero auto wkt5 = "PROJCS[\"NAD83(CSRS98) / UTM zone 20N (deprecated)\"," " GEOGCS[\"NAD83(CSRS98)\"," " DATUM[\"NAD83_Canadian_Spatial_Reference_System\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6140\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9108\"]]," " AUTHORITY[\"EPSG\",\"4140\"]]," " PROJECTION[\"Transverse_Mercator\"]," " PARAMETER[\"central_meridian\",-63]," " PARAMETER[\"scale_factor\",0.9996]," " PARAMETER[\"false_easting\",500000]," " PARAMETER[\"false_northing\",-99999]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," " AXIS[\"Easting\",EAST]," " AXIS[\"Northing\",NORTH]," " AUTHORITY[\"EPSG\",\"2038\"]]"; auto obj5 = WKTParser().createFromWKT(wkt5); auto crs5 = nn_dynamic_pointer_cast(obj5); ASSERT_TRUE(crs5 != nullptr); EXPECT_TRUE( crs1->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs1->isEquivalentTo(crs3.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs3->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs3.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs3->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs1->isEquivalentTo(crs4.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs4->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs1->isEquivalentTo(crs5.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs5->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, conversion_missing_parameter_scale) { auto wkt1 = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",-1],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",1],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1]]"; auto obj1 = WKTParser().createFromWKT(wkt1); auto crs1 = nn_dynamic_pointer_cast(obj1); ASSERT_TRUE(crs1 != nullptr); // Difference with wkt1: scale_factor missing auto wkt2 = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",-1],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1]]"; auto obj2 = WKTParser().createFromWKT(wkt2); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); // Difference with wkt1: scale_factor set to non-1 auto wkt3 = "PROJCS[\"test\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS 1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",-1],\n" " PARAMETER[\"central_meridian\",2],\n" " PARAMETER[\"scale_factor\",-1],\n" " PARAMETER[\"false_easting\",3],\n" " PARAMETER[\"false_northing\",4],\n" " UNIT[\"metre\",1]]"; auto obj3 = WKTParser().createFromWKT(wkt3); auto crs3 = nn_dynamic_pointer_cast(obj3); ASSERT_TRUE(crs3 != nullptr); EXPECT_TRUE( crs1->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs1->isEquivalentTo(crs3.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs3->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs2->isEquivalentTo(crs3.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE( crs3->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, hotine_oblique_mercator_variant_A_export_equivalent_modulo_360) { auto conv1 = Conversion::createHotineObliqueMercatorVariantA( PropertyMap(), Angle(1), Angle(2), Angle(-3), Angle(-4), Scale(5), Length(6), Length(7)); auto conv2 = Conversion::createHotineObliqueMercatorVariantA( PropertyMap(), Angle(1), Angle(2), Angle(-3 + 360), Angle(-4 + 360), Scale(5), Length(6), Length(7)); EXPECT_TRUE( conv1->isEquivalentTo(conv2.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(operation, createChangeVerticalUnit) { auto conv = Conversion::createChangeVerticalUnit(PropertyMap(), Scale(1)); EXPECT_TRUE(conv->validateParameters().empty()); } // --------------------------------------------------------------------------- TEST(operation, createChangeVerticalUnitNoconvFactor) { auto conv = Conversion::createChangeVerticalUnit(PropertyMap()); EXPECT_TRUE(conv->validateParameters().empty()); } // --------------------------------------------------------------------------- TEST(operation, createGeographicGeocentric) { auto conv = Conversion::createGeographicGeocentric(PropertyMap()); EXPECT_TRUE(conv->validateParameters().empty()); } // --------------------------------------------------------------------------- TEST(operation, validateParameters) { { auto conv = Conversion::create( PropertyMap(), PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"), {}, {}); auto validation = conv->validateParameters(); EXPECT_EQ(validation, std::list{"Unknown method unknown"}); } { auto conv = Conversion::create(PropertyMap(), PropertyMap().set(IdentifiedObject::NAME_KEY, "change of vertical unit"), {}, {}); auto validation = conv->validateParameters(); auto expected = std::list{ "Method name change of vertical unit is equivalent to official " "Change of Vertical Unit but not strictly equal", "Cannot find expected parameter Unit conversion scalar"}; EXPECT_EQ(validation, expected); } { auto conv = Conversion::create( PropertyMap(), PropertyMap() .set(IdentifiedObject::NAME_KEY, EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT) .set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, "1234"), {}, {}); auto validation = conv->validateParameters(); auto expected = std::list{ "Method of EPSG code 1234 does not match official code (1069)", "Cannot find expected parameter Unit conversion scalar"}; EXPECT_EQ(validation, expected); } { auto conv = Conversion::create( PropertyMap(), PropertyMap() .set(IdentifiedObject::NAME_KEY, "some fancy name") .set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), {}, {}); auto validation = conv->validateParameters(); auto expected = std::list{ "Method name some fancy name, matched to Change of Vertical Unit, " "through its EPSG code has not an equivalent name", "Cannot find expected parameter Unit conversion scalar"}; EXPECT_EQ(validation, expected); } { auto conv = Conversion::create( PropertyMap(), PropertyMap().set(IdentifiedObject::NAME_KEY, EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT), {OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "unit conversion scalar"))}, {ParameterValue::create(Measure(1.0, UnitOfMeasure::SCALE_UNITY))}); auto validation = conv->validateParameters(); auto expected = std::list{ "Parameter name unit conversion scalar is equivalent to official " "Unit conversion scalar but not strictly equal"}; EXPECT_EQ(validation, expected); } { auto conv = Conversion::create( PropertyMap(), PropertyMap().set(IdentifiedObject::NAME_KEY, EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT), {OperationParameter::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "fancy name") .set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR))}, {ParameterValue::create(Measure(1.0, UnitOfMeasure::SCALE_UNITY))}); auto validation = conv->validateParameters(); auto expected = std::list{ "Parameter name fancy name, matched to Unit conversion scalar, " "through its EPSG code has not an equivalent name"}; EXPECT_EQ(validation, expected); } { auto conv = Conversion::create( PropertyMap(), PropertyMap().set(IdentifiedObject::NAME_KEY, EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT), {OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "extra param"))}, {ParameterValue::create(Measure(1.0, UnitOfMeasure::SCALE_UNITY))}); auto validation = conv->validateParameters(); auto expected = std::list{ "Cannot find expected parameter Unit conversion scalar", "Parameter extra param found but not expected for this method"}; EXPECT_EQ(validation, expected); } } // --------------------------------------------------------------------------- TEST(operation, normalizeForVisualization) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); const auto checkThroughWKTRoundtrip = [](const CoordinateOperationNNPtr &opRef) { auto wkt = opRef->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); auto objFromWkt = WKTParser().createFromWKT(wkt); auto opFromWkt = nn_dynamic_pointer_cast(objFromWkt); ASSERT_TRUE(opFromWkt != nullptr); EXPECT_TRUE(opRef->_isEquivalentTo(opFromWkt.get())); EXPECT_EQ( opFromWkt->exportToPROJString(PROJStringFormatter::create().get()), opRef->exportToPROJString(PROJStringFormatter::create().get())); }; // Source(geographic) must be inverted { auto src = authFactory->createCoordinateReferenceSystem("4326"); auto dst = authFactory->createCoordinateReferenceSystem("32631"); auto op = CoordinateOperationFactory::create()->createOperation(src, dst); ASSERT_TRUE(op != nullptr); auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); EXPECT_EQ(opNormalized->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=31 +ellps=WGS84"); checkThroughWKTRoundtrip(opNormalized); } // Target(geographic) must be inverted { auto src = authFactory->createCoordinateReferenceSystem("32631"); auto dst = authFactory->createCoordinateReferenceSystem("4326"); auto op = CoordinateOperationFactory::create()->createOperation(src, dst); ASSERT_TRUE(op != nullptr); auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); EXPECT_EQ(opNormalized->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +inv +proj=utm +zone=31 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); checkThroughWKTRoundtrip(opNormalized); } // Source(geographic) and target(projected) must be inverted { auto src = authFactory->createCoordinateReferenceSystem("4326"); auto dst = authFactory->createCoordinateReferenceSystem("3040"); auto op = CoordinateOperationFactory::create()->createOperation(src, dst); ASSERT_TRUE(op != nullptr); auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); EXPECT_EQ(opNormalized->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=28 +ellps=GRS80"); checkThroughWKTRoundtrip(opNormalized); } // No inversion { auto src = authFactory->createCoordinateReferenceSystem("32631"); auto dst = authFactory->createCoordinateReferenceSystem("32632"); auto op = CoordinateOperationFactory::create()->createOperation(src, dst); ASSERT_TRUE(op != nullptr); auto opNormalized = op->normalizeForVisualization(); EXPECT_TRUE(opNormalized->_isEquivalentTo(op.get())); } // Source(compoundCRS) and target(geographic 3D) must be inverted { auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); ctxt->setUsePROJAlternativeGridNames(false); auto src = CompoundCRS::create( PropertyMap(), std::vector{ authFactory->createCoordinateReferenceSystem("4326"), // EGM2008 height authFactory->createCoordinateReferenceSystem("3855")}); auto list = CoordinateOperationFactory::create()->createOperations( src, authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 3D ctxt); ASSERT_GE(list.size(), 3U); auto op = list[1]; auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); EXPECT_EQ( opNormalized->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, authFactory->databaseContext()) .get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // Source(boundCRS) and target(geographic) must be inverted { auto src = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4269, std::vector{1, 2, 3, 4, 5, 6, 7}); auto dst = authFactory->createCoordinateReferenceSystem("4326"); auto op = CoordinateOperationFactory::create()->createOperation(src, dst); ASSERT_TRUE(op != nullptr); auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); EXPECT_EQ(opNormalized->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 " "+convention=position_vector " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // Test normalizeForVisualization on a concatenated operation with // non-overlapping sub-operation extents (EPSG:8047 = ED50 -> ED87 -> WGS84) // This should work even though EPSG:1147 (ED50->ED87) and EPSG:1146 // (ED87->WGS84) have non-overlapping validity areas. { auto op = authFactory->createCoordinateOperation("8047", false); ASSERT_TRUE(op != nullptr); // Verify it's a concatenated operation auto concat = dynamic_cast(op.get()); ASSERT_TRUE(concat != nullptr); // This should succeed (previously it threw due to extent intersection) auto opNormalized = op->normalizeForVisualization(); EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get())); // Verify the normalized operation can produce a PROJ string auto projString = opNormalized->exportToPROJString( PROJStringFormatter::create().get()); EXPECT_FALSE(projString.empty()); EXPECT_EQ(opNormalized->coordinateOperationAccuracies(), op->coordinateOperationAccuracies()); EXPECT_EQ(opNormalized->remarks(), op->remarks()); EXPECT_STREQ( opNormalized->nameStr().c_str(), (op->nameStr() + " (with axis order normalized for visualization)") .c_str()); EXPECT_EQ(opNormalized->domains().size(), 1U); } } // --------------------------------------------------------------------------- TEST(operation, export_of_Geographic3D_to_GravityRelatedHeight_gtx_unknown_grid) { auto wkt = "COORDINATEOPERATION[\"bla\",\n" " SOURCECRS[\n" " GEOGCRS[\"ETRS89\",\n" " DATUM[\"European Terrestrial Reference System 1989\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4937]]],\n" " TARGETCRS[\n" " VERTCRS[\"bar\",\n" " VDATUM[\"bar\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]],\n" " METHOD[\"Geographic3D to GravityRelatedHeight (gtx)\",\n" " ID[\"EPSG\",9665]],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"foo.gtx\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); // Test that even if the .gtx file is unknown, we export in the correct // direction EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=vgridshift +grids=foo.gtx +multiplier=1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_FALSE(transf->requiresPerCoordinateInputTime()); } // --------------------------------------------------------------------------- TEST(operation, export_of_boundCRS_with_proj_string_method) { auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " GEOGCRS[\"unknown\",\n" " DATUM[\"Unknown based on GRS80 ellipsoid\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7019]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"longitude\",east,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"latitude\",north,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " ABRIDGEDTRANSFORMATION[\"Transformation from unknown to WGS84\",\n" " METHOD[\"PROJ-based operation method: +proj=pipeline +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=axisswap " "+order=2,1 " "+step +proj=cart +ellps=GRS80 +step +proj=helmert " "+convention=coordinate_frame +exact +step +inv +proj=cart " "+ellps=WGS84 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg\"]]]"; auto obj = WKTParser().createFromWKT(wkt); auto boundCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ(boundCRS->transformation()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=axisswap +order=2,1 " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +convention=coordinate_frame +exact " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); } // --------------------------------------------------------------------------- TEST(operation, PointMotionOperation_with_epochs) { auto wkt = "POINTMOTIONOPERATION[\"Canada velocity grid v7 from epoch 2010 to " "epoch 2002\",\n" " SOURCECRS[\n" " GEOGCRS[\"NAD83(CSRS)v7\",\n" " DATUM[\"North American Datum of 1983 (CSRS) version 7\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",8254]]],\n" " METHOD[\"Point motion by grid (Canada NTv2_Vel)\",\n" " ID[\"EPSG\",1070]],\n" " PARAMETERFILE[\"Point motion velocity grid " "file\",\"ca_nrc_NAD83v70VG.tif\"],\n" " OPERATIONACCURACY[0.01],\n" " ID[\"DERIVED_FROM(EPSG)\",9483],\n" " REMARK[\"File initially published with name cvg70.cvb, later " "renamed to NAD83v70VG.gvb with no change of content.\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto pmo = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(pmo != nullptr); EXPECT_EQ(pmo->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=2010 +omit_fwd " "+step +proj=deformation +dt=-8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(pmo->inverse()->nameStr(), "Canada velocity grid v7 from epoch 2002 to epoch 2010"); EXPECT_EQ( pmo->inverse()->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=set +v_4=2002 +omit_fwd " "+step +proj=deformation +dt=8 +grids=ca_nrc_NAD83v70VG.tif " "+ellps=GRS80 " "+step +proj=set +v_4=2010 +omit_inv " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, export_of_Cartesian_Grid_Offsets_with_EngineeringCRS) { auto wkt = "COORDINATEOPERATION[\"CIG85 to GDA94 / MGA zone 48\",\n" " VERSION[\"GA-Cxr\"],\n" " SOURCECRS[\n" " ENGCRS[\"Christmas Island Grid 1985\",\n" " EDATUM[\"Christmas Island Datum 1985\"],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",6715]]],\n" " TARGETCRS[\n" " PROJCRS[\"GDA94 / MGA zone 48\",\n" " BASEGEOGCRS[\"GDA94\",\n" " DATUM[\"Geocentric Datum of Australia 1994\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4283]],\n" " CONVERSION[\"Map Grid of Australia zone 48\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",105,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",10000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",28348]]],\n" " METHOD[\"Cartesian Grid Offsets\",\n" " ID[\"EPSG\",9656]],\n" " PARAMETER[\"Easting offset\",550015,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8728]],\n" " PARAMETER[\"Northing offset\",8780001,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8729]],\n" " OPERATIONACCURACY[5],\n" " ID[\"EPSG\",6724]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +xoff=550015 +yoff=8780001"); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=affine +xoff=-550015 +yoff=-8780001"); } // --------------------------------------------------------------------------- TEST(operation, Geographic3DToGravityRelatedHeight) { auto wkt = "COORDINATEOPERATION[\"test\",\n" " SOURCECRS[\n" " GEOGCRS[\"foo\",\n" " DATUM[\"foo\",\n" " ELLIPSOID[\"International 1924\",6378388,297,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]]]],\n" " TARGETCRS[\n" " VERTCRS[\"foo height\",\n" " VDATUM[\"foo Vertical Datum\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]],\n" " METHOD[\"Geographic3D to GravityRelatedHeight\",\n" " ID[\"EPSG\",1136]],\n" " PARAMETER[\"Geoid height\",10,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8604]]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +zoff=-10"); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=affine +zoff=-10"); } // --------------------------------------------------------------------------- TEST(operation, inverse_of_Geographic3DToGravityRelatedHeight) { auto wkt = "COORDINATEOPERATION[\"test\",\n" " SOURCECRS[\n" " VERTCRS[\"foo height\",\n" " VDATUM[\"foo Vertical Datum\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]],\n" " TARGETCRS[\n" " GEOGCRS[\"foo\",\n" " DATUM[\"foo\",\n" " ELLIPSOID[\"International 1924\",6378388,297,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]]]],\n" " METHOD[\"Inverse of Geographic3D to GravityRelatedHeight\",\n" " ID[\"INVERSE(EPSG)\",1136]],\n" " PARAMETER[\"Geoid height\",10,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8604]]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +zoff=10"); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline +step +inv +proj=affine +zoff=10"); } // --------------------------------------------------------------------------- TEST(operation, CoordinateFrameRotationFullMatrixGeog2D) { auto wkt = "COORDINATEOPERATION[\"Saba to WGS 84 (1)\",\n" " VERSION[\"IOGP-Bes Saba\"],\n" " SOURCECRS[\n" " GEOGCRS[\"Saba\",\n" " DATUM[\"Saba\",\n" " ELLIPSOID[\"International 1924\",6378388,297,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",10636]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n" " MEMBER[\"World Geodetic System 1984 (Transit)\"],\n" " MEMBER[\"World Geodetic System 1984 (G730)\"],\n" " MEMBER[\"World Geodetic System 1984 (G873)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1150)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1674)\"],\n" " MEMBER[\"World Geodetic System 1984 (G1762)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2139)\"],\n" " MEMBER[\"World Geodetic System 1984 (G2296)\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[2.0]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " METHOD[\"Coordinate Frame rotation full matrix (geog2D)\",\n" " ID[\"EPSG\",1133]],\n" " PARAMETER[\"X-axis translation\",1138.7432,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",-2064.4761,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",110.7016,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",-214.615206,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",479.360036,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",-164.703951,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",-402.32073,\n" " SCALEUNIT[\"parts per million\",1E-06],\n" " ID[\"EPSG\",8611]],\n" " OPERATIONACCURACY[1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=intl " "+step +proj=helmert +exact " "+x=1138.7432 +y=-2064.4761 +z=110.7016 " "+rx=-214.615206 +ry=479.360036 +rz=-164.703951 +s=-402.32073 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=WGS84 " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=push +v_3 " "+step +proj=cart +ellps=WGS84 " "+step +inv +proj=helmert +exact " "+x=1138.7432 +y=-2064.4761 +z=110.7016 " "+rx=-214.615206 +ry=479.360036 +rz=-164.703951 +s=-402.32073 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=intl " "+step +proj=pop +v_3 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, operation_Geog3D_to_Geog2D_GravityRelatedHeight_with_compoundCRS) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("10753", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_EQ( op->inverse()->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); } // --------------------------------------------------------------------------- TEST(operation, helmert_between_geog3D_and_compound) { auto wkt = "COORDINATEOPERATION[\"ETRS89/DREF91/2016 to Asse 2025 + Asse 2025 " "height (1)\",\n" " VERSION[\"BGE-Deu Asse\"],\n" " SOURCECRS[\n" " GEOGCRS[\"ETRS89/DREF91/2016\",\n" " DATUM[\"ETRS89/DREF91 Realization 2016\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height (h)\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",10283]]],\n" " TARGETCRS[\n" " COMPOUNDCRS[\"Asse 2025 + Asse 2025 height\",\n" " GEOGCRS[\"Asse 2025\",\n" " DATUM[\"Asse geodetic datum 2025\",\n" " ELLIPSOID[\"Bessel " "1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " VERTCRS[\"Asse 2025 height\",\n" " VDATUM[\"Asse vertical datum 2025\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]],\n" " ID[\"EPSG\",10904]]],\n" " METHOD[\"Coordinate Frame rotation (geog3D domain)\",\n" " ID[\"EPSG\",1038]],\n" " PARAMETER[\"X-axis translation\",-646.6552,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",-165.0859,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",-437.6858,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",4.77773,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",-0.39139,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",-1.07485,\n" " ANGLEUNIT[\"arc-second\",4.84813681109536E-06],\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",2.0025,\n" " SCALEUNIT[\"parts per million\",1E-06],\n" " ID[\"EPSG\",8611]],\n" " OPERATIONACCURACY[0.04],\n" " USAGE[\n" " SCOPE[\"Engineering survey, GIS, topographic mapping.\"],\n" " AREA[\"Germany - Lower Saxony - Asse mining area.\"],\n" " BBOX[52.11,10.6,52.16,10.7]],\n" " ID[\"EPSG\",10905],\n" " REMARK[\"3-dimensional transformation defining the horizontal " "and vertical CRSs for the Asse 2 mining area.\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto transf = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(transf != nullptr); EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m " "+step +proj=cart +ellps=GRS80 " "+step +proj=helmert +x=-646.6552 +y=-165.0859 +z=-437.6858 " "+rx=4.77773 +ry=-0.39139 +rz=-1.07485 +s=2.0025 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=bessel " "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1"); EXPECT_EQ(transf->inverse()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=cart +ellps=bessel " "+step +inv +proj=helmert +x=-646.6552 +y=-165.0859 +z=-437.6858 " "+rx=4.77773 +ry=-0.39139 +rz=-1.07485 +s=2.0025 " "+convention=coordinate_frame " "+step +inv +proj=cart +ellps=GRS80 " "+step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(operation, operation_Geographic2D_Offsets_by_TIN_Interpolation_JSON) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto op = factory->createCoordinateOperation("10854", false); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=tinshift +file=no_kv_ETRS89NO_NGO48_TIN.json " "+step +proj=axisswap +order=2,1"); EXPECT_EQ( op->inverse()->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +inv +proj=tinshift +file=no_kv_ETRS89NO_NGO48_TIN.json " "+step +proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST( operation, operation_Position_Vector_geocen_and_geocen_translations_NEU_velocities_gtg_with_source_epoch) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto op = factory->createCoordinateOperation("10814", false); EXPECT_EQ(op->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=pipeline " "+step +proj=helmert +x=-0.05027 +y=-0.11595 +z=0.03012 " "+rx=-0.00310814 +ry=0.00457237 +rz=0.00472406 +s=0.003191 " "+convention=position_vector " "+step +proj=deformation +dt=-2.44 +grids=eur_nkg_nkgrf17vel.tif " "+ellps=GRS80"); EXPECT_EQ( op->inverse()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=pipeline " "+step +inv +proj=deformation +dt=-2.44 +grids=eur_nkg_nkgrf17vel.tif " "+ellps=GRS80 " "+step +inv +proj=helmert +x=-0.05027 +y=-0.11595 +z=0.03012 " "+rx=-0.00310814 +ry=0.00457237 +rz=0.00472406 +s=0.003191 " "+convention=position_vector"); } // --------------------------------------------------------------------------- TEST( operation, operation_geocen_translations_by_grid_and_geocen_translations_NEU_velocities_gtg) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto op = factory->createCoordinateOperation("10811", false); EXPECT_EQ(op->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=pipeline " "+step +proj=xyzgridshift " "+grids=no_kv_NKGETRF14_EPSG7922_2000.tif " "+step +proj=deformation +dt=-5 +grids=eur_nkg_nkgrf17vel.tif " "+ellps=GRS80"); EXPECT_EQ( op->inverse()->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=pipeline " "+step +inv +proj=deformation +dt=-5 +grids=eur_nkg_nkgrf17vel.tif " "+ellps=GRS80 " "+step +inv +proj=xyzgridshift " "+grids=no_kv_NKGETRF14_EPSG7922_2000.tif"); } // --------------------------------------------------------------------------- TEST(operation, operation_geocen_translations_NEU_velocities_gtg) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto op = factory->createCoordinateOperation("10809", false); EXPECT_EQ(op->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=pipeline " "+step +inv +proj=deformation +t_epoch=2000 " "+grids=eur_nkg_nkgrf17vel.tif +ellps=GRS80"); EXPECT_EQ(op->inverse()->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext) .get()), "+proj=deformation +t_epoch=2000 +grids=eur_nkg_nkgrf17vel.tif " "+ellps=GRS80"); } proj-9.8.1/test/unit/include_proj_h_from_c.c000664 001750 001750 00000003023 15166171715 021064 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Dummy test to check that we can include proj.h from a pure C file * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "proj.h" #include "proj_experimental.h" int main() { return 0; } proj-9.8.1/test/unit/test_datum.cpp000664 001750 001750 00000066252 15166171715 017301 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include "proj/common.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" using namespace osgeo::proj::common; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::metadata; using namespace osgeo::proj::util; namespace { struct UnrelatedObject : public IComparable { UnrelatedObject() = default; bool _isEquivalentTo(const IComparable *, Criterion, const DatabaseContextPtr &) const override { assert(false); return false; } }; static nn> createUnrelatedObject() { return nn_make_shared(); } } // namespace // --------------------------------------------------------------------------- TEST(datum, ellipsoid_from_sphere) { auto ellipsoid = Ellipsoid::createSphere(PropertyMap(), Length(6378137)); EXPECT_FALSE(ellipsoid->inverseFlattening().has_value()); EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value()); EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value()); EXPECT_TRUE(ellipsoid->isSphere()); EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137)); EXPECT_EQ(ellipsoid->celestialBody(), "Earth"); EXPECT_EQ(ellipsoid->computeSemiMinorAxis(), Length(6378137)); EXPECT_EQ(ellipsoid->computedInverseFlattening(), 0); EXPECT_EQ( ellipsoid->exportToPROJString(PROJStringFormatter::create().get()), "+R=6378137"); EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get())); EXPECT_FALSE(ellipsoid->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(datum, ellipsoid_non_earth) { auto ellipsoid = Ellipsoid::createSphere(PropertyMap(), Length(1), "Unity sphere"); EXPECT_EQ(ellipsoid->celestialBody(), "Unity sphere"); } // --------------------------------------------------------------------------- TEST(datum, ellipsoid_from_inverse_flattening) { auto ellipsoid = Ellipsoid::createFlattenedSphere( PropertyMap(), Length(6378137), Scale(298.257223563)); EXPECT_TRUE(ellipsoid->inverseFlattening().has_value()); EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value()); EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value()); EXPECT_FALSE(ellipsoid->isSphere()); EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137)); EXPECT_EQ(*ellipsoid->inverseFlattening(), Scale(298.257223563)); EXPECT_EQ(ellipsoid->computeSemiMinorAxis().unit(), ellipsoid->semiMajorAxis().unit()); EXPECT_NEAR(ellipsoid->computeSemiMinorAxis().value(), Length(6356752.31424518).value(), 1e-9); EXPECT_EQ(ellipsoid->computedInverseFlattening(), 298.257223563); EXPECT_EQ( ellipsoid->exportToPROJString(PROJStringFormatter::create().get()), "+ellps=WGS84"); EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get())); EXPECT_FALSE(ellipsoid->isEquivalentTo( Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137), Length(6356752.31424518)) .get())); EXPECT_TRUE(ellipsoid->isEquivalentTo( Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137), Length(6356752.31424518)) .get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(Ellipsoid::WGS84->isEquivalentTo( Ellipsoid::GRS1980.get(), IComparable::Criterion::EQUIVALENT)); auto sphere = Ellipsoid::createSphere(PropertyMap(), Length(6378137)); EXPECT_FALSE(Ellipsoid::WGS84->isEquivalentTo( sphere.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(sphere->isEquivalentTo(Ellipsoid::WGS84.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(datum, ellipsoid_from_null_inverse_flattening) { auto ellipsoid = Ellipsoid::createFlattenedSphere( PropertyMap(), Length(6378137), Scale(0)); EXPECT_FALSE(ellipsoid->inverseFlattening().has_value()); EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value()); EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value()); EXPECT_TRUE(ellipsoid->isSphere()); } // --------------------------------------------------------------------------- TEST(datum, ellipsoid_from_semi_minor_axis) { auto ellipsoid = Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137), Length(6356752.31424518)); EXPECT_FALSE(ellipsoid->inverseFlattening().has_value()); EXPECT_TRUE(ellipsoid->semiMinorAxis().has_value()); EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value()); EXPECT_FALSE(ellipsoid->isSphere()); EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137)); EXPECT_EQ(*ellipsoid->semiMinorAxis(), Length(6356752.31424518)); EXPECT_EQ(ellipsoid->computeSemiMinorAxis(), Length(6356752.31424518)); EXPECT_NEAR(ellipsoid->computedInverseFlattening(), 298.257223563, 1e-10); EXPECT_EQ( ellipsoid->exportToPROJString(PROJStringFormatter::create().get()), "+ellps=WGS84"); EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get())); EXPECT_FALSE(ellipsoid->isEquivalentTo( Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137), Scale(298.257223563)) .get())); EXPECT_TRUE(ellipsoid->isEquivalentTo( Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137), Scale(298.257223563)) .get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(datum, prime_meridian_to_PROJString) { EXPECT_EQ(PrimeMeridian::GREENWICH->exportToPROJString( PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_EQ(PrimeMeridian::PARIS->exportToPROJString( PROJStringFormatter::create().get()), "+pm=paris"); EXPECT_EQ(PrimeMeridian::create(PropertyMap(), Angle(3.5)) ->exportToPROJString(PROJStringFormatter::create().get()), "+pm=3.5"); EXPECT_EQ( PrimeMeridian::create(PropertyMap(), Angle(100, UnitOfMeasure::GRAD)) ->exportToPROJString(PROJStringFormatter::create().get()), "+pm=90"); EXPECT_EQ( PrimeMeridian::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Origin meridian"), Angle(0)) ->exportToPROJString(PROJStringFormatter::create().get()), "+proj=noop"); EXPECT_TRUE(PrimeMeridian::GREENWICH->isEquivalentTo( PrimeMeridian::GREENWICH.get())); EXPECT_FALSE(PrimeMeridian::GREENWICH->isEquivalentTo( createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(datum, prime_meridian_to_JSON) { EXPECT_EQ(PrimeMeridian::GREENWICH->exportToJSON( &(JSONFormatter::create()->setSchema(""))), "{\n" " \"type\": \"PrimeMeridian\",\n" " \"name\": \"Greenwich\",\n" " \"longitude\": 0,\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8901\n" " }\n" "}"); EXPECT_EQ(PrimeMeridian::PARIS->exportToJSON( &(JSONFormatter::create()->setSchema(""))), "{\n" " \"type\": \"PrimeMeridian\",\n" " \"name\": \"Paris\",\n" " \"longitude\": {\n" " \"value\": 2.5969213,\n" " \"unit\": {\n" " \"type\": \"AngularUnit\",\n" " \"name\": \"grad\",\n" " \"conversion_factor\": 0.015707963267949\n" " }\n" " },\n" " \"id\": {\n" " \"authority\": \"EPSG\",\n" " \"code\": 8903\n" " }\n" "}"); } // --------------------------------------------------------------------------- TEST(datum, datum_with_ANCHOR) { auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS_1984 with anchor"), Ellipsoid::WGS84, optional("My anchor"), PrimeMeridian::GREENWICH); ASSERT_TRUE(datum->anchorDefinition()); EXPECT_EQ(*datum->anchorDefinition(), "My anchor"); ASSERT_FALSE(datum->anchorEpoch()); auto expected = "DATUM[\"WGS_1984 with anchor\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOR[\"My anchor\"]]"; EXPECT_EQ(datum->exportToWKT(WKTFormatter::create().get()), expected); } // --------------------------------------------------------------------------- TEST(datum, datum_with_ANCHOREPOCH) { auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my_datum"), Ellipsoid::WGS84, optional(), optional(Measure(2002.5, UnitOfMeasure::YEAR)), PrimeMeridian::GREENWICH); ASSERT_FALSE(datum->anchorDefinition()); ASSERT_TRUE(datum->anchorEpoch()); EXPECT_NEAR(datum->anchorEpoch()->convertToUnit(UnitOfMeasure::YEAR), 2002.5, 1e-8); auto expected = "DATUM[\"my_datum\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOREPOCH[2002.5]]"; EXPECT_EQ( datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(datum, unknown_datum) { auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my_datum"), Ellipsoid::GRS1980, optional(), optional(), PrimeMeridian::GREENWICH); auto unknown_datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown"), Ellipsoid::GRS1980, optional(), optional(), PrimeMeridian::GREENWICH); EXPECT_FALSE(datum->isEquivalentTo(unknown_datum.get(), IComparable::Criterion::STRICT)); EXPECT_TRUE(datum->isEquivalentTo(unknown_datum.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(unknown_datum->isEquivalentTo(datum.get(), IComparable::Criterion::STRICT)); EXPECT_TRUE(unknown_datum->isEquivalentTo( datum.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(datum, compare_with_D_prefixing) { auto my_datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my_datum"), Ellipsoid::GRS1980, optional(), optional(), PrimeMeridian::GREENWICH); auto d_my_datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "D_my_datum"), Ellipsoid::GRS1980, optional(), optional(), PrimeMeridian::GREENWICH); EXPECT_FALSE(my_datum->isEquivalentTo(d_my_datum.get(), IComparable::Criterion::STRICT)); EXPECT_TRUE(my_datum->isEquivalentTo(d_my_datum.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(d_my_datum->isEquivalentTo(my_datum.get(), IComparable::Criterion::STRICT)); EXPECT_TRUE(d_my_datum->isEquivalentTo(my_datum.get(), IComparable::Criterion::EQUIVALENT)); auto d_other_datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "D_other_datum"), Ellipsoid::GRS1980, optional(), optional(), PrimeMeridian::GREENWICH); EXPECT_FALSE(my_datum->isEquivalentTo(d_other_datum.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(d_my_datum->isEquivalentTo( d_other_datum.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(d_other_datum->isEquivalentTo( my_datum.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(datum, dynamic_geodetic_reference_frame) { auto drf = DynamicGeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), Ellipsoid::WGS84, optional("My anchor"), PrimeMeridian::GREENWICH, Measure(2018.5, UnitOfMeasure::YEAR), optional("My model")); auto expected = "DATUM[\"test\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOR[\"My anchor\"]]"; EXPECT_EQ(drf->exportToWKT(WKTFormatter::create().get()), expected); auto expected_wtk2_2019 = "DYNAMIC[\n" " FRAMEEPOCH[2018.5],\n" " MODEL[\"My model\"]],\n" "DATUM[\"test\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ANCHOR[\"My anchor\"]]"; EXPECT_EQ( drf->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected_wtk2_2019); EXPECT_TRUE(drf->isEquivalentTo(drf.get())); EXPECT_TRUE( drf->isEquivalentTo(drf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(drf->isEquivalentTo(createUnrelatedObject().get())); // "Same" datum, except that it is a non-dynamic one auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), Ellipsoid::WGS84, optional("My anchor"), PrimeMeridian::GREENWICH); EXPECT_FALSE(datum->isEquivalentTo(drf.get())); EXPECT_FALSE(drf->isEquivalentTo(datum.get())); EXPECT_TRUE( datum->isEquivalentTo(drf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( drf->isEquivalentTo(datum.get(), IComparable::Criterion::EQUIVALENT)); auto unrelated_datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test2"), Ellipsoid::WGS84, optional("My anchor"), PrimeMeridian::GREENWICH); EXPECT_FALSE(unrelated_datum->isEquivalentTo(drf.get())); EXPECT_FALSE(drf->isEquivalentTo(unrelated_datum.get())); EXPECT_FALSE(unrelated_datum->isEquivalentTo( drf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(drf->isEquivalentTo(unrelated_datum.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(datum, ellipsoid_to_PROJString) { EXPECT_EQ(Ellipsoid::WGS84->exportToPROJString( PROJStringFormatter::create().get()), "+ellps=WGS84"); EXPECT_EQ(Ellipsoid::GRS1980->exportToPROJString( PROJStringFormatter::create().get()), "+ellps=GRS80"); EXPECT_EQ( Ellipsoid::createFlattenedSphere( PropertyMap(), Length(10, UnitOfMeasure("km", 1000)), Scale(0.5)) ->exportToPROJString(PROJStringFormatter::create().get()), "+a=10000 +rf=0.5"); EXPECT_EQ(Ellipsoid::createTwoAxis(PropertyMap(), Length(10, UnitOfMeasure("km", 1000)), Length(5, UnitOfMeasure("km", 1000))) ->exportToPROJString(PROJStringFormatter::create().get()), "+a=10000 +b=5000"); } // --------------------------------------------------------------------------- TEST(datum, temporal_datum_WKT2) { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"), DateTime::create("0000-01-01"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); auto expected = "TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]]"; EXPECT_EQ(datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); EXPECT_THROW( datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); EXPECT_TRUE(datum->isEquivalentTo(datum.get())); EXPECT_FALSE(datum->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(datum, temporal_datum_time_origin_non_ISO8601) { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"), DateTime::create("0001 January 1st"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); auto expected = "TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[\"0001 January 1st\"]]"; EXPECT_EQ(datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); } // --------------------------------------------------------------------------- TEST(datum, temporal_datum_WKT2_2019) { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"), DateTime::create("0000-01-01"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); auto expected = "TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]"; EXPECT_EQ( datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(datum, dynamic_vertical_reference_frame) { auto drf = DynamicVerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), optional("My anchor"), optional(), Measure(2018.5, UnitOfMeasure::YEAR), optional("My model")); auto expected = "VDATUM[\"test\",\n" " ANCHOR[\"My anchor\"]]"; EXPECT_EQ(drf->exportToWKT(WKTFormatter::create().get()), expected); auto expected_wtk2_2019 = "DYNAMIC[\n" " FRAMEEPOCH[2018.5],\n" " MODEL[\"My model\"]],\n" "VDATUM[\"test\",\n" " ANCHOR[\"My anchor\"]]"; EXPECT_EQ( drf->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected_wtk2_2019); EXPECT_TRUE(drf->isEquivalentTo(drf.get())); EXPECT_TRUE( drf->isEquivalentTo(drf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(drf->isEquivalentTo(createUnrelatedObject().get())); // "Same" datum, except that it is a non-dynamic one auto datum = VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), optional("My anchor"), optional()); EXPECT_FALSE(datum->isEquivalentTo(drf.get())); EXPECT_FALSE(drf->isEquivalentTo(datum.get())); EXPECT_TRUE( datum->isEquivalentTo(drf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( drf->isEquivalentTo(datum.get(), IComparable::Criterion::EQUIVALENT)); auto unrelated_datum = VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test2"), optional("My anchor"), optional()); EXPECT_FALSE(unrelated_datum->isEquivalentTo(drf.get())); EXPECT_FALSE(drf->isEquivalentTo(unrelated_datum.get())); EXPECT_FALSE(unrelated_datum->isEquivalentTo( drf.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(drf->isEquivalentTo(unrelated_datum.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(datum, datum_ensemble) { auto otherDatum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "other datum"), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH); auto ensemble = DatumEnsemble::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), std::vector{GeodeticReferenceFrame::EPSG_6326, otherDatum}, PositionalAccuracy::create("100")); EXPECT_EQ(ensemble->datums().size(), 2U); EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100"); EXPECT_EQ( ensemble->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), "ENSEMBLE[\"test\",\n" " MEMBER[\"World Geodetic System 1984\",\n" " ID[\"EPSG\",6326]],\n" " MEMBER[\"other datum\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ENSEMBLEACCURACY[100]]"); EXPECT_EQ( ensemble->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()), "DATUM[\"test\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]]]"); } // --------------------------------------------------------------------------- TEST(datum, datum_ensemble_vertical) { auto ensemble = DatumEnsemble::create( PropertyMap(), std::vector{ VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")), VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))}, PositionalAccuracy::create("100")); EXPECT_EQ( ensemble->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), "ENSEMBLE[\"unnamed\",\n" " MEMBER[\"vdatum1\"],\n" " MEMBER[\"vdatum2\"],\n" " ENSEMBLEACCURACY[100]]"); } // --------------------------------------------------------------------------- TEST(datum, datum_ensemble_exceptions) { // No datum EXPECT_THROW(DatumEnsemble::create(PropertyMap(), std::vector{}, PositionalAccuracy::create("100")), Exception); // Single datum EXPECT_THROW(DatumEnsemble::create( PropertyMap(), std::vector{GeodeticReferenceFrame::EPSG_6326}, PositionalAccuracy::create("100")), Exception); auto vdatum = VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")); // Different datum type EXPECT_THROW( DatumEnsemble::create( PropertyMap(), std::vector{GeodeticReferenceFrame::EPSG_6326, vdatum}, PositionalAccuracy::create("100")), Exception); // Different datum type EXPECT_THROW( DatumEnsemble::create( PropertyMap(), std::vector{vdatum, GeodeticReferenceFrame::EPSG_6326}, PositionalAccuracy::create("100")), Exception); // Different ellipsoid EXPECT_THROW(DatumEnsemble::create( PropertyMap(), std::vector{GeodeticReferenceFrame::EPSG_6326, GeodeticReferenceFrame::EPSG_6267}, PositionalAccuracy::create("100")), Exception); // Different prime meridian EXPECT_THROW(DatumEnsemble::create( PropertyMap(), std::vector{ GeodeticReferenceFrame::EPSG_6326, GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "other datum"), Ellipsoid::WGS84, optional(), PrimeMeridian::PARIS)}, PositionalAccuracy::create("100")), Exception); } // --------------------------------------------------------------------------- TEST(datum, edatum) { auto datum = EngineeringDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering datum"), optional("my anchor")); auto expected = "EDATUM[\"Engineering datum\",\n" " ANCHOR[\"my anchor\"]]"; EXPECT_EQ(datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); } // --------------------------------------------------------------------------- TEST(datum, pdatum) { auto datum = ParametricDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric datum"), optional("my anchor")); auto expected = "PDATUM[\"Parametric datum\",\n" " ANCHOR[\"my anchor\"]]"; EXPECT_EQ(datum->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); } proj-9.8.1/test/unit/test_metadata.cpp000664 001750 001750 00000053061 15166171715 017741 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" #include #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" using namespace osgeo::proj::io; using namespace osgeo::proj::metadata; using namespace osgeo::proj::util; // --------------------------------------------------------------------------- TEST(metadata, citation) { Citation c("my citation"); Citation c2(c); ASSERT_TRUE(c2.title().has_value()); ASSERT_EQ(*(c2.title()), "my citation"); } // --------------------------------------------------------------------------- static bool equals(ExtentNNPtr extent1, ExtentNNPtr extent2) { return extent1->contains(extent2) && extent2->contains(extent1); } static bool equals(GeographicExtentNNPtr extent1, GeographicExtentNNPtr extent2) { return extent1->contains(extent2) && extent2->contains(extent1); } static GeographicExtentNNPtr getBBox(ExtentNNPtr extent) { assert(extent->geographicElements().size() == 1); return extent->geographicElements()[0]; } TEST(metadata, extent) { Extent::create( optional(), std::vector(), std::vector(), std::vector()); auto world = Extent::createFromBBOX(-180, -90, 180, 90); EXPECT_TRUE(world->isEquivalentTo(world.get())); EXPECT_TRUE(world->contains(world)); auto west_hemisphere = Extent::createFromBBOX(-180, -90, 0, 90); EXPECT_TRUE(!world->isEquivalentTo(west_hemisphere.get())); EXPECT_TRUE(world->contains(west_hemisphere)); EXPECT_TRUE(!west_hemisphere->contains(world)); auto world_inter_world = world->intersection(world); ASSERT_TRUE(world_inter_world != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_world), world)); auto france = Extent::createFromBBOX(-5, 40, 12, 51); EXPECT_TRUE(france->contains(france)); EXPECT_TRUE(world->contains(france)); EXPECT_TRUE(!france->contains( world)); // We are only speaking about geography here ;-) EXPECT_TRUE(world->intersects(france)); EXPECT_TRUE(france->intersects(world)); auto france_inter_france = france->intersection(france); ASSERT_TRUE(france_inter_france != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(france_inter_france), france)); auto france_inter_world = france->intersection(world); ASSERT_TRUE(france_inter_world != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(france_inter_world), france)); auto world_inter_france = world->intersection(france); ASSERT_TRUE(world_inter_france != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_france), france)); auto france_shifted = Extent::createFromBBOX(-5 + 5, 40 + 5, 12 + 5, 51 + 5); EXPECT_TRUE(france->intersects(france_shifted)); EXPECT_TRUE(france_shifted->intersects(france)); EXPECT_TRUE(!france->contains(france_shifted)); EXPECT_TRUE(!france_shifted->contains(france)); auto europe = Extent::createFromBBOX(-30, 25, 30, 70); EXPECT_TRUE(europe->contains(france)); EXPECT_TRUE(!france->contains(europe)); auto france_inter_europe = france->intersection(europe); ASSERT_TRUE(france_inter_europe != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(france_inter_europe), france)); auto europe_intersects_france = europe->intersection(france); ASSERT_TRUE(europe_intersects_france != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(europe_intersects_france), france)); auto nz = Extent::createFromBBOX(155.0, -60.0, -170.0, -25.0); EXPECT_TRUE(nz->contains(nz)); EXPECT_TRUE(world->contains(nz)); EXPECT_TRUE(nz->intersects(world)); EXPECT_TRUE(world->intersects(nz)); EXPECT_TRUE(!nz->contains(world)); EXPECT_TRUE(!nz->contains(france)); EXPECT_TRUE(!france->contains(nz)); EXPECT_TRUE(!nz->intersects(france)); EXPECT_TRUE(!france->intersects(nz)); { auto nz_inter_world = nz->intersection(world); ASSERT_TRUE(nz_inter_world != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world), nz)); } { auto nz_inter_world = getBBox(nz)->intersection(getBBox(world)); ASSERT_TRUE(nz_inter_world != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world), getBBox(nz))); } { auto world_inter_nz = world->intersection(nz); ASSERT_TRUE(world_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_nz), nz)); } { auto world_inter_nz = getBBox(world)->intersection(getBBox(nz)); ASSERT_TRUE(world_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_nz), getBBox(nz))); } EXPECT_TRUE(nz->intersection(france) == nullptr); EXPECT_TRUE(france->intersection(nz) == nullptr); auto bbox_antimeridian_north = Extent::createFromBBOX(155.0, 10.0, -170.0, 30.0); EXPECT_TRUE(!nz->contains(bbox_antimeridian_north)); EXPECT_TRUE(!bbox_antimeridian_north->contains(nz)); EXPECT_TRUE(!nz->intersects(bbox_antimeridian_north)); EXPECT_TRUE(!bbox_antimeridian_north->intersects(nz)); EXPECT_TRUE(!nz->intersection(bbox_antimeridian_north)); EXPECT_TRUE(!bbox_antimeridian_north->intersection(nz)); auto nz_pos_long = Extent::createFromBBOX(155.0, -60.0, 180.0, -25.0); EXPECT_TRUE(nz->contains(nz_pos_long)); EXPECT_TRUE(!nz_pos_long->contains(nz)); EXPECT_TRUE(nz->intersects(nz_pos_long)); EXPECT_TRUE(nz_pos_long->intersects(nz)); auto nz_inter_nz_pos_long = nz->intersection(nz_pos_long); ASSERT_TRUE(nz_inter_nz_pos_long != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_nz_pos_long), nz_pos_long)); auto nz_pos_long_inter_nz = nz_pos_long->intersection(nz); ASSERT_TRUE(nz_pos_long_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_pos_long_inter_nz), nz_pos_long)); auto nz_neg_long = Extent::createFromBBOX(-180.0, -60.0, -170.0, -25.0); EXPECT_TRUE(nz->contains(nz_neg_long)); EXPECT_TRUE(!nz_neg_long->contains(nz)); EXPECT_TRUE(nz->intersects(nz_neg_long)); EXPECT_TRUE(nz_neg_long->intersects(nz)); auto nz_inter_nz_neg_long = nz->intersection(nz_neg_long); ASSERT_TRUE(nz_inter_nz_neg_long != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_nz_neg_long), nz_neg_long)); auto nz_neg_long_inter_nz = nz_neg_long->intersection(nz); ASSERT_TRUE(nz_neg_long_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_neg_long_inter_nz), nz_neg_long)); auto nz_smaller = Extent::createFromBBOX(160, -55.0, -175.0, -30.0); EXPECT_TRUE(nz->contains(nz_smaller)); EXPECT_TRUE(!nz_smaller->contains(nz)); auto nz_pos_long_shifted_west = Extent::createFromBBOX(150.0, -60.0, 175.0, -25.0); EXPECT_TRUE(!nz->contains(nz_pos_long_shifted_west)); EXPECT_TRUE(!nz_pos_long_shifted_west->contains(nz)); EXPECT_TRUE(nz->intersects(nz_pos_long_shifted_west)); EXPECT_TRUE(nz_pos_long_shifted_west->intersects(nz)); auto nz_smaller_shifted = Extent::createFromBBOX(165, -60.0, -170.0, -25.0); EXPECT_TRUE(!nz_smaller->contains(nz_smaller_shifted)); EXPECT_TRUE(!nz_smaller_shifted->contains(nz_smaller)); EXPECT_TRUE(nz_smaller->intersects(nz_smaller_shifted)); EXPECT_TRUE(nz_smaller_shifted->intersects(nz_smaller)); auto nz_shifted = Extent::createFromBBOX(165.0, -60.0, -160.0, -25.0); auto nz_intersect_nz_shifted = nz->intersection(nz_shifted); ASSERT_TRUE(nz_intersect_nz_shifted != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_intersect_nz_shifted), Extent::createFromBBOX(165, -60.0, -170.0, -25.0))); auto nz_inter_nz_smaller = nz->intersection(nz_smaller); ASSERT_TRUE(nz_inter_nz_smaller != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_nz_smaller), nz_smaller)); auto nz_smaller_inter_nz = nz_smaller->intersection(nz); ASSERT_TRUE(nz_smaller_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_smaller_inter_nz), nz_smaller)); auto world_smaller = Extent::createFromBBOX(-179, -90, 179, 90); EXPECT_TRUE(!world_smaller->contains(nz)); EXPECT_TRUE(!nz->contains(world_smaller)); auto nz_inter_world_smaller = nz->intersection(world_smaller); ASSERT_TRUE(nz_inter_world_smaller != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world_smaller), Extent::createFromBBOX(155, -60, 179, -25))); auto world_smaller_inter_nz = world_smaller->intersection(nz); ASSERT_TRUE(world_smaller_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_smaller_inter_nz), Extent::createFromBBOX(155, -60, 179, -25))); auto world_smaller_east = Extent::createFromBBOX(-179, -90, 150, 90); EXPECT_TRUE(!world_smaller_east->contains(nz)); EXPECT_TRUE(!nz->contains(world_smaller_east)); auto nz_inter_world_smaller_east = nz->intersection(world_smaller_east); ASSERT_TRUE(nz_inter_world_smaller_east != nullptr); EXPECT_EQ(nn_dynamic_pointer_cast( nz_inter_world_smaller_east->geographicElements()[0]) ->westBoundLongitude(), -179); EXPECT_EQ(nn_dynamic_pointer_cast( nz_inter_world_smaller_east->geographicElements()[0]) ->eastBoundLongitude(), -170); EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world_smaller_east), Extent::createFromBBOX(-179, -60, -170, -25))); auto world_smaller_east_inter_nz = world_smaller_east->intersection(nz); ASSERT_TRUE(world_smaller_east_inter_nz != nullptr); EXPECT_EQ(nn_dynamic_pointer_cast( world_smaller_east_inter_nz->geographicElements()[0]) ->westBoundLongitude(), -179); EXPECT_EQ(nn_dynamic_pointer_cast( world_smaller_east_inter_nz->geographicElements()[0]) ->eastBoundLongitude(), -170); EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_smaller_east_inter_nz), Extent::createFromBBOX(-179, -60, -170, -25))); auto east_hemisphere = Extent::createFromBBOX(0, -90, 180, 90); auto east_hemisphere_inter_nz = east_hemisphere->intersection(nz); ASSERT_TRUE(east_hemisphere_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(east_hemisphere_inter_nz), Extent::createFromBBOX(155.0, -60.0, 180.0, -25.0))); auto minus_180_to_156 = Extent::createFromBBOX(-180, -90, 156, 90); auto minus_180_to_156_inter_nz = minus_180_to_156->intersection(nz); ASSERT_TRUE(minus_180_to_156_inter_nz != nullptr); EXPECT_TRUE(equals(NN_CHECK_ASSERT(minus_180_to_156_inter_nz), Extent::createFromBBOX(-180.0, -60.0, -170.0, -25.0))); } // --------------------------------------------------------------------------- TEST(metadata, extent_edge_cases) { Extent::create( optional(), std::vector(), std::vector(), std::vector()); EXPECT_THROW(Extent::createFromBBOX( std::numeric_limits::quiet_NaN(), -90, 180, 90), InvalidValueTypeException); EXPECT_THROW(Extent::createFromBBOX( -180, std::numeric_limits::quiet_NaN(), 180, 90), InvalidValueTypeException); EXPECT_THROW(Extent::createFromBBOX( -180, -90, std::numeric_limits::quiet_NaN(), 90), InvalidValueTypeException); EXPECT_THROW(Extent::createFromBBOX( -180, -90, 180, std::numeric_limits::quiet_NaN()), InvalidValueTypeException); // South > north EXPECT_THROW(Extent::createFromBBOX(-100, 10, 100, 0), InvalidValueTypeException); // Scenario of https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=57328 // and https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=60084 { auto A = Extent::createFromBBOX(0, 1, 2, 3); auto B = Extent::createFromBBOX(200, -80, -100, 80); EXPECT_FALSE(A->intersects(B)); EXPECT_FALSE(B->intersects(A)); EXPECT_TRUE(A->intersection(B) == nullptr); EXPECT_TRUE(B->intersection(A) == nullptr); } { auto A = Extent::createFromBBOX(0, 1, 2, 3); auto B = Extent::createFromBBOX(100, -80, -200, 80); EXPECT_FALSE(A->intersects(B)); EXPECT_FALSE(B->intersects(A)); EXPECT_TRUE(A->intersection(B) == nullptr); EXPECT_TRUE(B->intersection(A) == nullptr); } // Test degenerate bounding box on a point { auto A = Extent::createFromBBOX(1, 2, 1, 2); auto B = Extent::createFromBBOX(-10, -10, 10, 10); EXPECT_TRUE(A->intersects(B)); EXPECT_TRUE(B->intersects(A)); EXPECT_FALSE(A->contains(B)); EXPECT_TRUE(B->contains(A)); EXPECT_TRUE(A->intersection(B) != nullptr); EXPECT_TRUE(B->intersection(A) != nullptr); } // Test degenerate bounding box on a line at long=-180 { auto A = Extent::createFromBBOX(-180, 2, -180, 3); auto B = Extent::createFromBBOX(-180, -90, 180, 90); EXPECT_TRUE(A->intersects(B)); EXPECT_TRUE(B->intersects(A)); EXPECT_FALSE(A->contains(B)); EXPECT_TRUE(B->contains(A)); EXPECT_TRUE(A->intersection(B) != nullptr); EXPECT_TRUE(B->intersection(A) != nullptr); } // Test degenerate bounding box on a line at long=180 { auto A = Extent::createFromBBOX(180, 2, 180, 3); auto B = Extent::createFromBBOX(-180, -90, 180, 90); EXPECT_TRUE(A->intersects(B)); EXPECT_TRUE(B->intersects(A)); EXPECT_FALSE(A->contains(B)); EXPECT_TRUE(B->contains(A)); EXPECT_TRUE(A->intersection(B) != nullptr); EXPECT_TRUE(B->intersection(A) != nullptr); } // Test degenerate bounding box on a line at lat=90 { auto A = Extent::createFromBBOX(2, 90, 3, 90); auto B = Extent::createFromBBOX(-180, -90, 180, 90); EXPECT_TRUE(A->intersects(B)); EXPECT_TRUE(B->intersects(A)); EXPECT_FALSE(A->contains(B)); EXPECT_TRUE(B->contains(A)); EXPECT_TRUE(A->intersection(B) != nullptr); EXPECT_TRUE(B->intersection(A) != nullptr); } // Test degenerate bounding box on a line at lat=-90 { auto A = Extent::createFromBBOX(2, -90, 3, -90); auto B = Extent::createFromBBOX(-180, -90, 180, 90); EXPECT_TRUE(A->intersects(B)); EXPECT_TRUE(B->intersects(A)); EXPECT_FALSE(A->contains(B)); EXPECT_TRUE(B->contains(A)); EXPECT_TRUE(A->intersection(B) != nullptr); EXPECT_TRUE(B->intersection(A) != nullptr); } } // --------------------------------------------------------------------------- TEST(metadata, identifier_empty) { auto id(Identifier::create()); Identifier id2(*id); ASSERT_TRUE(!id2.authority().has_value()); ASSERT_TRUE(id2.code().empty()); ASSERT_TRUE(!id2.codeSpace().has_value()); ASSERT_TRUE(!id2.version().has_value()); ASSERT_TRUE(!id2.description().has_value()); } // --------------------------------------------------------------------------- TEST(metadata, identifier_properties) { PropertyMap properties; properties.set(Identifier::AUTHORITY_KEY, "authority"); properties.set(Identifier::CODESPACE_KEY, "codespace"); properties.set(Identifier::VERSION_KEY, "version"); properties.set(Identifier::DESCRIPTION_KEY, "description"); auto id(Identifier::create("my code", properties)); Identifier id2(*id); ASSERT_TRUE(id2.authority().has_value()); ASSERT_EQ(*(id2.authority()->title()), "authority"); ASSERT_EQ(id2.code(), "my code"); ASSERT_TRUE(id2.codeSpace().has_value()); ASSERT_EQ(*(id2.codeSpace()), "codespace"); ASSERT_TRUE(id2.version().has_value()); ASSERT_EQ(*(id2.version()), "version"); ASSERT_TRUE(id2.description().has_value()); ASSERT_EQ(*(id2.description()), "description"); } // --------------------------------------------------------------------------- TEST(metadata, identifier_code_integer) { PropertyMap properties; properties.set(Identifier::CODE_KEY, 1234); auto id(Identifier::create(std::string(), properties)); ASSERT_EQ(id->code(), "1234"); } // --------------------------------------------------------------------------- TEST(metadata, identifier_code_string) { PropertyMap properties; properties.set(Identifier::CODE_KEY, "1234"); auto id(Identifier::create(std::string(), properties)); ASSERT_EQ(id->code(), "1234"); } // --------------------------------------------------------------------------- TEST(metadata, identifier_code_invalid_type) { PropertyMap properties; properties.set(Identifier::CODE_KEY, true); ASSERT_THROW(Identifier::create(std::string(), properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(metadata, identifier_authority_citation) { PropertyMap properties; properties.set(Identifier::AUTHORITY_KEY, nn_make_shared("authority")); auto id(Identifier::create(std::string(), properties)); ASSERT_TRUE(id->authority().has_value()); ASSERT_EQ(*(id->authority()->title()), "authority"); } // --------------------------------------------------------------------------- TEST(metadata, identifier_authority_invalid_type) { PropertyMap properties; properties.set(Identifier::AUTHORITY_KEY, true); ASSERT_THROW(Identifier::create(std::string(), properties), InvalidValueTypeException); } // --------------------------------------------------------------------------- TEST(metadata, id) { auto in_wkt = "ID[\"EPSG\",4946,1.5,\n" " CITATION[\"my citation\"],\n" " URI[\"urn:ogc:def:crs:EPSG::4946\"]]"; auto id = nn_dynamic_pointer_cast(WKTParser().createFromWKT(in_wkt)); ASSERT_TRUE(id != nullptr); EXPECT_TRUE(id->authority().has_value()); EXPECT_EQ(*(id->authority()->title()), "my citation"); EXPECT_EQ(*(id->codeSpace()), "EPSG"); EXPECT_EQ(id->code(), "4946"); EXPECT_TRUE(id->version().has_value()); EXPECT_EQ(*(id->version()), "1.5"); EXPECT_TRUE(id->uri().has_value()); EXPECT_EQ(*(id->uri()), "urn:ogc:def:crs:EPSG::4946"); auto got_wkt = id->exportToWKT(WKTFormatter::create().get()); EXPECT_EQ(got_wkt, in_wkt); } // --------------------------------------------------------------------------- TEST(metadata, Identifier_isEquivalentName) { EXPECT_TRUE(Identifier::isEquivalentName("", "")); EXPECT_TRUE(Identifier::isEquivalentName("x", "x")); EXPECT_TRUE(Identifier::isEquivalentName("x", "X")); EXPECT_TRUE(Identifier::isEquivalentName("X", "x")); EXPECT_FALSE(Identifier::isEquivalentName("x", "")); EXPECT_FALSE(Identifier::isEquivalentName("", "x")); EXPECT_FALSE(Identifier::isEquivalentName("x", "y")); EXPECT_TRUE(Identifier::isEquivalentName("Central_Meridian", "Central_- ()/Meridian")); EXPECT_TRUE(Identifier::isEquivalentName("\xc3\xa1", "a")); EXPECT_FALSE(Identifier::isEquivalentName("\xc3", "a")); EXPECT_TRUE(Identifier::isEquivalentName("a", "\xc3\xa1")); EXPECT_FALSE(Identifier::isEquivalentName("a", "\xc3")); EXPECT_TRUE(Identifier::isEquivalentName("\xc3\xa4", "\xc3\xa1")); EXPECT_TRUE(Identifier::isEquivalentName( "Unknown based on International 1924 (Hayford 1909, 1910) ellipsoid", "Unknown_based_on_International_1924_Hayford_1909_1910_ellipsoid")); EXPECT_TRUE(Identifier::isEquivalentName("foo + ", "foo + ")); EXPECT_TRUE(Identifier::isEquivalentName("foo + bar", "foo + bar")); EXPECT_TRUE(Identifier::isEquivalentName("foo + bar", "foobar")); EXPECT_TRUE(Identifier::isEquivalentName("foo_IntlFeet", "foo_Feet")); EXPECT_TRUE(Identifier::isEquivalentName("foo_Feet", "foo_IntlFeet")); EXPECT_FALSE( Identifier::isEquivalentName("foo_IntlFeet_bar", "foo_Feet_bar")); EXPECT_FALSE( Identifier::isEquivalentName("foo_Feet_bar", "foo_IntlFeet_bar")); } proj-9.8.1/test/unit/test_crs.cpp000664 001750 001750 00001304665 15166171715 016762 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Test ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "gtest_include.h" // to be able to use internal::replaceAll #define FROM_PROJ_CPP #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" using namespace osgeo::proj::common; using namespace osgeo::proj::crs; using namespace osgeo::proj::cs; using namespace osgeo::proj::datum; using namespace osgeo::proj::io; using namespace osgeo::proj::internal; using namespace osgeo::proj::metadata; using namespace osgeo::proj::operation; using namespace osgeo::proj::util; namespace { struct UnrelatedObject : public IComparable { UnrelatedObject() = default; bool _isEquivalentTo(const IComparable *, Criterion, const DatabaseContextPtr &) const override { assert(false); return false; } }; static nn> createUnrelatedObject() { return nn_make_shared(); } } // namespace // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_get_components) { auto crs = GeographicCRS::EPSG_4326; ASSERT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->identifiers()[0]->code(), "4326"); EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(crs->nameStr(), "WGS 84"); auto datum = crs->datum(); ASSERT_EQ(datum->identifiers().size(), 1U); EXPECT_EQ(datum->identifiers()[0]->code(), "6326"); EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984"); auto ellipsoid = datum->ellipsoid(); EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0); EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE); EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563); ASSERT_EQ(ellipsoid->identifiers().size(), 1U); EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030"); EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(ellipsoid->nameStr(), "WGS 84"); } // --------------------------------------------------------------------------- TEST(crs, GeographicCRS_isEquivalentTo) { auto crs = GeographicCRS::EPSG_4326; EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE( crs->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(crs->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_FALSE(crs->isEquivalentTo(GeographicCRS::EPSG_4979.get())); EXPECT_FALSE(crs->isEquivalentTo(GeographicCRS::EPSG_4979.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(crs->isEquivalentTo(GeographicCRS::OGC_CRS84.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(crs->isEquivalentTo( GeographicCRS::OGC_CRS84.get(), IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); EXPECT_TRUE(GeographicCRS::OGC_CRS84->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); EXPECT_FALSE( GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->isEquivalentTo(crs.get())); EXPECT_FALSE( GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->isEquivalentTo( GeographicCRS::create(PropertyMap(), GeodeticReferenceFrame::create( PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude( UnitOfMeasure::DEGREE)) .get())); } // --------------------------------------------------------------------------- TEST(crs, GeographicCRS_datum_ensemble) { auto ensemble_vdatum = DatumEnsemble::create( PropertyMap(), std::vector{GeodeticReferenceFrame::EPSG_6326, GeodeticReferenceFrame::EPSG_6326}, PositionalAccuracy::create("100")); { auto crs = GeographicCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "unnamed") .set(Identifier::CODESPACE_KEY, "MY_CODESPACE") .set(Identifier::CODE_KEY, "MY_ID"), nullptr, ensemble_vdatum, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); crs->exportToWKT(f.get()); auto expected = "GEOGCRS[\"unnamed\",\n" " ENSEMBLE[\"unnamed\",\n" " MEMBER[\"World Geodetic System 1984\"],\n" " MEMBER[\"World Geodetic System 1984\"],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[100]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"MY_CODESPACE\",\"MY_ID\"]]"; EXPECT_EQ(f->toString(), expected); } { auto crs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), nullptr, ensemble_vdatum, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); crs->exportToWKT(f.get()); auto expected = "GEOGCRS[\"unnamed\",\n" " ENSEMBLE[\"unnamed\",\n" " MEMBER[\"World Geodetic System 1984\",\n" " ID[\"EPSG\",6326]],\n" " MEMBER[\"World Geodetic System 1984\",\n" " ID[\"EPSG\",6326]],\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",7030]],\n" " ENSEMBLEACCURACY[100]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; EXPECT_EQ(f->toString(), expected); } } // --------------------------------------------------------------------------- TEST(crs, GeographicCRS_ensemble_exception_in_create) { EXPECT_THROW(GeographicCRS::create(PropertyMap(), nullptr, nullptr, EllipsoidalCS::createLatitudeLongitude( UnitOfMeasure::DEGREE)), Exception); auto ensemble_vdatum = DatumEnsemble::create( PropertyMap(), std::vector{ VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")), VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))}, PositionalAccuracy::create("100")); EXPECT_THROW(GeographicCRS::create(PropertyMap(), nullptr, ensemble_vdatum, EllipsoidalCS::createLatitudeLongitude( UnitOfMeasure::DEGREE)), Exception); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT2) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f(WKTFormatter::create()); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT2_2019) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT2_SIMPLIFIED) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " UNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",4326]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT2_SIMPLIFIED_single_line) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED)); f->setMultiLine(false); crs->exportToWKT(f.get()); EXPECT_EQ( f->toString(), "GEODCRS[\"WGS 84\",DATUM[\"World Geodetic System " "1984\",ELLIPSOID[\"WGS " "84\",6378137,298.257223563]]," "CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS[\"longitude\",east]," "UNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",4326]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT2_2019_SIMPLIFIED) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019_SIMPLIFIED)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " UNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",4326]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT1_GDAL) { auto crs = GeographicCRS::EPSG_4326; auto wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()); EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT1_GDAL_with_axis) { auto crs = GeographicCRS::EPSG_4326; auto wkt = crs->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) ->setOutputAxis(WKTFormatter::OutputAxisRule::YES))); EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_from_db_as_WKT1_GDAL_with_axis) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createCoordinateReferenceSystem("4326"); auto wkt = crs->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) ->setOutputAxis(WKTFormatter::OutputAxisRule::YES))); EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST],\n" " AUTHORITY[\"EPSG\",\"4326\"]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT1_ESRI_with_database) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_" "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4901_as_WKT1_ESRI_with_PRIMEM_unit_name_morphing) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createCoordinateReferenceSystem("4901"); WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"GCS_ATF_Paris\",DATUM[\"D_ATF\"," "SPHEROID[\"Plessis_1817\",6376523.0,308.64]]," "PRIMEM[\"Paris_RGS\",2.33720833333333]," "UNIT[\"Grad\",0.0157079632679489]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_WKT1_ESRI_without_database) { auto crs = GeographicCRS::EPSG_4326; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI)); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_" "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4326_as_PROJ_string) { auto crs = GeographicCRS::EPSG_4326; EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +datum=WGS84 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4979_as_WKT2_SIMPLIFIED) { auto crs = GeographicCRS::EPSG_4979; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " UNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4979_as_WKT2_2019_SIMPLIFIED) { auto crs = GeographicCRS::EPSG_4979; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019_SIMPLIFIED)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " UNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4979_as_WKT1_GDAL_with_axis_not_strict_mode) { auto crs = GeographicCRS::EPSG_4979; auto wkt = crs->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) ->setStrict(false) .setOutputAxis(WKTFormatter::OutputAxisRule::YES))); // WKT1 only supports 2 axis for GEOGCS. So this is an extension of // WKT1 as it // and GDAL doesn't really export such as beast, although it can import it // so allow it only in non-strict more EXPECT_EQ(wkt, "GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AXIS[\"Latitude\",NORTH],\n" " AXIS[\"Longitude\",EAST],\n" " AXIS[\"Ellipsoidal height\",UP],\n" " AUTHORITY[\"EPSG\",\"4979\"]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4979_as_WKT1_GDAL) { auto crs = GeographicCRS::EPSG_4979; EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4979_as_WKT1_GDAL_with_ellipsoidal_height_as_vertical_crs) { auto crs = GeographicCRS::EPSG_4979; auto wkt = crs->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) ->setAllowEllipsoidalHeightAsVerticalCRS(true))); // For LAS 1.4 WKT1... EXPECT_EQ(wkt, "COMPD_CS[\"WGS 84 + Ellipsoid (metre)\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " VERT_CS[\"Ellipsoid (metre)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Ellipsoidal height\",UP]]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4979_as_WKT1_ESRI) { auto crs = GeographicCRS::EPSG_4979; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI)); const auto wkt = "GEOGCS[\"WGS_1984_3D\",DATUM[\"D_WGS_1984\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]," "LINUNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT(f.get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, geographic3D_crs_as_WKT1_ESRI_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("7087"); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext)); const auto wkt = "GEOGCS[\"RGTAAF07_(lon-lat)_3D\"," "DATUM[\"D_Reseau_Geodesique_des_Terres_Australes_et_" "Antarctiques_Francaises_2007\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]," "LINUNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT(f.get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, geographic3D_NAD83_as_WKT1_ESRI_database) { auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "GEOGCS[\"GCS_North_American_1983\",DATUM[\"D_North_American_1983\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "VERTCS[\"NAD_1983\",DATUM[\"D_North_American_1983\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext)); const auto wkt = "GEOGCS[\"GCS_NAD83\",DATUM[\"D_North_American_1983\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]," "LINUNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT(f.get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, GeographicCRS_is2DPartOf3D) { EXPECT_TRUE(GeographicCRS::EPSG_4326->is2DPartOf3D( NN_NO_CHECK(GeographicCRS::EPSG_4979.get()))); EXPECT_FALSE(GeographicCRS::EPSG_4326->is2DPartOf3D( NN_NO_CHECK(GeographicCRS::EPSG_4326.get()))); EXPECT_FALSE(GeographicCRS::EPSG_4979->is2DPartOf3D( NN_NO_CHECK(GeographicCRS::EPSG_4326.get()))); EXPECT_FALSE(GeographicCRS::EPSG_4979->is2DPartOf3D( NN_NO_CHECK(GeographicCRS::EPSG_4979.get()))); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4807_as_WKT2) { auto crs = GeographicCRS::EPSG_4807; WKTFormatterNNPtr f(WKTFormatter::create(WKTFormatter::Convention::WKT2)); crs->exportToWKT(f.get()); EXPECT_EQ( f->toString(), "GEODCRS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n" " ELLIPSOID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Paris\",2.5969213,\n" " ANGLEUNIT[\"grad\",0.015707963267949]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"grad\",0.015707963267949]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"grad\",0.015707963267949]],\n" " ID[\"EPSG\",4807]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4807_as_WKT2_SIMPLIFIED) { auto crs = GeographicCRS::EPSG_4807; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEODCRS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n" " ELLIPSOID[\"Clarke 1880 " "(IGN)\",6378249.2,293.466021293627]],\n" " PRIMEM[\"Paris\",2.5969213],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " UNIT[\"grad\",0.015707963267949],\n" " ID[\"EPSG\",4807]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4807_as_WKT1_GDAL) { auto crs = GeographicCRS::EPSG_4807; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)); crs->exportToWKT(f.get()); EXPECT_EQ( f->toString(), "GEOGCS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n" " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n" " AUTHORITY[\"EPSG\",\"7011\"]],\n" " AUTHORITY[\"EPSG\",\"6807\"]],\n" " PRIMEM[\"Paris\",2.33722917,\n" /* WKT1_GDAL weirdness: PRIMEM is converted to degree */ " AUTHORITY[\"EPSG\",\"8903\"]],\n" " UNIT[\"grad\",0.015707963267949,\n" " AUTHORITY[\"EPSG\",\"9105\"]],\n" " AUTHORITY[\"EPSG\",\"4807\"]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4807_as_WKT1_ESRI_with_database) { auto crs = GeographicCRS::EPSG_4807; WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"GCS_NTF_Paris\"," "DATUM[\"Nouvelle_Triangulation_Francaise_(Paris)\"," "SPHEROID[\"Clarke_1880_IGN\",6378249.2,293.466021293627]]," "PRIMEM[\"Paris\",2.33722917],UNIT[\"Grad\",0.015707963267949]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4807_as_WKT1_ESRI_without_database) { auto crs = GeographicCRS::EPSG_4807; WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI)); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"GCS_NTF_Paris\",DATUM[\"D_Nouvelle_Triangulation_" "Francaise_Paris\",SPHEROID[\"Clarke_1880_IGN\",6378249.2,293." "466021293627]],PRIMEM[\"Paris\",2.33722917],UNIT[\"Grad\",0." "015707963267949]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4807_as_PROJ_string) { auto crs = GeographicCRS::EPSG_4807; EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +ellps=clrk80ign +pm=paris +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4267) { auto crs = GeographicCRS::EPSG_4267; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), "GEODCRS[\"NAD27\",\n" " DATUM[\"North American Datum 1927\",\n" " ELLIPSOID[\"Clarke 1866\",6378206.4,294.978698213898,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4267]]"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +datum=NAD27 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4267_as_WKT1_ESRI_with_database) { auto crs = GeographicCRS::EPSG_4267; WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"GCS_North_American_1927\"," "DATUM[\"D_North_American_1927\",SPHEROID[\"Clarke_1866\"," "6378206.4,294.978698213898]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4269) { auto crs = GeographicCRS::EPSG_4269; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), "GEODCRS[\"NAD83\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4269]]"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +datum=NAD83 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4268_geogcrs_deprecated_as_WKT1_GDAL) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("4268"); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)); auto wkt = crs->exportToWKT(f.get()); EXPECT_TRUE(wkt.find("GEOGCS[\"NAD27 Michigan (deprecated)\"") == 0) << wkt; } // --------------------------------------------------------------------------- TEST(crs, ESRI_104971_as_WKT1_ESRI_with_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "ESRI"); auto crs = factory->createCoordinateReferenceSystem("104971"); WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); // Check that the _(Sphere) suffix is preserved EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"Mars_2000_(Sphere)\",DATUM[\"Mars_2000_(Sphere)\"," "SPHEROID[\"Mars_2000_(Sphere)\",3396190.0,0.0]]," "PRIMEM[\"Reference_Meridian\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"); } // --------------------------------------------------------------------------- TEST(crs, implicit_compound_ESRI_104024_plus_115844_as_WKT1_ESRI_with_database) { auto dbContext = DatabaseContext::create(); auto obj = createFromUserInput("ESRI:104024+115844", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U); WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); f->setAllowLINUNITNode(false); // Situation where there is no EPSG official name EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"California_SRS_Epoch_2017.50_(NAD83)\"," "DATUM[\"California_SRS_Epoch_2017.50_(NAD83)\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "VERTCS[\"California_SRS_Epoch_2017.50_(NAD83)\"," "DATUM[\"California_SRS_Epoch_2017.50_(NAD83)\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"); } // --------------------------------------------------------------------------- TEST(crs, implicit_compound_ESRI_104971_to_3D_as_WKT1_ESRI_with_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "ESRI"); auto crs = factory->createGeographicCRS("104971")->promoteTo3D( std::string(), dbContext); WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); f->setAllowLINUNITNode(false); // Situation where there is no ESRI vertical CRS, but the GEOGCS does exist // This will be only partly recognized by ESRI software. // See https://github.com/OSGeo/PROJ/issues/2757 const char *wkt = "GEOGCS[\"Mars_2000_(Sphere)\"," "DATUM[\"Mars_2000_(Sphere)\"," "SPHEROID[\"Mars_2000_(Sphere)\",3396190.0,0.0]]," "PRIMEM[\"Reference_Meridian\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "VERTCS[\"Mars_2000_(Sphere)\"," "DATUM[\"Mars_2000_(Sphere)\"," "SPHEROID[\"Mars_2000_(Sphere)\",3396190.0,0.0]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT(f.get()), wkt); auto obj2 = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); EXPECT_EQ(crs2->coordinateSystem()->axisList().size(), 3U); } // --------------------------------------------------------------------------- TEST(crs, ITRF2020_as_WKT1_ESRI_with_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("9990"); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext)); EXPECT_EQ(crs->exportToWKT(f.get()), "GEOGCS[\"ITRF2020\"," "DATUM[\"International_Terrestrial_Reference_Frame_2020\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]"); } // --------------------------------------------------------------------------- TEST(crs, IAU_1000_as_WKT2) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "IAU_2015"); auto crs = factory->createCoordinateReferenceSystem("1000"); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019, dbContext)); auto wkt = crs->exportToWKT(f.get()); // Check that IAU_2015 is split into a authority name and version EXPECT_TRUE(wkt.find("ID[\"IAU\",1000,2015]") != std::string::npos) << wkt; auto obj = createFromUserInput(wkt, dbContext); auto crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); auto wkt2 = crs2->exportToWKT(f.get()); // Check that IAU_2015 is split into a authority name and version EXPECT_TRUE(wkt2.find("ID[\"IAU\",1000,2015]") != std::string::npos) << wkt2; } // --------------------------------------------------------------------------- TEST(crs, IAU_1000_as_PROJJSON) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "IAU_2015"); auto crs = factory->createCoordinateReferenceSystem("1000"); auto projjson = crs->exportToJSON(JSONFormatter::create(dbContext).get()); // Check that IAU_2015 is split into a authority name and version EXPECT_TRUE(projjson.find("\"authority\": \"IAU\",") != std::string::npos) << projjson; EXPECT_TRUE(projjson.find("\"code\": 1000,") != std::string::npos) << projjson; EXPECT_TRUE(projjson.find("\"version\": 2015") != std::string::npos) << projjson; auto obj = createFromUserInput(projjson, dbContext); auto crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019, dbContext)); auto wkt2 = crs2->exportToWKT(f.get()); // Check that IAU_2015 is split into a authority name and version EXPECT_TRUE(wkt2.find("ID[\"IAU\",1000,2015]") != std::string::npos) << wkt2; } // --------------------------------------------------------------------------- TEST(crs, EPSG_2008_projcrs_deprecated_as_WKT1_GDAL) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("2008"); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)); auto wkt = crs->exportToWKT(f.get()); EXPECT_TRUE( wkt.find("PROJCS[\"NAD27(CGQ77) / SCoPQ zone 2 (deprecated)\"") == 0) << wkt; } // --------------------------------------------------------------------------- TEST(crs, EPSG_27561_projected_with_geodetic_in_grad_as_PROJ_string_and_WKT1) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"NTF (Paris) / Lambert Nord France\",\n" " BASEGEODCRS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n" " ELLIPSOID[\"Clarke 1880 " "(IGN)\",6378249.2,293.4660213,LENGTHUNIT[\"metre\",1.0]]],\n" " PRIMEM[\"Paris\",2.5969213,ANGLEUNIT[\"grad\",0.015707963268]]],\n" " CONVERSION[\"Lambert Nord France\",\n" " METHOD[\"Lambert Conic Conformal (1SP)\",ID[\"EPSG\",9801]],\n" " PARAMETER[\"Latitude of natural " "origin\",55,ANGLEUNIT[\"grad\",0.015707963268]],\n" " PARAMETER[\"Longitude of natural " "origin\",0,ANGLEUNIT[\"grad\",0.015707963268]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.999877341,SCALEUNIT[\"unity\",1.0]],\n" " PARAMETER[\"False easting\",600000,LENGTHUNIT[\"metre\",1.0]],\n" " PARAMETER[\"False northing\",200000,LENGTHUNIT[\"metre\",1.0]]],\n" " CS[cartesian,2],\n" " AXIS[\"easting (X)\",east,ORDER[1]],\n" " AXIS[\"northing (Y)\",north,ORDER[2]],\n" " LENGTHUNIT[\"metre\",1.0],\n" " ID[\"EPSG\",27561]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=lcc +lat_1=49.5 +lat_0=49.5 +lon_0=0 +k_0=0.999877341 " "+x_0=600000 +y_0=200000 +ellps=clrk80ign +pm=paris +units=m " "+no_defs +type=crs"); auto nn_crs = NN_CHECK_ASSERT(crs); EXPECT_TRUE(nn_crs->isEquivalentTo(nn_crs.get())); EXPECT_FALSE(nn_crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_FALSE( nn_crs->DerivedCRS::isEquivalentTo(createUnrelatedObject().get())); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) .get()); EXPECT_EQ( wkt1, "PROJCS[\"NTF (Paris) / Lambert Nord France\",\n" " GEOGCS[\"NTF (Paris)\",\n" " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n" " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.4660213]],\n" " PRIMEM[\"Paris\",2.33722917000759],\n" " UNIT[\"grad\",0.015707963268]],\n" " PROJECTION[\"Lambert_Conformal_Conic_1SP\"],\n" " PARAMETER[\"latitude_of_origin\",55],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"scale_factor\",0.999877341],\n" " PARAMETER[\"false_easting\",600000],\n" " PARAMETER[\"false_northing\",200000],\n" " UNIT[\"metre\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"27561\"]]"); auto wkt1_esri = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()); EXPECT_EQ(wkt1_esri, "PROJCS[\"NTF_Paris_Lambert_Nord_France\"," "GEOGCS[\"GCS_NTF_Paris\"," "DATUM[\"Nouvelle_Triangulation_Francaise_(Paris)\"," "SPHEROID[\"Clarke_1880_IGN\",6378249.2,293.4660213]]," "PRIMEM[\"Paris\",2.33722917000759]," "UNIT[\"Grad\",0.015707963268]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",600000.0]," "PARAMETER[\"False_Northing\",200000.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",55.0]," "PARAMETER[\"Scale_Factor\",0.999877341]," "PARAMETER[\"Latitude_Of_Origin\",55.0]," "UNIT[\"Meter\",1.0]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_3040_projected_northing_easting_as_PROJ_string) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"ETRS89 / UTM zone 28N (N-E)\",\n" " BASEGEODCRS[\"ETRS89\",\n" " DATUM[\"European Terrestrial Reference System 1989\",\n" " ELLIPSOID[\"GRS " "1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1.0]]]],\n" " CONVERSION[\"UTM zone 28N\",\n" " METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural " "origin\",0,ANGLEUNIT[\"degree\",0.01745329252]],\n" " PARAMETER[\"Longitude of natural " "origin\",-15,ANGLEUNIT[\"degree\",0.01745329252]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9996,SCALEUNIT[\"unity\",1.0]],\n" " PARAMETER[\"False easting\",500000,LENGTHUNIT[\"metre\",1.0]],\n" " PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1.0]]],\n" " CS[cartesian,2],\n" " AXIS[\"northing (N)\",north,ORDER[1]],\n" " AXIS[\"easting (E)\",east,ORDER[2]],\n" " LENGTHUNIT[\"metre\",1.0],\n" " ID[\"EPSG\",3040]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=28 +ellps=GRS80 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_2222_projected_unit_foot_as_PROJ_string_and_WKT1) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"NAD83 / Arizona East (ft)\",\n" " BASEGEODCRS[\"NAD83\",\n" " DATUM[\"North American Datum 1983\",\n" " ELLIPSOID[\"GRS " "1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1.0]]]],\n" " CONVERSION[\"SPCS83 Arizona East zone (International feet)\",\n" " METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural " "origin\",31,ANGLEUNIT[\"degree\",0.01745329252]],\n" " PARAMETER[\"Longitude of natural " "origin\",-110.166666666667,ANGLEUNIT[\"degree\",0.01745329252]],\n" " PARAMETER[\"Scale factor at natural " "origin\",0.9999,SCALEUNIT[\"unity\",1.0]],\n" " PARAMETER[\"False easting\",700000,LENGTHUNIT[\"foot\",0.3048]],\n" " PARAMETER[\"False northing\",0,LENGTHUNIT[\"foot\",0.3048]]],\n" " CS[cartesian,2],\n" " AXIS[\"easting (X)\",east,ORDER[1]],\n" " AXIS[\"northing (Y)\",north,ORDER[2]],\n" " LENGTHUNIT[\"foot\",0.3048],\n" " ID[\"EPSG\",2222]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=tmerc +lat_0=31 +lon_0=-110.166666666667 +k=0.9999 " "+x_0=213360 +y_0=0 +datum=NAD83 +units=ft +no_defs +type=crs"); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) .get()); EXPECT_EQ(wkt1, "PROJCS[\"NAD83 / Arizona East (ft)\",\n" " GEOGCS[\"NAD83\",\n" " DATUM[\"North_American_Datum_1983\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",31],\n" " PARAMETER[\"central_meridian\",-110.166666666667],\n" " PARAMETER[\"scale_factor\",0.9999],\n" " PARAMETER[\"false_easting\",700000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"foot\",0.3048],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"2222\"]]"); } // --------------------------------------------------------------------------- TEST(crs, projected_with_parameter_unit_different_than_cs_unit_as_WKT1) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"unknown\"," " BASEGEODCRS[\"unknown\"," " DATUM[\"Unknown based on GRS80 ellipsoid\"," " ELLIPSOID[\"GRS 1980\",6378137,298.257222101," " LENGTHUNIT[\"metre\",1]]]," " PRIMEM[\"Greenwich\",0]]," " CONVERSION[\"UTM zone 32N\"," " METHOD[\"Transverse Mercator\"]," " PARAMETER[\"Latitude of natural origin\",0]," " PARAMETER[\"Longitude of natural origin\",9]," " PARAMETER[\"Scale factor at natural origin\",0.9996]," " PARAMETER[\"False easting\",500000,LENGTHUNIT[\"metre\",1]]," " PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1]]]," " CS[Cartesian,2]," " AXIS[\"(E)\",east]," " AXIS[\"(N)\",north]," " LENGTHUNIT[\"US survey foot\",0.304800609601219]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) .get()); EXPECT_EQ(wkt1, "PROJCS[\"unknown\",\n" " GEOGCS[\"unknown\",\n" " DATUM[\"Unknown based on GRS80 ellipsoid\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",9],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",1640416.66666667],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"US survey foot\",0.304800609601219],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_32661_projected_north_pole_north_east) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("32661"); auto proj_crs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(proj_crs != nullptr); auto proj_string = "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere " "+lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84 +step +proj=axisswap +order=2,1"; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(proj_crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), proj_string); auto opNormalized = op->normalizeForVisualization(); auto proj_string_normalized = "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step " "+proj=stere +lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84"; EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), proj_string_normalized); EXPECT_EQ(opNormalized->sourceCRS()->domains().size(), 1U); EXPECT_EQ(opNormalized->sourceCRS()->remarks(), "Axis order reversed compared to EPSG:4326"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_5041_projected_north_pole_east_north) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("5041"); auto proj_crs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(proj_crs != nullptr); auto proj_string = "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere " "+lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84"; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(proj_crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), proj_string); auto opNormalized = op->normalizeForVisualization(); auto proj_string_normalized = "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step " "+proj=stere +lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84"; EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), proj_string_normalized); } // --------------------------------------------------------------------------- TEST(crs, EPSG_32761_projected_south_pole_north_east) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("32761"); auto proj_crs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(proj_crs != nullptr); auto proj_string = "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere " "+lat_0=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84 +step +proj=axisswap +order=2,1"; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(proj_crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), proj_string); auto opNormalized = op->normalizeForVisualization(); auto proj_string_normalized = "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step " "+proj=stere +lat_0=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84"; EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), proj_string_normalized); } // --------------------------------------------------------------------------- TEST(crs, EPSG_5042_projected_south_pole_east_north) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("5042"); auto proj_crs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(proj_crs != nullptr); auto proj_string = "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere " "+lat_0=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84"; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, NN_NO_CHECK(proj_crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), proj_string); auto opNormalized = op->normalizeForVisualization(); auto proj_string_normalized = "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step " "+proj=stere +lat_0=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 " "+ellps=WGS84"; EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), proj_string_normalized); } // --------------------------------------------------------------------------- TEST(crs, EPSG_5482_projected_south_pole_south_west) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factory->createCoordinateReferenceSystem("5482"); auto proj_crs = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(proj_crs != nullptr); auto op = CoordinateOperationFactory::create()->createOperation( factory->createCoordinateReferenceSystem("4764"), NN_NO_CHECK(proj_crs)); ASSERT_TRUE(op != nullptr); auto proj_string = "+proj=pipeline " "+step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=stere +lat_0=-90 +lon_0=180 +k=0.994 " "+x_0=5000000 +y_0=1000000 +ellps=GRS80 " "+step +proj=axisswap +order=2,1"; EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), proj_string); auto opNormalized = op->normalizeForVisualization(); auto proj_string_normalized = "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=stere +lat_0=-90 +lon_0=180 +k=0.994 " "+x_0=5000000 +y_0=1000000 +ellps=GRS80"; EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), proj_string_normalized); } // --------------------------------------------------------------------------- TEST(crs, spherical_mercator_on_sphere_to_WKT1) { auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCRS[\"Moon (2015) - Sphere / Ocentric / Mercator\",\n" " BASEGEOGCRS[\"Moon (2015) - Sphere / Ocentric\",\n" " DATUM[\"Moon (2015) - Sphere\",\n" " ELLIPSOID[\"Moon (2015) - Sphere\",1737400,0,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"IAU\",30100,2015]],\n" " CONVERSION[\"Mercator\",\n" " METHOD[\"Mercator (Spherical)\",\n" " ID[\"EPSG\",1026]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"IAU\",30190,2015]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt1 = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()); EXPECT_EQ(wkt1, "PROJCS[\"Moon (2015) - Sphere / Ocentric / Mercator\",\n" " GEOGCS[\"Moon (2015) - Sphere / Ocentric\",\n" " DATUM[\"Moon (2015) - Sphere\",\n" " SPHEROID[\"Moon (2015) - Sphere\",1737400,0]],\n" " PRIMEM[\"Reference Meridian\",0],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"IAU\",\"30100\"]],\n" " PROJECTION[\"Mercator_1SP\"],\n" " PARAMETER[\"central_meridian\",0],\n" " PARAMETER[\"false_easting\",0],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"IAU\",\"30190\"]]"); } // --------------------------------------------------------------------------- TEST(crs, mercator_variant_A_lat0_non_zero_to_WKT1) { auto obj = WKTParser().createFromWKT( "PROJCRS[\"test\",\n" " BASEGEOGCRS[\"Moon (2015) - Sphere / Ocentric\",\n" " DATUM[\"Moon (2015) - Sphere\",\n" " ELLIPSOID[\"Moon (2015) - Sphere\",1737400,0,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Reference Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"IAU\",30100,2015]],\n" " CONVERSION[\"Mercator\",\n" " METHOD[\"Mercator (Spherical)\",\n" " ID[\"EPSG\",1026]],\n" " PARAMETER[\"Latitude of natural origin\",10,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"IAU\",30190,2015]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_THROW(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) .get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, geodetic_crs_both_datum_datum_ensemble_null) { EXPECT_THROW(GeodeticCRS::create( PropertyMap(), nullptr, nullptr, CartesianCS::createGeocentric(UnitOfMeasure::METRE)), Exception); } // --------------------------------------------------------------------------- TEST(crs, geodetic_crs_both_datum_datum_ensemble_non_null) { auto ensemble = DatumEnsemble::create( PropertyMap(), std::vector{GeodeticReferenceFrame::EPSG_6326, GeodeticReferenceFrame::EPSG_6326}, PositionalAccuracy::create("100")); EXPECT_THROW(GeodeticCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, ensemble, CartesianCS::createGeocentric(UnitOfMeasure::METRE)), Exception); } // --------------------------------------------------------------------------- static GeodeticCRSNNPtr createGeocentric() { PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 4328) .set(IdentifiedObject::NAME_KEY, "WGS 84"); return GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, geocentricCRS_as_WKT2) { auto crs = createGeocentric(); auto expected = "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4328]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(crs, geocentricCRS_as_WKT2_simplified) { auto crs = createGeocentric(); auto expected = "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " AXIS[\"(Z)\",geocentricZ],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",4328]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create( WKTFormatter::Convention::WKT2_SIMPLIFIED) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, geocentricCRS_as_WKT1_GDAL) { auto crs = createGeocentric(); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)); crs->exportToWKT(f.get()); EXPECT_EQ(f->toString(), "GEOCCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Geocentric X\",OTHER],\n" " AXIS[\"Geocentric Y\",OTHER],\n" " AXIS[\"Geocentric Z\",NORTH],\n" " AUTHORITY[\"EPSG\",\"4328\"]]"); } // --------------------------------------------------------------------------- TEST(crs, EPSG_4978_as_WKT1_GDAL_with_database) { auto crs = GeodeticCRS::EPSG_4978; auto wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, DatabaseContext::create()) .get()); EXPECT_EQ(wkt, "GEOCCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Geocentric X\",OTHER],\n" " AXIS[\"Geocentric Y\",OTHER],\n" " AXIS[\"Geocentric Z\",NORTH],\n" " AUTHORITY[\"EPSG\",\"4978\"]]"); } // --------------------------------------------------------------------------- TEST(crs, geocentricCRS_as_PROJ_string) { auto crs = createGeocentric(); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=geocent +datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, geocentricCRS_non_meter_unit_as_PROJ_string) { auto crs = GeodeticCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric( UnitOfMeasure("kilometre", 1000.0, UnitOfMeasure::Type::LINEAR))); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, crs); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart " "+ellps=WGS84 +step +proj=unitconvert +xy_in=m +z_in=m " "+xy_out=km +z_out=km"); EXPECT_THROW(crs->exportToPROJString(PROJStringFormatter::create().get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, geocentricCRS_unsupported_unit_as_PROJ_string) { auto crs = GeodeticCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric( UnitOfMeasure("my unit", 500.0, UnitOfMeasure::Type::LINEAR))); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, crs); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart " "+ellps=WGS84 +step +proj=unitconvert +xy_in=m +z_in=m " "+xy_out=500 +z_out=500"); } // --------------------------------------------------------------------------- TEST(crs, geodeticcrs_identify_no_db) { { auto res = GeodeticCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, nullptr, CartesianCS::createGeocentric(UnitOfMeasure::METRE)) ->identify(nullptr); ASSERT_EQ(res.size(), 0U); } { auto res = GeographicCRS::EPSG_4326->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 100); } { // Using virtual method auto res = static_cast(GeographicCRS::EPSG_4326.get()) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 100); } { auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 100); } { auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS84"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 90); } { // Long Lat order auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::DEGREE)) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 25); } { // WKT1 identification auto obj = WKTParser() .attachDatabaseContext(DatabaseContext::create()) .createFromWKT( "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\"," "6378137,298.257223563]],PRIMEM[\"Greenwich\",0]," "UNIT[\"Degree\",0.0174532925199433]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 100); } } // --------------------------------------------------------------------------- TEST(crs, geodeticcrs_identify_db) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); { // No match auto res = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::create( PropertyMap(), Ellipsoid::createFlattenedSphere( PropertyMap(), Length(6378137), Scale(10)), optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(nullptr); ASSERT_EQ(res.size(), 0U); } { // Identify by datum code auto res = GeodeticCRS::create( PropertyMap(), GeodeticReferenceFrame::EPSG_6326, nullptr, CartesianCS::createGeocentric(UnitOfMeasure::METRE)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4978); EXPECT_EQ(res.front().second, 70); } { // Identify by datum code auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 70); } { // Identify by datum code (as a fallback) auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "foobar"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 70); } { // Perfect match, and ID available. Hardcoded case auto res = GeographicCRS::EPSG_4326->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 100); } { // Perfect match, but no ID available. Hardcoded case auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get()); EXPECT_EQ(res.front().second, 100); } { // Perfect match, but no ID available auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "NTF (Paris)"), GeographicCRS::EPSG_4807->datum(), nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::GRAD)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4807); EXPECT_EQ(res.front().second, 100); } { // Perfect match, and ID available auto res = GeographicCRS::EPSG_4807->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4807); EXPECT_EQ(res.front().second, 100); } { // The object has an unexisting ID auto res = GeographicCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "NTF (Paris)") .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 1), GeographicCRS::EPSG_4807->datum(), nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::GRAD)) ->identify(factory); ASSERT_EQ(res.size(), 0U); } { // The object has a unreliable ID auto res = GeographicCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "NTF (Paris)") .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 4326), GeographicCRS::EPSG_4807->datum(), nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::GRAD)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 25); } { // Approximate match by name auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS84"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 90); } { // Long Lat order auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"), GeodeticReferenceFrame::EPSG_6326, nullptr, EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_EQ(res.size(), 3U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 25); } { // Identify by ellipsoid code auto res = GeographicCRS::create( PropertyMap(), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_GT(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 60.0); } { // Identify by ellipsoid code (as a fallback) auto res = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84, optional(), PrimeMeridian::GREENWICH), EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_GT(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 60.0); } { // Identify by ellipsoid description auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +a=6378521.049 +rf=298.257222100883 +axis=neu " "+type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); EXPECT_EQ(res.size(), 5U); } { // Identify by name, without any code auto wkt = "GEODCRS[\"GCS_Datum_Lisboa_Bessel\",\n" " DATUM[\"D_Datum_Lisboa_Bessel\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); ASSERT_TRUE(!res.front().first->identifiers().empty()); EXPECT_EQ(*res.front().first->identifiers()[0]->codeSpace(), "ESRI"); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "104105"); EXPECT_EQ(res.front().second, 100); } { // Identification by non-existing code auto res = GeographicCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "foobar") .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, "i_dont_exist"), GeodeticReferenceFrame::create( PropertyMap(), Ellipsoid::createFlattenedSphere( PropertyMap(), Length(6378137), Scale(10)), optional(), PrimeMeridian::GREENWICH), nullptr, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)) ->identify(factory); ASSERT_EQ(res.size(), 0U); } { // Test identification from PROJ string auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +datum=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); ASSERT_TRUE(!res.front().first->identifiers().empty()); EXPECT_EQ(*res.front().first->identifiers()[0]->codeSpace(), "EPSG"); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "4326"); EXPECT_EQ(res.front().second, 70); } { // Test identification from PROJ string with just the ellipsoid auto obj = PROJStringParser().createFromPROJString( "+proj=longlat +ellps=GRS80 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); bool foundGDA2020 = false; for (const auto &pair : res) { // one among many others... if (pair.first->getEPSGCode() == 7844) { foundGDA2020 = true; EXPECT_EQ(pair.second, 60); } } EXPECT_TRUE(foundGDA2020); } { // Identify by code, but datum name is an alias of the official one auto wkt = "GEOGCRS[\"GDA2020\",\n" " DATUM[\"GDA2020\",\n" " ELLIPSOID[\"GRS_1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",7844]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); ASSERT_TRUE(!res.front().first->identifiers().empty()); EXPECT_EQ(*res.front().first->identifiers()[0]->codeSpace(), "EPSG"); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "7844"); EXPECT_EQ(res.front().second, 100); EXPECT_TRUE(crs->_isEquivalentTo(res.front().first.get(), IComparable::Criterion::EQUIVALENT, dbContext)); EXPECT_TRUE(res.front().first->_isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT, dbContext)); } { // Identify by name, but datum name is an alias of the official one auto wkt = "GEOGCRS[\"GDA2020\",\n" " DATUM[\"GDA2020\",\n" " ELLIPSOID[\"GRS_1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); ASSERT_TRUE(!res.front().first->identifiers().empty()); EXPECT_EQ(*res.front().first->identifiers()[0]->codeSpace(), "EPSG"); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "7844"); EXPECT_EQ(res.front().second, 100); EXPECT_TRUE(crs->_isEquivalentTo(res.front().first.get(), IComparable::Criterion::EQUIVALENT, dbContext)); EXPECT_TRUE(res.front().first->_isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT, dbContext)); } { // Identify "a" ESRI WKT representation of GDA2020. See #1911 auto wkt = "GEOGCS[\"GDA2020\",DATUM[\"D_GDA2020\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.017453292519943295]]"; auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); ASSERT_TRUE(!res.front().first->identifiers().empty()); EXPECT_EQ(*res.front().first->identifiers()[0]->codeSpace(), "EPSG"); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "7844"); EXPECT_EQ(res.front().second, 100); } { // Identify with DatumEnsemble auto wkt = "GEOGCRS[\"WGS 84\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " MEMBER[\"World Geodetic System 1984 (Transit)\"," " ID[\"EPSG\",1166]]," " MEMBER[\"World Geodetic System 1984 (G730)\"," " ID[\"EPSG\",1152]]," " MEMBER[\"World Geodetic System 1984 (G873)\"," " ID[\"EPSG\",1153]]," " MEMBER[\"World Geodetic System 1984 (G1150)\"," " ID[\"EPSG\",1154]]," " MEMBER[\"World Geodetic System 1984 (G1674)\"," " ID[\"EPSG\",1155]]," " MEMBER[\"World Geodetic System 1984 (G1762)\"," " ID[\"EPSG\",1156]]," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]," " ID[\"EPSG\",7030]]," " ENSEMBLEACCURACY[2]]," " PRIMEM[\"Greenwich\",0," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]," " ID[\"EPSG\",8901]]," " CS[ellipsoidal,2," " ID[\"EPSG\",6422]]," " AXIS[\"Geodetic latitude (Lat)\",north," " ORDER[1]]," " AXIS[\"Geodetic longitude (Lon)\",east," " ORDER[2]]," " ANGLEUNIT[\"degree (supplier to define representation)\"," "0.0174532925199433,ID[\"EPSG\",9122]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 100.0); } { // Identify with DatumEnsemble and unknown CRS name auto wkt = "GEOGCRS[\"unknown\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " MEMBER[\"World Geodetic System 1984 (Transit)\"," " ID[\"EPSG\",1166]]," " MEMBER[\"World Geodetic System 1984 (G730)\"," " ID[\"EPSG\",1152]]," " MEMBER[\"World Geodetic System 1984 (G873)\"," " ID[\"EPSG\",1153]]," " MEMBER[\"World Geodetic System 1984 (G1150)\"," " ID[\"EPSG\",1154]]," " MEMBER[\"World Geodetic System 1984 (G1674)\"," " ID[\"EPSG\",1155]]," " MEMBER[\"World Geodetic System 1984 (G1762)\"," " ID[\"EPSG\",1156]]," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]," " ID[\"EPSG\",7030]]," " ENSEMBLEACCURACY[2]]," " PRIMEM[\"Greenwich\",0," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]," " ID[\"EPSG\",8901]]," " CS[ellipsoidal,2," " ID[\"EPSG\",6422]]," " AXIS[\"Geodetic latitude (Lat)\",north," " ORDER[1]]," " AXIS[\"Geodetic longitude (Lon)\",east," " ORDER[2]]," " ANGLEUNIT[\"degree (supplier to define representation)\"," "0.0174532925199433,ID[\"EPSG\",9122]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 4326); EXPECT_EQ(res.front().second, 70.0); } } // --------------------------------------------------------------------------- static ProjectedCRSNNPtr createProjected() { PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 32631) .set(IdentifiedObject::NAME_KEY, "WGS 84 / UTM zone 31N"); return ProjectedCRS::create( propertiesCRS, GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_derivingConversion) { auto crs = createProjected(); auto conv = crs->derivingConversion(); EXPECT_TRUE(conv->sourceCRS() != nullptr); ASSERT_TRUE(conv->targetCRS() != nullptr); EXPECT_EQ(conv->targetCRS().get(), crs.get()); // derivingConversion() returns a copy of the internal conversion auto targetCRSAsProjCRS = std::dynamic_pointer_cast(conv->targetCRS()); ASSERT_TRUE(targetCRSAsProjCRS != nullptr); EXPECT_NE(targetCRSAsProjCRS->derivingConversion(), conv); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_shallowClone) { { auto crs = createProjected(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); auto clone = nn_dynamic_pointer_cast(crs->shallowClone()); EXPECT_TRUE(clone->isEquivalentTo(crs.get())); EXPECT_EQ(clone->derivingConversion()->targetCRS().get(), clone.get()); } { ProjectedCRSPtr clone; { auto crs = ProjectedCRS::create( PropertyMap(), createGeocentric(), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); clone = nn_dynamic_pointer_cast(crs->shallowClone()); } EXPECT_EQ(clone->baseCRS()->exportToPROJString( PROJStringFormatter::create().get()), "+proj=geocent +datum=WGS84 +units=m +no_defs +type=crs"); } } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_WKT2) { auto crs = createProjected(); auto expected = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32631]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_WKT2_2019) { auto crs = createProjected(); auto expected = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",32631]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_WKT2_simplified) { auto crs = createProjected(); auto expected = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0],\n" " PARAMETER[\"Longitude of natural origin\",3],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996],\n" " PARAMETER[\"False easting\",500000],\n" " PARAMETER[\"False northing\",0]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",32631]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create( WKTFormatter::Convention::WKT2_SIMPLIFIED) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_WKT2_2019_simplified) { auto crs = createProjected(); auto expected = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\"],\n" " PARAMETER[\"Latitude of natural origin\",0],\n" " PARAMETER[\"Longitude of natural origin\",3],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996],\n" " PARAMETER[\"False easting\",500000],\n" " PARAMETER[\"False northing\",0]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east],\n" " AXIS[\"(N)\",north],\n" " UNIT[\"metre\",1],\n" " ID[\"EPSG\",32631]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019_SIMPLIFIED) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_WKT1_GDAL) { auto crs = createProjected(); auto expected = "PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected); EXPECT_EQ(crs->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) ->setOutputAxis(WKTFormatter::OutputAxisRule::YES))), expected); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_WKT1_ESRI) { auto crs = createProjected(); auto expected = "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0," "298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",500000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",3.0]," "PARAMETER[\"Scale_Factor\",0.9996]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_3D_as_WKT1_GDAL_with_ellipsoidal_height_as_vertical_crs) { auto dbContext = DatabaseContext::create(); auto crs = AuthorityFactory::create(dbContext, "EPSG") ->createProjectedCRS("32631") ->promoteTo3D(std::string(), dbContext); auto wkt = crs->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) ->setAllowEllipsoidalHeightAsVerticalCRS(true))); // For LAS 1.4 WKT1... EXPECT_EQ(wkt, "COMPD_CS[\"WGS 84 / UTM zone 31N + Ellipsoid (metre)\",\n" " PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]],\n" " VERT_CS[\"Ellipsoid (metre)\",\n" " VERT_DATUM[\"Ellipsoid\",2002],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Ellipsoidal height\",UP]]]"); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_with_other_deprecated_crs_of_same_name_as_WKT1_ESRI) { auto dbContext = DatabaseContext::create(); // EPSG:3800 is the non-deprecated version of EPSG:3774 // This used to cause an issue when looking for the ESRI CRS name auto crs = AuthorityFactory::create(dbContext, "EPSG")->createProjectedCRS("3800"); auto esri_wkt = "PROJCS[\"NAD_1927_3TM_120\",GEOGCS[\"GCS_North_American_1927\"," "DATUM[\"D_North_American_1927\"," "SPHEROID[\"Clarke_1866\",6378206.4,294.978698213898]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",-120.0]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Latitude_Of_Origin\",0.0],UNIT[\"Meter\",1.0]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), esri_wkt); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(esri_wkt); auto crs2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs2 != nullptr); EXPECT_EQ(crs2->nameStr(), "NAD27 / Alberta 3TM ref merid 120 W"); EXPECT_EQ( crs2->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), esri_wkt); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_with_ESRI_code_as_WKT1_ESRI) { auto dbContext = DatabaseContext::create(); auto crs = AuthorityFactory::create(dbContext, "ESRI") ->createProjectedCRS("102113"); // Comes literally from the text_definition column of // projected_crs table auto esri_wkt = "PROJCS[\"WGS_1984_Web_Mercator\"," "GEOGCS[\"GCS_WGS_1984_Major_Auxiliary_Sphere\"," "DATUM[\"D_WGS_1984_Major_Auxiliary_Sphere\"," "SPHEROID[\"WGS_1984_Major_Auxiliary_Sphere\",6378137.0,0.0]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Mercator\"],PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",0.0],UNIT[\"Meter\",1.0]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), esri_wkt); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_from_WKT1_ESRI_as_WKT1_ESRI) { auto dbContext = DatabaseContext::create(); // Comes literally from the text_definition column of // projected_crs table auto esri_wkt = "PROJCS[\"WGS_1984_Web_Mercator\"," "GEOGCS[\"GCS_WGS_1984_Major_Auxiliary_Sphere\"," "DATUM[\"D_WGS_1984_Major_Auxiliary_Sphere\"," "SPHEROID[\"WGS_1984_Major_Auxiliary_Sphere\",6378137.0,0.0]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Mercator\"],PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",0.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(esri_wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), esri_wkt); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_from_WKT1_ESRI_as_WKT1_ESRI_s_jtsk03_krovak_east_north) { // EPSG:8353 auto wkt = "PROJCS[\"S-JTSK_[JTSK03]_Krovak_East_North\"," "GEOGCS[\"S-JTSK_[JTSK03]\",DATUM[\"S-JTSK_[JTSK03]\"," "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Krovak\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Azimuth\",30.2881397527778]," "PARAMETER[\"Longitude_Of_Center\",24.8333333333333]," "PARAMETER[\"Latitude_Of_Center\",49.5]," "PARAMETER[\"X_Scale\",-1.0]," "PARAMETER[\"Y_Scale\",1.0]," "PARAMETER[\"XY_Plane_Rotation\",90.0]," "UNIT[\"Meter\",1.0]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected_wkt2 = "PROJCRS[\"S-JTSK [JTSK03] / Krovak East North\",\n" " BASEGEOGCRS[\"S-JTSK [JTSK03]\",\n" " DATUM[\"System of the Unified Trigonometrical Cadastral " "Network [JTSK03]\",\n" " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",1201]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Krovak (North Orientated)\",\n" " ID[\"EPSG\",1041]],\n" " PARAMETER[\"Latitude of projection centre\",49.5,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8811]],\n" " PARAMETER[\"Longitude of origin\",24.8333333333333,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8833]],\n" " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",1036]],\n" " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n" " ANGLEUNIT[\"Degree\",0.0174532925199433],\n" " ID[\"EPSG\",8818]],\n" " PARAMETER[\"Scale factor on pseudo standard " "parallel\",0.9999,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8819]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019, dbContext) .get()), expected_wkt2); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_from_EPSG_as_WKT1_ESRI_s_jtsk03_krovak_east_north) { auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto crs = factoryEPSG->createProjectedCRS("8353"); auto wkt = "PROJCS[\"S-JTSK_[JTSK03]_Krovak_East_North\"," "GEOGCS[\"S-JTSK_[JTSK03]\",DATUM[\"S-JTSK_[JTSK03]\"," "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Krovak\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Azimuth\",30.2881397527778]," "PARAMETER[\"Longitude_Of_Center\",24.8333333333333]," "PARAMETER[\"Latitude_Of_Center\",49.5]," "PARAMETER[\"X_Scale\",-1.0]," "PARAMETER[\"Y_Scale\",1.0]," "PARAMETER[\"XY_Plane_Rotation\",90.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_from_EPSG_with_deprecated_ESRI_name_as_WKT1_ESRI) { auto dbContext = DatabaseContext::create(); auto crs = AuthorityFactory::create(dbContext, "EPSG")->createProjectedCRS("5186"); // Check we use the non-deprecated ESRI names, so: // "KGD2002_Central_Belt_2010" and not "Korea_2000_Korea_Central_Belt_2010" // "KGD2002" and not "GCS_Korea_2000" // "D_Korea_Geodetic_Datum_2002" and not "D_Korea_2000" auto esri_wkt = "PROJCS[\"KGD2002_Central_Belt_2010\",GEOGCS[\"KGD2002\"," "DATUM[\"D_Korea_Geodetic_Datum_2002\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",200000.0]," "PARAMETER[\"False_Northing\",600000.0]," "PARAMETER[\"Central_Meridian\",127.0]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Latitude_Of_Origin\",38.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), esri_wkt); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_from_WKT1_ETRS89_with_db) { auto wkt = "PROJCS[\"ETRS89 / UTM zone 32N (N-E)\"," "GEOGCS[\"ETRS89\"," " DATUM[\"European_Terrestrial_Reference_System_1989\"," " SPHEROID[\"GRS 1980\",6378137,298.257222101," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6258\"]]," " PRIMEM[\"Greenwich\",0," " AUTHORITY[\"EPSG\",\"8901\"]]," " UNIT[\"degree\",0.0174532925199433," " AUTHORITY[\"EPSG\",\"9122\"]]," " AUTHORITY[\"EPSG\",\"4258\"]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0]," "PARAMETER[\"central_meridian\",9]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"false_easting\",500000]," "PARAMETER[\"false_northing\",0]," "UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"Northing\",NORTH]," "AXIS[\"Easting\",EAST]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->baseCRS()->datum(); ASSERT_TRUE(datum != nullptr); EXPECT_STREQ(datum->nameStr().c_str(), "European Terrestrial Reference System 1989"); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_from_WKT1_ESRI_ETRS89_with_db) { auto wkt = "PROJCS[\"ETRS89 / ETRS-LAEA\"," "GEOGCS[\"ETRS89\"," "DATUM[\"European_Terrestrial_Reference_System_1989\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.01745329251994328]]," "PROJECTION[\"Lambert_Azimuthal_Equal_Area\"]," "PARAMETER[\"latitude_of_center\",52]," "PARAMETER[\"longitude_of_center\",10]," "PARAMETER[\"false_easting\",4321000]," "PARAMETER[\"false_northing\",3210000]," "UNIT[\"metre\",1]]"; auto dbContext = DatabaseContext::create(); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto datum = crs->baseCRS()->datum(); ASSERT_TRUE(datum != nullptr); EXPECT_STREQ(datum->nameStr().c_str(), "European Terrestrial Reference System 1989"); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_as_PROJ_string) { auto crs = createProjected(); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, crs); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm " "+zone=31 +ellps=WGS84"); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_3D_is_WKT1_equivalent_to_WKT2) { auto dbContext = DatabaseContext::create(); // "Illegal" WKT1 with a Projected 3D CRS auto wkt1 = "PROJCS[\"WGS 84 / UTM zone 16N [EGM08-1]\"," "GEOGCS[\"WGS 84 / UTM zone 16N [EGM08-1]\"," "DATUM[\"WGS84\",SPHEROID[\"WGS84\",6378137.000,298.257223563," "AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0.0000000000000000," "AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"Degree\",0.01745329251994329547," "AUTHORITY[\"EPSG\",\"9102\"]],AUTHORITY[\"EPSG\",\"32616\"]]," "UNIT[\"Meter\",1.00000000000000000000," "AUTHORITY[\"EPSG\",\"9001\"]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0.0000000000000000]," "PARAMETER[\"central_meridian\",-87.0000000002777938]," "PARAMETER[\"scale_factor\",0.9996000000000000]," "PARAMETER[\"false_easting\",500000.000]," "PARAMETER[\"false_northing\",0.000]," "AXIS[\"Easting\",EAST]," "AXIS[\"Northing\",NORTH]," "AXIS[\"Height\",UP]," "AUTHORITY[\"EPSG\",\"32616\"]]"; auto obj = WKTParser() .setStrict(false) .attachDatabaseContext(dbContext) .createFromWKT(wkt1); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); auto wkt2 = crs->exportToWKT(f.get()); auto obj2 = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt2); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); EXPECT_TRUE(crs->isEquivalentTo( crs2.get(), IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_Krovak_EPSG_5221_as_PROJ_string) { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createProjectedCRS("5221"); // 30deg 17' 17.30311'' = 30.28813975277777776 auto op = CoordinateOperationFactory::create()->createOperation( crs->baseCRS(), crs); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=longlat +ellps=bessel +pm=ferro " "+step +proj=krovak +lat_0=49.5 +lon_0=42.5 " "+alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 " "+ellps=bessel +pm=ferro"); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_Krovak_with_approximate_alpha_as_PROJ_string) { // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1 auto obj = PROJStringParser().createFromPROJString( "+proj=krovak +lat_0=49.5 +lon_0=42.5 +alpha=30.28813972222222 " "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs " "+type=crs"); auto crs = nn_dynamic_pointer_cast(obj); auto op = CoordinateOperationFactory::create()->createOperation( crs->baseCRS(), NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +inv +proj=longlat +ellps=bessel +pm=ferro " "+step +proj=krovak +lat_0=49.5 +lon_0=42.5 " "+alpha=30.2881397222222 +k=0.9999 +x_0=0 +y_0=0 " "+ellps=bessel +pm=ferro"); } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_identify_no_db) { { // Hard-coded case: WGS 84 / UTM. No name auto res = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 1, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32601); EXPECT_EQ(res.front().second, 70); EXPECT_TRUE(res.front().first->isEquivalentTo( AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createProjectedCRS("32601") .get(), IComparable::Criterion::EQUIVALENT)); } { // Hard-coded case: WGS 84 / UTM (south). Exact name. // Using virtual method auto crs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84 / UTM zone 60S"), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 60, false), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto res = static_cast(crs.get())->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32760); EXPECT_EQ(res.front().second, 100); EXPECT_TRUE(res.front().first->isEquivalentTo( AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createProjectedCRS("32760") .get(), IComparable::Criterion::EQUIVALENT)); } { // Hard-coded case: NAD27 / UTM. Approximate name. auto res = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "NAD27_UTM_zone_11N"), GeographicCRS::EPSG_4267, Conversion::createUTM(PropertyMap(), 11, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 26711); EXPECT_EQ(res.front().second, 90); EXPECT_TRUE(res.front().first->isEquivalentTo( AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createProjectedCRS("26711") .get(), IComparable::Criterion::EQUIVALENT)); } { // Hard-coded case: NAD83 / UTM auto res = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4269, Conversion::createUTM(PropertyMap(), 11, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)) ->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 26911); EXPECT_EQ(res.front().second, 70); EXPECT_TRUE(res.front().first->isEquivalentTo( AuthorityFactory::create(DatabaseContext::create(), "EPSG") ->createProjectedCRS("26911") .get(), IComparable::Criterion::EQUIVALENT)); } { // Tolerance on axis order auto obj = PROJStringParser().createFromPROJString( "+proj=utm +zone=31 +datum=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(nullptr); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32631); EXPECT_EQ(res.front().second, 70); EXPECT_TRUE(res.front().first->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)); } } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_identify_db) { auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); auto factoryIGNF = AuthorityFactory::create(dbContext, "IGNF"); auto factoryAnonymous = AuthorityFactory::create(dbContext, std::string()); { // Identify by existing code auto crs = factoryEPSG->createProjectedCRS("2172"); { auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 100); } { auto res = crs->identify(factoryAnonymous); ASSERT_EQ(res.size(), 1U); } { auto res = crs->identify(factoryIGNF); ASSERT_EQ(res.size(), 0U); } } { // Identify by existing code auto crs = factoryIGNF->createProjectedCRS("ETRS89UTM28"); { auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 25828); EXPECT_EQ(res.front().second, 70); } } { // Non-existing code auto sourceCRS = factoryEPSG->createProjectedCRS("2172"); auto crs = ProjectedCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "Pulkovo 1942(58) / Poland zone II") .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 1), sourceCRS->baseCRS(), sourceCRS->derivingConversion(), sourceCRS->coordinateSystem()); auto res = crs->identify(factoryEPSG); EXPECT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 70); } { // Existing code, but not matching content auto sourceCRS = factoryEPSG->createProjectedCRS("2172"); auto crs = ProjectedCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, "Pulkovo 1942(58) / Poland zone II") .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 32631), sourceCRS->baseCRS(), sourceCRS->derivingConversion(), sourceCRS->coordinateSystem()); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 70); } { // Identify by exact name auto sourceCRS = factoryEPSG->createProjectedCRS("2172"); auto crs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Pulkovo 1942(58) / Poland zone II"), sourceCRS->baseCRS(), sourceCRS->derivingConversion(), sourceCRS->coordinateSystem()); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 100); } { // Identify by equivalent name auto sourceCRS = factoryEPSG->createProjectedCRS("2172"); auto crs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Pulkovo_1942_58_Poland_zone_II"), sourceCRS->baseCRS(), sourceCRS->derivingConversion(), sourceCRS->coordinateSystem()); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 90); } { // Identify by properties auto sourceCRS = factoryEPSG->createProjectedCRS("2172"); auto crs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "i am a faked name"), sourceCRS->baseCRS(), sourceCRS->derivingConversion(), sourceCRS->coordinateSystem()); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 70); } { // Identify by name, but objects aren't equivalent auto sourceCRS = factoryEPSG->createProjectedCRS("3375"); auto crs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Pulkovo 1942(58) / Poland zone II"), sourceCRS->baseCRS(), sourceCRS->derivingConversion(), sourceCRS->coordinateSystem()); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2172); EXPECT_EQ(res.front().second, 25); } { // Identify from a PROJ string auto obj = PROJStringParser().createFromPROJString( "+type=crs +proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=omerc " "+no_uoff +lat_0=4 +lonc=102.25 +alpha=323.025796466667 " "+gamma=323.130102361111 +k=0.99984 +x_0=804671 +y_0=0 " "+ellps=GRS80"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3375); EXPECT_EQ(res.front().second, 70); } { // Identify from a PROJ string (with "wrong" axis order for the geodetic // part) auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +no_uoff +lat_0=4 +lonc=102.25 " "+alpha=323.025796466667 +gamma=323.130102361111 +k=0.99984 " "+x_0=804671 +y_0=0 +ellps=GRS80 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3375); EXPECT_EQ(res.front().second, 70); } { // Identify from a WKT1 string with explicit correct axis order auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"ETRS89 / UTM zone 32N (N-E)\",GEOGCS[\"ETRS89\"," "DATUM[\"European_Terrestrial_Reference_System_1989\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0]," "PARAMETER[\"central_meridian\",9]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"false_easting\",500000]," "PARAMETER[\"false_northing\",0]," "UNIT[\"metre\",1]," "AXIS[\"Northing\",NORTH],AXIS[\"Easting\",EAST]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3044); EXPECT_EQ(res.front().second, 100); } { // Identify from a WKT1 string with wrong axis order auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"ETRS89 / UTM zone 32N (N-E)\",GEOGCS[\"ETRS89\"," "DATUM[\"European_Terrestrial_Reference_System_1989\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0]," "PARAMETER[\"central_meridian\",9]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"false_easting\",500000]," "PARAMETER[\"false_northing\",0]," "UNIT[\"metre\",1]," "AXIS[\"Easting\",EAST], AXIS[\"Northing\",NORTH]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3044); EXPECT_EQ(res.front().second, 50); } { // Identify from a WKT1 string, without explicit axis auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"ETRS89 / UTM zone 32N (N-E)\",GEOGCS[\"ETRS89\"," "DATUM[\"European_Terrestrial_Reference_System_1989\"," "SPHEROID[\"GRS 1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0]," "PARAMETER[\"central_meridian\",9]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"false_easting\",500000]," "PARAMETER[\"false_northing\",0]," "UNIT[\"metre\",1]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3044); EXPECT_EQ(res.front().second, 100); } { // Identify from a WKT ESRI with bad PROJCS and GEOGCS names. auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"Lambert Conformal Conic\",GEOGCS[\"grs80\"," "DATUM[\"D_North_American_1983\"," "SPHEROID[\"Geodetic_Reference_System_1980\"," "6378137,298.257222101]],PRIMEM[\"Greenwich\",0]," "UNIT[\"Degree\",0.017453292519943295]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"standard_parallel_1\",34.33333333333334]," "PARAMETER[\"standard_parallel_2\",36.16666666666666]," "PARAMETER[\"latitude_of_origin\",33.75]," "PARAMETER[\"central_meridian\",-79]," "PARAMETER[\"false_easting\",609601.22]," "PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32119); EXPECT_EQ(res.front().second, 70); } { // No equivalent CRS to input one in result set auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +datum=WGS84 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 0U); } { // ESRI:103729 definition as a PROJ string auto obj = PROJStringParser() .attachDatabaseContext(dbContext) .createFromPROJString( "+proj=lcc +lat_0=43.5 +lon_0=-93.95 " "+lat_1=43.5666666666667 " "+lat_2=43.8 +x_0=152400.30480061 +y_0=30480.0609601219 " "+a=6378521.049 +rf=298.257222100883 +units=us-ft " "+type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); EXPECT_GE(res.size(), 1U); bool found = false; for (const auto &pair : res) { if (pair.first->identifiers()[0]->code() == "103729") { found = true; EXPECT_EQ(pair.second, 70); break; } } EXPECT_TRUE(found); } { // EPSG:2327 as PROJ.4 string (so with easting, northing order whereas // official CRS is northing, easting) auto obj = PROJStringParser() .attachDatabaseContext(dbContext) .createFromPROJString( "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=13500000 +y_0=0 " "+a=6378140 +b=6356755.288157528 +units=m +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); EXPECT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2327); EXPECT_EQ(res.front().second, 70); } { // EPSG:6646 as PROJ.4 string, using clrk80 which is pretty generic auto obj = PROJStringParser() .attachDatabaseContext(dbContext) .createFromPROJString( "+proj=tmerc +lat_0=29.02626833333333 +lon_0=46.5 " "+k=0.9994 +x_0=800000 +y_0=0 +ellps=clrk80 " "+units=m +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); EXPECT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 6646); EXPECT_EQ(res.front().second, 70); } { // Identify from a WKT ESRI that has the same name has ESRI:102039 // but uses us-ft instead of metres! auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"USA_Contiguous_Albers_Equal_Area_Conic_USGS_version\"," "GEOGCS[\"GCS_North_American_1983\"," "DATUM[\"D_North_American_1983\",SPHEROID[\"GRS_1980\"," "6378137.0,298.257222101]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Albers\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",-96.0]," "PARAMETER[\"Standard_Parallel_1\",29.5]," "PARAMETER[\"Standard_Parallel_2\",45.5]," "PARAMETER[\"Latitude_Of_Origin\",23.0]," "UNIT[\"Foot_US\",0.3048006096012192]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); EXPECT_GE(res.size(), 1U); bool found = false; for (const auto &pair : res) { if (pair.first->identifiers()[0]->code() == "102039") { found = true; EXPECT_EQ(pair.second, 50); break; } } EXPECT_TRUE(found); } { // Identify a ESRI WKT where the EPSG system has Northing/Easting order auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"NZGD2000_New_Zealand_Transverse_Mercator_2000\"," " GEOGCS[\"GCS_NZGD2000\"," " DATUM[\"New_Zealand_Geodetic_Datum_2000\"," " SPHEROID[\"GRS 1980\",6378137,298.2572221010042," " AUTHORITY[\"EPSG\",\"7019\"]]," " AUTHORITY[\"EPSG\",\"6167\"]]," " PRIMEM[\"Greenwich\",0]," " UNIT[\"degree\",0.0174532925199433]]," " PROJECTION[\"Transverse_Mercator\"]," " PARAMETER[\"latitude_of_origin\",0]," " PARAMETER[\"central_meridian\",173]," " PARAMETER[\"scale_factor\",0.9996]," " PARAMETER[\"false_easting\",1600000]," " PARAMETER[\"false_northing\",10000000]," " UNIT[\"metre\",1," " AUTHORITY[\"EPSG\",\"9001\"]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2193); EXPECT_EQ(res.front().second, 90); } { // Special case for https://github.com/OSGeo/PROJ/issues/2086 // The name of the CRS to identify is // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501 // whereas it should be // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501\"," "GEOGCS[\"GCS_North_American_1983_HARN\"," "DATUM[\"D_North_American_1983_HARN\",SPHEROID[\"GRS_1980\"," "6378137.0,298.257222101]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",3000000.000316083]," "PARAMETER[\"False_Northing\",999999.999996]," "PARAMETER[\"Central_Meridian\",-105.5]," "PARAMETER[\"Standard_Parallel_1\",39.71666666666667]," "PARAMETER[\"Standard_Parallel_2\",40.78333333333333]," "PARAMETER[\"Latitude_Of_Origin\",39.33333333333334]," "UNIT[\"Foot_US\",0.3048006096012192]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2876); EXPECT_EQ(res.front().second, 100); } { // Test case of https://github.com/OSGeo/PROJ/issues/2099 // The name of the CRS to identify is // JGD2011_Japan_Zone_2 // whereas the official ESRI alias is // JGD_2011_Japan_Zone_2 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"JGD2011_Japan_Zone_2\",GEOGCS[\"GCS_JGD_2011\"," "DATUM[\"D_JGD_2011\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",131]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Latitude_Of_Origin\",33],UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 6670); EXPECT_EQ(res.front().second, 90); } { // Test case of https://github.com/OSGeo/PROJ/issues/2116 // The NAD_1983_CSRS_Prince_Edward_Island has entries in the alias // table under the ESRI authority for deprecated EPSG:2292 and // non-deprecated EPSG:2954 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"NAD_1983_CSRS_Prince_Edward_Island\"," "GEOGCS[\"GCS_North_American_1983_CSRS\"," "DATUM[\"D_North_American_1983_CSRS\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Double_Stereographic\"]," "PARAMETER[\"False_Easting\",400000.0]," "PARAMETER[\"False_Northing\",800000.0]," "PARAMETER[\"Central_Meridian\",-63.0]," "PARAMETER[\"Scale_Factor\",0.999912]," "PARAMETER[\"Latitude_Of_Origin\",47.25],UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2954); EXPECT_EQ(res.front().second, 100); } { // Test identification of LCC_2SP with switched standard parallels. auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"foo\",\n" " GEOGCS[\"RGF93\",\n" " DATUM[\"Reseau_Geodesique_Francais_1993\",\n" " SPHEROID[\"GRS 1980\",6378137,298.257222101]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"Lambert_Conformal_Conic_2SP\"],\n" " PARAMETER[\"latitude_of_origin\",46.5],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"standard_parallel_1\",44],\n" " PARAMETER[\"standard_parallel_2\",49],\n" " PARAMETER[\"false_easting\",700000],\n" " PARAMETER[\"false_northing\",6600000],\n" " UNIT[\"metre\",1]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 2154); EXPECT_EQ(res.front().second, 70); } { // Test identification of LKS92_Latvia_TM (#2214) auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"LKS92_Latvia_TM\",GEOGCS[\"GCS_LKS92\"," "DATUM[\"D_Latvia_1992\"," "SPHEROID[\"GRS_1980\",6378137,298.257222101]]," "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"latitude_of_origin\",0]," "PARAMETER[\"central_meridian\",24]," "PARAMETER[\"scale_factor\",0.9996]," "PARAMETER[\"false_easting\",500000]," "PARAMETER[\"false_northing\",-6000000]," "UNIT[\"Meter\",1]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3059); EXPECT_EQ(res.front().second, 90); } { // Test identification of CRS where everything but datum names matches auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"wrong_datum_name\"," "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",500000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",3.0]," "PARAMETER[\"Scale_Factor\",0.9996]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32631); EXPECT_EQ(res.front().second, 60); } { // Test case of https://github.com/qgis/QGIS/issues/36111 // The name of the CRS to identify is // ETRS89_LAEA_Europe // We identify it through a registered EPSG alias "ETRS89 / LAEA Europe" auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"ETRS89_LAEA_Europe\"," "GEOGCS[\"GCS_ETRS_1989\",DATUM[\"D_ETRS_1989\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Azimuthal_Equal_Area\"]," "PARAMETER[\"false_easting\",4321000.0]," "PARAMETER[\"false_northing\",3210000.0]," "PARAMETER[\"central_meridian\",10.0]," "PARAMETER[\"latitude_of_origin\",52.0]," "UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 3035); EXPECT_EQ(res.front().second, 90); } { // Test case of https://github.com/qgis/QGIS/issues/32255 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"RGF93_Lambert_93\",GEOGCS[\"GCS_RGF_1993\"," "DATUM[\"D_RGF_1993\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",700000.0]," "PARAMETER[\"False_Northing\",6600000.0]," "PARAMETER[\"Central_Meridian\",3.0]," "PARAMETER[\"Standard_Parallel_1\",44.0]," "PARAMETER[\"Standard_Parallel_2\",49.0]," "PARAMETER[\"Latitude_Of_Origin\",46.5],UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); bool found = false; for (const auto &candidate : res) { if (candidate.first->getEPSGCode() == 2154) { found = true; EXPECT_EQ(candidate.second, 90); } } EXPECT_TRUE(found); } { // Identify with DatumEnsemble auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\"," " BASEGEOGCRS[\"WGS 84\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " MEMBER[\"World Geodetic System 1984 (Transit)\"," " ID[\"EPSG\",1166]]," " MEMBER[\"World Geodetic System 1984 (G730)\"," " ID[\"EPSG\",1152]]," " MEMBER[\"World Geodetic System 1984 (G873)\"," " ID[\"EPSG\",1153]]," " MEMBER[\"World Geodetic System 1984 (G1150)\"," " ID[\"EPSG\",1154]]," " MEMBER[\"World Geodetic System 1984 (G1674)\"," " ID[\"EPSG\",1155]]," " MEMBER[\"World Geodetic System 1984 (G1762)\"," " ID[\"EPSG\",1156]]," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]," " ENSEMBLEACCURACY[2]]]," " CONVERSION[\"UTM zone 31N\"," " METHOD[\"Transverse Mercator\"," " ID[\"EPSG\",9807]]," " PARAMETER[\"Latitude of natural origin\",0," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]]," " PARAMETER[\"Longitude of natural origin\",3," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]]," " PARAMETER[\"Scale factor at natural origin\",0.9996," " SCALEUNIT[\"unity\",1,ID[\"EPSG\",9201]]]," " PARAMETER[\"False easting\",500000," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]," " PARAMETER[\"False northing\",0," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]]," " CS[Cartesian,2]," " AXIS[\"Easting (E)\",east," " ORDER[1]]," " AXIS[\"Northing (N)\",north," " ORDER[2]]," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32631); EXPECT_EQ(res.front().second, 100.0); } { // Identify with DatumEnsemble and unknown CRS name auto wkt = "PROJCRS[\"unknown\"," " BASEGEOGCRS[\"unknown\"," " ENSEMBLE[\"World Geodetic System 1984 ensemble\"," " MEMBER[\"World Geodetic System 1984 (Transit)\"," " ID[\"EPSG\",1166]]," " MEMBER[\"World Geodetic System 1984 (G730)\"," " ID[\"EPSG\",1152]]," " MEMBER[\"World Geodetic System 1984 (G873)\"," " ID[\"EPSG\",1153]]," " MEMBER[\"World Geodetic System 1984 (G1150)\"," " ID[\"EPSG\",1154]]," " MEMBER[\"World Geodetic System 1984 (G1674)\"," " ID[\"EPSG\",1155]]," " MEMBER[\"World Geodetic System 1984 (G1762)\"," " ID[\"EPSG\",1156]]," " ELLIPSOID[\"WGS 84\",6378137,298.257223563," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]," " ENSEMBLEACCURACY[2]]]," " CONVERSION[\"UTM zone 31N\"," " METHOD[\"Transverse Mercator\"," " ID[\"EPSG\",9807]]," " PARAMETER[\"Latitude of natural origin\",0," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]]," " PARAMETER[\"Longitude of natural origin\",3," " ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]]]," " PARAMETER[\"Scale factor at natural origin\",0.9996," " SCALEUNIT[\"unity\",1,ID[\"EPSG\",9201]]]," " PARAMETER[\"False easting\",500000," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]," " PARAMETER[\"False northing\",0," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]]," " CS[Cartesian,2]," " AXIS[\"Easting (E)\",east," " ORDER[1]]," " AXIS[\"Northing (N)\",north," " ORDER[2]]," " LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 32631); EXPECT_GE(res.front().second, 70.0); } { // Identify a ESRI WKT where the datum name doesn't start with D_ auto wkt = "PROJCS[\"S-JTSK_[JTSK03]_Krovak_East_North\"," "GEOGCS[\"S-JTSK_[JTSK03]\"," " DATUM[\"S-JTSK_[JTSK03]\"," " SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," " PRIMEM[\"Greenwich\",0.0]," " UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Krovak\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Azimuth\",30.2881397527778]," "PARAMETER[\"Longitude_Of_Center\",24.8333333333333]," "PARAMETER[\"Latitude_Of_Center\",49.5]," "PARAMETER[\"X_Scale\",-1.0]," "PARAMETER[\"Y_Scale\",1.0]," "PARAMETER[\"XY_Plane_Rotation\",90.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(WKTParser().guessDialect(wkt), WKTParser::WKTGuessedDialect::WKT1_ESRI); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8353); EXPECT_EQ(res.front().second, 100); } { // Identify from a pseudo WKT ESRI with has an AUTHORITY node that // points to another object. // Cf // https://lists.osgeo.org/pipermail/qgis-user/2023-January/052299.html auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"ETRS_1989_UTM_Zone_32N_6Stellen\"," "GEOGCS[\"GCS_ETRS_1989\",DATUM[\"D_ETRS_1989\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",500000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",9.0]," "PARAMETER[\"Scale_Factor\",0.9996]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]," "AUTHORITY[\"ESRI\",\"102328\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 25832); EXPECT_EQ(res.front().second, 70); } { // Identify a projected CRS whose base CRS is not a geographic CRS but // a geodetic CRS with a spherical -ocentric coordinate system. // Cf https://github.com/OSGeo/PROJ/issues/3828 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCRS[\"unknown\",\n" " BASEGEODCRS[\"Mercury (2015) / Ocentric\",\n" " DATUM[\"Mercury (2015)\",\n" " ELLIPSOID[\"Mercury " "(2015)\",2440530,1075.12334801762,\n" " LENGTHUNIT[\"metre\",1]],\n" " ANCHOR[\"Hun Kal: 20 W.0\"]],\n" " PRIMEM[\"Reference Meridian\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"IAU\",19902,2015]],\n" " CONVERSION[\"Equirectangular, clon = 0\",\n" " METHOD[\"Equidistant Cylindrical\",\n" " ID[\"EPSG\",1028]],\n" " PARAMETER[\"Latitude of 1st standard parallel\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8823]],\n" " PARAMETER[\"Longitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"False easting\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "19912"); EXPECT_EQ(*(res.front().first->identifiers()[0]->codeSpace()), "IAU_2015"); EXPECT_EQ(res.front().second, 90); } { // Identify a WKT ESRI using deprecated ESRI names // Cf https://github.com/OSGeo/PROJ/issues/4281 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"Korea_2000_Korea_Central_Belt_2010\"," "GEOGCS[\"GCS_Korea_2000\",DATUM[\"D_Korea_2000\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",200000.0]," "PARAMETER[\"False_Northing\",600000.0]," "PARAMETER[\"Central_Meridian\",127.0]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Latitude_Of_Origin\",38.0]," "UNIT[\"Meter\",1.0]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "5186"); EXPECT_EQ(*(res.front().first->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(res.front().second, 100); } { // Identify a WKT ESRI using deprecated ESRI names // Cf https://github.com/OSGeo/gdal/issues/12511 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"NAD_1983_StatePlane_Arizona_Central_FIPS_0202_IntlFeet\"," "GEOGCS[\"GCS_North_American_1983\"," "DATUM[\"D_North_American_1983\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",700000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",-111.9166666666667]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Latitude_Of_Origin\",31.0],UNIT[\"Foot\",0.3048]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto allFactory = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(allFactory); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "2223"); EXPECT_EQ(*(res.front().first->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(res.front().second, 70); } #ifdef requires_epsg_12_054 { // Identify a CRS after ETRS89 -> ETRS89-PRT [1995] changes auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"ETRS89 / Portugal TM06\"," "GEOGCS[\"ETRS89\"," "DATUM[\"European Terrestrial Reference System 1989\"," "SPHEROID[\"GRS 1980\", 6378137.0, 298.257222101," "AUTHORITY[\"EPSG\",\"7019\"]]," "AUTHORITY[\"EPSG\",\"6258\"]]," "PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree\", 0.017453292519943295]," "AXIS[\"Geodetic latitude\", NORTH]," "AXIS[\"Geodetic longitude\", EAST]," "AUTHORITY[\"EPSG\",\"4258\"]]," "PROJECTION[\"Transverse_Mercator\"," "AUTHORITY[\"EPSG\",\"9807\"]]," "PARAMETER[\"central_meridian\", -8.133108333333334]," "PARAMETER[\"latitude_of_origin\", 39.66825833333334]," "PARAMETER[\"scale_factor\", 1.0]," "PARAMETER[\"false_easting\", 0.0]," "PARAMETER[\"false_northing\", 0.0]," "UNIT[\"m\", 1.0]," "AXIS[\"Easting\", EAST]," "AXIS[\"Northing\", NORTH]," "AUTHORITY[\"EPSG\",\"3763\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_GE(res.size(), 1U); EXPECT_EQ(res.front().first->identifiers()[0]->code(), "3763"); EXPECT_EQ(*(res.front().first->identifiers()[0]->codeSpace()), "EPSG"); EXPECT_EQ(res.front().second, 100); } #endif { // The ellipsoid definition uses a unusual value for the inverse // flattening Check that we only identify CRS whose ellipsoid shape is // close to the input one (in particular no IAU_2015...) auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCRS[\"Transverse Mercator\",\n" " BASEGEOGCRS[\"DE_DHDN (whole country, 2001) to ETRS89\",\n" " DATUM[\"DE_DHDN (whole country, 2001) to ETRS89\",\n" " ELLIPSOID[\"Bessel\",6377397.155,299.15281310608,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CONVERSION[\"Transverse Mercator\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",12,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",4500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"easting\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"meters\",1]],\n" " AXIS[\"northing\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"meters\",1]]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto factoryAll = AuthorityFactory::create(dbContext, std::string()); auto res = crs->identify(factoryAll); EXPECT_EQ(res.size(), 13U); } } // --------------------------------------------------------------------------- TEST(crs, projectedCRS_identify_wrong_auth_name_case) { auto dbContext = DatabaseContext::create(); auto factoryAnonymous = AuthorityFactory::create(dbContext, std::string()); auto obj = WKTParser() .attachDatabaseContext(dbContext) .setStrict(false) .createFromWKT( "PROJCS[\"World_Cylindrical_Equal_Area\"," "GEOGCS[\"GCS_WGS_1984\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Cylindrical_Equal_Area\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",0.0],UNIT[\"Meter\",1.0]," "AUTHORITY[\"Esri\",54034]]"); // should be ESRI all caps auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryAnonymous); ASSERT_EQ(res.size(), 1U); const auto &ids = res.front().first->identifiers(); ASSERT_EQ(ids.size(), 1U); EXPECT_EQ(*(ids.front()->codeSpace()), "ESRI"); EXPECT_EQ(ids.front()->code(), "54034"); } // --------------------------------------------------------------------------- TEST(crs, mercator_1SP_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=merc +lon_0=110 +k=0.997 +x_0=3900000 +y_0=900000 " "+ellps=bessel +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_Unknown_based_on_Bessel_1841_ellipsoid\"," "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Mercator\"]," "PARAMETER[\"False_Easting\",3900000.0]," "PARAMETER[\"False_Northing\",900000.0]," "PARAMETER[\"Central_Meridian\",110.0]," "PARAMETER[\"Standard_Parallel_1\",4.45405154589748]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Plate_Carree_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+title=my Plate carree +proj=eqc +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"my_Plate_carree\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Plate_Carree\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Equidistant_Cylindrical_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString("+proj=eqc +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Equidistant_Cylindrical\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Hotine_Oblique_Mercator_Azimuth_Natural_Origin_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +no_uoff +gamma=295 +alpha=295 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[" "\"Hotine_Oblique_Mercator_Azimuth_Natural_Origin\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Scale_Factor\",1.0]," // we renormalize angles to [-180,180] "PARAMETER[\"Azimuth\",-65.0]," "PARAMETER[\"Longitude_Of_Center\",0.0]," "PARAMETER[\"Latitude_Of_Center\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Rectified_Skew_Orthomorphic_Natural_Origin_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +no_uoff +gamma=3 +alpha=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[" "\"Rectified_Skew_Orthomorphic_Natural_Origin\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Azimuth\",2.0]," "PARAMETER[\"Longitude_Of_Center\",0.0]," "PARAMETER[\"Latitude_Of_Center\",0.0]," "PARAMETER[\"XY_Plane_Rotation\",3.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Hotine_Oblique_Mercator_Azimuth_Center_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +gamma=2 +alpha=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[" "\"Hotine_Oblique_Mercator_Azimuth_Center\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Azimuth\",2.0]," "PARAMETER[\"Longitude_Of_Center\",0.0]," "PARAMETER[\"Latitude_Of_Center\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Rectified_Skew_Orthomorphic_Center_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=omerc +gamma=3 +alpha=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[" "\"Rectified_Skew_Orthomorphic_Center\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Azimuth\",2.0]," "PARAMETER[\"Longitude_Of_Center\",0.0]," "PARAMETER[\"Latitude_Of_Center\",0.0]," "PARAMETER[\"XY_Plane_Rotation\",3.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Gauss_Kruger_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+title=my Gauss Kruger +proj=tmerc +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"my_Gauss_Kruger\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0," "298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Gauss_Kruger\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Latitude_Of_Origin\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Stereographic_North_Pole_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=stere +lat_0=90 +lat_ts=70 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Stereographic_North_Pole\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",70.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Stereographic_South_Pole_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=stere +lat_0=-90 +lat_ts=-70 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Stereographic_South_Pole\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",-70.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Krovak_North_Orientated_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString("+proj=krovak +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_Unknown_based_on_Bessel_1841_ellipsoid\"," "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Krovak\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Azimuth\",30.2881397527778]," "PARAMETER[\"Longitude_Of_Center\",24.8333333333333]," "PARAMETER[\"Latitude_Of_Center\",49.5]," "PARAMETER[\"X_Scale\",-1.0]," "PARAMETER[\"Y_Scale\",1.0]," "PARAMETER[\"XY_Plane_Rotation\",90.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, Krovak_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=krovak +axis=swu +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_Unknown_based_on_Bessel_1841_ellipsoid\"," "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]]," "PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Krovak\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5]," "PARAMETER[\"Scale_Factor\",0.9999]," "PARAMETER[\"Azimuth\",30.2881397527778]," "PARAMETER[\"Longitude_Of_Center\",24.8333333333333]," "PARAMETER[\"Latitude_Of_Center\",49.5]," "PARAMETER[\"X_Scale\",1.0]," "PARAMETER[\"Y_Scale\",1.0]," "PARAMETER[\"XY_Plane_Rotation\",0.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, LCC_1SP_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=lcc +lat_1=1 +lat_0=1 +k=0.9 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",1.0]," "PARAMETER[\"Scale_Factor\",0.9]," "PARAMETER[\"Latitude_Of_Origin\",1.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, LCC_2SP_as_WKT1_ESRI) { auto obj = PROJStringParser().createFromPROJString( "+proj=lcc +lat_0=1.5 +lat_1=1 +lat_2=2 +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\"," "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\"," "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0]," "UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",0.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",0.0]," "PARAMETER[\"Standard_Parallel_1\",1.0]," "PARAMETER[\"Standard_Parallel_2\",2.0]," "PARAMETER[\"Latitude_Of_Origin\",1.5]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, ESRI_WKT1_to_ESRI_WKT1) { auto in_wkt = "PROJCS[\"NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US\"," "GEOGCS[\"GCS_NAD_1983_CORS96\",DATUM[\"D_NAD_1983_CORS96\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",2000000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",-79.0]," "PARAMETER[\"Standard_Parallel_1\",34.33333333333334]," "PARAMETER[\"Standard_Parallel_2\",36.16666666666666]," "PARAMETER[\"Latitude_Of_Origin\",33.75]," "UNIT[\"Foot_US\",0.3048006096012192]]"; auto obj = WKTParser().createFromWKT(in_wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto expected = "PROJCS[\"NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US\"," "GEOGCS[\"GCS_NAD_1983_CORS96\",DATUM[\"D_NAD_1983_CORS96\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Lambert_Conformal_Conic\"]," "PARAMETER[\"False_Easting\",2000000.0]," "PARAMETER[\"False_Northing\",0.0]," "PARAMETER[\"Central_Meridian\",-79.0]," "PARAMETER[\"Standard_Parallel_1\",34.3333333333333]," "PARAMETER[\"Standard_Parallel_2\",36.1666666666667]," "PARAMETER[\"Latitude_Of_Origin\",33.75]," "UNIT[\"Foot_US\",0.304800609601219]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(datum, cs_with_MERIDIAN) { std::vector axis{ CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Easting"), "X", AxisDirection::SOUTH, UnitOfMeasure::METRE, Meridian::create(Angle(90))), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Northing"), "Y", AxisDirection::SOUTH, UnitOfMeasure::METRE, Meridian::create(Angle(180.0)))}; auto cs(CartesianCS::create(PropertyMap(), axis[0], axis[1])); auto expected = "CS[Cartesian,2],\n" " AXIS[\"easting (X)\",south,\n" " MERIDIAN[90,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"northing (Y)\",south,\n" " MERIDIAN[180,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]"; auto formatter = WKTFormatter::create(); formatter->setOutputId(false); EXPECT_EQ(cs->exportToWKT(formatter.get()), expected); } // --------------------------------------------------------------------------- TEST(crs, scope_area_bbox_remark) { auto in_wkt = "GEODETICCRS[\"JGD2000\"," "DATUM[\"Japanese Geodetic Datum 2000\"," " ELLIPSOID[\"GRS 1980\",6378137,298.257222101]]," "CS[Cartesian,3]," " AXIS[\"(X)\",geocentricX]," " AXIS[\"(Y)\",geocentricY]," " AXIS[\"(Z)\",geocentricZ]," " LENGTHUNIT[\"metre\",1.0]," "SCOPE[\"Geodesy, topographic mapping and cadastre\"]," "AREA[\"Japan\"]," "BBOX[17.09,122.38,46.05,157.64]," "VERTICALEXTENT[-10000,10000]," "TIMEEXTENT[2002-04-01,2011-10-21]," "ID[\"EPSG\",4946],\n" "REMARK[\"some_remark\"]]"; auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(in_wkt)); ASSERT_TRUE(crs != nullptr); ASSERT_EQ(crs->domains().size(), 1U); auto domain = crs->domains()[0]; EXPECT_TRUE(domain->scope().has_value()); EXPECT_EQ(*(domain->scope()), "Geodesy, topographic mapping and cadastre"); ASSERT_TRUE(domain->domainOfValidity() != nullptr); EXPECT_TRUE(domain->domainOfValidity()->description().has_value()); EXPECT_EQ(*(domain->domainOfValidity()->description()), "Japan"); ASSERT_EQ(domain->domainOfValidity()->geographicElements().size(), 1U); auto geogElement = domain->domainOfValidity()->geographicElements()[0]; auto bbox = nn_dynamic_pointer_cast(geogElement); ASSERT_TRUE(bbox != nullptr); EXPECT_EQ(bbox->southBoundLatitude(), 17.09); EXPECT_EQ(bbox->westBoundLongitude(), 122.38); EXPECT_EQ(bbox->northBoundLatitude(), 46.05); EXPECT_EQ(bbox->eastBoundLongitude(), 157.64); ASSERT_EQ(domain->domainOfValidity()->verticalElements().size(), 1U); auto verticalElement = domain->domainOfValidity()->verticalElements()[0]; EXPECT_EQ(verticalElement->minimumValue(), -10000); EXPECT_EQ(verticalElement->maximumValue(), 10000); EXPECT_EQ(*(verticalElement->unit()), UnitOfMeasure::METRE); ASSERT_EQ(domain->domainOfValidity()->temporalElements().size(), 1U); auto temporalElement = domain->domainOfValidity()->temporalElements()[0]; EXPECT_EQ(temporalElement->start(), "2002-04-01"); EXPECT_EQ(temporalElement->stop(), "2011-10-21"); auto got_wkt = crs->exportToWKT(WKTFormatter::create().get()); auto expected = "GEODCRS[\"JGD2000\",\n" " DATUM[\"Japanese Geodetic Datum 2000\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " SCOPE[\"Geodesy, topographic mapping and cadastre\"],\n" " AREA[\"Japan\"],\n" " BBOX[17.09,122.38,46.05,157.64],\n" " VERTICALEXTENT[-10000,10000,\n" " LENGTHUNIT[\"metre\",1]],\n" " TIMEEXTENT[2002-04-01,2011-10-21],\n" " ID[\"EPSG\",4946],\n" " REMARK[\"some_remark\"]]"; EXPECT_EQ(got_wkt, expected); } // --------------------------------------------------------------------------- TEST(crs, usage) { auto in_wkt = "GEODETICCRS[\"JGD2000\"," "DATUM[\"Japanese Geodetic Datum 2000\"," " ELLIPSOID[\"GRS 1980\",6378137,298.257222101]]," "CS[Cartesian,3]," " AXIS[\"(X)\",geocentricX]," " AXIS[\"(Y)\",geocentricY]," " AXIS[\"(Z)\",geocentricZ]," " LENGTHUNIT[\"metre\",1.0]," "USAGE[SCOPE[\"scope\"],AREA[\"area.\"]]]"; auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(in_wkt)); ASSERT_TRUE(crs != nullptr); auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); auto expected = "GEODCRS[\"JGD2000\",\n" " DATUM[\"Japanese Geodetic Datum 2000\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8901]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"scope\"],\n" " AREA[\"area.\"]]]"; EXPECT_EQ(got_wkt, expected); } // --------------------------------------------------------------------------- TEST(crs, multiple_ID) { PropertyMap propertiesCRS; propertiesCRS.set(IdentifiedObject::NAME_KEY, "WGS 84"); auto identifiers = ArrayOfBaseObject::create(); identifiers->add(Identifier::create( "codeA", PropertyMap().set(Identifier::CODESPACE_KEY, "authorityA"))); identifiers->add(Identifier::create( "codeB", PropertyMap().set(Identifier::CODESPACE_KEY, "authorityB"))); propertiesCRS.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); auto crs = GeodeticCRS::create( propertiesCRS, GeodeticReferenceFrame::EPSG_6326, CartesianCS::createGeocentric(UnitOfMeasure::METRE)); auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED).get()); auto expected = "GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX],\n" " AXIS[\"(Y)\",geocentricY],\n" " AXIS[\"(Z)\",geocentricZ],\n" " UNIT[\"metre\",1],\n" " ID[\"authorityA\",\"codeA\"],\n" " ID[\"authorityB\",\"codeB\"]]"; EXPECT_EQ(got_wkt, expected); } // --------------------------------------------------------------------------- static VerticalCRSNNPtr createVerticalCRS() { PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create(propertiesVDatum); PropertyMap propertiesCRS; propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5701) .set(IdentifiedObject::NAME_KEY, "ODN height"); return VerticalCRS::create( propertiesCRS, vdatum, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_as_WKT2) { auto crs = createVerticalCRS(); auto expected = "VERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",5701]]"; EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_as_WKT1_GDAL) { auto crs = createVerticalCRS(); auto expected = "VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n" " AUTHORITY[\"EPSG\",\"5101\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5701\"]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_as_WKT1_ESRI) { auto crs = createVerticalCRS(); auto expected = "VERTCS[\"ODN_height\",VDATUM[\"Ordnance_Datum_Newlyn\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_as_WKT1_ESRI_context) { auto crs = createVerticalCRS(); auto expected = "VERTCS[\"Newlyn\",VDATUM[\"Ordnance_Datum_Newlyn\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0]," "UNIT[\"Meter\",1.0]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()) .get()), expected); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_down_as_WKT1_ESRI) { auto wkt = "VERTCS[\"Caspian\",VDATUM[\"Caspian_Sea\"]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",-1.0],UNIT[\"Meter\",1.0]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI).get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_ESRI_115834_as_WKT1_ESRI_with_database) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "ESRI"); auto crs = factory->createCoordinateReferenceSystem("115834"); WKTFormatterNNPtr f(WKTFormatter::create( WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create())); // Check that the parentheses in the VERTCS and DATUM names are preserved EXPECT_EQ(crs->exportToWKT(f.get()), "VERTCS[\"NAD83(CSRS)v5\"," "DATUM[\"North_American_Datum_of_1983_(CSRS)_version_5\"," "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PARAMETER[\"Vertical_Shift\",0.0]," "PARAMETER[\"Direction\",1.0],UNIT[\"Meter\",1.0]]"); } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_identify_db) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); { // Identify by existing code auto res = factory->createVerticalCRS("7651")->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 7651); EXPECT_EQ(res.front().second, 100); // Test with null EXPECT_TRUE( factory->createVerticalCRS("7651")->identify(nullptr).empty()); } { // Non-existing code auto sourceCRS = factory->createVerticalCRS("7651"); auto crs = VerticalCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()) .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 1), sourceCRS->datum(), sourceCRS->datumEnsemble(), sourceCRS->coordinateSystem()); auto res = crs->identify(factory); EXPECT_EQ(res.size(), 0U); } { // Existing code, but not matching content auto sourceCRS = factory->createVerticalCRS("7651"); auto crs = VerticalCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()) .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 7652), sourceCRS->datum(), sourceCRS->datumEnsemble(), sourceCRS->coordinateSystem()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 7652); EXPECT_EQ(res.front().second, 25); } { // Identify by exact name auto sourceCRS = factory->createVerticalCRS("7651"); auto crs = VerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()), sourceCRS->datum(), sourceCRS->datumEnsemble(), sourceCRS->coordinateSystem()); auto res = static_cast(crs.get())->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 7651); EXPECT_EQ(res.front().second, 100); } { // Identify by equivalent name auto sourceCRS = factory->createVerticalCRS("7651"); auto crs = VerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Kumul_34_height"), sourceCRS->datum(), sourceCRS->datumEnsemble(), sourceCRS->coordinateSystem()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 7651); EXPECT_EQ(res.front().second, 90); } { // Identify by name, but objects aren't equivalent auto sourceCRS = factory->createVerticalCRS("7652"); auto crs = VerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Kumul_34_height"), sourceCRS->datum(), sourceCRS->datumEnsemble(), sourceCRS->coordinateSystem()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 7651); EXPECT_EQ(res.front().second, 25); } { auto sourceCRS = factory->createVerticalCRS("7651"); auto crs = VerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "no match"), sourceCRS->datum(), sourceCRS->datumEnsemble(), sourceCRS->coordinateSystem()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 0U); } } // --------------------------------------------------------------------------- TEST(crs, verticalCRS_datum_ensemble) { auto ensemble = DatumEnsemble::create( PropertyMap(), std::vector{ VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")), VerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))}, PositionalAccuracy::create("100")); auto crs = VerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), nullptr, ensemble, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); f->simulCurNodeHasId(); crs->exportToWKT(f.get()); auto expected = "VERTCRS[\"unnamed\",\n" " ENSEMBLE[\"unnamed\",\n" " MEMBER[\"vdatum1\"],\n" " MEMBER[\"vdatum2\"],\n" " ENSEMBLEACCURACY[100]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]]"; EXPECT_EQ(f->toString(), expected); } // --------------------------------------------------------------------------- TEST(crs, VerticalCRS_ensemble_exception_in_create) { EXPECT_THROW(VerticalCRS::create(PropertyMap(), nullptr, nullptr, VerticalCS::createGravityRelatedHeight( UnitOfMeasure::METRE)), Exception); auto ensemble_hdatum = DatumEnsemble::create( PropertyMap(), std::vector{GeodeticReferenceFrame::EPSG_6326, GeodeticReferenceFrame::EPSG_6326}, PositionalAccuracy::create("100")); EXPECT_THROW(VerticalCRS::create(PropertyMap(), nullptr, ensemble_hdatum, VerticalCS::createGravityRelatedHeight( UnitOfMeasure::METRE)), Exception); } // --------------------------------------------------------------------------- TEST(datum, vdatum_with_anchor) { PropertyMap propertiesVDatum; propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 5101) .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn"); auto vdatum = VerticalReferenceFrame::create( propertiesVDatum, optional("my anchor"), optional(RealizationMethod::LEVELLING)); EXPECT_TRUE(vdatum->realizationMethod().has_value()); EXPECT_EQ(*(vdatum->realizationMethod()), RealizationMethod::LEVELLING); auto expected = "VDATUM[\"Ordnance Datum Newlyn\",\n" " ANCHOR[\"my anchor\"],\n" " ID[\"EPSG\",5101]]"; EXPECT_EQ(vdatum->exportToWKT(WKTFormatter::create().get()), expected); EXPECT_TRUE(vdatum->isEquivalentTo(vdatum.get())); EXPECT_FALSE(vdatum->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- static CompoundCRSNNPtr createCompoundCRS() { PropertyMap properties; properties.set(Identifier::CODESPACE_KEY, "codespace") .set(Identifier::CODE_KEY, "code") .set(IdentifiedObject::NAME_KEY, "horizontal + vertical"); return CompoundCRS::create( properties, std::vector{createProjected(), createVerticalCRS()}); } // --------------------------------------------------------------------------- static DerivedProjectedCRSNNPtr createDerivedProjectedCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "derived projectedCRS"), createProjected(), derivingConversion, CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } static DerivedProjectedCRSNNPtr createDerivedProjectedCRSNorthingEasting() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "derived projectedCRS"), createProjected(), derivingConversion, CartesianCS::createNorthingEasting(UnitOfMeasure::FOOT)); } // --------------------------------------------------------------------------- static DerivedVerticalCRSNNPtr createDerivedVerticalCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedVerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived vertCRS"), createVerticalCRS(), derivingConversion, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_valid) { // geographic 2D + vertical CompoundCRS::create( PropertyMap(), std::vector{GeographicCRS::EPSG_4326, createVerticalCRS()}); // projected 2D + vertical CompoundCRS::create( PropertyMap(), std::vector{createProjected(), createVerticalCRS()}); // derived projected 2D + vertical CompoundCRS::create(PropertyMap(), std::vector{createDerivedProjectedCRS(), createVerticalCRS()}); // derived projected 2D + derived vertical CompoundCRS::create(PropertyMap(), std::vector{createDerivedProjectedCRS(), createDerivedVerticalCRS()}); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_invalid) { EXPECT_THROW(CompoundCRS::create(PropertyMap(), {}), InvalidCompoundCRSException); // Only one component EXPECT_THROW(CompoundCRS::create(PropertyMap(), std::vector{createProjected()}), InvalidCompoundCRSException); // Two geographic EXPECT_THROW( CompoundCRS::create(PropertyMap(), std::vector{GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326}), InvalidCompoundCRSException); // geographic 3D + vertical EXPECT_THROW( CompoundCRS::create(PropertyMap(), std::vector{GeographicCRS::EPSG_4979, createVerticalCRS()}), InvalidCompoundCRSException); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_as_WKT2) { auto crs = createCompoundCRS(); auto expected = "COMPOUNDCRS[\"horizontal + vertical\",\n" " PROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]]],\n" " VERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\"],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1]]],\n" " ID[\"codespace\",\"code\"]]"; EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_isEquivalentTo) { auto crs = createCompoundCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); auto otherCompoundCRS = CompoundCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, ""), std::vector{GeographicCRS::EPSG_4326, createVerticalCRS()}); EXPECT_FALSE(crs->isEquivalentTo(otherCompoundCRS.get())); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_as_WKT1_GDAL) { auto crs = createCompoundCRS(); auto expected = "COMPD_CS[\"horizontal + vertical\",\n" " PROJCS[\"WGS 84 / UTM zone 31N\",\n" " GEOGCS[\"WGS 84\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]],\n" " AUTHORITY[\"EPSG\",\"4326\"]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH],\n" " AUTHORITY[\"EPSG\",\"32631\"]],\n" " VERT_CS[\"ODN height\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n" " AUTHORITY[\"EPSG\",\"5101\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"5701\"]],\n" " AUTHORITY[\"codespace\",\"code\"]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_as_PROJ_string) { auto crs = createCompoundCRS(); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=31 +datum=WGS84 +units=m +vunits=m +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_no_name_provided) { auto crs = CompoundCRS::create( PropertyMap(), std::vector{createProjected(), createVerticalCRS()}); EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N + ODN height"); } // --------------------------------------------------------------------------- TEST(crs, compoundCRS_identify_db) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); { // Identify by existing code auto res = factory->createCompoundCRS("8769")->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8769); EXPECT_EQ(res.front().second, 100); // Test with null EXPECT_TRUE( factory->createCompoundCRS("8769")->identify(nullptr).empty()); } { // Non-existing code auto sourceCRS = factory->createCompoundCRS("8769"); auto crs = CompoundCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()) .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 1), sourceCRS->componentReferenceSystems()); auto res = crs->identify(factory); EXPECT_EQ(res.size(), 0U); } { // Existing code, but not matching content auto sourceCRS = factory->createCompoundCRS("8769"); auto crs = CompoundCRS::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()) .set(Identifier::CODESPACE_KEY, "EPSG") .set(Identifier::CODE_KEY, 8770), sourceCRS->componentReferenceSystems()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8770); EXPECT_EQ(res.front().second, 25); } { // Identify by exact name auto sourceCRS = factory->createCompoundCRS("8769"); auto crs = CompoundCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()), sourceCRS->componentReferenceSystems()); auto res = static_cast(crs.get())->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8769); EXPECT_EQ(res.front().second, 100); } { EXPECT_TRUE(Identifier::isEquivalentName( "NAD83_Ohio_North_ftUS_NAVD88_height_ftUS", "NAD83 / Ohio North (ftUS) + NAVD88 height (ftUS)")); // Identify by equivalent name auto sourceCRS = factory->createCompoundCRS("8769"); auto crs = CompoundCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "NAD83_Ohio_North_ftUS_NAVD88_height_ftUS"), sourceCRS->componentReferenceSystems()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8769); EXPECT_EQ(res.front().second, 90); } { // Identify by name, but objects aren't equivalent auto sourceCRS = factory->createCompoundCRS("8770"); auto crs = CompoundCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "NAD83_Ohio_North_ftUS_NAVD88_height_ftUS"), sourceCRS->componentReferenceSystems()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8769); EXPECT_EQ(res.front().second, 25); } { auto sourceCRS = factory->createCompoundCRS("8769"); auto crs = CompoundCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unrelated name"), sourceCRS->componentReferenceSystems()); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 8769); EXPECT_EQ(res.front().second, 70); } { auto obj = PROJStringParser().createFromPROJString( "+proj=tmerc +lat_0=0 +lon_0=72.05 +k=1 +x_0=3500000 " "+y_0=-5811057.63 +ellps=krass " "+towgs84=23.57,-140.95,-79.8,0,-0.35,-0.79,-0.22 " "+geoidgrids=egm08_25.gtx +units=m +no_defs +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); // Just check we don't get an exception crs->identify(factory); } // Identify from ESRI WKT { auto sourceCRS = factory->createCompoundCRS("7405"); auto wkt = sourceCRS->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 7405); EXPECT_EQ(res.front().second, 100); } // Identify a CompoundCRS whose horizontal and vertical parts are known // but not the composition. { auto obj = createFromUserInput("EPSG:4326+5703", dbContext); auto sourceCRS = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(sourceCRS != nullptr); auto wkt = sourceCRS->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()); auto obj2 = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first->getEPSGCode(), 0); EXPECT_EQ(res.front().second, 100); const auto &components = res.front().first->componentReferenceSystems(); EXPECT_EQ(components[0]->getEPSGCode(), 4326); EXPECT_EQ(components[1]->getEPSGCode(), 5703); } } // --------------------------------------------------------------------------- TEST(crs, boundCRS_to_WKT2) { auto projcrs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"), GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto crs = BoundCRS::createFromTOWGS84( projcrs, std::vector{1, 2, 3, 4, 5, 6, 7}); EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr()); EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr); EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), projcrs->baseCRS()->nameStr()); ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr); EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr()); auto values = crs->transformation()->parameterValues(); ASSERT_EQ(values.size(), 7U); { const auto &opParamvalue = nn_dynamic_pointer_cast(values[0]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8605); EXPECT_EQ(paramName, "X-axis translation"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE); EXPECT_EQ(measure.value(), 1.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[1]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8606); EXPECT_EQ(paramName, "Y-axis translation"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE); EXPECT_EQ(measure.value(), 2.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[2]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8607); EXPECT_EQ(paramName, "Z-axis translation"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE); EXPECT_EQ(measure.value(), 3.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[3]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8608); EXPECT_EQ(paramName, "X-axis rotation"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::ARC_SECOND); EXPECT_EQ(measure.value(), 4.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[4]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8609); EXPECT_EQ(paramName, "Y-axis rotation"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::ARC_SECOND); EXPECT_EQ(measure.value(), 5.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[5]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8610); EXPECT_EQ(paramName, "Z-axis rotation"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::ARC_SECOND); EXPECT_EQ(measure.value(), 6.0); } { const auto &opParamvalue = nn_dynamic_pointer_cast(values[6]); ASSERT_TRUE(opParamvalue); const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶meterValue = opParamvalue->parameterValue(); EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8611); EXPECT_EQ(paramName, "Scale difference"); EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE); auto measure = parameterValue->value(); EXPECT_EQ(measure.unit(), UnitOfMeasure::PARTS_PER_MILLION); EXPECT_EQ(measure.value(), 7.0); } auto expected = "BOUNDCRS[SOURCECRS[" + projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" + "TARGETCRS[" + GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) + "],\n" " ABRIDGEDTRANSFORMATION[\"Transformation from myGEOGCRS to " "WGS84\",\n" " METHOD[\"Position Vector transformation (geog2D " "domain)\",\n" " ID[\"EPSG\",9606]],\n" " PARAMETER[\"X-axis translation\",1,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",2,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",3,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",4,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",5,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",6,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",1.000007,\n" " ID[\"EPSG\",8611]]]]"; EXPECT_EQ( replaceAll( replaceAll(crs->exportToWKT(WKTFormatter::create().get()), " ", ""), "\n", ""), replaceAll(replaceAll(expected, " ", ""), "\n", "")); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_with_usage) { auto wkt = "BOUNDCRS[\n" " SOURCECRS[\n" " PROJCRS[\"Monte Mario / Italy zone 2\",\n" " BASEGEOGCRS[\"Monte Mario\",\n" " DATUM[\"Monte Mario\",\n" " ELLIPSOID[\"International 1924\",6378388,297,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4265]],\n" " CONVERSION[\"unnamed\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",15,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",2520000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"x\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"y\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",3004]]],\n" " TARGETCRS[\n" " GEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"geodetic latitude (Lat)\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"geodetic longitude (Lon)\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]]],\n" " ABRIDGEDTRANSFORMATION[\"Transformation from Monte Mario to " "WGS84\",\n" " METHOD[\"Position Vector transformation (geog2D domain)\",\n" " ID[\"EPSG\",9606]],\n" " PARAMETER[\"X-axis translation\",-50.2,\n" " ID[\"EPSG\",8605]],\n" " PARAMETER[\"Y-axis translation\",-50.4,\n" " ID[\"EPSG\",8606]],\n" " PARAMETER[\"Z-axis translation\",84.8,\n" " ID[\"EPSG\",8607]],\n" " PARAMETER[\"X-axis rotation\",-0.69,\n" " ID[\"EPSG\",8608]],\n" " PARAMETER[\"Y-axis rotation\",-2.012,\n" " ID[\"EPSG\",8609]],\n" " PARAMETER[\"Z-axis rotation\",0.459,\n" " ID[\"EPSG\",8610]],\n" " PARAMETER[\"Scale difference\",0.99997192,\n" " ID[\"EPSG\",8611]]],\n" " USAGE[\n" " SCOPE[\"unknown\"],\n" " AREA[\"Italy - Sicily onshore\"],\n" " BBOX[36.59,12.36,38.35,15.71]]]"; auto crs = nn_dynamic_pointer_cast(WKTParser().createFromWKT(wkt)); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->nameStr(), "Monte Mario / Italy zone 2"); auto got_wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); EXPECT_EQ(got_wkt, wkt); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_crs_link) { { std::weak_ptr oriBaseCRS; { auto baseCRSIn = GeographicCRS::EPSG_4267->shallowClone(); oriBaseCRS = baseCRSIn.as_nullable(); EXPECT_EQ(oriBaseCRS.use_count(), 1); { auto boundCRS = BoundCRS::createFromTOWGS84( baseCRSIn, std::vector{1, 2, 3, 4, 5, 6, 7}); EXPECT_EQ(oriBaseCRS.use_count(), 3); } EXPECT_EQ(oriBaseCRS.use_count(), 1); } EXPECT_TRUE(oriBaseCRS.expired()); } { CRSPtr baseCRS; { auto baseCRSIn = GeographicCRS::EPSG_4267->shallowClone(); CRS *baseCRSPtr = baseCRSIn.get(); auto boundCRS = BoundCRS::createFromTOWGS84( baseCRSIn, std::vector{1, 2, 3, 4, 5, 6, 7}); baseCRS = boundCRS->baseCRS().as_nullable(); EXPECT_TRUE(baseCRS.get() == baseCRSPtr); } EXPECT_TRUE(baseCRS->isEquivalentTo(GeographicCRS::EPSG_4267.get())); EXPECT_TRUE(baseCRS->canonicalBoundCRS() == nullptr); } { CRSPtr baseCRS; { auto boundCRS = BoundCRS::createFromTOWGS84( GeographicCRS::EPSG_4267->shallowClone(), std::vector{1, 2, 3, 4, 5, 6, 7}); baseCRS = boundCRS->baseCRSWithCanonicalBoundCRS().as_nullable(); } EXPECT_TRUE(baseCRS->isEquivalentTo(GeographicCRS::EPSG_4267.get())); EXPECT_TRUE(baseCRS->canonicalBoundCRS() != nullptr); EXPECT_TRUE( baseCRS ->createBoundCRSToWGS84IfPossible( nullptr, CoordinateOperationContext::IntermediateCRSUse::NEVER) ->isEquivalentTo(baseCRS->canonicalBoundCRS().get())); } { std::weak_ptr oriBaseCRS; { BoundCRSPtr boundCRSExterior; { auto baseCRS = GeographicCRS::EPSG_4267->shallowClone(); oriBaseCRS = baseCRS.as_nullable(); EXPECT_EQ(oriBaseCRS.use_count(), 1); auto boundCRS = BoundCRS::createFromTOWGS84( baseCRS, std::vector{1, 2, 3, 4, 5, 6, 7}); EXPECT_EQ(oriBaseCRS.use_count(), 3); boundCRSExterior = boundCRS->baseCRSWithCanonicalBoundCRS() ->canonicalBoundCRS(); EXPECT_EQ(oriBaseCRS.use_count(), 4); } EXPECT_EQ(oriBaseCRS.use_count(), 2); EXPECT_TRUE(!oriBaseCRS.expired()); EXPECT_TRUE(boundCRSExterior->baseCRS()->isEquivalentTo( GeographicCRS::EPSG_4267.get())); } EXPECT_EQ(oriBaseCRS.use_count(), 0); EXPECT_TRUE(oriBaseCRS.expired()); } { std::weak_ptr oriBaseCRS; { BoundCRSPtr boundCRSExterior; { auto baseCRS = createProjected(); oriBaseCRS = baseCRS.as_nullable(); EXPECT_EQ(oriBaseCRS.use_count(), 1); auto boundCRS = BoundCRS::createFromTOWGS84( baseCRS, std::vector{1, 2, 3, 4, 5, 6, 7}); EXPECT_EQ(oriBaseCRS.use_count(), 2); boundCRSExterior = boundCRS->baseCRSWithCanonicalBoundCRS() ->canonicalBoundCRS(); EXPECT_EQ(oriBaseCRS.use_count(), 3); } EXPECT_EQ(oriBaseCRS.use_count(), 1); EXPECT_TRUE(!oriBaseCRS.expired()); EXPECT_TRUE(boundCRSExterior->baseCRS()->isEquivalentTo( createProjected().get())); } EXPECT_EQ(oriBaseCRS.use_count(), 0); EXPECT_TRUE(oriBaseCRS.expired()); } } // --------------------------------------------------------------------------- TEST(crs, boundCRS_to_WKT1) { auto projcrs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"), GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto crs = BoundCRS::createFromTOWGS84( projcrs, std::vector{1, 2, 3, 4, 5, 6, 7}); auto expected = "PROJCS[\"my PROJCRS\",\n" " GEOGCS[\"my GEOGCRS\",\n" " DATUM[\"WGS_1984\",\n" " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" " AUTHORITY[\"EPSG\",\"7030\"]],\n" " TOWGS84[1,2,3,4,5,6,7],\n" " AUTHORITY[\"EPSG\",\"6326\"]],\n" " PRIMEM[\"Greenwich\",0,\n" " AUTHORITY[\"EPSG\",\"8901\"]],\n" " UNIT[\"degree\",0.0174532925199433,\n" " AUTHORITY[\"EPSG\",\"9122\"]]],\n" " PROJECTION[\"Transverse_Mercator\"],\n" " PARAMETER[\"latitude_of_origin\",0],\n" " PARAMETER[\"central_meridian\",3],\n" " PARAMETER[\"scale_factor\",0.9996],\n" " PARAMETER[\"false_easting\",500000],\n" " PARAMETER[\"false_northing\",0],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_geographicCRS_to_PROJ_string) { auto basecrs = GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); auto crs = BoundCRS::createFromTOWGS84( basecrs, std::vector{1, 2, 3, 4, 5, 6, 7}); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +ellps=WGS84 +towgs84=1,2,3,4,5,6,7 +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_projectedCRS_to_PROJ_string) { auto projcrs = ProjectedCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"), GeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"), GeodeticReferenceFrame::EPSG_6326, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)), Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto crs = BoundCRS::createFromTOWGS84( projcrs, std::vector{1, 2, 3, 4, 5, 6, 7}); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=utm +zone=31 +ellps=WGS84 +towgs84=1,2,3,4,5,6,7 +units=m " "+no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_nadcon_to_PROJ_string_usePROJAlternativeGridNames_false) { auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); // NAD27 to WGS 84 (79) auto op = factoryEPSG->createCoordinateOperation( "15851", /* usePROJAlternativeGridNames = */ false); auto transf = nn_dynamic_pointer_cast(op); auto crs = BoundCRS::create(GeographicCRS::EPSG_4267, // NAD27 GeographicCRS::EPSG_4326, // WGS84 NN_CHECK_THROW(transf)); EXPECT_EQ(crs->exportToPROJString( PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_4, dbContext) .get()), "+proj=longlat +ellps=clrk66 +nadgrids=us_noaa_conus.tif " "+no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_nadcon_to_PROJ_string_usePROJAlternativeGridNames_true) { auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); // NAD27 to WGS 84 (79) auto op = factoryEPSG->createCoordinateOperation( "15851", /* usePROJAlternativeGridNames = */ true); auto transf = nn_dynamic_pointer_cast(op); auto crs = BoundCRS::create(GeographicCRS::EPSG_4267, // NAD27 GeographicCRS::EPSG_4326, // WGS84 NN_CHECK_THROW(transf)); EXPECT_EQ( crs->exportToPROJString( PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4) .get()), "+proj=longlat +ellps=clrk66 +nadgrids=us_noaa_conus.tif +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(crs, boundCRS_identify_db) { auto dbContext = DatabaseContext::create(); auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG"); { auto obj = PROJStringParser() .attachDatabaseContext(dbContext) .createFromPROJString( "+proj=tmerc +lat_0=-37.76111111111111 " "+lon_0=176.4661111111111 +k=1 " "+x_0=400000 +y_0=800000 +ellps=GRS80 " "+towgs84=0,0,0,0,0,0,0 +units=m +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); auto boundCRS = dynamic_cast(res.front().first.get()); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 2106); EXPECT_EQ(boundCRS->transformation()->nameStr(), "NZGD2000 to WGS 84 (1)"); EXPECT_EQ(res.front().second, 70); } { // WKT has EPSG code but the definition doesn't match with the official // one (namely linear units are different) // https://github.com/OSGeo/gdal/issues/990 // Also test that we can handle the synthetic Null geographic offset // between NAD83 and WGS84 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"NAD83 / Ohio North\",GEOGCS[\"NAD83\"," "DATUM[\"North_American_Datum_1983\",SPHEROID[\"GRS 1980\"," "6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]]," "TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",\"6269\"]]," "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]," "UNIT[\"degree\",0.0174532925199433, AUTHORITY[\"EPSG\",\"9122\"]]," "AUTHORITY[\"EPSG\",\"4269\"]]," "PROJECTION[\"Lambert_Conformal_Conic_2SP\"]," "PARAMETER[\"standard_parallel_1\",41.7]," "PARAMETER[\"standard_parallel_2\",40.43333333333333]," "PARAMETER[\"latitude_of_origin\",39.66666666666666]," "PARAMETER[\"central_meridian\",-82.5]," "PARAMETER[\"false_easting\",1968503.937007874]," "PARAMETER[\"false_northing\",0]," "UNIT[\"International Foot\",0.3048,AUTHORITY[\"EPSG\",\"9002\"]]," "AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],AUTHORITY[\"EPSG\",\"32122\"]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().second, 25); auto boundCRS = dynamic_cast(res.front().first.get()); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 32122); EXPECT_EQ(boundCRS->transformation()->method()->getEPSGCode(), 9603); } { // Identify from a PROJ string with +towgs84 auto obj = PROJStringParser().createFromPROJString( "+proj=utm +zone=48 +a=6377276.345 +b=6356075.41314024 " "+towgs84=198,881,317,0,0,0,0 +units=m +no_defs +type=crs"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); auto boundCRS = dynamic_cast(res.front().first.get()); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 3148); EXPECT_EQ(res.front().second, 70); } { // Identify a WKT with datum WGS84 and TOWGS84 auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "GEOGCS[\"WGS84 Coordinate System\",DATUM[\"WGS 1984\"," "SPHEROID[\"WGS 1984\",6378137,298.257223563]," "TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",\"6326\"]]," "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]," "AUTHORITY[\"EPSG\",\"4326\"]]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().second, 100); auto boundCRS = dynamic_cast(res.front().first.get()); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 4326); EXPECT_EQ(boundCRS->transformation()->method()->getEPSGCode(), 9606); } { // BoundCRS where the transformation to WGS84 is actually a concatenated // operation if taking from "NTF (Paris)". auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT( "PROJCS[\"NTF (Paris) / Lambert zone II\"," "GEOGCS[\"NTF (Paris)\"," "DATUM[\"Nouvelle_Triangulation_Francaise_Paris\"," "SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.4660212936269," "AUTHORITY[\"EPSG\",\"7011\"]],TOWGS84[-168,-60,320,0,0,0,0]," "AUTHORITY[\"EPSG\",\"6807\"]],PRIMEM[\"Paris\",2.33722917," "AUTHORITY[\"EPSG\",\"8903\"]],UNIT[\"grad\",0.01570796326794897," "AUTHORITY[\"EPSG\",\"9105\"]],AUTHORITY[\"EPSG\",\"4807\"]]," "PROJECTION[\"Lambert_Conformal_Conic_1SP\"]," "PARAMETER[\"latitude_of_origin\",52]," "PARAMETER[\"central_meridian\",0]," "PARAMETER[\"scale_factor\",0.99987742]," "PARAMETER[\"false_easting\",600000]," "PARAMETER[\"false_northing\",2200000]," "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]," "AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],AUTHORITY[\"EPSG\",\"27572\"]" "]"); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->identify(factoryEPSG); ASSERT_EQ(res.size(), 1U); auto boundCRS = dynamic_cast(res.front().first.get()); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 27572); EXPECT_EQ(boundCRS->transformation()->nameStr(), "NTF to WGS 84 (1)"); EXPECT_EQ(res.front().second, 100); } } // --------------------------------------------------------------------------- TEST(crs, incompatible_boundCRS_hubCRS_to_WKT1) { auto crs = BoundCRS::create( GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, Transformation::createGeocentricTranslations( PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, 1.0, 2.0, 3.0, std::vector())); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, incompatible_boundCRS_transformation_to_WKT1) { auto crs = BoundCRS::create( GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326, Transformation::create(PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326, nullptr, PropertyMap(), std::vector(), std::vector(), std::vector())); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, WKT1_DATUM_EXTENSION_to_WKT1_and_PROJ_string) { auto wkt = "PROJCS[\"unnamed\",\n" " GEOGCS[\"International 1909 (Hayford)\",\n" " DATUM[\"unknown\",\n" " SPHEROID[\"intl\",6378388,297],\n" " EXTENSION[\"PROJ4_GRIDS\",\"nzgd2kgrid0005.gsb\"]],\n" " PRIMEM[\"Greenwich\",0],\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PROJECTION[\"New_Zealand_Map_Grid\"],\n" " PARAMETER[\"latitude_of_origin\",-41],\n" " PARAMETER[\"central_meridian\",173],\n" " PARAMETER[\"false_easting\",2510000],\n" " PARAMETER[\"false_northing\",6023150],\n" " UNIT[\"Meter\",1],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 " "+ellps=intl +nadgrids=nzgd2kgrid0005.gsb +units=m +no_defs +type=crs"); } // --------------------------------------------------------------------------- TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_WKT1) { auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n" " VERT_DATUM[\"EGM2008 geoid\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n" " AUTHORITY[\"EPSG\",\"1027\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Gravity-related height\",UP],\n" " AUTHORITY[\"EPSG\",\"3855\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), wkt); } // --------------------------------------------------------------------------- TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_WKT2) { auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n" " VERT_DATUM[\"EGM2008 geoid\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n" " AUTHORITY[\"EPSG\",\"1027\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Up\",UP],\n" " AUTHORITY[\"EPSG\",\"3855\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto wkt2 = "BOUNDCRS[\n" " SOURCECRS[\n" " VERTCRS[\"EGM2008 geoid height\",\n" " VDATUM[\"EGM2008 geoid\"],\n" " CS[vertical,1],\n" " AXIS[\"up\",up,\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",3855]]],\n" " TARGETCRS[\n" " GEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " CS[ellipsoidal,3],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"ellipsoidal height\",up,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1]],\n" " ID[\"EPSG\",4979]]],\n" " ABRIDGEDTRANSFORMATION[\"EGM2008 geoid height to WGS 84 " "ellipsoidal height\",\n" " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n" " PARAMETERFILE[\"Geoid (height correction) model " "file\",\"egm08_25.gtx\",\n" " ID[\"EPSG\",8666]]]]"; EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), wkt2); } // --------------------------------------------------------------------------- TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_PROJ_string) { auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n" " VERT_DATUM[\"EGM2008 geoid\",2005,\n" " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n" " AUTHORITY[\"EPSG\",\"1027\"]],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Up\",UP],\n" " AUTHORITY[\"EPSG\",\"3855\"]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), "+geoidgrids=egm08_25.gtx +geoid_crs=WGS84 +vunits=m +no_defs " "+type=crs"); } // --------------------------------------------------------------------------- TEST(crs, extractGeographicCRS) { EXPECT_EQ(GeographicCRS::EPSG_4326->extractGeographicCRS(), GeographicCRS::EPSG_4326); EXPECT_EQ(createProjected()->extractGeographicCRS(), GeographicCRS::EPSG_4326); EXPECT_EQ(CompoundCRS::create( PropertyMap(), std::vector{GeographicCRS::EPSG_4326, createVerticalCRS()}) ->extractGeographicCRS(), GeographicCRS::EPSG_4326); } // --------------------------------------------------------------------------- TEST(crs, extractVerticalCRS) { EXPECT_EQ(GeographicCRS::EPSG_4326->extractVerticalCRS(), nullptr); { auto vertcrs = createCompoundCRS()->extractVerticalCRS(); ASSERT_TRUE(vertcrs != nullptr); EXPECT_TRUE(vertcrs->isEquivalentTo(createVerticalCRS().get())); } } // --------------------------------------------------------------------------- static DerivedGeographicCRSNNPtr createDerivedGeographicCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Atlantic pole"), PropertyMap().set(IdentifiedObject::NAME_KEY, "Pole rotation"), std::vector{ OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "Latitude of rotated pole")), OperationParameter::create(PropertyMap().set( IdentifiedObject::NAME_KEY, "Longitude of rotated pole")), OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Axis rotation")), }, std::vector{ ParameterValue::create(Angle(52.0)), ParameterValue::create(Angle(-30.0)), ParameterValue::create(Angle(-25)), }); return DerivedGeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WMO Atlantic Pole"), GeographicCRS::EPSG_4326, derivingConversion, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- TEST(crs, derivedGeographicCRS_basic) { auto derivedCRS = createDerivedGeographicCRS(); EXPECT_TRUE(derivedCRS->isEquivalentTo(derivedCRS.get())); EXPECT_FALSE(derivedCRS->isEquivalentTo( derivedCRS->baseCRS().get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(derivedCRS->baseCRS()->isEquivalentTo( derivedCRS.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(crs, derivedGeographicCRS_WKT2) { auto expected = "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"Atlantic pole\",\n" " METHOD[\"Pole rotation\"],\n" " PARAMETER[\"Latitude of rotated pole\",52,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Longitude of rotated pole\",-30,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Axis rotation\",-25,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; auto crs = createDerivedGeographicCRS(); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(crs, derivedGeographicCRS_WKT2_2019) { auto expected = "GEOGCRS[\"WMO Atlantic Pole\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " DERIVINGCONVERSION[\"Atlantic pole\",\n" " METHOD[\"Pole rotation\"],\n" " PARAMETER[\"Latitude of rotated pole\",52,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Longitude of rotated pole\",-30,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " PARAMETER[\"Axis rotation\",-25,\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433,\n" " ID[\"EPSG\",9122]]]]"; EXPECT_EQ( createDerivedGeographicCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, derivedGeographicCRS_WKT1) { EXPECT_THROW( createDerivedGeographicCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, derivedGeographicCRS_to_PROJ) { auto wkt = "GEODCRS[\"WMO Atlantic Pole\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ ob_tran o_proj=longlat\"],\n" " PARAMETER[\"o_lat_p\",52,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"o_lon_p\",-30,\n" " UNIT[\"degree\",0.0174532925199433]],\n" " PARAMETER[\"lon_0\",-25,\n" " UNIT[\"degree\",0.0174532925199433]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north,\n" " ORDER[1],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " AXIS[\"longitude\",east,\n" " ORDER[2],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_EQ( crs->exportToPROJString(PROJStringFormatter::create().get()), "+proj=ob_tran +o_proj=longlat +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 " "+datum=WGS84 +no_defs +type=crs"); auto op = CoordinateOperationFactory::create()->createOperation( crs->baseCRS(), NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ( op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran " "+o_proj=longlat +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step " "+proj=axisswap +order=2,1"); } // --------------------------------------------------------------------------- TEST(crs, derivedGeographicCRS_with_affine_transform_to_PROJ) { auto wkt = "GEODCRS[\"WGS 84 Translated\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0]],\n" " DERIVINGCONVERSION[\"Translation\",\n" " METHOD[\"Affine parametric transformation\",\n" " ID[\"EPSG\",9624]],\n" " PARAMETER[\"A0\",0.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8623]],\n" " PARAMETER[\"A1\",1,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8624]],\n" " PARAMETER[\"A2\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8625]],\n" " PARAMETER[\"B0\",2.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8639]],\n" " PARAMETER[\"B1\",0,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8640]],\n" " PARAMETER[\"B2\",1,\n" " SCALEUNIT[\"coefficient\",1],\n" " ID[\"EPSG\",8641]]],\n" " CS[ellipsoidal,2],\n" " AXIS[\"latitude\",north],\n" " AXIS[\"longitude\",east],\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]"; auto obj = WKTParser().createFromWKT(wkt); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); EXPECT_TRUE(crs->derivingConversion()->validateParameters().empty()); auto op = CoordinateOperationFactory::create()->createOperation( crs->baseCRS(), NN_NO_CHECK(crs)); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "+proj=affine +xoff=0.5 +s11=1 +s12=0 +yoff=2.5 +s21=0 +s22=1"); } // --------------------------------------------------------------------------- static DerivedGeodeticCRSNNPtr createDerivedGeodeticCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Some conversion"), PropertyMap().set(IdentifiedObject::NAME_KEY, "Some method"), std::vector{}, std::vector{}); return DerivedGeodeticCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived geodetic CRS"), GeographicCRS::EPSG_4326, derivingConversion, CartesianCS::createGeocentric(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, derivedGeodeticCRS_basic) { auto derivedCRS = createDerivedGeodeticCRS(); EXPECT_TRUE(derivedCRS->isEquivalentTo(derivedCRS.get())); EXPECT_FALSE(derivedCRS->isEquivalentTo( derivedCRS->baseCRS().get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(derivedCRS->baseCRS()->isEquivalentTo( derivedCRS.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(crs, derivedGeodeticCRS_WKT2) { auto expected = "GEODCRS[\"Derived geodetic CRS\",\n" " BASEGEODCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " DERIVINGCONVERSION[\"Some conversion\",\n" " METHOD[\"Some method\"]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto crs = createDerivedGeodeticCRS(); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(crs, derivedGeodeticCRS_WKT2_2019) { auto expected = "GEODCRS[\"Derived geodetic CRS\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4326]],\n" " DERIVINGCONVERSION[\"Some conversion\",\n" " METHOD[\"Some method\"]],\n" " CS[Cartesian,3],\n" " AXIS[\"(X)\",geocentricX,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Y)\",geocentricY,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(Z)\",geocentricZ,\n" " ORDER[3],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; EXPECT_EQ( createDerivedGeodeticCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, derivedGeodeticCRS_WKT1) { EXPECT_THROW( createDerivedGeodeticCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, derivedProjectedCRS_WKT2_2019) { auto expected = "DERIVEDPROJCRS[\"derived projectedCRS\",\n" " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n" " BASEGEOGCRS[\"WGS 84\",\n" " DATUM[\"World Geodetic System 1984\",\n" " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" " CONVERSION[\"UTM zone 31N\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",3,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9996,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",0,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " ID[\"EPSG\",32631]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto crs = createDerivedProjectedCRS(); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get())); auto geodCRS = crs->extractGeodeticCRS(); EXPECT_TRUE(geodCRS != nullptr); } // --------------------------------------------------------------------------- TEST(crs, derivedProjectedCRS_WKT2_2015) { auto crs = createDerivedProjectedCRS(); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()), FormattingException); } // --------------------------------------------------------------------------- static DateTimeTemporalCSNNPtr createDateTimeTemporalCS() { return DateTimeTemporalCS::create( PropertyMap(), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Time"), "T", AxisDirection::FUTURE, UnitOfMeasure::NONE)); } // --------------------------------------------------------------------------- static TemporalCRSNNPtr createDateTimeTemporalCRS() { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"), DateTime::create("0000-01-01"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); return TemporalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Temporal CRS"), datum, createDateTimeTemporalCS()); } // --------------------------------------------------------------------------- TEST(crs, dateTimeTemporalCRS_WKT2) { auto expected = "TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " TIMEORIGIN[0000-01-01]],\n" " CS[temporal,1],\n" " AXIS[\"time (T)\",future]]"; auto crs = createDateTimeTemporalCRS(); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); EXPECT_THROW( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); } // --------------------------------------------------------------------------- TEST(crs, dateTimeTemporalCRS_WKT2_2019) { auto expected = "TIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"; EXPECT_EQ( createDateTimeTemporalCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- static TemporalCRSNNPtr createTemporalCountCRSWithConvFactor() { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "GPS time origin"), DateTime::create("1980-01-01T00:00:00.0Z"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); auto cs = TemporalCountCS::create( PropertyMap(), CoordinateSystemAxis::create(PropertyMap(), "T", AxisDirection::FUTURE, UnitOfMeasure("milliseconds (ms)", 0.001, UnitOfMeasure::Type::TIME))); return TemporalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "GPS milliseconds"), datum, cs); } // --------------------------------------------------------------------------- TEST(crs, temporalCountCRSWithConvFactor_WKT2_2019) { auto expected = "TIMECRS[\"GPS milliseconds\",\n" " TDATUM[\"GPS time origin\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n" " CS[TemporalCount,1],\n" " AXIS[\"(T)\",future,\n" " TIMEUNIT[\"milliseconds (ms)\",0.001]]]"; EXPECT_EQ( createTemporalCountCRSWithConvFactor()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- static TemporalCRSNNPtr createTemporalCountCRSWithoutConvFactor() { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "29 December 1979"), DateTime::create("1979-12-29T00"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); auto cs = TemporalCountCS::create( PropertyMap(), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Time"), "", AxisDirection::FUTURE, UnitOfMeasure("hour", 0, UnitOfMeasure::Type::TIME))); return TemporalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Calendar hours from 1979-12-29"), datum, cs); } // --------------------------------------------------------------------------- TEST(crs, temporalCountCRSWithoutConvFactor_WKT2_2019) { auto expected = "TIMECRS[\"Calendar hours from 1979-12-29\",\n" " TDATUM[\"29 December 1979\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[1979-12-29T00]],\n" " CS[TemporalCount,1],\n" " AXIS[\"time\",future,\n" " TIMEUNIT[\"hour\"]]]"; EXPECT_EQ( createTemporalCountCRSWithoutConvFactor()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- static TemporalCRSNNPtr createTemporalMeasureCRSWithoutConvFactor() { auto datum = TemporalDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Common Era"), DateTime::create("0000"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN); auto cs = TemporalMeasureCS::create( PropertyMap(), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Decimal years"), "a", AxisDirection::FUTURE, UnitOfMeasure("year", 0, UnitOfMeasure::Type::TIME))); return TemporalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Decimal Years CE"), datum, cs); } // --------------------------------------------------------------------------- TEST(crs, temporalMeasureCRSWithoutConvFactor_WKT2_2019) { auto expected = "TIMECRS[\"Decimal Years CE\",\n" " TDATUM[\"Common Era\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000]],\n" " CS[TemporalMeasure,1],\n" " AXIS[\"decimal years (a)\",future,\n" " TIMEUNIT[\"year\"]]]"; EXPECT_EQ( createTemporalMeasureCRSWithoutConvFactor()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- static EngineeringCRSNNPtr createEngineeringCRS() { auto datum = EngineeringDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering datum")); return EngineeringCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering CRS"), datum, CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, engineeringCRS_WKT2) { auto expected = "ENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto crs = createEngineeringCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, engineeringCRS_WKT1) { auto expected = "LOCAL_CS[\"Engineering CRS\",\n" " LOCAL_DATUM[\"Engineering datum\",32767],\n" " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AXIS[\"Easting\",EAST],\n" " AXIS[\"Northing\",NORTH]]"; EXPECT_EQ( createEngineeringCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, engineeringCRS_unknown_equivalence) { // Test equivalent of CRS definition got from GPKG (wkt2) and its equivalent // from GeoTIFF (wkt1) // Cf https://github.com/r-spatial/sf/issues/2049#issuecomment-1486600723 auto wkt1 = "ENGCRS[\"Undefined Cartesian SRS with unknown unit\",\n" " EDATUM[\"\"],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto wkt2 = "ENGCRS[\"Undefined Cartesian SRS with unknown unit\",\n" " EDATUM[\"Unknown engineering datum\"],\n" " CS[Cartesian,2],\n" " AXIS[\"x\",unspecified,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"y\",unspecified,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto obj1 = WKTParser().createFromWKT(wkt1); auto crs1 = nn_dynamic_pointer_cast(obj1); ASSERT_TRUE(crs1 != nullptr); auto obj2 = WKTParser().createFromWKT(wkt2); auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); EXPECT_TRUE( crs1->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE( crs2->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- static ParametricCSNNPtr createParametricCS() { return ParametricCS::create( PropertyMap(), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "pressure"), "hPa", AxisDirection::UP, UnitOfMeasure("HectoPascal", 100, UnitOfMeasure::Type::PARAMETRIC))); } // --------------------------------------------------------------------------- static ParametricCRSNNPtr createParametricCRS() { auto datum = ParametricDatum::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric datum")); return ParametricCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric CRS"), datum, createParametricCS()); } // --------------------------------------------------------------------------- TEST(crs, default_identify_method) { EXPECT_TRUE(createParametricCRS()->identify(nullptr).empty()); } // --------------------------------------------------------------------------- TEST(crs, parametricCRS_WKT2) { auto expected = "PARAMETRICCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"; auto crs = createParametricCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, parametricCRS_WKT1) { EXPECT_THROW( createParametricCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, derivedVerticalCRS_basic) { auto crs = createDerivedVerticalCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_FALSE(crs->isEquivalentTo(crs->baseCRS().get())); EXPECT_FALSE(crs->baseCRS()->isEquivalentTo(crs.get())); } // --------------------------------------------------------------------------- TEST(crs, DerivedVerticalCRS_WKT2) { auto expected = "VERTCRS[\"Derived vertCRS\",\n" " BASEVERTCRS[\"ODN height\",\n" " VDATUM[\"Ordnance Datum Newlyn\",\n" " ID[\"EPSG\",5101]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"]],\n" " CS[vertical,1],\n" " AXIS[\"gravity-related height (H)\",up,\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto crs = createDerivedVerticalCRS(); EXPECT_EQ(crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, DerivedVerticalCRS_WKT1) { EXPECT_THROW( createDerivedVerticalCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, DerivedVerticalCRS_WKT1_when_simple_derivation) { auto derivingConversion = Conversion::createChangeVerticalUnit(PropertyMap().set( IdentifiedObject::NAME_KEY, "Vertical Axis Unit Conversion")); auto crs = DerivedVerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived vertCRS"), createVerticalCRS(), derivingConversion, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::FOOT)); auto expected = "VERT_CS[\"Derived vertCRS\",\n" " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n" " AUTHORITY[\"EPSG\",\"5101\"]],\n" " UNIT[\"foot\",0.3048,\n" " AUTHORITY[\"EPSG\",\"9002\"]],\n" " AXIS[\"Gravity-related height\",UP]]"; EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), expected); } // --------------------------------------------------------------------------- static DerivedEngineeringCRSNNPtr createDerivedEngineeringCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedEngineeringCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived EngineeringCRS"), createEngineeringCRS(), derivingConversion, CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- TEST(crs, DerivedEngineeringCRS_WKT2) { auto expected = "ENGCRS[\"Derived EngineeringCRS\",\n" " BASEENGCRS[\"Engineering CRS\",\n" " EDATUM[\"Engineering datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"]],\n" " CS[Cartesian,2],\n" " AXIS[\"(E)\",east,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]],\n" " AXIS[\"(N)\",north,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1,\n" " ID[\"EPSG\",9001]]]]"; auto crs = createDerivedEngineeringCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( CartesianCS::createEastingNorthing(UnitOfMeasure::METRE).get())); EXPECT_TRUE( crs->datum()->isEquivalentTo(createEngineeringCRS()->datum().get())); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); EXPECT_THROW( createDerivedEngineeringCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, DerivedEngineeringCRS_WKT1) { EXPECT_THROW( createDerivedEngineeringCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- static DerivedParametricCRSNNPtr createDerivedParametricCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedParametricCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived ParametricCRS"), createParametricCRS(), derivingConversion, createParametricCS()); } // --------------------------------------------------------------------------- TEST(crs, DerivedParametricCRS_WKT2) { auto expected = "PARAMETRICCRS[\"Derived ParametricCRS\",\n" " BASEPARAMCRS[\"Parametric CRS\",\n" " PDATUM[\"Parametric datum\"]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"]],\n" " CS[parametric,1],\n" " AXIS[\"pressure (hPa)\",up,\n" " PARAMETRICUNIT[\"HectoPascal\",100]]]"; auto crs = createDerivedParametricCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_TRUE( crs->coordinateSystem()->isEquivalentTo(createParametricCS().get())); EXPECT_TRUE( crs->datum()->isEquivalentTo(createParametricCRS()->datum().get())); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, DerivedParametricCRS_WKT1) { EXPECT_THROW( createDerivedParametricCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- static DerivedTemporalCRSNNPtr createDerivedTemporalCRS() { auto derivingConversion = Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"), std::vector{}, std::vector{}); return DerivedTemporalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived TemporalCRS"), createDateTimeTemporalCRS(), derivingConversion, createDateTimeTemporalCS()); } // --------------------------------------------------------------------------- TEST(crs, DeriveTemporalCRS_WKT2) { auto expected = "TIMECRS[\"Derived TemporalCRS\",\n" " BASETIMECRS[\"Temporal CRS\",\n" " TDATUM[\"Gregorian calendar\",\n" " CALENDAR[\"proleptic Gregorian\"],\n" " TIMEORIGIN[0000-01-01]]],\n" " DERIVINGCONVERSION[\"unnamed\",\n" " METHOD[\"PROJ unimplemented\"]],\n" " CS[TemporalDateTime,1],\n" " AXIS[\"time (T)\",future]]"; auto crs = createDerivedTemporalCRS(); EXPECT_TRUE(crs->isEquivalentTo(crs.get())); EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get())); EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get())); EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo( createDateTimeTemporalCS().get())); EXPECT_TRUE(crs->datum()->isEquivalentTo( createDateTimeTemporalCRS()->datum().get())); EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), expected); } // --------------------------------------------------------------------------- TEST(crs, DeriveTemporalCRS_WKT1) { EXPECT_THROW( createDerivedTemporalCRS()->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), FormattingException); } // --------------------------------------------------------------------------- TEST(crs, crs_createBoundCRSToWGS84IfPossible) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); { auto crs_4326 = factory->createCoordinateReferenceSystem("4326"); EXPECT_EQ(crs_4326->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_4326); } { auto crs_32631 = factory->createCoordinateReferenceSystem("32631"); EXPECT_EQ(crs_32631->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_32631); } { // Pulkovo 42 East Germany auto crs_5670 = factory->createCoordinateReferenceSystem("5670"); EXPECT_EQ(crs_5670->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_5670); } { // Pulkovo 42 Romania auto crs_3844 = factory->createCoordinateReferenceSystem("3844"); EXPECT_EQ(crs_3844->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_3844); } { // Pulkovo 42 Poland auto crs_2171 = factory->createCoordinateReferenceSystem("2171"); EXPECT_EQ(crs_2171->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_2171); } { // NTF (Paris) auto crs_4807 = factory->createCoordinateReferenceSystem("4807"); auto bound = crs_4807->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_NE(bound, crs_4807); EXPECT_EQ(bound->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), bound); auto boundCRS = nn_dynamic_pointer_cast(bound); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ( boundCRS->exportToPROJString(PROJStringFormatter::create().get()), "+proj=longlat +ellps=clrk80ign +pm=paris " "+towgs84=-168,-60,320,0,0,0,0 +no_defs +type=crs"); } { // WGS 84 + EGM2008 height auto obj = createFromUserInput("EPSG:4326+3855", dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); auto res = crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_NE(res, crs); EXPECT_EQ(res->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), res); auto compoundCRS = nn_dynamic_pointer_cast(res); ASSERT_TRUE(compoundCRS != nullptr); EXPECT_EQ(compoundCRS->exportToPROJString( PROJStringFormatter::create().get()), "+proj=longlat +datum=WGS84 +geoidgrids=us_nga_egm08_25.tif " "+geoid_crs=WGS84 +vunits=m +no_defs +type=crs"); } #ifdef disabled_since_epsg_10_035 // There are now too many transformations from NGF-IGN69 height to WGS 84 // for createBoundCRSToWGS84IfPossible() to be able to select a // vertical geoidgrid { // NTF (Paris) / Lambert zone II + NGF-IGN69 height auto crs_7421 = factory->createCoordinateReferenceSystem("7421"); auto res = crs_7421->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_NE(res, crs_7421); EXPECT_EQ(res->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), res); auto compoundCRS = nn_dynamic_pointer_cast(res); ASSERT_TRUE(compoundCRS != nullptr); EXPECT_EQ(compoundCRS->exportToPROJString( PROJStringFormatter::create().get()), "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 " "+x_0=600000 +y_0=2200000 +ellps=clrk80ign +pm=paris " "+towgs84=-168,-60,320,0,0,0,0 +units=m " "+geoidgrids=fr_ign_RAF18.tif +vunits=m +no_defs +type=crs"); } #endif { auto crs = createVerticalCRS(); EXPECT_EQ(crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs); } { auto crs = createCompoundCRS(); EXPECT_EQ(crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs); } { auto factoryIGNF = AuthorityFactory::create(DatabaseContext::create(), "IGNF"); auto crs = factoryIGNF->createCoordinateReferenceSystem("TERA50STEREO"); auto bound = crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_NE(bound, crs); auto boundCRS = nn_dynamic_pointer_cast(bound); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ( boundCRS->exportToPROJString(PROJStringFormatter::create().get()), "+proj=stere +lat_0=-90 +lon_0=140 +k=0.960272946 " "+x_0=300000 +y_0=-2299363.482 +ellps=intl " "+towgs84=324.8,153.6,172.1,0,0,0,0 +units=m +no_defs +type=crs"); } { auto factoryIGNF = AuthorityFactory::create(DatabaseContext::create(), "IGNF"); auto crs = factoryIGNF->createCoordinateReferenceSystem("PGP50"); auto bound = crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_NE(bound, crs); auto boundCRS = nn_dynamic_pointer_cast(bound); ASSERT_TRUE(boundCRS != nullptr); EXPECT_EQ( boundCRS->exportToPROJString(PROJStringFormatter::create().get()), "+proj=geocent +ellps=intl " "+towgs84=324.8,153.6,172.1,0,0,0,0 +units=m +no_defs +type=crs"); } { auto crs = factory->createCoordinateReferenceSystem("4269"); // NAD83 auto bound = crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_EQ(bound, crs); } { // GDA2020 geocentric auto crs = factory->createCoordinateReferenceSystem("7842"); const auto time_before = ::testing::UnitTest::GetInstance()->elapsed_time(); crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse:: IF_NO_DIRECT_TRANSFORMATION); const auto time_after = ::testing::UnitTest::GetInstance()->elapsed_time(); EXPECT_LE(time_after - time_before, 500); } { // POSGAR 2007: it has 2 helmert shifts to WGS84 (#2356). Don't take // an arbitrary one auto crs_5340 = factory->createCoordinateReferenceSystem("5340"); EXPECT_EQ(crs_5340->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_5340); } { // "MGI 1901 / Balkans zone 7": it has 2 area of validity, one // for Bosnia and Herzegovina/Kosovo/Montenegro/Serbia and another // one for North macedonie auto crs_6316 = factory->createCoordinateReferenceSystem("6316"); EXPECT_EQ(crs_6316->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_6316); } // Check that we get the same result from an EPSG code and a CRS created // from its WKT1 representation. { // Pulkovo 1942 / CS63 zone A2 auto crs = factory->createCoordinateReferenceSystem("2936"); // Two candidate transformations found, so not picking up any EXPECT_EQ(crs->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs); auto wkt = crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext) .get()); auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt); auto crs_from_wkt = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs_from_wkt != nullptr); EXPECT_EQ(crs_from_wkt->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER), crs_from_wkt); } } // --------------------------------------------------------------------------- TEST(crs, crs_stripVerticalComponent) { { auto crs = GeographicCRS::EPSG_4979->stripVerticalComponent(); auto geogCRS = nn_dynamic_pointer_cast(crs); ASSERT_TRUE(geogCRS != nullptr); EXPECT_EQ(geogCRS->coordinateSystem()->axisList().size(), 2U); } { auto crs = GeographicCRS::EPSG_4326->stripVerticalComponent(); EXPECT_TRUE(crs->isEquivalentTo(GeographicCRS::EPSG_4326.get())); } { std::vector axis{ CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Easting"), "E", AxisDirection::EAST, UnitOfMeasure::METRE), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Northing"), "N", AxisDirection::NORTH, UnitOfMeasure::METRE), CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "Height"), "z", AxisDirection::UP, UnitOfMeasure::METRE)}; auto cs(CartesianCS::create(PropertyMap(), axis[0], axis[1], axis[2])); auto projected3DCrs = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 31, true), cs); auto projCRS = nn_dynamic_pointer_cast( projected3DCrs->stripVerticalComponent()); ASSERT_TRUE(projCRS != nullptr); EXPECT_EQ(projCRS->coordinateSystem()->axisList().size(), 2U); } { auto crs3D = createDerivedProjectedCRS()->promoteTo3D(std::string(), nullptr); auto derivedProj3D = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(derivedProj3D != nullptr); EXPECT_EQ(derivedProj3D->coordinateSystem()->axisList().size(), 3U); auto derivedProj2D = nn_dynamic_pointer_cast( derivedProj3D->stripVerticalComponent()); ASSERT_TRUE(derivedProj2D != nullptr); EXPECT_EQ(derivedProj2D->coordinateSystem()->axisList().size(), 2U); } } // --------------------------------------------------------------------------- TEST(crs, crs_alterGeodeticCRS) { { auto crs = GeographicCRS::EPSG_4326->alterGeodeticCRS( GeographicCRS::EPSG_4979); EXPECT_TRUE(crs->isEquivalentTo(GeographicCRS::EPSG_4979.get())); } { auto crs = createProjected()->alterGeodeticCRS(GeographicCRS::EPSG_4979); auto projCRS = dynamic_cast(crs.get()); ASSERT_TRUE(projCRS != nullptr); EXPECT_TRUE( projCRS->baseCRS()->isEquivalentTo(GeographicCRS::EPSG_4979.get())); } { auto crs = createCompoundCRS()->alterGeodeticCRS(GeographicCRS::EPSG_4979); auto compoundCRS = dynamic_cast(crs.get()); ASSERT_TRUE(compoundCRS != nullptr); EXPECT_TRUE(compoundCRS->componentReferenceSystems()[0] ->extractGeographicCRS() ->isEquivalentTo(GeographicCRS::EPSG_4979.get())); } { auto crs = createVerticalCRS()->alterGeodeticCRS(GeographicCRS::EPSG_4979); EXPECT_TRUE(crs->isEquivalentTo(createVerticalCRS().get())); } { auto crs = createDerivedProjectedCRS()->alterGeodeticCRS( GeographicCRS::EPSG_4979); auto derivedProjCRS = dynamic_cast(crs.get()); ASSERT_TRUE(derivedProjCRS != nullptr); EXPECT_TRUE(derivedProjCRS->baseCRS()->baseCRS()->isEquivalentTo( GeographicCRS::EPSG_4979.get())); } } // --------------------------------------------------------------------------- TEST(crs, crs_alterCSLinearUnit) { { auto crs = createProjected()->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); auto projCRS = dynamic_cast(crs.get()); ASSERT_TRUE(projCRS != nullptr); auto cs = projCRS->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[1]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[1]->unit().conversionToSI(), 2); } { auto crs = createDerivedProjectedCRS()->alterCSLinearUnit( UnitOfMeasure("my unit", 2)); auto derivedProjCRS = dynamic_cast(crs.get()); ASSERT_TRUE(derivedProjCRS != nullptr); auto cs = derivedProjCRS->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[1]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[1]->unit().conversionToSI(), 2); } { auto crs = createDerivedProjectedCRSNorthingEasting(); auto alteredCRS = crs->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); auto leftHandedDerivedCRS = dynamic_cast(alteredCRS.get()); ASSERT_TRUE(leftHandedDerivedCRS != nullptr); auto cs = dynamic_cast( leftHandedDerivedCRS->coordinateSystem().get()); ASSERT_EQ(cs->axisList().size(), 2U); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[1]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST); EXPECT_EQ(cs->axisList()[1]->unit().conversionToSI(), 2); } { auto crs = GeodeticCRS::EPSG_4978->alterCSLinearUnit( UnitOfMeasure("my unit", 2)); auto geodCRS = dynamic_cast(crs.get()); ASSERT_TRUE(geodCRS != nullptr); auto cs = dynamic_cast(geodCRS->coordinateSystem().get()); ASSERT_EQ(cs->axisList().size(), 3U); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[1]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[1]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[2]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[2]->unit().conversionToSI(), 2); } { auto crs = GeographicCRS::EPSG_4979->alterCSLinearUnit( UnitOfMeasure("my unit", 2)); auto geogCRS = dynamic_cast(crs.get()); ASSERT_TRUE(geogCRS != nullptr); auto cs = geogCRS->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 3U); EXPECT_NE(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_NE(cs->axisList()[0]->unit().conversionToSI(), 2); EXPECT_NE(cs->axisList()[1]->unit().name(), "my unit"); EXPECT_NE(cs->axisList()[1]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[2]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[2]->unit().conversionToSI(), 2); } { auto crs = createVerticalCRS()->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); auto vertCRS = dynamic_cast(crs.get()); ASSERT_TRUE(vertCRS != nullptr); auto cs = vertCRS->coordinateSystem(); ASSERT_EQ(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); } { auto obj = WKTParser().createFromWKT("LOCAL_CS[\"foo\"]"); auto crs = nn_dynamic_pointer_cast(obj); auto alteredCRS = crs->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); auto wkt = alteredCRS->exportToWKT( &(WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL) ->setMultiLine(false))); EXPECT_EQ(wkt, "LOCAL_CS[\"foo\",UNIT[\"my unit\",2]," "AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]"); } { auto crs = createCompoundCRS()->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); auto compoundCRS = dynamic_cast(crs.get()); ASSERT_TRUE(compoundCRS != nullptr); EXPECT_EQ(compoundCRS->componentReferenceSystems().size(), 2U); for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { auto singleCrs = dynamic_cast(subCrs.get()); ASSERT_TRUE(singleCrs != nullptr); auto cs = singleCrs->coordinateSystem(); ASSERT_GE(cs->axisList().size(), 1U); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); } } { auto crs = BoundCRS::createFromTOWGS84( createProjected(), std::vector{1, 2, 3, 4, 5, 6, 7}) ->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); auto boundCRS = dynamic_cast(crs.get()); ASSERT_TRUE(boundCRS != nullptr); auto baseCRS = boundCRS->baseCRS(); auto projCRS = dynamic_cast(baseCRS.get()); ASSERT_TRUE(projCRS != nullptr); auto cs = projCRS->coordinateSystem(); EXPECT_EQ(cs->axisList()[0]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[0]->unit().conversionToSI(), 2); EXPECT_EQ(cs->axisList()[1]->unit().name(), "my unit"); EXPECT_EQ(cs->axisList()[1]->unit().conversionToSI(), 2); } // Not implemented on parametricCRS auto crs = createParametricCRS()->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); EXPECT_TRUE(createParametricCRS()->isEquivalentTo(crs.get())); } // --------------------------------------------------------------------------- TEST(crs, alterParametersLinearUnit) { { auto crs = createProjected()->alterParametersLinearUnit( UnitOfMeasure("my unit", 2), false); auto wkt = crs->exportToWKT(&WKTFormatter::create()->setMultiLine(false)); EXPECT_TRUE(wkt.find("PARAMETER[\"Longitude of natural origin\",3") != std::string::npos) << wkt; EXPECT_TRUE( wkt.find( "PARAMETER[\"False easting\",500000,UNIT[\"my unit\",2]") != std::string::npos) << wkt; } { auto crs = createProjected()->alterParametersLinearUnit( UnitOfMeasure("my unit", 2), true); auto wkt = crs->exportToWKT(&WKTFormatter::create()->setMultiLine(false)); EXPECT_TRUE( wkt.find( "PARAMETER[\"False easting\",250000,UNIT[\"my unit\",2]") != std::string::npos) << wkt; } } // --------------------------------------------------------------------------- TEST(crs, getNonDeprecated) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); { // No id auto crs = ProjectedCRS::create( PropertyMap(), GeographicCRS::EPSG_4326, Conversion::createUTM(PropertyMap(), 31, true), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 0U); } { // Non-deprecated auto crs = factory->createGeodeticCRS("4326"); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 0U); } { // Non supported CRS type auto crs = BoundCRS::createFromTOWGS84( createProjected(), std::vector{1, 2, 3, 4, 5, 6, 7}); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 0U); } { auto crs = factory->createGeodeticCRS("4226"); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 2U); } { auto crs = factory->createProjectedCRS("26591"); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 1U); } { auto crs = factory->createVerticalCRS("5704"); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 1U); } { auto crs = factory->createCompoundCRS("7401"); auto list = crs->getNonDeprecated(dbContext); ASSERT_EQ(list.size(), 1U); } } // --------------------------------------------------------------------------- TEST(crs, promoteTo3D_and_demoteTo2D) { auto dbContext = DatabaseContext::create(); { auto crs = GeographicCRS::EPSG_4326; auto crs3D = crs->promoteTo3D(std::string(), nullptr); auto crs3DAsGeog = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsGeog != nullptr); EXPECT_EQ(crs3DAsGeog->coordinateSystem()->axisList().size(), 3U); EXPECT_TRUE(crs3D->promoteTo3D(std::string(), nullptr) ->isEquivalentTo(crs3D.get())); } { auto crs = GeographicCRS::EPSG_4326; auto crs3D = crs->promoteTo3D(std::string(), dbContext); auto crs3DAsGeog = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsGeog != nullptr); EXPECT_EQ(crs3DAsGeog->coordinateSystem()->axisList().size(), 3U); EXPECT_TRUE(!crs3DAsGeog->identifiers().empty()); auto demoted = crs3DAsGeog->demoteTo2D(std::string(), dbContext); EXPECT_EQ(demoted->coordinateSystem()->axisList().size(), 2U); EXPECT_TRUE(!demoted->identifiers().empty()); } { auto crs = createProjected(); auto crs3D = crs->promoteTo3D(std::string(), nullptr); auto crs3DAsProjected = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsProjected != nullptr); EXPECT_EQ(crs3DAsProjected->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ( crs3DAsProjected->baseCRS()->coordinateSystem()->axisList().size(), 3U); // Check that importing an exported Projected 3D CRS as WKT keeps // the 3D aspect of the baseCRS (see #2122) { WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); crs3DAsProjected->exportToWKT(f.get()); auto obj = WKTParser().createFromWKT(f->toString()); auto crsFromWkt = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crsFromWkt != nullptr); EXPECT_EQ(crsFromWkt->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ( crsFromWkt->baseCRS()->coordinateSystem()->axisList().size(), 3U); } EXPECT_TRUE(crs3D->promoteTo3D(std::string(), nullptr) ->isEquivalentTo(crs3D.get())); auto demoted = crs3DAsProjected->demoteTo2D(std::string(), nullptr); EXPECT_EQ(demoted->coordinateSystem()->axisList().size(), 2U); EXPECT_EQ(demoted->baseCRS()->coordinateSystem()->axisList().size(), 2U); } { auto crs = createProjected(); auto crs3D = crs->promoteTo3D(std::string(), dbContext); auto crs3DAsProjected = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsProjected != nullptr); EXPECT_EQ(crs3DAsProjected->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ( crs3DAsProjected->baseCRS()->coordinateSystem()->axisList().size(), 3U); EXPECT_TRUE(!crs3DAsProjected->baseCRS()->identifiers().empty()); auto demoted = crs3DAsProjected->demoteTo2D(std::string(), dbContext); EXPECT_EQ(demoted->coordinateSystem()->axisList().size(), 2U); EXPECT_EQ(demoted->baseCRS()->coordinateSystem()->axisList().size(), 2U); EXPECT_TRUE(!demoted->baseCRS()->identifiers().empty()); } { auto crs = BoundCRS::createFromTOWGS84( createProjected(), std::vector{1, 2, 3, 4, 5, 6, 7}); auto crs3D = crs->promoteTo3D(std::string(), dbContext); auto crs3DAsBound = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsBound != nullptr); { auto baseCRS = nn_dynamic_pointer_cast(crs3DAsBound->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 3U); auto hubCRS = nn_dynamic_pointer_cast(crs3DAsBound->hubCRS()); ASSERT_TRUE(hubCRS != nullptr); EXPECT_EQ(hubCRS->coordinateSystem()->axisList().size(), 3U); auto transfSrcCRS = nn_dynamic_pointer_cast( crs3DAsBound->transformation()->sourceCRS()); ASSERT_TRUE(transfSrcCRS != nullptr); EXPECT_EQ(transfSrcCRS->coordinateSystem()->axisList().size(), 3U); auto transfDstCRS = nn_dynamic_pointer_cast( crs3DAsBound->transformation()->targetCRS()); ASSERT_TRUE(transfDstCRS != nullptr); EXPECT_EQ(transfDstCRS->coordinateSystem()->axisList().size(), 3U); } auto demoted = crs3DAsBound->demoteTo2D(std::string(), nullptr); auto crs2DAsBound = nn_dynamic_pointer_cast(demoted); ASSERT_TRUE(crs2DAsBound != nullptr); { auto baseCRS = nn_dynamic_pointer_cast(crs2DAsBound->baseCRS()); ASSERT_TRUE(baseCRS != nullptr); EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 2U); auto hubCRS = nn_dynamic_pointer_cast(crs2DAsBound->hubCRS()); ASSERT_TRUE(hubCRS != nullptr); EXPECT_EQ(hubCRS->coordinateSystem()->axisList().size(), 2U); auto transfSrcCRS = nn_dynamic_pointer_cast( crs2DAsBound->transformation()->sourceCRS()); ASSERT_TRUE(transfSrcCRS != nullptr); EXPECT_EQ(transfSrcCRS->coordinateSystem()->axisList().size(), 2U); auto transfDstCRS = nn_dynamic_pointer_cast( crs2DAsBound->transformation()->targetCRS()); ASSERT_TRUE(transfDstCRS != nullptr); EXPECT_EQ(transfDstCRS->coordinateSystem()->axisList().size(), 2U); } } { auto compoundCRS = createCompoundCRS(); auto demoted = compoundCRS->demoteTo2D(std::string(), nullptr); EXPECT_TRUE(dynamic_cast(demoted.get()) != nullptr); } { auto crs = createDerivedGeographicCRS(); auto crs3D = crs->promoteTo3D(std::string(), dbContext); auto crs3DAsDerivedGeog = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsDerivedGeog != nullptr); EXPECT_EQ(crs3DAsDerivedGeog->baseCRS() ->coordinateSystem() ->axisList() .size(), 3U); EXPECT_EQ(crs3DAsDerivedGeog->coordinateSystem()->axisList().size(), 3U); EXPECT_TRUE(crs3DAsDerivedGeog->promoteTo3D(std::string(), nullptr) ->isEquivalentTo(crs3DAsDerivedGeog.get())); auto demoted = crs3DAsDerivedGeog->demoteTo2D(std::string(), dbContext); EXPECT_EQ(demoted->baseCRS()->coordinateSystem()->axisList().size(), 2U); EXPECT_EQ(demoted->coordinateSystem()->axisList().size(), 2U); EXPECT_TRUE(demoted->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(demoted->demoteTo2D(std::string(), nullptr) ->isEquivalentTo(demoted.get())); } { auto crs = createDerivedProjectedCRS(); auto crs3D = crs->promoteTo3D(std::string(), dbContext); auto crs3DAsDerivedProj = nn_dynamic_pointer_cast(crs3D); ASSERT_TRUE(crs3DAsDerivedProj != nullptr); EXPECT_EQ(crs3DAsDerivedProj->baseCRS() ->coordinateSystem() ->axisList() .size(), 3U); EXPECT_EQ(crs3DAsDerivedProj->coordinateSystem()->axisList().size(), 3U); EXPECT_TRUE(crs3DAsDerivedProj->promoteTo3D(std::string(), nullptr) ->isEquivalentTo(crs3DAsDerivedProj.get())); // Check that importing an exported DerivedProjected 3D CRS as WKT keeps // the 3D aspect of the baseCRS (see #3340) { WKTFormatterNNPtr f( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019)); crs3DAsDerivedProj->exportToWKT(f.get()); auto obj = WKTParser().createFromWKT(f->toString()); auto crsFromWkt = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crsFromWkt != nullptr); EXPECT_EQ(crsFromWkt->coordinateSystem()->axisList().size(), 3U); EXPECT_EQ( crsFromWkt->baseCRS()->coordinateSystem()->axisList().size(), 3U); } auto demoted = crs3DAsDerivedProj->demoteTo2D(std::string(), dbContext); EXPECT_EQ(demoted->baseCRS()->coordinateSystem()->axisList().size(), 2U); EXPECT_EQ(demoted->coordinateSystem()->axisList().size(), 2U); EXPECT_TRUE(demoted->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(demoted->demoteTo2D(std::string(), nullptr) ->isEquivalentTo(demoted.get())); } } // --------------------------------------------------------------------------- TEST(crs, normalizeForVisualization_derivedprojected_operation) { auto crs = createDerivedProjectedCRSNorthingEasting(); auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, crs); auto proj_string = "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm +zone=31 " "+ellps=WGS84 +step +proj=unimplemented +step +proj=unitconvert " "+xy_in=m +xy_out=ft +step +proj=axisswap +order=2,1"; ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), proj_string); auto opNormalized = op->normalizeForVisualization(); auto proj_string_normalized = "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step " "+proj=utm +zone=31 +ellps=WGS84 +step +proj=unimplemented +step " "+proj=unitconvert +xy_in=m +xy_out=ft"; EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), proj_string_normalized); } // --------------------------------------------------------------------------- TEST(crs, normalizeForVisualization_bound) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // NTF (Paris) const auto crs_4807 = factory->createCoordinateReferenceSystem("4807"); const auto bound = crs_4807->createBoundCRSToWGS84IfPossible( dbContext, CoordinateOperationContext::IntermediateCRSUse::NEVER); EXPECT_NE(crs_4807, bound); std::string normalized_proj_string = "+proj=pipeline +step +proj=axisswap +order=2,1 +step " "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step " "+proj=cart +ellps=WGS84 +step +proj=helmert +x=168 +y=60 +z=-320 " "+step +inv +proj=cart +ellps=clrk80ign +step +proj=pop +v_3 +step " "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=unitconvert " "+xy_in=rad +xy_out=grad"; auto orig_proj_string = normalized_proj_string + " +step +proj=axisswap +order=2,1"; auto op = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, bound); ASSERT_TRUE(op != nullptr); EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), orig_proj_string); const auto normalizedCrs = bound->normalizeForVisualization(); auto normalizedCrsAsBound = nn_dynamic_pointer_cast(normalizedCrs); ASSERT_TRUE(normalizedCrsAsBound != nullptr); auto singleCrs = nn_dynamic_pointer_cast(normalizedCrsAsBound->baseCRS()); ASSERT_TRUE(singleCrs != nullptr); const auto &normalizedAxisList = singleCrs->coordinateSystem()->axisList(); ASSERT_EQ(normalizedAxisList.size(), 2U); EXPECT_EQ(normalizedAxisList[0]->direction(), osgeo::proj::cs::AxisDirection::EAST); EXPECT_EQ(normalizedAxisList[1]->direction(), osgeo::proj::cs::AxisDirection::NORTH); auto opNormalized = CoordinateOperationFactory::create()->createOperation( GeographicCRS::EPSG_4326, normalizedCrs); ASSERT_TRUE(opNormalized != nullptr); EXPECT_EQ( opNormalized->exportToPROJString(PROJStringFormatter::create().get()), normalized_proj_string); } // --------------------------------------------------------------------------- TEST(crs, normalizeForVisualization_derivedprojected) { auto crs = createDerivedProjectedCRSNorthingEasting(); { const auto &axisList = crs->coordinateSystem()->axisList(); ASSERT_EQ(axisList.size(), 2U); EXPECT_EQ(axisList[0]->direction(), osgeo::proj::cs::AxisDirection::NORTH); EXPECT_EQ(axisList[1]->direction(), osgeo::proj::cs::AxisDirection::EAST); } { auto normalized = nn_dynamic_pointer_cast( crs->normalizeForVisualization()); const auto &normalizedAxisList = normalized->coordinateSystem()->axisList(); ASSERT_EQ(normalizedAxisList.size(), 2U); EXPECT_EQ(normalizedAxisList[0]->direction(), osgeo::proj::cs::AxisDirection::EAST); EXPECT_EQ(normalizedAxisList[1]->direction(), osgeo::proj::cs::AxisDirection::NORTH); } { auto normalized3D = nn_dynamic_pointer_cast( crs->promoteTo3D(std::string(), nullptr) ->normalizeForVisualization()); const auto &normalized3DAxisList = normalized3D->coordinateSystem()->axisList(); ASSERT_EQ(normalized3DAxisList.size(), 3U); EXPECT_EQ(normalized3DAxisList[0]->direction(), osgeo::proj::cs::AxisDirection::EAST); EXPECT_EQ(normalized3DAxisList[1]->direction(), osgeo::proj::cs::AxisDirection::NORTH); EXPECT_EQ(normalized3DAxisList[2]->direction(), osgeo::proj::cs::AxisDirection::UP); } } // --------------------------------------------------------------------------- TEST(crs, projected_normalizeForVisualization_do_not_mess_deriving_conversion) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); // Something with non standard order auto projCRS = authFactory->createProjectedCRS("3035"); { auto src = GeographicCRS::EPSG_4326; auto op = CoordinateOperationFactory::create()->createOperation(src, projCRS); ASSERT_TRUE(op != nullptr); // Make sure to run that in a scope, so that the object get destroyed op->normalizeForVisualization(); } EXPECT_EQ(projCRS->derivingConversion()->targetCRS().get(), projCRS.get()); } // --------------------------------------------------------------------------- TEST(crs, projected_promoteTo3D_do_not_mess_deriving_conversion) { auto projCRS = createProjected(); { // Make sure to run that in a scope, so that the object get destroyed projCRS->promoteTo3D(std::string(), nullptr); } EXPECT_EQ(projCRS->derivingConversion()->targetCRS().get(), projCRS.get()); } // --------------------------------------------------------------------------- TEST(crs, projected_demoteTo2D_do_not_mess_deriving_conversion) { auto projCRS = nn_dynamic_pointer_cast( createProjected()->promoteTo3D(std::string(), nullptr)); { // Make sure to run that in a scope, so that the object get destroyed projCRS->demoteTo2D(std::string(), nullptr); } EXPECT_EQ(projCRS->derivingConversion()->targetCRS().get(), projCRS.get()); } // --------------------------------------------------------------------------- TEST(crs, projected_alterGeodeticCRS_do_not_mess_deriving_conversion) { auto projCRS = createProjected(); { // Make sure to run that in a scope, so that the object get destroyed projCRS->alterGeodeticCRS(NN_NO_CHECK(projCRS->extractGeographicCRS())); } EXPECT_EQ(projCRS->derivingConversion()->targetCRS().get(), projCRS.get()); } // --------------------------------------------------------------------------- TEST(crs, projected_alterCSLinearUnit_do_not_mess_deriving_conversion) { auto projCRS = createProjected(); { // Make sure to run that in a scope, so that the object get destroyed projCRS->alterCSLinearUnit(UnitOfMeasure("my unit", 2)); } EXPECT_EQ(projCRS->derivingConversion()->targetCRS().get(), projCRS.get()); } // --------------------------------------------------------------------------- TEST(crs, projected_alterParametersLinearUnit_do_not_mess_deriving_conversion) { auto projCRS = createProjected(); { // Make sure to run that in a scope, so that the object get destroyed projCRS->alterParametersLinearUnit(UnitOfMeasure::METRE, false); } EXPECT_EQ(projCRS->derivingConversion()->targetCRS().get(), projCRS.get()); } // --------------------------------------------------------------------------- TEST(crs, projected_is_equivalent_to_with_proj4_extension) { const auto obj1 = PROJStringParser().createFromPROJString( "+proj=omerc +lat_0=50 +alpha=50.0 +no_rot +a=6378144.0 +b=6356759.0 " "+lon_0=8.0 +type=crs"); const auto crs1 = nn_dynamic_pointer_cast(obj1); ASSERT_TRUE(crs1 != nullptr); const auto wkt = crs1->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); const auto obj_from_wkt = WKTParser().createFromWKT(wkt); const auto crs_from_wkt = nn_dynamic_pointer_cast(obj_from_wkt); // Check equivalence of the CRS from PROJ.4 and WKT EXPECT_TRUE(crs1->isEquivalentTo(crs_from_wkt.get(), IComparable::Criterion::EQUIVALENT)); ASSERT_TRUE(crs_from_wkt != nullptr); // Same as above but with different option order const auto obj2 = PROJStringParser().createFromPROJString( "+proj=omerc +lat_0=50 +no_rot +alpha=50.0 +a=6378144.0 +b=6356759.0 " "+lon_0=8.0 +type=crs"); const auto crs2 = nn_dynamic_pointer_cast(obj2); ASSERT_TRUE(crs2 != nullptr); // Check equivalence of the 2 PROJ.4 based CRS EXPECT_TRUE( crs1->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); // Without +no_rot --> no PROJ.4 extension const auto objNoRot = PROJStringParser().createFromPROJString( "+proj=omerc +lat_0=50 +alpha=50.0 +a=6378144.0 +b=6356759.0 " "+lon_0=8.0 +type=crs"); const auto crsNoRot = nn_dynamic_pointer_cast(objNoRot); ASSERT_TRUE(crsNoRot != nullptr); EXPECT_FALSE(crs1->isEquivalentTo(crsNoRot.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_FALSE(crsNoRot->isEquivalentTo(crs1.get(), IComparable::Criterion::EQUIVALENT)); // Change alpha value const auto objDifferent = PROJStringParser().createFromPROJString( "+proj=omerc +lat_0=50 +alpha=49.0 +no_rot +a=6378144.0 +b=6356759.0 " "+lon_0=8.0 +type=crs"); const auto crsDifferent = nn_dynamic_pointer_cast(objDifferent); ASSERT_TRUE(crsDifferent != nullptr); EXPECT_FALSE(crs1->isEquivalentTo(crsDifferent.get(), IComparable::Criterion::EQUIVALENT)); } // --------------------------------------------------------------------------- TEST(crs, is_dynamic) { EXPECT_FALSE(GeographicCRS::EPSG_4326->isDynamic()); EXPECT_TRUE( GeographicCRS::EPSG_4326->isDynamic(/*considerWGS84AsDynamic=*/true)); { auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto crs = factory->createCoordinateReferenceSystem("4326"); EXPECT_FALSE(crs->isDynamic()); EXPECT_TRUE(crs->isDynamic(/*considerWGS84AsDynamic=*/true)); } { auto drf = DynamicGeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), Ellipsoid::WGS84, optional("My anchor"), PrimeMeridian::GREENWICH, Measure(2018.5, UnitOfMeasure::YEAR), optional("My model")); auto crs = GeographicCRS::create( PropertyMap(), drf, EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)); EXPECT_TRUE(crs->isDynamic()); } EXPECT_FALSE(createVerticalCRS()->isDynamic()); { auto drf = DynamicVerticalReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), optional("My anchor"), optional(), Measure(2018.5, UnitOfMeasure::YEAR), optional("My model")); auto crs = VerticalCRS::create( PropertyMap(), drf, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE)); EXPECT_TRUE(crs->isDynamic()); } EXPECT_FALSE(createCompoundCRS()->isDynamic()); } // --------------------------------------------------------------------------- TEST(crs, norway_ntm) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "ETRS89-NOR [EUREF89] / NTM zone 5" auto crs = factory->createCoordinateReferenceSystem("5105"); auto esri_wkt = "PROJCS[\"ETRS_1989_NTM_Zone_5\",GEOGCS[\"GCS_ETRS_1989\"," "DATUM[\"D_ETRS_1989\",SPHEROID[\"GRS_1980\",6378137.0,298.257222101]]," "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]," "PROJECTION[\"Transverse_Mercator\"]," "PARAMETER[\"False_Easting\",100000.0]," "PARAMETER[\"False_Northing\",1000000.0]," "PARAMETER[\"Central_Meridian\",5.5]," "PARAMETER[\"Scale_Factor\",1.0]," "PARAMETER[\"Latitude_Of_Origin\",58.0]," "UNIT[\"Meter\",1.0]]"; // May change in a future version of the ESRI db EXPECT_EQ( crs->exportToWKT( WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI, dbContext) .get()), esri_wkt); { auto obj = createFromUserInput(esri_wkt, dbContext, true); auto crs_from_esri = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs_from_esri != nullptr); auto res = crs_from_esri->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), crs.get()); EXPECT_EQ(res.front().second, 100); } auto wkt2_before_epsg_12_025 = "PROJCRS[\"ETRS89 / NTM zone 5\",\n" " BASEGEOGCRS[\"ETRS89\",\n" " ENSEMBLE[\"European Terrestrial Reference System 1989 " "ensemble\",\n" " MEMBER[\"European Terrestrial Reference Frame 1989\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1990\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1991\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1992\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1993\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1994\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1996\"],\n" " MEMBER[\"European Terrestrial Reference Frame 1997\"],\n" " MEMBER[\"European Terrestrial Reference Frame 2000\"],\n" " MEMBER[\"European Terrestrial Reference Frame 2005\"],\n" " MEMBER[\"European Terrestrial Reference Frame 2014\"],\n" " MEMBER[\"European Terrestrial Reference Frame 2020\"],\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]],\n" " ENSEMBLEACCURACY[0.1]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",4258]],\n" " CONVERSION[\"Norway TM zone 5\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",58,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",5.5,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",1,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",100000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",1000000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"northing (N)\",north,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"easting (E)\",east,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Engineering survey.\"],\n" " AREA[\"Norway - onshore - west of 6°E.\"],\n" " BBOX[58.32,4.39,62.64,6.01]],\n" " ID[\"EPSG\",5105]]"; { auto obj = createFromUserInput(wkt2_before_epsg_12_025, dbContext, true); auto crs_from_wkt2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs_from_wkt2 != nullptr); EXPECT_TRUE(crs->isEquivalentTo(crs_from_wkt2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(crs_from_wkt2->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); auto res = crs_from_wkt2->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), crs.get()); EXPECT_EQ(res.front().second, 100); } } // --------------------------------------------------------------------------- #ifdef requires_epsg_12_054 TEST(crs, ETRF2000_PL_CS92) { auto dbContext = DatabaseContext::create(); auto factory = AuthorityFactory::create(dbContext, "EPSG"); // "ETRS89 / PL-1992" (formerly ETRF2000-PL / CS92) auto crs = factory->createCoordinateReferenceSystem("2180"); auto wkt2_before_epsg_12_041 = "PROJCRS[\"ETRF2000-PL / CS92\",\n" " BASEGEOGCRS[\"ETRF2000-PL\",\n" " DATUM[\"ETRF2000 Poland\",\n" " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" " LENGTHUNIT[\"metre\",1]]],\n" " PRIMEM[\"Greenwich\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" " ID[\"EPSG\",9702]],\n" " CONVERSION[\"Poland CS92\",\n" " METHOD[\"Transverse Mercator\",\n" " ID[\"EPSG\",9807]],\n" " PARAMETER[\"Latitude of natural origin\",0,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8801]],\n" " PARAMETER[\"Longitude of natural origin\",19,\n" " ANGLEUNIT[\"degree\",0.0174532925199433],\n" " ID[\"EPSG\",8802]],\n" " PARAMETER[\"Scale factor at natural origin\",0.9993,\n" " SCALEUNIT[\"unity\",1],\n" " ID[\"EPSG\",8805]],\n" " PARAMETER[\"False easting\",500000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8806]],\n" " PARAMETER[\"False northing\",-5300000,\n" " LENGTHUNIT[\"metre\",1],\n" " ID[\"EPSG\",8807]]],\n" " CS[Cartesian,2],\n" " AXIS[\"northing (x)\",north,\n" " ORDER[1],\n" " LENGTHUNIT[\"metre\",1]],\n" " AXIS[\"easting (y)\",east,\n" " ORDER[2],\n" " LENGTHUNIT[\"metre\",1]],\n" " USAGE[\n" " SCOPE[\"Topographic mapping (medium and small scale).\"],\n" " AREA[\"Poland - onshore and offshore.\"],\n" " BBOX[49,14.14,55.93,24.15]],\n" " ID[\"EPSG\",2180]]"; { auto obj = createFromUserInput(wkt2_before_epsg_12_041, dbContext, true); auto crs_from_wkt2 = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs_from_wkt2 != nullptr); EXPECT_STREQ(crs_from_wkt2->nameStr().c_str(), "ETRS89 / PL-1992"); EXPECT_STREQ(crs_from_wkt2->baseCRS()->nameStr().c_str(), "ETRS89"); EXPECT_TRUE(crs->isEquivalentTo(crs_from_wkt2.get(), IComparable::Criterion::EQUIVALENT)); EXPECT_TRUE(crs_from_wkt2->isEquivalentTo( crs.get(), IComparable::Criterion::EQUIVALENT)); auto res = crs_from_wkt2->identify(factory); ASSERT_EQ(res.size(), 1U); EXPECT_EQ(res.front().first.get(), crs.get()); EXPECT_EQ(res.front().second, 100); } } #endif proj-9.8.1/test/unit/proj_errno_string_test.cpp000664 001750 001750 00000004737 15166171715 021734 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Unit test for proj_errno_string() * Author: Kristian Evers * ****************************************************************************** * Copyright (c) 2018, Kristian Evers. * * 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 #include "proj.h" #include "proj_config.h" #include "gtest_include.h" namespace { TEST(ProjErrnoStringTest, NoError) { EXPECT_EQ(nullptr, proj_errno_string(0)); } TEST(ProjErrnoStringTest, ProjErrnos) { EXPECT_STREQ("Unknown error (code -1)", proj_errno_string(-1)); EXPECT_STREQ("Invalid PROJ string syntax", proj_errno_string(PROJ_ERR_INVALID_OP_WRONG_SYNTAX)); EXPECT_STREQ( "Unspecified error related to coordinate operation initialization", proj_errno_string(PROJ_ERR_INVALID_OP)); EXPECT_STREQ("Unspecified error related to coordinate transformation", proj_errno_string(PROJ_ERR_COORD_TRANSFM)); } TEST(ProjErrnoStringTest, proj_context_errno_string) { EXPECT_STREQ("Unknown error (code -1)", proj_context_errno_string(nullptr, -1)); PJ_CONTEXT *ctx = proj_context_create(); EXPECT_STREQ("Unknown error (code -999)", proj_context_errno_string(ctx, -999)); proj_context_destroy(ctx); } } // namespace proj-9.8.1/test/cli/000775 001750 001750 00000000000 15166171735 014203 5ustar00eveneven000000 000000 proj-9.8.1/test/cli/test_cct.yaml000664 001750 001750 00000017227 15166171715 016706 0ustar00eveneven000000 000000 exe: cct tests: - comment: Test version argument args: --version sub: ["cct(_d)?(\\.exe)?: Rel.*", "cct: Rel"] stdout: "cct: Rel" exitcode: 0 - comment: Test help argument args: -h grep: "Usage: " sub: ["cct(_d)?(\\.exe)?", "cct"] stdout: "Usage: cct [-options]... [+operator_specs]... infile..." exitcode: 0 - args: -d 8 +proj=merc +R=1 in: 90 45 0 out: " 1.57079633 0.88137359 0.00000000 inf" # tests without specifying the number of decimals (by default: 10 for radians and degrees, 4 for meters) - args: -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad in: 0.5 2 out: " 0.5000000000 2.0000000000 0.0000 0.0000" - args: -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg in: 0.5 2 out: " 0.5000000000 2.0000000000 0.0000 0.0000" - args: -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km in: 0.5 2 out: " 0.0005 0.0020 0.0000 0.0000" # tests for which the number of decimals has been specified (-d 6) - args: -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad in: 0.5 2 out: " 0.500000 2.000000 0.000000 0.0000" - args: -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=deg in: 0.5 2 out: " 0.500000 2.000000 0.000000 0.0000" - args: -d 6 -z 0 -t 0 +proj=pipeline +step +proj=unitconvert +xy_in=m +xy_out=km in: 0.5 2 out: " 0.000500 0.002000 0.000000 0.0000" - comment: Test cct with object code initialization args: EPSG:8366 in: 3541657.3778 948984.2343 5201383.5231 2020.5 out: " 3541657.9112 948983.7503 5201383.2482 2020.5000" - comment: Test cct with object that is not a CRS args: - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984 (G2139)",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ANGLEUNIT["degree",0.0174532925199433]]] in: 0 0 0 sub: ["(_d)?\\.exe", ""] stderr: "cct: Input object is not a coordinate operation, but a CRS." exitcode: 1 - comment: Test cct with object name initialization args: - ITRF2014 to ETRF2014 (1) in: 3541657.3778 948984.2343 5201383.5231 2020.5 out: " 3541657.9112 948983.7503 5201383.2482 2020.5000" - comment: Test cct with object code initialization and file input file: - name: a content: 3541657.3778 948984.2343 5201383.5231 2020.5 - name: b content: 3541658.0000 948985.0000 5201384.0000 2020.5 args: "EPSG:8366 a b" out: |2 3541657.9112 948983.7503 5201383.2482 2020.5000 3541658.5334 948984.5160 5201383.7251 2020.5000 - comment: Test cct with WKT in a file file: - name: in.wkt content: | COORDINATEOPERATION["ITRF2014 to ETRF2014 (1)", VERSION["EUREF-Eur"], SOURCECRS[ GEODCRS["ITRF2014", DYNAMIC[ FRAMEEPOCH[2010]], DATUM["International Terrestrial Reference Frame 2014", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[Cartesian,3], AXIS["(X)",geocentricX, ORDER[1], LENGTHUNIT["metre",1]], AXIS["(Y)",geocentricY, ORDER[2], LENGTHUNIT["metre",1]], AXIS["(Z)",geocentricZ, ORDER[3], LENGTHUNIT["metre",1]], ID["EPSG",7789]]], TARGETCRS[ GEODCRS["ETRF2014", DATUM["European Terrestrial Reference Frame 2014", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[Cartesian,3], AXIS["(X)",geocentricX, ORDER[1], LENGTHUNIT["metre",1]], AXIS["(Y)",geocentricY, ORDER[2], LENGTHUNIT["metre",1]], AXIS["(Z)",geocentricZ, ORDER[3], LENGTHUNIT["metre",1]], ID["EPSG",8401]]], METHOD["Time-dependent Position Vector tfm (geocentric)", ID["EPSG",1053]], PARAMETER["X-axis translation",0, LENGTHUNIT["millimetre",0.001], ID["EPSG",8605]], PARAMETER["Y-axis translation",0, LENGTHUNIT["millimetre",0.001], ID["EPSG",8606]], PARAMETER["Z-axis translation",0, LENGTHUNIT["millimetre",0.001], ID["EPSG",8607]], PARAMETER["X-axis rotation",0, ANGLEUNIT["milliarc-second",4.84813681109536E-09], ID["EPSG",8608]], PARAMETER["Y-axis rotation",0, ANGLEUNIT["milliarc-second",4.84813681109536E-09], ID["EPSG",8609]], PARAMETER["Z-axis rotation",0, ANGLEUNIT["milliarc-second",4.84813681109536E-09], ID["EPSG",8610]], PARAMETER["Scale difference",0, SCALEUNIT["parts per billion",1E-09], ID["EPSG",8611]], PARAMETER["Rate of change of X-axis translation",0, LENGTHUNIT["millimetres per year",3.16887651727315E-11], ID["EPSG",1040]], PARAMETER["Rate of change of Y-axis translation",0, LENGTHUNIT["millimetres per year",3.16887651727315E-11], ID["EPSG",1041]], PARAMETER["Rate of change of Z-axis translation",0, LENGTHUNIT["millimetres per year",3.16887651727315E-11], ID["EPSG",1042]], PARAMETER["Rate of change of X-axis rotation",0.085, ANGLEUNIT["milliarc-seconds per year",1.53631468932076E-16], ID["EPSG",1043]], PARAMETER["Rate of change of Y-axis rotation",0.531, ANGLEUNIT["milliarc-seconds per year",1.53631468932076E-16], ID["EPSG",1044]], PARAMETER["Rate of change of Z-axis rotation",-0.77, ANGLEUNIT["milliarc-seconds per year",1.53631468932076E-16], ID["EPSG",1045]], PARAMETER["Rate of change of Scale difference",0, SCALEUNIT["parts per billion per year",3.16887651727315E-17], ID["EPSG",1046]], PARAMETER["Parameter reference epoch",1989, TIMEUNIT["year",31556925.445], ID["EPSG",1047]]] args: "@in.wkt" in: 3541657.3778 948984.2343 5201383.5231 2020.5 out: " 3541657.9112 948983.7503 5201383.2482 2020.5000" - comment: Test cct with input file with UTF-8 BOM marker file: name: input_file1_with_utf8_bom.txt content: "\uFEFF0 3 0\n" args: +proj=noop input_file1_with_utf8_bom.txt # no BOM with output out: " 0.0000 3.0000 0.0000 inf" - comment: Test cct with non existing input file args: +proj=noop i_do_not_exist.txt stderr: "Cannot open file i_do_not_exist.txt" exitcode: 1 - comment: Test robustness to non-ASCII characters (cf https://github.com/OSGeo/PROJ/issues/4528 case 1) args: "-\uFF56" exitcode: 1 - comment: Test robustness to non-ASCII characters (cf https://github.com/OSGeo/PROJ/issues/4528 case 2) args: "--\xF3" exitcode: 1 proj-9.8.1/test/cli/test_proj_nad83.yaml000664 001750 001750 00000123242 15166171715 020077 0ustar00eveneven000000 000000 comment: > These tests are from an old script "test83" from the 1990s used to test nad83 init files with most of the SPCS zones. It was originally generated from execution of NMD's program l176, where the second pair of numbers are respective easting and northing output. Original versions of proj varied in the .001ft range with projections using Transverse Mercator due to greater precision of meridional distance function. exe: proj tests: - args: +units=us-ft +init=nad83:5001 -E -f %.3f in: -134d00'00.000 55d00'00.000 2616018.154 1156379.643 AK 1 GP1 out: "-134d00'00.000 55d00'00.000\t2616018.154\t1156379.643 2616018.154 1156379.643 AK 1 GP1" - args: +units=us-ft +init=nad83:5001 -f %.3f in: -133d40'00.000 57d00'00.000 2685941.919 1886799.668 AK 1 GP2 out: "2685941.919\t1886799.668 2685941.919 1886799.668 AK 1 GP2" - args: +units=us-ft +init=nad83:5001 -f %.3f in: -131d35'45.432 54d39'02.654 3124531.426 1035343.511 AK 1 GP3 out: "3124531.426\t1035343.511 3124531.426 1035343.511 AK 1 GP3" - args: +units=us-ft +init=nad83:5001 -f %.3f in: -129d32'30.000 54d32'30.000 3561448.345 1015025.876 AK 1 GP4 out: "3561448.345\t1015025.876 3561448.345 1015025.876 AK 1 GP4" - args: +units=us-ft +init=nad83:5001 -f %.3f in: -141d30'00.000 60d30'00.000 1276328.587 3248159.207 AK 1 GP6 out: "1276328.587\t3248159.207 1276328.587 3248159.207 AK 1 GP6" - args: +units=us-ft +init=nad83:5002 -f %.3f in: -142d00'00.000 56d30'30.000 1640416.667 916074.825 AK 2 GP1 out: "1640416.667\t916074.824 1640416.667 916074.825 AK 2 GP1" - args: +units=us-ft +init=nad83:5003 -f %.3f in: -146d00'00.000 56d30'30.000 1640416.667 916074.825 AK 3 GP1 out: "1640416.667\t916074.824 1640416.667 916074.825 AK 3 GP1" - args: +units=us-ft +init=nad83:5004 -f %.3f in: -150d00'00.000 56d30'30.000 1640416.667 916074.825 AK 4 GP1 out: "1640416.667\t916074.824 1640416.667 916074.825 AK 4 GP1" - args: +units=us-ft +init=nad83:5005 -f %.3f in: -152d28'56.134 60d53'28.765 1910718.662 2520810.680 AK 5 GP1 out: "1910718.662\t2520810.679 1910718.662 2520810.680 AK 5 GP1" - args: +units=us-ft +init=nad83:5005 -f %.3f in: -154d00'00.000 56d30'30.000 1640416.667 916074.825 AK 5 GP2 out: "1640416.667\t916074.824 1640416.667 916074.825 AK 5 GP2" - args: +units=us-ft +init=nad83:5006 -f %.3f in: -155d00'00.000 71d00'00.000 1998036.998 6224208.217 AK 6 GP1 out: "1998036.998\t6224208.215 1998036.998 6224208.217 AK 6 GP1" - args: +units=us-ft +init=nad83:5006 -f %.3f in: -158d00'00.000 71d00'00.000 1640416.667 6215353.367 AK 6 GP1 out: "1640416.667\t6215353.365 1640416.667 6215353.367 AK 6 GP1" - args: +units=us-ft +init=nad83:5007 -f %.3f in: -162d00'00.000 65d15'00.000 1640416.667 4111446.441 AK 7 GP1 out: "1640416.667\t4111446.440 1640416.667 4111446.441 AK 7 GP1" - args: +units=us-ft +init=nad83:5008 -f %.3f in: -166d00'00.000 65d15'00.000 1640416.667 4111446.441 AK 8 GP1 out: "1640416.667\t4111446.440 1640416.667 4111446.441 AK 8 GP1" - args: +units=us-ft +init=nad83:5009 -f %.3f in: -170d00'00.000 63d20'00.000 1640416.667 3410489.717 AK 9 GP1 out: "1640416.667\t3410489.716 1640416.667 3410489.717 AK 9 GP1" - args: +units=us-ft +init=nad83:5010 -f %.3f in: -164d02'30.000 54d27'30.000 5814167.604 1473788.834 AK10 GP1 out: "5814167.604\t1473788.834 5814167.604 1473788.834 AK10 GP1" - args: +units=us-ft +init=nad83:5010 -f %.3f in: -176d00'00.000 52d30'00.000 3280833.333 547580.542 AK10 GP2 out: "3280833.333\t547580.542 3280833.333 547580.542 AK10 GP2" - args: +units=us-ft +init=nad83:101 -f %.3f in: -85d50'00.000 31d20'00.000 656166.667 303104.183 AL E GP1 out: "656166.667\t303104.183 656166.667 303104.183 AL E GP1" - args: +units=us-ft +init=nad83:101 -f %.3f in: -85d12'41.738 32d38'57.737 847539.085 782420.807 AL E GP2 out: "847539.085\t782420.807 847539.085 782420.807 AL E GP2" - args: +units=us-ft +init=nad83:101 -f %.3f in: -86d36'58.670 34d48'58.708 421151.975 1571298.908 AL E GP3 out: "421151.975\t1571298.908 421151.975 1571298.908 AL E GP3" - args: +units=us-ft +init=nad83:102 -f %.3f in: -87d30'00.000 33d20'00.000 1968500.000 1212527.587 AL W GP1 out: "1968500.000\t1212527.586 1968500.000 1212527.587 AL W GP1" - args: +units=us-ft +init=nad83:102 -f %.3f in: -87d30'00.000 33d20'30.000 1968500.000 1215559.708 AL W GP2 out: "1968500.000\t1215559.708 1968500.000 1215559.708 AL W GP2" - args: +units=us-ft +init=nad83:301 -f %.3f in: -91d34'46.321 35d18'37.443 1437779.156 355900.759 AR N GP1 out: "1437779.156\t355900.759 1437779.156 355900.759 AR N GP1" - args: +units=us-ft +init=nad83:301 -f %.3f in: -92d04'11.625 35d19'34.269 1291483.982 361385.695 AR N GP2 out: "1291483.982\t361385.695 1291483.982 361385.695 AR N GP2" - args: +units=us-ft +init=nad83:302 -f %.3f in: -92d00'00.000 34d45'00.000 1312333.333 2070451.744 AR S GP1 out: "1312333.333\t2070451.744 1312333.333 2070451.744 AR S GP1" - args: +units=us-ft +init=nad83:302 -f %.3f in: -92d00'00.000 33d15'00.000 1312333.333 1524603.730 AR S GP2 out: "1312333.333\t1524603.730 1312333.333 1524603.730 AR S GP2" - args: +units=us-ft +init=nad83:202 -f %.3f in: -111d55'00.000 34d45'00.000 699998.600 1364309.666 AZ C GP1 out: "699998.600\t1364309.666 699998.600 1364309.666 AZ C GP1" - args: +units=us-ft +init=nad83:202 -f %.3f in: -111d55'00.000 32d20'00.000 699998.600 484994.340 AZ C GP2 out: "699998.600\t484994.340 699998.600 484994.340 AZ C GP2" - args: +units=us-ft +init=nad83:201 -f %.3f in: -110d24'59.771 35d09'58.568 625301.460 1515899.830 AZ E GP1 out: "625301.460\t1515899.830 625301.460 1515899.830 AZ E GP1" - args: +units=us-ft +init=nad83:201 -f %.3f in: -109d34'33.127 31d59'53.103 883142.524 363539.663 AZ E GP2 out: "883142.524\t363539.663 883142.524 363539.663 AZ E GP2" - args: +units=us-ft +init=nad83:201 -f %.3f in: -110d30'34.948 35d07'28.243 597423.277 1500785.235 AZ E GP3 out: "597423.277\t1500785.234 597423.277 1500785.235 AZ E GP3" - args: +units=us-ft +init=nad83:201 -f %.3f in: -109d45'13.226 32d08'41.778 827818.955 416705.394 AZ E GP4 out: "827818.955\t416705.394 827818.955 416705.394 AZ E GP4" - args: +units=us-ft +init=nad83:203 -f %.3f in: -113d45'00.000 34d45'00.000 699998.600 1364355.147 AZ W GP1 out: "699998.600\t1364355.147 699998.600 1364355.147 AZ W GP1" - args: +units=us-ft +init=nad83:203 -f %.3f in: -113d45'00.000 34d45'30.000 699998.600 1367387.968 AZ W GP2 out: "699998.600\t1367387.968 699998.600 1367387.968 AZ W GP2" - args: +units=us-ft +init=nad83:203 -f %.3f in: -113d45'00.000 34d46'00.000 699998.600 1370420.793 AZ W GP3 out: "699998.600\t1370420.793 699998.600 1370420.793 AZ W GP3" - args: +units=us-ft +init=nad83:401 -f %.3f in: -122d00'00.000 41d30'00.000 6561666.667 2429744.729 CA 1 GP1 out: "6561666.667\t2429744.729 6561666.667 2429744.729 CA 1 GP1" - args: +units=us-ft +init=nad83:401 -f %.3f in: -122d00'00.000 41d30'30.000 6561666.667 2432781.128 CA 1 GP2 out: "6561666.667\t2432781.128 6561666.667 2432781.128 CA 1 GP2" - args: +units=us-ft +init=nad83:402 -f %.3f in: -122d00'00.000 39d20'00.000 6561666.667 2247404.250 CA 2 GP1 out: "6561666.667\t2247404.250 6561666.667 2247404.250 CA 2 GP1" - args: +units=us-ft +init=nad83:402 -f %.3f in: -122d00'00.000 39d20'30.000 6561666.667 2250439.391 CA 2 GP2 out: "6561666.667\t2250439.391 6561666.667 2250439.391 CA 2 GP2" - args: +units=us-ft +init=nad83:403 -f %.3f in: -120d30'00.000 37d05'00.000 6561666.667 1852815.760 CA 3 GP1 out: "6561666.667\t1852815.760 6561666.667 1852815.760 CA 3 GP1" - args: +units=us-ft +init=nad83:403 -f %.3f in: -121d22'26.019 37d30'30.324 6308189.835 2008776.145 CA 3 GP2 out: "6308189.835\t2008776.145 6308189.835 2008776.145 CA 3 GP2" - args: +units=us-ft +init=nad83:403 -f %.3f in: -119d46'32.733 37d07'41.470 6772808.251 1869963.783 CA 3 GP3 out: "6772808.251\t1869963.783 6772808.251 1869963.783 CA 3 GP3" - args: +units=us-ft +init=nad83:403 -f %.3f in: -119d38'26.434 36d55'48.009 6812851.254 1798140.563 CA 3 GP4 out: "6812851.254\t1798140.563 6812851.254 1798140.563 CA 3 GP4" - args: +units=us-ft +init=nad83:403 -f %.3f in: -120d42'59.779 38d06'52.815 6499349.432 2228414.867 CA 3 GP5 out: "6499349.432\t2228414.867 6499349.432 2228414.867 CA 3 GP5" - args: +units=us-ft +init=nad83:404 -f %.3f in: -119d00'00.000 36d20'00.000 6561666.667 2004462.102 CA 4 GP1 out: "6561666.667\t2004462.102 6561666.667 2004462.102 CA 4 GP1" - args: +units=us-ft +init=nad83:404 -f %.3f in: -119d00'00.000 36d20'30.000 6561666.667 2007495.782 CA 4 GP2 out: "6561666.667\t2007495.782 6561666.667 2007495.782 CA 4 GP2" - args: +units=us-ft +init=nad83:405 -f %.3f in: -118d00'00.000 34d45'00.000 6561666.667 2095323.781 CA 5 GP1 out: "6561666.667\t2095323.781 6561666.667 2095323.781 CA 5 GP1" - args: +units=us-ft +init=nad83:405 -f %.3f in: -118d00'00.000 34d45'30.000 6561666.667 2098356.568 CA 5 GP2 out: "6561666.667\t2098356.568 6561666.667 2098356.568 CA 5 GP2" - args: +units=us-ft +init=nad83:406 -f %.3f in: -116d15'00.000 33d20'00.000 6561666.667 2064911.626 CA 6 GP1 out: "6561666.667\t2064911.626 6561666.667 2064911.626 CA 6 GP1" - args: +units=us-ft +init=nad83:406 -f %.3f in: -116d15'00.000 33d20'30.000 6561666.667 2067943.810 CA 6 GP2 out: "6561666.667\t2067943.810 6561666.667 2067943.810 CA 6 GP2" - args: +units=us-ft +init=nad83:406 -f %.3f in: -118d20'00.000 34d30'00.000 5933874.572 2495758.727 CA 7 GP1 out: "5933874.572\t2495758.727 5933874.572 2495758.727 CA 7 GP1" - args: +units=us-ft +init=nad83:502 -f %.3f in: -105d30'00.000 39d15'00.000 3000000.000 1515946.820 CO C GP1 out: "3000000.000\t1515946.820 3000000.000 1515946.820 CO C GP1" - args: +units=us-ft +init=nad83:502 -f %.3f in: -105d30'00.000 39d15'30.000 3000000.000 1518981.963 CO C GP2 out: "3000000.000\t1518981.963 3000000.000 1518981.963 CO C GP2" - args: +units=us-ft +init=nad83:501 -f %.3f in: -108d45'55.378 40d25'33.504 2091110.958 1414758.884 CO N GP1 out: "2091110.958\t1414758.884 2091110.958 1414758.884 CO N GP1" - args: +units=us-ft +init=nad83:501 -f %.3f in: -105d14'45.588 40d12'42.711 3070938.779 1320125.979 CO N GP2 out: "3070938.779\t1320125.979 3070938.779 1320125.979 CO N GP2" - args: +units=us-ft +init=nad83:503 -f %.3f in: -105d30'00.000 37d30'00.000 3000000.000 1303432.168 CO S GP1 out: "3000000.000\t1303432.168 3000000.000 1303432.168 CO S GP1" - args: +units=us-ft +init=nad83:503 -f %.3f in: -105d30'00.000 37d30'30.000 3000000.000 1306466.471 CO S GP2 out: "3000000.000\t1306466.471 3000000.000 1306466.471 CO S GP2" - args: +units=us-ft +init=nad83:600 -f %.3f in: -72d43'30.515 41d16'55.847 1006831.954 663542.786 CT GP1 out: "1006831.954\t663542.786 1006831.954 663542.786 CT GP1" - args: +units=us-ft +init=nad83:600 -f %.3f in: -73d01'15.609 41d13'25.985 925448.220 642418.129 CT GP2 out: "925448.220\t642418.129 925448.220 642418.129 CT GP2" - args: +units=us-ft +init=nad83:700 -f %.3f in: -75d33'00.748 39d21'15.214 618403.524 493238.843 DE GP1 out: "618403.524\t493238.843 618403.524 493238.843 DE GP1" - args: +units=us-ft +init=nad83:700 -f %.3f in: -75d19'01.889 39d45'14.765 684135.532 638883.528 DE GP2 out: "684135.532\t638883.528 684135.532 638883.528 DE GP2" - args: +units=us-ft +init=nad83:903 -f %.3f in: -82d45'52.412 29d39'06.589 2519743.236 241248.726 FL N GP1 out: "2519743.236\t241248.726 2519743.236 241248.726 FL N GP1" - args: +units=us-ft +init=nad83:903 -f %.3f in: -84d55'11.533 29d38'51.982 1835122.674 235823.399 FL N GP2 out: "1835122.674\t235823.399 1835122.674 235823.399 FL N GP2" - args: +units=us-ft +init=nad83:1001 -f %.3f in: -81d27'15.592 32d38'03.003 875449.222 958850.568 GA E GP1 out: "875449.222\t958850.568 875449.222 958850.568 GA E GP1" - args: +units=us-ft +init=nad83:1001 -f %.3f in: -83d15'39.990 33d29'58.626 322535.391 1274748.301 GA E GP2 out: "322535.391\t1274748.301 322535.391 1274748.301 GA E GP2" - args: +units=us-ft +init=nad83:5101 -f %.3f in: -155d59'16.911 19d37'23.477 1472470.137 287083.198 HI 1 GP1 out: "1472470.137\t287083.198 1472470.137 287083.198 HI 1 GP1" - args: +units=us-ft +init=nad83:5101 -f %.3f in: -155d18'06.262 19d31'24.578 1708685.701 250676.240 HI 1 GP2 out: "1708685.701\t250676.240 1708685.701 250676.240 HI 1 GP2" - args: +units=us-ft +init=nad83:5101 -f %.3f in: -155d30'00.000 19d42'00.000 1640416.667 314739.275 HI 1 GP3 out: "1640416.667\t314739.275 1640416.667 314739.275 HI 1 GP3" - args: +units=us-ft +init=nad83:5101 -f %.3f in: -155d30'00.000 19d42'30.000 1640416.667 317765.760 HI 1 GP4 out: "1640416.667\t317765.760 1640416.667 317765.760 HI 1 GP4" - args: +units=us-ft +init=nad83:5102 -f %.3f in: -156d40'00.000 20d42'00.000 1640416.667 133177.588 HI 2 GP1 out: "1640416.667\t133177.588 1640416.667 133177.588 HI 2 GP1" - args: +units=us-ft +init=nad83:5102 -f %.3f in: -156d40'00.000 20d42'30.000 1640416.667 136204.417 HI 2 GP2 out: "1640416.667\t136204.417 1640416.667 136204.417 HI 2 GP2" - args: +units=us-ft +init=nad83:5103 -f %.3f in: -158d00'00.000 21d30'00.000 1640416.667 121084.931 HI 3 GP1 out: "1640416.667\t121084.931 1640416.667 121084.931 HI 3 GP1" - args: +units=us-ft +init=nad83:5103 -f %.3f in: -158d01'30.000 21d37'30.000 1631925.017 166493.704 HI 3 GP2 out: "1631925.017\t166493.704 1631925.017 166493.704 HI 3 GP2" - args: +units=us-ft +init=nad83:5104 -f %.3f in: -159d30'00.000 22d05'00.000 1640416.667 90820.525 HI 4 GP1 out: "1640416.667\t90820.525 1640416.667 90820.525 HI 4 GP1" - args: +units=us-ft +init=nad83:5105 -f %.3f in: -160d10'00.000 21d42'00.000 1640416.667 12109.121 HI 5 GP1 out: "1640416.667\t12109.121 1640416.667 12109.121 HI 5 GP1" - args: +units=us-ft +init=nad83:1401 -f %.3f in: -93d28'33.966 42d44'50.101 4927669.136 3735362.601 IA N GP1 out: "4927669.136\t3735362.601 4927669.136 3735362.601 IA N GP1" - args: +units=us-ft +init=nad83:1401 -f %.3f in: -93d54'22.084 42d40'23.699 4812032.409 3708655.393 IA N GP2 out: "4812032.409\t3708655.393 4812032.409 3708655.393 IA N GP2" - args: +units=us-ft +init=nad83:1101 -f %.3f in: -111d42'29.824 43d48'07.616 777180.670 778579.414 ID E GP1 out: "777180.670\t778579.414 777180.670 778579.414 ID E GP1" - args: +units=us-ft +init=nad83:1101 -f %.3f in: -112d22'35.516 43d35'26.260 600566.613 701226.817 ID E GP2 out: "600566.613\t701226.817 600566.613 701226.817 ID E GP2" - args: +units=us-ft +init=nad83:1103 -f %.3f in: -116d22'02.592 48d07'50.941 2473902.726 2357266.577 ID W GP1 out: "2473902.726\t2357266.576 2473902.726 2357266.577 ID W GP1" - args: +units=us-ft +init=nad83:1201 -f %.3f in: -88d07'06.790 41d46'11.855 1042839.901 1858837.259 IL E GP1 out: "1042839.901\t1858837.259 1042839.901 1858837.259 IL E GP1" - args: +units=us-ft +init=nad83:1201 -f %.3f in: -88d41'35.208 40d43'37.202 884532.422 1478959.912 IL E GP2 out: "884532.422\t1478959.911 884532.422 1478959.912 IL E GP2" - args: +units=us-ft +init=nad83:1301 -f %.3f in: -85d40'00.000 40d00'00.000 328083.333 1730697.447 IN E GP1 out: "328083.333\t1730697.447 328083.333 1730697.447 IN E GP1" - args: +units=us-ft +init=nad83:1301 -f %.3f in: -85d40'00.000 40d00'30.000 328083.333 1733733.066 IN E GP2 out: "328083.333\t1733733.065 328083.333 1733733.066 IN E GP2" - args: +units=us-ft +init=nad83:1301 -f %.3f in: -86d14'27.780 40d00'12.690 167175.533 1732499.995 IN E GP3 out: "167175.533\t1732499.995 167175.533 1732499.995 IN E GP3" - args: +units=us-ft +init=nad83:1301 -f %.3f in: -86d14'27.790 40d00'31.660 167187.126 1734419.540 IN E GP4 out: "167187.126\t1734419.540 167187.126 1734419.540 IN E GP4" - args: +units=us-ft +init=nad83:1301 -f %.3f in: -86d14'28.103 40d00'47.412 167173.047 1736013.616 IN E GP6 out: "167173.047\t1736013.615 167173.047 1736013.616 IN E GP6" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -87d05'00.000 40d00'00.000 2952750.000 1730697.447 IN W GP1 out: "2952750.000\t1730697.447 2952750.000 1730697.447 IN W GP1" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -87d05'00.000 40d00'30.000 2952750.000 1733733.066 IN W GP2 out: "2952750.000\t1733733.065 2952750.000 1733733.066 IN W GP2" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -86d45'10.717 39d41'24.840 3045717.498 1618031.699 IN W GP3 out: "3045717.498\t1618031.699 3045717.498 1618031.699 IN W GP3" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -87d41'44.075 37d54'24.755 2776105.988 968944.255 IN W GP4 out: "2776105.988\t968944.255 2776105.988 968944.255 IN W GP4" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -86d32'13.179 39d32'46.419 3106817.690 1565874.113 IN W GP5 out: "3106817.690\t1565874.112 3106817.690 1565874.113 IN W GP5" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -87d25'26.675 38d26'17.646 2855150.544 1162044.125 IN W GP6 out: "2855150.544\t1162044.125 2855150.544 1162044.125 IN W GP6" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -86d14'28.103 40d00'47.412 3188649.790 1736609.724 IN W GP7 out: "3188649.790\t1736609.724 3188649.790 1736609.724 IN W GP7" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -86d14'27.780 40d00'12.690 3188708.130 1733096.467 IN W GP8 out: "3188708.130\t1733096.467 3188708.130 1733096.467 IN W GP8" - args: +units=us-ft +init=nad83:1302 -f %.3f in: -86d14'27.790 40d00'31.660 3188689.210 1735016.020 IN W GP9 out: "3188689.210\t1735016.020 3188689.210 1735016.020 IN W GP9" - args: +units=us-ft +init=nad83:1501 -f %.3f in: -96d47'54.567 38d58'52.096 1653880.047 238201.110 KS N GP1 out: "1653880.047\t238201.110 1653880.047 238201.110 KS N GP1" - args: +units=us-ft +init=nad83:1501 -f %.3f in: -98d35'23.954 39d58'41.967 1146983.460 599694.197 KS N GP2 out: "1146983.460\t599694.197 1146983.460 599694.197 KS N GP2" - args: +units=us-ft +init=nad83:1601 -f %.3f in: -84d05'43.283 38d14'35.963 1684830.325 270726.733 KY N GP1 out: "1684830.325\t270726.733 1684830.325 270726.733 KY N GP1" - args: +units=us-ft +init=nad83:1601 -f %.3f in: -84d26'49.265 39d04'03.099 1584475.157 570918.805 KY N GP2 out: "1584475.157\t570918.805 1584475.157 570918.805 KY N GP2" - args: +units=us-ft +init=nad83:1701 -f %.3f in: -91d34'46.483 31d57'26.243 3566283.410 531318.874 LA N GP1 out: "3566283.410\t531318.874 3566283.410 531318.874 LA N GP1" - args: +units=us-ft +init=nad83:1701 -f %.3f in: -92d52'46.615 32d54'52.264 3164322.062 878564.036 LA N GP2 out: "3164322.062\t878564.036 3164322.062 878564.036 LA N GP2" - args: +units=us-ft +init=nad83:1701 -f %.3f in: -91d29'09.480 31d56'44.721 3595353.711 527382.519 LA N GP3 out: "3595353.711\t527382.519 3595353.711 527382.519 LA N GP3" - args: +units=us-ft +init=nad83:1701 -f %.3f in: -93d59'38.241 32d48'43.467 2821809.119 844247.864 LA N GP4 out: "2821809.119\t844247.864 2821809.119 844247.864 LA N GP4" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d00'00.000 28d50'00.000 4027995.272 128836.330 LA S GP1 out: "4027995.272\t128836.330 4027995.272 128836.330 LA S GP1" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d30'00.000 28d50'00.000 3867904.667 125925.406 LA S GP2 out: "3867904.667\t125925.406 3867904.667 125925.406 LA S GP2" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d29'59.999 29d19'59.994 3864995.756 307730.820 LA S GP3 out: "3864995.756\t307730.820 3864995.756 307730.820 LA S GP3" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d00'00.004 29d19'59.998 4024292.645 310627.715 LA S GP4 out: "4024292.645\t310627.715 4024292.645 310627.715 LA S GP4" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d10'23.487 29d20'32.615 3969054.663 312839.922 LA S GP5 out: "3969054.663\t312839.922 3969054.663 312839.922 LA S GP5" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d06'34.632 29d15'19.642 3989919.298 281618.678 LA S GP6 out: "3989919.298\t281618.678 3989919.298 281618.678 LA S GP6" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d01'33.803 29d07'47.918 4017480.813 236523.957 LA S GP7 out: "4017480.813\t236523.957 4017480.813 236523.957 LA S GP7" - args: +units=us-ft +init=nad83:1702 -f %.3f in: -89d08'45.781 28d58'27.979 3980254.597 179219.900 LA S GP9 out: "3980254.597\t179219.900 3980254.597 179219.900 LA S GP9" - args: +units=us-ft +init=nad83:2001 -f %.3f in: -70d27'00.716 41d40'15.808 942982.782 2706924.168 MA M GP1 out: "942982.782\t2706924.168 942982.782 2706924.168 MA M GP1" - args: +units=us-ft +init=nad83:2001 -f %.3f in: -73d25'59.173 42d06'06.860 131613.265 2868104.007 MA M GP2 out: "131613.265\t2868104.007 131613.265 2868104.007 MA M GP2" - args: +units=us-ft +init=nad83:1900 -f %.3f in: -76d11'27.492 39d12'06.132 1541600.105 560062.872 MD GP1 out: "1541600.105\t560062.872 1541600.105 560062.872 MD GP1" - args: +units=us-ft +init=nad83:1900 -f %.3f in: -77d02'30.406 38d26'37.492 1300367.185 283004.738 MD GP2 out: "1300367.185\t283004.738 1300367.185 283004.738 MD GP2" - args: +units=us-ft +init=nad83:1900 -f %.3f in: -77d30'10.460 38d59'25.903 1169392.711 482527.897 MD GP3 out: "1169392.711\t482527.897 1169392.711 482527.897 MD GP3" - args: +units=us-ft +init=nad83:1801 -f %.3f in: -68d24'25.489 46d32'46.920 1007629.154 1049880.999 ME E GP1 out: "1007629.154\t1049880.999 1007629.154 1049880.999 ME E GP1" - args: +units=us-ft +init=nad83:1801 -f %.3f in: -68d37'29.366 47d02'12.659 953127.598 1228762.971 ME E GP2 out: "953127.598\t1228762.971 953127.598 1228762.971 ME E GP2" - args: +units=us-ft +init=nad83:2113 -f %.3f in: -83d29'17.919 42d19'19.299 13360865.013 300809.378 MI S GP1 out: "13360865.013\t300809.378 13360865.013 300809.378 MI S GP1" - args: +units=us-ft +init=nad83:2113 -f %.3f in: -83d35'24.656 42d20'02.682 13333276.507 304929.978 MI S GP2 out: "13333276.507\t304929.978 13333276.507 304929.978 MI S GP2" - args: +units=us-ft +init=nad83:2113 -f %.3f in: -85d55'26.569 41d50'10.236 12698916.149 126441.631 MI S GP3 out: "12698916.149\t126441.631 12698916.149 126441.631 MI S GP3" - args: +units=us-ft +init=nad83:2113 -f %.3f in: -85d45'59.490 41d49'22.346 12741759.240 120840.463 MI S GP4 out: "12741759.240\t120840.463 12741759.240 120840.463 MI S GP4" - args: +units=us-ft +init=nad83:2201 -f %.3f in: -91d27'51.183 47d08'19.177 3031741.394 565338.600 MN N GP1 out: "3031741.394\t565338.600 3031741.394 565338.600 MN N GP1" - args: +units=us-ft +init=nad83:2201 -f %.3f in: -95d51'05.998 48d19'26.552 1955378.869 1005314.701 MN N GP2 out: "1955378.869\t1005314.701 1955378.869 1005314.701 MN N GP2" - args: +units=us-ft +init=nad83:2402 -f %.3f in: -92d30'00.000 38d15'00.000 1640416.667 879854.176 MO C GP1 out: "1640416.667\t879854.176 1640416.667 879854.176 MO C GP1" - args: +units=us-ft +init=nad83:2402 -f %.3f in: -92d30'00.000 38d15'30.000 1640416.667 882888.780 MO C GP2 out: "1640416.667\t882888.780 1640416.667 882888.780 MO C GP2" - args: +units=us-ft +init=nad83:2401 -f %.3f in: -91d42'04.297 37d22'05.932 471136.507 561031.592 MO E GP1 out: "471136.507\t561031.592 471136.507 561031.592 MO E GP1" - args: +units=us-ft +init=nad83:2401 -f %.3f in: -90d08'08.896 36d53'44.124 926703.606 386902.829 MO E GP2 out: "926703.606\t386902.829 926703.606 386902.829 MO E GP2" - args: +units=us-ft +init=nad83:2403 -f %.3f in: -94d30'00.000 38d15'00.000 2788708.333 758522.219 MO W GP1 out: "2788708.333\t758522.219 2788708.333 758522.219 MO W GP1" - args: +units=us-ft +init=nad83:2403 -f %.3f in: -94d30'00.000 38d15'30.000 2788708.333 761556.846 MO W GP2 out: "2788708.333\t761556.846 2788708.333 761556.846 MO W GP2" - args: +units=us-ft +init=nad83:2301 -f %.3f in: -89d10'14.013 30d30'51.338 878059.046 369015.468 MS E GP1 out: "878059.046\t369015.468 878059.046 369015.468 MS E GP1" - args: +units=us-ft +init=nad83:2301 -f %.3f in: -88d26'04.338 30d43'01.454 1109567.483 442842.466 MS E GP2 out: "1109567.483\t442842.466 1109567.483 442842.466 MS E GP2" - args: +units=us-ft +init=nad83:2500 -f %.3f in: -106d29'11.521 47d52'21.103 2707564.623 1334850.273 MT N GP1 out: "2707564.623\t1334850.273 2707564.623 1334850.273 MT N GP1" - args: +units=us-ft +init=nad83:2500 -f %.3f in: -114d30'43.122 48d52'46.764 763315.457 1726511.247 MT N GP2 out: "763315.457\t1726511.247 763315.457 1726511.247 MT N GP2" - args: +units=us-ft +init=nad83:3200 -f %.3f in: -81d12'31.790 35d09'31.049 1339869.379 520003.003 NC GP1 out: "1339869.379\t520003.003 1339869.379 520003.003 NC GP1" - args: +units=us-ft +init=nad83:3200 -f %.3f in: -76d31'54.918 35d33'51.452 2733923.842 669426.932 NC GP2 out: "2733923.842\t669426.932 2733923.842 669426.932 NC GP2" - args: +units=us-ft +init=nad83:3200 -f %.3f in: -78d28'26.580 36d15'15.480 2155084.559 911885.081 NC GP3 out: "2155084.559\t911885.081 2155084.559 911885.081 NC GP3" - args: +units=us-ft +init=nad83:3301 -f %.3f in: -98d46'03.232 48d08'13.483 2391470.474 419526.909 ND N GP1 out: "2391470.474\t419526.909 2391470.474 419526.909 ND N GP1" - args: +units=us-ft +init=nad83:3301 -f %.3f in: -101d18'21.456 47d39'18.935 1769873.906 240054.790 ND N GP2 out: "1769873.906\t240054.790 1769873.906 240054.790 ND N GP2" - args: +units=us-ft +init=nad83:2600 -f %.3f in: -96d17'52.930 42d04'48.305 2644820.409 839912.565 NE N GP1 out: "2644820.409\t839912.565 2644820.409 839912.565 NE N GP1" - args: +units=us-ft +init=nad83:2600 -f %.3f in: -100d49'26.949 41d58'54.025 1416403.828 783622.046 NE N GP2 out: "1416403.828\t783622.046 1416403.828 783622.046 NE N GP2" - args: +units=us-ft +init=nad83:2800 -f %.3f in: -70d56'11.287 43d08'15.006 1179151.981 233188.620 NH GP1 out: "1179151.981\t233188.619 1179151.981 233188.620 NH GP1" - args: +units=us-ft +init=nad83:2800 -f %.3f in: -72d32'32.197 42d51'25.984 749470.166 131406.173 NH GP2 out: "749470.166\t131406.173 749470.166 131406.173 NH GP2" - args: +units=us-ft +init=nad83:2900 -f %.3f in: -74d13'55.737 39d52'02.095 567304.543 376673.733 NJ GP1 out: "567304.543\t376673.733 567304.543 376673.733 NJ GP1" - args: +units=us-ft +init=nad83:2900 -f %.3f in: -74d51'24.058 41d12'07.401 393979.614 863010.549 NJ GP2 out: "393979.614\t863010.549 393979.614 863010.549 NJ GP2" - args: +units=us-ft +init=nad83:3002 -f %.3f in: -106d15'00.000 33d30'00.000 1640416.667 909448.493 NM C GP1 out: "1640416.667\t909448.493 1640416.667 909448.493 NM C GP1" - args: +units=us-ft +init=nad83:3002 -f %.3f in: -106d15'00.000 33d30'30.000 1640416.667 912480.595 NM C GP2 out: "1640416.667\t912480.595 1640416.667 912480.595 NM C GP2" - args: +units=us-ft +init=nad83:3001 -f %.3f in: -104d11'42.410 33d17'21.732 583573.491 832847.194 NM E GP1 out: "583573.491\t832847.194 583573.491 832847.194 NM E GP1" - args: +units=us-ft +init=nad83:3001 -f %.3f in: -104d47'37.948 33d22'32.349 400747.149 864523.566 NM E GP2 out: "400747.149\t864523.566 400747.149 864523.566 NM E GP2" - args: +units=us-ft +init=nad83:3003 -f %.3f in: -107d50'00.000 32d30'00.000 2723091.667 545634.896 NM W GP1 out: "2723091.667\t545634.896 2723091.667 545634.896 NM W GP1" - args: +units=us-ft +init=nad83:3003 -f %.3f in: -107d50'00.000 32d30'30.000 2723091.667 548666.562 NM W GP2 out: "2723091.667\t548666.562 2723091.667 548666.562 NM W GP2" - args: +units=us-ft +init=nad83:2701 -f %.3f in: -114d49'09.337 35d43'09.299 882966.545 26600313.129 NV E GP1 out: "882966.545\t26600313.129 882966.545 26600313.129 NV E GP1" - args: +units=us-ft +init=nad83:2701 -f %.3f in: -116d50'32.766 41d30'37.869 311338.993 28710910.565 NV E GP2 out: "311338.993\t28710910.564 311338.993 28710910.565 NV E GP2" - args: +units=us-ft +init=nad83:3101 -f %.3f in: -74d02'53.671 42d17'01.775 614362.369 1257287.611 NY E GP1 out: "614362.369\t1257287.611 614362.369 1257287.611 NY E GP1" - args: +units=us-ft +init=nad83:3101 -f %.3f in: -74d44'39.818 42d30'07.382 426225.275 1336579.561 NY E GP2 out: "426225.275\t1336579.561 426225.275 1336579.561 NY E GP2" - args: +units=us-ft +init=nad83:3104 -f %.3f in: -73d02'36.247 40d47'50.624 1249103.533 231235.845 NY L GP1 out: "1249103.533\t231235.845 1249103.533 231235.845 NY L GP1" - args: +units=us-ft +init=nad83:3104 -f %.3f in: -74d06'58.125 40d36'07.281 951997.667 158630.811 NY L GP2 out: "951997.667\t158630.811 951997.667 158630.811 NY L GP2" - args: +units=us-ft +init=nad83:3104 -f %.3f in: -74d00'00.000 40d45'00.000 984250.000 212521.887 NY L GP3 out: "984250.000\t212521.887 984250.000 212521.887 NY L GP3" - args: +units=us-ft +init=nad83:3104 -f %.3f in: -73d15'00.000 40d37'30.000 1192442.028 167871.999 NY L GP4 out: "1192442.028\t167871.999 1192442.028 167871.999 NY L GP4" - args: +units=us-ft +init=nad83:3104 -f %.3f in: -73d22'30.000 40d45'00.000 1157419.074 213139.664 NY L GP5 out: "1157419.074\t213139.664 1157419.074 213139.664 NY L GP5" - args: +units=us-ft +init=nad83:3401 -f %.3f in: -80d49'28.238 40d17'50.894 2435851.621 234309.717 OH N GP1 out: "2435851.621\t234309.717 2435851.621 234309.717 OH N GP1" - args: +units=us-ft +init=nad83:3401 -f %.3f in: -82d37'31.021 40d20'14.678 1933572.857 244396.244 OH N GP2 out: "1933572.857\t244396.244 1933572.857 244396.244 OH N GP2" - args: +units=us-ft +init=nad83:3501 -f %.3f in: -98d42'45.414 36d50'19.568 1759953.675 670136.468 OK N GP1 out: "1759953.675\t670136.468 1759953.675 670136.468 OK N GP1" - args: +units=us-ft +init=nad83:3501 -f %.3f in: -95d38'44.046 35d20'36.925 2670659.833 133589.112 OK N GP2 out: "2670659.833\t133589.112 2670659.833 133589.112 OK N GP2" - args: +units=us-ft +init=nad83:3602 -f %.3f in: -119d46'26.562 44d24'25.943 5110990.827 999684.042 OR S GP1 out: "5110990.827\t999684.042 5110990.827 999684.042 OR S GP1" - args: +units=us-ft +init=nad83:3602 -f %.3f in: -121d09'56.105 44d23'08.924 4747225.642 991752.635 OR S GP2 out: "4747225.642\t991752.635 4747225.642 991752.635 OR S GP2" - args: +units=us-ft +init=nad83:3701 -f %.3f in: -74d33'20.644 41d23'48.566 2844678.533 464365.610 PA N GP1 out: "2844678.533\t464365.610 2844678.533 464365.610 PA N GP1" - args: +units=us-ft +init=nad83:3701 -f %.3f in: -78d09'48.121 40d51'35.455 1854155.505 252833.700 PA N GP2 out: "1854155.505\t252833.700 1854155.505 252833.700 PA N GP2" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -67d08'56.930 18d29'56.972 408161.046 898432.808 PR F GP1 out: "408161.046\t898432.808 408161.046 898432.808 PR F GP1" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d52'30.000 18d15'00.000 502925.440 807654.009 PR F GP2 out: "502925.440\t807654.009 502925.440 807654.009 PR F GP2" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d26'00.000 18d15'00.000 656166.667 807469.207 PR F GP3 out: "656166.667\t807469.207 656166.667 807469.207 PR F GP3" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d26'00.000 18d30'00.000 656166.667 898253.524 PR F GP4 out: "656166.667\t898253.524 656166.667 898253.524 PR F GP4" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -67d08'56.930 18d29'56.972 408161.046 898432.808 PR M GP1 out: "408161.046\t898432.808 408161.046 898432.808 PR M GP1" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d52'30.000 18d15'00.000 502925.440 807654.009 PR M GP2 out: "502925.440\t807654.009 502925.440 807654.009 PR M GP2" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d26'00.000 18d15'00.000 656166.667 807469.207 PR M GP3 out: "656166.667\t807469.207 656166.667 807469.207 PR M GP3" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d26'00.000 18d30'00.000 656166.667 898253.524 PR M GP4 out: "656166.667\t898253.524 656166.667 898253.524 PR M GP4" - args: +units=us-ft +init=nad83:3800 -f %.3f in: -71d16'00.833 41d32'24.848 391898.667 166566.121 RI GP1 out: "391898.667\t166566.121 391898.667 166566.121 RI GP1" - args: +units=us-ft +init=nad83:3800 -f %.3f in: -71d37'13.730 41d23'53.266 295027.785 114722.837 RI GP2 out: "295027.785\t114722.837 295027.785 114722.837 RI GP2" - args: +units=us-ft +init=nad83:3900 -f %.3f in: -80d32'30.000 34d32'30.000 2138015.480 985710.127 SC N GP1 out: "2138015.480\t985710.127 2138015.480 985710.127 SC N GP1" - args: +units=us-ft +init=nad83:3900 -f %.3f in: -81d00'00.000 34d32'30.000 1999996.000 985404.077 SC N GP2 out: "1999996.000\t985404.077 1999996.000 985404.077 SC N GP2" - args: +units=us-ft +init=nad83:3900 -f %.3f in: -80d32'30.000 33d32'30.000 2139629.138 621856.156 SC S GP1 out: "2139629.138\t621856.156 2139629.138 621856.156 SC S GP1" - args: +units=us-ft +init=nad83:3900 -f %.3f in: -81d00'00.000 33d32'30.000 1999996.000 621546.527 SC S GP2 out: "1999996.000\t621546.527 1999996.000 621546.527 SC S GP2" - args: +units=us-ft +init=nad83:4001 -f %.3f in: -99d12'21.983 44d06'08.121 2177060.848 99066.761 SD N GP1 out: "2177060.848\t99066.761 2177060.848 99066.761 SD N GP1" - args: +units=us-ft +init=nad83:4001 -f %.3f in: -100d32'28.873 44d32'34.917 1827356.330 259209.712 SD N GP2 out: "1827356.330\t259209.712 1827356.330 259209.712 SD N GP2" - args: +units=us-ft +init=nad83:4100 -f %.3f in: -85d13'55.967 36d21'48.503 2194569.476 739881.374 TN GP1 out: "2194569.476\t739881.374 2194569.476 739881.374 TN GP1" - args: +units=us-ft +init=nad83:4100 -f %.3f in: -88d43'05.849 36d30'08.410 1169616.875 800645.091 TN GP2 out: "1169616.875\t800645.091 1169616.875 800645.091 TN GP2" - args: +units=us-ft +init=nad83:4201 -f %.3f in: -100d33'06.303 34d39'35.684 941333.504 3522390.511 TX N GP1 out: "941333.504\t3522390.511 941333.504 3522390.511 TX N GP1" - args: +units=us-ft +init=nad83:4201 -f %.3f in: -102d48'50.949 34d43'39.249 261294.654 3548271.494 TX N GP2 out: "261294.654\t3548271.494 261294.654 3548271.494 TX N GP2" - args: +units=us-ft +init=nad83:4302 -f %.3f in: -111d30'00.000 38d40'00.000 1640416.667 6683084.515 UT C GP1 out: "1640416.667\t6683084.515 1640416.667 6683084.515 UT C GP1" - args: +units=us-ft +init=nad83:4302 -f %.3f in: -111d30'00.000 38d40'30.000 1640416.667 6686119.851 UT C GP2 out: "1640416.667\t6686119.851 1640416.667 6686119.851 UT C GP2" - args: +units=us-ft +init=nad83:4301 -f %.3f in: -111d30'00.000 41d30'00.000 1640416.667 3705897.565 UT N GP1 out: "1640416.667\t3705897.565 1640416.667 3705897.565 UT N GP1" - args: +units=us-ft +init=nad83:4301 -f %.3f in: -111d30'00.000 41d30'30.000 1640416.667 3708933.975 UT N GP2 out: "1640416.667\t3708933.975 1640416.667 3708933.975 UT N GP2" - args: +units=us-ft +init=nad83:4303 -f %.3f in: -109d48'37.967 38d29'30.877 2123972.902 10511502.846 UT S GP1 out: "2123972.902\t10511502.846 2123972.902 10511502.846 UT S GP1" - args: +units=us-ft +init=nad83:4303 -f %.3f in: -113d52'56.922 37d09'18.788 946139.893 10029235.592 UT S GP2 out: "946139.893\t10029235.592 946139.893 10029235.592 UT S GP2" - args: +units=us-ft +init=nad83:4501 -f %.3f in: -77d13'46.945 38d55'12.407 11844323.043 7020638.975 VA N GP1 out: "11844323.043\t7020638.975 11844323.043 7020638.975 VA N GP1" - args: +units=us-ft +init=nad83:4501 -f %.3f in: -79d18'51.557 38d09'59.020 11248797.976 6744688.474 VA N GP2 out: "11248797.976\t6744688.474 11248797.976 6744688.474 VA N GP2" - args: +units=us-ft +init=nad83:4501 -f %.3f in: -77d38'10.823 37d49'23.964 11732395.294 6619889.590 VA N GP3 out: "11732395.294\t6619889.590 11732395.294 6619889.590 VA N GP3" - args: +units=us-ft +init=nad83:4501 -f %.3f in: -79d26'19.475 37d47'25.852 11211628.032 6608155.232 VA N GP4 out: "11211628.032\t6608155.232 11211628.032 6608155.232 VA N GP4" - args: +units=us-ft +init=nad83:4501 -f %.3f in: -77d44'30.336 39d00'06.804 11698399.159 7048812.266 VA N GP6 out: "11698399.159\t7048812.266 11698399.159 7048812.266 VA N GP6" - args: +units=us-ft +init=nad83:4501 -f %.3f in: -77d43'47.013 38d59'55.454 11701828.676 7047692.496 VA N GP9 out: "11701828.676\t7047692.496 11701828.676 7047692.496 VA N GP9" - args: +units=us-ft +init=nad83:4502 -f %.3f in: -78d30'00.000 37d30'00.000 11482916.667 3705606.876 VA S GP1 out: "11482916.667\t3705606.876 11482916.667 3705606.876 VA S GP1" - args: +units=us-ft +init=nad83:4502 -f %.3f in: -78d30'00.000 37d30'30.000 11482916.667 3708641.137 VA S GP2 out: "11482916.667\t3708641.137 11482916.667 3708641.137 VA S GP2" - args: +units=us-ft +init=nad83:4502 -f %.3f in: -77d32'33.000 36d54'42.507 11762849.074 3492868.579 VA S GP3 out: "11762849.074\t3492868.579 11762849.074 3492868.579 VA S GP3" - args: +units=us-ft +init=nad83:4502 -f %.3f in: -77d21'55.732 38d04'53.901 11809480.679 3919367.025 VA S GP4 out: "11809480.679\t3919367.025 11809480.679 3919367.025 VA S GP4" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -64d45'30.000 17d45'30.000 1238952.313 631597.723 VI F GP1 out: "1238952.313\t631597.723 1238952.313 631597.723 VI F GP1" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d26'00.000 17d45'56.426 656166.667 631597.858 VI F GP2 out: "656166.667\t631597.858 656166.667 631597.858 VI F GP2" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -64d45'30.000 17d45'30.000 1238952.313 631597.723 VI M GP1 out: "1238952.313\t631597.723 1238952.313 631597.723 VI M GP1" - args: +units=us-ft +init=nad83:5200 -f %.3f in: -66d26'00.000 17d45'56.426 656166.667 631597.858 VI M GP2 out: "656166.667\t631597.858 656166.667 631597.858 VI M GP2" - args: +units=us-ft +init=nad83:4400 -f %.3f in: -72d29'31.418 43d09'58.526 1642534.834 242819.594 VT GP1 out: "1642534.834\t242819.594 1642534.834 242819.594 VT GP1" - args: +units=us-ft +init=nad83:4400 -f %.3f in: -73d12'06.978 44d22'22.810 1456873.971 683480.189 VT GP2 out: "1456873.971\t683480.189 1456873.971 683480.189 VT GP2" - args: +units=us-ft +init=nad83:4601 -f %.3f in: -119d51'37.006 47d50'51.069 1879336.368 310659.110 WA N GP1 out: "1879336.368\t310659.110 1879336.368 310659.110 WA N GP1" - args: +units=us-ft +init=nad83:4601 -f %.3f in: -123d59'49.087 48d09'29.131 868484.545 438307.526 WA N GP2 out: "868484.545\t438307.526 868484.545 438307.526 WA N GP2" - args: +units=us-ft +init=nad83:4801 -f %.3f in: -88d44'40.778 45d22'21.598 2291123.165 77666.637 WI N GP1 out: "2291123.165\t77666.637 2291123.165 77666.637 WI N GP1" - args: +units=us-ft +init=nad83:4801 -f %.3f in: -92d12'19.275 45d48'35.812 1406198.343 242375.264 WI N GP2 out: "1406198.343\t242375.264 1406198.343 242375.264 WI N GP2" - args: +units=us-ft +init=nad83:4701 -f %.3f in: -77d53'39.269 39d14'39.339 2423253.113 275144.536 WV N GP1 out: "2423253.113\t275144.536 2423253.113 275144.536 WV N GP1" - args: +units=us-ft +init=nad83:4701 -f %.3f in: -81d33'23.549 39d18'08.535 1386588.889 298906.239 WV N GP2 out: "1386588.889\t298906.239 1386588.889 298906.239 WV N GP2" - args: +units=us-ft +init=nad83:4701 -f %.3f in: -77d30'10.460 38d59'25.903 2536117.742 184974.384 WV N GP3 out: "2536117.742\t184974.384 2536117.742 184974.384 WV N GP3" - args: +units=us-ft +init=nad83:4901 -f %.3f in: -105d31'02.882 43d30'40.600 563107.342 1097477.489 WY E GP1 out: "563107.342\t1097477.489 563107.342 1097477.489 WY E GP1" - args: +units=us-ft +init=nad83:4901 -f %.3f in: -105d22'42.856 43d30'14.685 599946.619 1094729.119 WY E GP2 out: "599946.619\t1094729.118 599946.619 1094729.119 WY E GP2" - args: +units=us-ft +init=nad83:4901 -f %.3f in: -105d28'42.827 43d36'33.391 573561.723 1133155.514 WY E GP3 out: "573561.723\t1133155.513 573561.723 1133155.514 WY E GP3" - args: +units=us-ft +init=nad83:4901 -f %.3f in: -105d23'43.223 42d00'59.422 594028.794 552611.396 WY E GP4 out: "594028.794\t552611.396 594028.794 552611.396 WY E GP4" - args: +units=us-ft +init=nad83:4901 -f %.3f in: -104d35'06.686 42d34'50.366 812768.620 758647.940 WY E GP5 out: "812768.620\t758647.940 812768.620 758647.940 WY E GP5" - args: +units=us-ft +init=nad83:4902 -f %.3f in: -106d13'03.224 41d36'14.640 1617477.770 732300.770 WYEC GP1 out: "1617477.770\t732300.770 1617477.770 732300.770 WYEC GP1" - args: +units=us-ft +init=nad83:4902 -f %.3f in: -108d01'56.720 41d51'57.518 1121920.469 826536.345 WYEC GP2 out: "1121920.469\t826536.345 1121920.469 826536.345 WYEC GP2" proj-9.8.1/test/cli/test_cs2cs_flaky.yaml000664 001750 001750 00000002071 15166171715 020327 0ustar00eveneven000000 000000 comment: > Do some testing of flaky transformation that do not depend on datum files. exe: cs2cs tests: - comment: Test healpix inverse projection on sphere args: > +proj=latlong +a=1 +lon_0=0 +to +proj=healpix +a=1 +lon_0=0 -f %.5f -I in: | 0 0.7853981633974483 -1.5707963267948966 0 out: |- 0.00000 41.81031 0.00000 -90.00000 0.00000 0.00000 - args: > +proj=latlong +a=5 +to +proj=healpix +a=5 -f %.5f -I in: | 0.0 0.0 0.0 3.9269908169872414 0.0 -3.9269908169872414 7.853981633974483 0.0 -7.853981633974483 0.0 -15.707963267948966 0.0 -11.780972450961723 7.853981633974483 -11.780972450961723 -7.853981633974483 1.437378399445537 5.364369216432778 1.437378399445537 -5.364369216432778 out: |- 0.00000 0.00000 0.00000 0.00000 41.81031 0.00000 0.00000 -41.81031 0.00000 90.00000 0.00000 0.00000 -90.00000 0.00000 0.00000 -180.00000 0.00000 0.00000 -180.00000 90.00000 0.00000 -180.00000 -90.00000 0.00000 0.00000 60.00000 0.00000 0.00000 -60.00000 0.00000 proj-9.8.1/test/cli/CMakeLists.txt000664 001750 001750 00000010462 15166171715 016744 0ustar00eveneven000000 000000 # # test # if(BUILD_CCT) set(CCT_EXE "$") endif() if(BUILD_CS2CS) set(CS2CS_EXE "$") endif() if(BUILD_PROJ) set(PROJ_EXE "$") if(UNIX) # invproj is a symlink # set(INVPROJ_EXE "$") # TODO: find working genexpr set(INVPROJ_EXE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/invproj${CMAKE_EXECUTABLE_SUFFIX}") else() set(INVPROJ_EXE "$") endif() endif() if(BUILD_PROJINFO) set(PROJINFO_EXE "$") endif() if(BUILD_PROJSYNC) set(PROJSYNC_EXE "$") endif() if(BUILD_GIE) set(GIE_EXE "$") endif() if(UNIX) if(BUILD_CS2CS) proj_add_test_script_sh(test_cs2cs_locale.sh CS2CS_EXE) endif() if(BUILD_PROJINFO) proj_add_test_script_sh(test_projinfo.sh PROJINFO_EXE) endif() if(BUILD_PROJSYNC) proj_add_test_script_sh(test_projsync.sh PROJSYNC_EXE) endif() endif() macro(find_Python_package PACKAGE VARIABLE) if(NOT DEFINED "${VARIABLE}") if(NOT CMAKE_REQUIRED_QUIET) # CMake 3.17+ use CHECK_START/CHECK_PASS/CHECK_FAIL message(STATUS "Looking for ${PACKAGE}") endif() if(Python_VERSION VERSION_GREATER_EQUAL "3.8.0") # importlib.metadata was added in version 3.8 set(CMD "from importlib.metadata import version; print(version('${PACKAGE}'), end='')") execute_process( COMMAND ${Python_EXECUTABLE} -c "${CMD}" RESULT_VARIABLE EXIT_CODE OUTPUT_VARIABLE ${PACKAGE}_VERSION ERROR_QUIET ) else() # importlib_metadata backport needed for older Python versions execute_process( COMMAND ${Python_EXECUTABLE} -c "import importlib_metadata" RESULT_VARIABLE EXIT_CODE OUTPUT_QUIET ERROR_QUIET ) if(EXIT_CODE EQUAL 0) set(CMD "from importlib_metadata import version; print(version('${PACKAGE}'), end='')") execute_process( COMMAND ${Python_EXECUTABLE} -c "${CMD}" RESULT_VARIABLE EXIT_CODE OUTPUT_VARIABLE ${PACKAGE}_VERSION ERROR_QUIET ) else() message(WARNING "importlib_metadata backport package required for Python <3.8") endif() endif() if(EXIT_CODE EQUAL 0) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Looking for ${PACKAGE} - found version \"${${PACKAGE}_VERSION}\"") endif() set(${VARIABLE} 1 CACHE INTERNAL "Have package ${PACKAGE}") else() if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Looking for ${PACKAGE} - not found") endif() set(${VARIABLE} "" CACHE INTERNAL "Have package ${PACKAGE}") endif() endif() endmacro() # Evaluate if Python can be used for CLI testing find_package(Python COMPONENTS Interpreter) set(Python_for_cli_tests OFF) if(Python_FOUND) if(Python_VERSION VERSION_GREATER_EQUAL "3.7.0") find_Python_package("ruamel.yaml" HAS_RUAMEL_YAML) if(HAS_RUAMEL_YAML) set(Python_for_cli_tests ON) else() find_Python_package("pyyaml" HAS_PYYAML) if(HAS_PYYAML) set(Python_for_cli_tests ON) endif() endif() else() message(WARNING "Python Interpreter version is too old") endif() endif() if(Python_for_cli_tests) if(BUILD_CCT) proj_run_cli_test(test_cct.yaml CCT_EXE) endif() if(BUILD_CS2CS) proj_run_cli_test(test_cs2cs_datumfile.yaml CS2CS_EXE) proj_run_cli_test(test_cs2cs_flaky.yaml CS2CS_EXE) proj_run_cli_test(test_cs2cs_ignf.yaml CS2CS_EXE) proj_run_cli_test(test_cs2cs_ntv2.yaml CS2CS_EXE) proj_run_cli_test(test_cs2cs_various.yaml CS2CS_EXE) endif() if(BUILD_PROJ) proj_run_cli_test(test_proj.yaml PROJ_EXE) proj_run_cli_test(test_proj_nad27.yaml PROJ_EXE) proj_run_cli_test(test_proj_nad83.yaml PROJ_EXE) proj_run_cli_test(test_invproj.yaml INVPROJ_EXE) endif() if(BUILD_PROJINFO) proj_run_cli_test(test_projinfo.yaml PROJINFO_EXE) endif() if(BUILD_GIE) proj_run_cli_test(test_gie.yaml GIE_EXE) endif() # auto-test run_cli_test.py if pytest available find_Python_package("pytest" HAS_PYTEST) if(HAS_PYTEST) add_test(NAME pytest_run_cli_test COMMAND ${Python_EXECUTABLE} -m pytest -vv ${PROJ_SOURCE_DIR}/test/cli/run_cli_test.py ) endif() else() message(WARNING "Python/YAML command-line interface tests not run") endif() proj-9.8.1/test/cli/test_cs2cs_locale_out.dist000664 001750 001750 00000003134 15166171715 021351 0ustar00eveneven000000 000000 ############################################################## Between two 3parameter approximations on same ellipsoid 0d00'00.000"W 0d00'00.000"N 0.0 0dE 0dN 0.000 79d00'00.000"W 45d00'00.000"N 0.0 78d59'59.821"W 44d59'59.983"N 0.000 ############################################################## Test input in grad 64.44444444 2.9586342556 760724.02 3457334.86 0.00 ############################################################## Test geocentric x/y/z generation. 0d00'00.001"W 0d00'00.001"N 0.0 6378137.00 -0.03 0.03 0d00'00.001"W 0d00'00.001"N 10.0 6378147.00 -0.03 0.03 79d00'00.000"W 45d00'00.000"N 0.0 861996.98 -4434590.01 4487348.41 45d00'00.000"W 89d59'59.990"N 0.0 0.22 -0.22 6356752.31 ############################################################## Test geocentric x/y/z consumption. 6378137.00 -0.00 0.00 0dE 0dN 0.000 6378147.00 -0.00 0.00 0dE 0dN 10.000 861996.98 -4434590.01 4487348.41 79dW 45dN 0.001 0.00 -0.00 6356752.31 0dE 90dN -0.004 ############################################################# Test conversion from geodetic latlong to geocentric latlong 0d00'00.000"W 0d00'00.000"N 0.0 0dE 0dN 0.000 79d00'00.000"W 45d00'00.000"N 0.0 79dW 44d48'27.276"N 0.000 12d00'00.000"W 45d00'00.000"N 0.0 12dW 44d48'27.276"N 0.000 0d00'00.000"W 90d00'00.000"N 0.0 0dE 90dN 0.000 ############################################################# Test conversion from geocentric latlong to geodetic latlong 0d00'00.000"W 0d00'00.000"N 0.0 0dE 0dN 0.000 79d00'00.000"W 44d48'27.276"N 0.000 79dW 45dN 0.000 12d00'00.000"W 44d48'27.276"N 0.0 12dW 45dN 0.000 0d00'00.000"W 90d00'00.000"N 0.0 0dE 90dN 0.000 proj-9.8.1/test/cli/test_gie.yaml000664 001750 001750 00000001223 15166171715 016666 0ustar00eveneven000000 000000 exe: gie tests: - comment: Test invalid argument args: --invalid stderr: "Invalid option \"invalid\"" exitcode: 1 - comment: Test version argument args: --version sub: ["gie(_d)?(\\.exe)?: Rel.*", "gie: Rel"] stdout: "gie: Rel" exitcode: 0 - comment: Test help argument args: -h grep: "Usage: " sub: ["gie(_d)?(\\.exe)?", "gie"] stdout: "Usage: gie [-options]... infile..." exitcode: 0 - comment: Test gie with non existing input file args: i_do_not_exist.txt stderr: "-------------------------------------------------------------------------------\nCannot open specified input file 'i_do_not_exist.txt' - bye!\n" exitcode: 1 proj-9.8.1/test/cli/test_projinfo.yaml000664 001750 001750 00000274562 15166171715 017772 0ustar00eveneven000000 000000 comment: > Test basic capabilities of the projinfo command. See test_projinfo.sh for other advanced testing. exe: projinfo env: PROJINFO_NO_GRID_CHECK: YES tests: - args: EPSG:4326 out: | PROJ.4 string: +proj=longlat +datum=WGS84 +no_defs +type=crs WKT2:2019 string: GEOGCRS["WGS 84", ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)"], MEMBER["World Geodetic System 1984 (G730)"], MEMBER["World Geodetic System 1984 (G873)"], MEMBER["World Geodetic System 1984 (G1150)"], MEMBER["World Geodetic System 1984 (G1674)"], MEMBER["World Geodetic System 1984 (G1762)"], MEMBER["World Geodetic System 1984 (G2139)"], MEMBER["World Geodetic System 1984 (G2296)"], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ENSEMBLEACCURACY[2.0]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], USAGE[ SCOPE["Horizontal component of 3D system."], AREA["World."], BBOX[-90,-180,90,180]], ID["EPSG",4326]] - args: -o WKT1_GDAL EPSG:4326 out: |+ WKT1:GDAL string: GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.0174532925199433, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4326"]] - args: -o WKT2_2015 EPSG:4326 out: | WKT2:2015 string: GEODCRS["WGS 84", DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], SCOPE["Horizontal component of 3D system."], AREA["World."], BBOX[-90,-180,90,180], ID["EPSG",4326]] - args: -o WKT2_2019 EPSG:4326 out: | WKT2:2019 string: GEOGCRS["WGS 84", ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)"], MEMBER["World Geodetic System 1984 (G730)"], MEMBER["World Geodetic System 1984 (G873)"], MEMBER["World Geodetic System 1984 (G1150)"], MEMBER["World Geodetic System 1984 (G1674)"], MEMBER["World Geodetic System 1984 (G1762)"], MEMBER["World Geodetic System 1984 (G2139)"], MEMBER["World Geodetic System 1984 (G2296)"], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ENSEMBLEACCURACY[2.0]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], USAGE[ SCOPE["Horizontal component of 3D system."], AREA["World."], BBOX[-90,-180,90,180]], ID["EPSG",4326]] - args: -o ALL EPSG:4326 stdout: | PROJ.4 string: +proj=longlat +datum=WGS84 +no_defs +type=crs WKT2:2015 string: GEODCRS["WGS 84", DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], SCOPE["Horizontal component of 3D system."], AREA["World."], BBOX[-90,-180,90,180], ID["EPSG",4326]] WKT2:2019 string: GEOGCRS["WGS 84", ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)"], MEMBER["World Geodetic System 1984 (G730)"], MEMBER["World Geodetic System 1984 (G873)"], MEMBER["World Geodetic System 1984 (G1150)"], MEMBER["World Geodetic System 1984 (G1674)"], MEMBER["World Geodetic System 1984 (G1762)"], MEMBER["World Geodetic System 1984 (G2139)"], MEMBER["World Geodetic System 1984 (G2296)"], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ENSEMBLEACCURACY[2.0]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], USAGE[ SCOPE["Horizontal component of 3D system."], AREA["World."], BBOX[-90,-180,90,180]], ID["EPSG",4326]] WKT1:GDAL string: GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.0174532925199433, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4326"]] WKT1:ESRI string: GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] PROJJSON: { "$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", "type": "GeographicCRS", "name": "WGS 84", "datum_ensemble": { "name": "World Geodetic System 1984 ensemble", "members": [ { "name": "World Geodetic System 1984 (Transit)", "id": { "authority": "EPSG", "code": 1166 } }, { "name": "World Geodetic System 1984 (G730)", "id": { "authority": "EPSG", "code": 1152 } }, { "name": "World Geodetic System 1984 (G873)", "id": { "authority": "EPSG", "code": 1153 } }, { "name": "World Geodetic System 1984 (G1150)", "id": { "authority": "EPSG", "code": 1154 } }, { "name": "World Geodetic System 1984 (G1674)", "id": { "authority": "EPSG", "code": 1155 } }, { "name": "World Geodetic System 1984 (G1762)", "id": { "authority": "EPSG", "code": 1156 } }, { "name": "World Geodetic System 1984 (G2139)", "id": { "authority": "EPSG", "code": 1309 } }, { "name": "World Geodetic System 1984 (G2296)", "id": { "authority": "EPSG", "code": 1383 } } ], "ellipsoid": { "name": "WGS 84", "semi_major_axis": 6378137, "inverse_flattening": 298.257223563 }, "accuracy": "2.0", "id": { "authority": "EPSG", "code": 6326 } }, "coordinate_system": { "subtype": "ellipsoidal", "axis": [ { "name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree" }, { "name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree" } ] }, "scope": "Horizontal component of 3D system.", "area": "World.", "bbox": { "south_latitude": -90, "west_longitude": -180, "north_latitude": 90, "east_longitude": 180 }, "id": { "authority": "EPSG", "code": 4326 } } - args: > "+proj=merc +lat_ts=5 +datum=WGS84 +type=crs" --output-id HOBU:MY_CRS -o SQL -q out: | INSERT INTO geodetic_crs VALUES('HOBU','GEODETIC_CRS_MY_CRS','unknown','','geographic 2D','EPSG','6424','EPSG','6326',NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_GEODETIC_CRS_MY_CRS','geodetic_crs','HOBU','GEODETIC_CRS_MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); INSERT INTO conversion VALUES('HOBU','CONVERSION_MY_CRS','unknown','','EPSG','9805','Mercator (variant B)','EPSG','8823','Latitude of 1st standard parallel',5,'EPSG','9122','EPSG','8802','Longitude of natural origin',0,'EPSG','9122','EPSG','8806','False easting',0,'EPSG','9001','EPSG','8807','False northing',0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_CONVERSION_MY_CRS','conversion','HOBU','CONVERSION_MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); INSERT INTO projected_crs VALUES('HOBU','MY_CRS','unknown','','EPSG','4400','HOBU','GEODETIC_CRS_MY_CRS','HOBU','CONVERSION_MY_CRS',NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_PROJECTED_CRS_MY_CRS','projected_crs','HOBU','MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); - args: > "+proj=merc +lat_ts=5 +datum=WGS84 +type=crs" --output-id HOBU:MY_CRS --authority HOBU -o SQL -q out: | INSERT INTO ellipsoid VALUES('HOBU','ELLPS_GEODETIC_DATUM_GEODETIC_CRS_MY_CRS','WGS 84','','IAU_2015','399',6378137,'EPSG','9001',298.257223563,NULL,0); INSERT INTO prime_meridian VALUES('HOBU','PM_GEODETIC_DATUM_GEODETIC_CRS_MY_CRS','Greenwich',0,'EPSG','9122',0); INSERT INTO geodetic_datum VALUES('HOBU','GEODETIC_DATUM_GEODETIC_CRS_MY_CRS','World Geodetic System 1984','','HOBU','ELLPS_GEODETIC_DATUM_GEODETIC_CRS_MY_CRS','HOBU','PM_GEODETIC_DATUM_GEODETIC_CRS_MY_CRS',NULL,NULL,NULL,NULL,NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_GEODETIC_DATUM_GEODETIC_CRS_MY_CRS','geodetic_datum','HOBU','GEODETIC_DATUM_GEODETIC_CRS_MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); INSERT INTO geodetic_crs VALUES('HOBU','GEODETIC_CRS_MY_CRS','unknown','','geographic 2D','EPSG','6424','HOBU','GEODETIC_DATUM_GEODETIC_CRS_MY_CRS',NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_GEODETIC_CRS_MY_CRS','geodetic_crs','HOBU','GEODETIC_CRS_MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); INSERT INTO conversion VALUES('HOBU','CONVERSION_MY_CRS','unknown','','EPSG','9805','Mercator (variant B)','EPSG','8823','Latitude of 1st standard parallel',5,'EPSG','9122','EPSG','8802','Longitude of natural origin',0,'EPSG','9122','EPSG','8806','False easting',0,'EPSG','9001','EPSG','8807','False northing',0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_CONVERSION_MY_CRS','conversion','HOBU','CONVERSION_MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); INSERT INTO projected_crs VALUES('HOBU','MY_CRS','unknown','','EPSG','4400','HOBU','GEODETIC_CRS_MY_CRS','HOBU','CONVERSION_MY_CRS',NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_PROJECTED_CRS_MY_CRS','projected_crs','HOBU','MY_CRS','PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN'); - args: -s EPSG:4326 -t EPSG:32631 --single-line out: | Candidate operations found: 1 ------------------------------------- Operation No. 1: EPSG:16031, UTM zone 31N, 0 m, Between 0°E and 6°E, northern hemisphere between equator and 84°N, onshore and offshore. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm +zone=31 +ellps=WGS84 WKT2:2019 string: CONVERSION["UTM zone 31N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",3,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]],ID["EPSG",16031]] - args: EPSG:4326 EPSG:32631 --single-line out: | Candidate operations found: 1 ------------------------------------- Operation No. 1: EPSG:16031, UTM zone 31N, 0 m, Between 0°E and 6°E, northern hemisphere between equator and 84°N, onshore and offshore. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm +zone=31 +ellps=WGS84 WKT2:2019 string: CONVERSION["UTM zone 31N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",3,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]],ID["EPSG",16031]] - args: EPSG:4326 EPSG:32631 EPSG:4327 grep: Too many parameters stderr: | Too many parameters: EPSG:32631 exitcode: 1 - args: --source-crs NAD27 --target-crs NAD83 out: | Candidate operations found: 1 Note: using '--spatial-test intersects' would bring more results (10) ------------------------------------- Operation No. 1: unknown id, Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation PROJ string: +proj=noop WKT2:2019 string: COORDINATEOPERATION["Ballpark geographic offset from NAD27 to NAD83", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["Geographic2D offsets", ID["EPSG",9619]], PARAMETER["Latitude offset",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8601]], PARAMETER["Longitude offset",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8602]], USAGE[ SCOPE["unknown"], AREA["World"], BBOX[-90,-180,90,180]]] - args: NAD27 NAD83 out: | Candidate operations found: 1 Note: using '--spatial-test intersects' would bring more results (10) ------------------------------------- Operation No. 1: unknown id, Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation PROJ string: +proj=noop WKT2:2019 string: COORDINATEOPERATION["Ballpark geographic offset from NAD27 to NAD83", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["Geographic2D offsets", ID["EPSG",9619]], PARAMETER["Latitude offset",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8601]], PARAMETER["Longitude offset",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8602]], USAGE[ SCOPE["unknown"], AREA["World"], BBOX[-90,-180,90,180]]] - args: -s NAD27 -t NAD83 --grid-check none --spatial-test intersects --summary --hide-ballpark out: | Candidate operations found: 9 DERIVED_FROM(EPSG):1313, NAD27 to NAD83 (4), 1.5 m, Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast west of 44°W and north of 40°N. DERIVED_FROM(EPSG):1312, NAD27 to NAD83 (3), 2.0 m, Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast west of 44°W and north of 40°N. DERIVED_FROM(EPSG):1241, NAD27 to NAD83 (1), 0.15 m, United States (USA) - CONUS including EEZ - onshore and offshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico (GoM) OCS. DERIVED_FROM(EPSG):8555, NAD27 to NAD83 (7), 0.15 m, United States (USA) - CONUS onshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico offshore continental shelf (GoM OCS). DERIVED_FROM(EPSG):1243, NAD27 to NAD83 (2), 0.5 m, United States (USA) - Alaska including EEZ. DERIVED_FROM(EPSG):8549, NAD27 to NAD83 (8), 0.5 m, United States (USA) - Alaska. DERIVED_FROM(EPSG):1573, NAD27 to NAD83 (6), 1.5 m, Canada - Quebec. EPSG:1462, NAD27 to NAD83 (5), 2.0 m, Canada - Quebec. EPSG:9111, NAD27 to NAD83 (9), 1.5 m, Canada - Saskatchewan. - args: -s NAD27 -t NAD83 --grid-check none --spatial-test intersects out: | Candidate operations found: 10 ------------------------------------- Operation No. 1: DERIVED_FROM(EPSG):1313, NAD27 to NAD83 (4), 1.5 m, Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast west of 44°W and north of 40°N. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=ca_nrc_ntv2_0.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (4)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["HORIZONTAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","ca_nrc_ntv2_0.tif"], OPERATIONACCURACY[1.5], USAGE[ SCOPE["Transformation of coordinates at 1m to 2m level of accuracy."], AREA["Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast west of 44°W and north of 40°N."], BBOX[40,-141.01,83.17,-44]], ID["DERIVED_FROM(EPSG)",1313], REMARK["Uses NTv2 data files. Replaces NTv1 (transformation code 1312) except in Quebec. Input expects longitudes to be positive west; EPSG GeogCRS NAD27 (code 4267) and (code 4269) have longitudes positive east. May be used as tfm to WGS 84 - see code 1693."]] ------------------------------------- Operation No. 2: DERIVED_FROM(EPSG):1312, NAD27 to NAD83 (3), 2.0 m, Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast west of 44°W and north of 40°N. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=ca_nrc_ntv1_can.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (3)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["HORIZONTAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","ca_nrc_ntv1_can.tif"], OPERATIONACCURACY[2.0], USAGE[ SCOPE["Historic record only - now superseded - see remarks."], AREA["Canada - onshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon; offshore east coast west of 44°W and north of 40°N."], BBOX[40,-141.01,83.17,-44]], ID["DERIVED_FROM(EPSG)",1312], REMARK["Uses NTv1 method. Replaced in Quebec by code 1462 and elsewhere in 1997 by NTv2 (transformation code 1313). Input expects longitudes to be positive west; EPSG GeogCRS NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east."]] ------------------------------------- Operation No. 3: DERIVED_FROM(EPSG):1241, NAD27 to NAD83 (1), 0.15 m, United States (USA) - CONUS including EEZ - onshore and offshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico (GoM) OCS. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=us_noaa_conus.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (1)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["HORIZONTAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","us_noaa_conus.tif"], OPERATIONACCURACY[0.15], USAGE[ SCOPE["Transformation of coordinates at 0.2m level of accuracy."], AREA["United States (USA) - CONUS including EEZ - onshore and offshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico (GoM) OCS."], BBOX[23.81,-129.17,49.38,-65.69]], ID["DERIVED_FROM(EPSG)",1241], REMARK["Uses NADCON method which expects longitudes positive west; EPSG GeogCRS NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east. For application in Gulf of Mexico refer to IOGP report 373-26."]] ------------------------------------- Operation No. 4: DERIVED_FROM(EPSG):8555, NAD27 to NAD83 (7), 0.15 m, United States (USA) - CONUS onshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico offshore continental shelf (GoM OCS). PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=gridshift +grids=us_noaa_nadcon5_nad27_nad83_1986_conus.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (7)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["GENERAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","us_noaa_nadcon5_nad27_nad83_1986_conus.tif"], OPERATIONACCURACY[0.15], USAGE[ SCOPE["Geodesy."], AREA["United States (USA) - CONUS onshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. US Gulf of Mexico offshore continental shelf (GoM OCS)."], BBOX[23.82,-124.79,49.38,-66.91]], ID["DERIVED_FROM(EPSG)",8555], REMARK["NADCON5 method expects longitudes 0-360°; source and target CRS longitudes in range ±180°. Accuracy at 67% confidence level is 0.15m onshore, 1m nearshore and undetermined farther offshore. For application in Gulf of Mexico refer to IOGP report 373-26."]] ------------------------------------- Operation No. 5: DERIVED_FROM(EPSG):1243, NAD27 to NAD83 (2), 0.5 m, United States (USA) - Alaska including EEZ. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=us_noaa_alaska.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (2)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["HORIZONTAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","us_noaa_alaska.tif"], OPERATIONACCURACY[0.5], USAGE[ SCOPE["Geodesy."], AREA["United States (USA) - Alaska including EEZ."], BBOX[47.88,167.65,74.71,-129.99]], ID["DERIVED_FROM(EPSG)",1243], REMARK["Uses NADCON method which expects longitudes positive west; EPSG GeogCRS NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east. May be used as transformation to WGS 84 - see NAD27 to WGS 84 (85) (code 15864)."]] ------------------------------------- Operation No. 6: DERIVED_FROM(EPSG):8549, NAD27 to NAD83 (8), 0.5 m, United States (USA) - Alaska. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=gridshift +grids=us_noaa_nadcon5_nad27_nad83_1986_alaska.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (8)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["GENERAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","us_noaa_nadcon5_nad27_nad83_1986_alaska.tif"], OPERATIONACCURACY[0.5], USAGE[ SCOPE["Geodesy."], AREA["United States (USA) - Alaska."], BBOX[51.3,172.42,71.4,-129.99]], ID["DERIVED_FROM(EPSG)",8549], REMARK["Uses NADCON5 method which expects longitudes positive east in range 0-360°; source and target CRSs have longitudes positive east in range -180° to +180°. Accuracy at 67% confidence level is 0.5m onshore, 5m nearshore and undetermined farther offshore."]] ------------------------------------- Operation No. 7: DERIVED_FROM(EPSG):1573, NAD27 to NAD83 (6), 1.5 m, Canada - Quebec. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=ca_que_mern_na27na83.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (6)", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["HORIZONTAL_SHIFT_GTIFF"], PARAMETERFILE["Latitude and longitude difference file","ca_que_mern_na27na83.tif"], OPERATIONACCURACY[1.5], USAGE[ SCOPE["Transformation of coordinates at 1m to 2m level of accuracy."], AREA["Canada - Quebec."], BBOX[44.99,-79.85,62.62,-57.1]], ID["DERIVED_FROM(EPSG)",1573], REMARK["Also distributed with file name QUE27-83.gsb. Replaces NAD27 to NAD83 (5) (code 1462). Uses NT method which expects longitudes positive west; EPSG GeogCRSs NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east."]] ------------------------------------- Operation No. 8: EPSG:1462, NAD27 to NAD83 (5), 2.0 m, Canada - Quebec. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GS2783v1.QUE +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (5)", VERSION["SGQ-Can QC NT1"], SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["NTv1", ID["EPSG",9614]], PARAMETERFILE["Latitude and longitude difference file","GS2783v1.QUE"], OPERATIONACCURACY[2.0], USAGE[ SCOPE["Historic record only - now superseded - see remarks."], AREA["Canada - Quebec."], BBOX[44.99,-79.85,62.62,-57.1]], ID["EPSG",1462], REMARK["Densification for Quebec of code 1312. Replaced by NAD27 to NAD83 (6) (code 1573). Uses NT method which expects longitudes positive west; EPSG GeogCRSs NAD27 (code 4267) and NAD83 (code 4269) have longitudes positive east."]] ------------------------------------- Operation No. 9: EPSG:9111, NAD27 to NAD83 (9), 1.5 m, Canada - Saskatchewan. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=SK27-83.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["NAD27 to NAD83 (9)", VERSION["ISC-Can SK"], SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["NTv2", ID["EPSG",9615]], PARAMETERFILE["Latitude and longitude difference file","SK27-83.gsb"], OPERATIONACCURACY[1.5], USAGE[ SCOPE["Geodesy."], AREA["Canada - Saskatchewan."], BBOX[49,-110,60.01,-101.34]], ID["EPSG",9111]] ------------------------------------- Operation No. 10: unknown id, Ballpark geographic offset from NAD27 to NAD83, unknown accuracy, World, has ballpark transformation PROJ string: +proj=noop WKT2:2019 string: COORDINATEOPERATION["Ballpark geographic offset from NAD27 to NAD83", SOURCECRS[ GEOGCRS["NAD27", DATUM["North American Datum 1927", ELLIPSOID["Clarke 1866",6378206.4,294.978698213898, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4267]]], TARGETCRS[ GEOGCRS["NAD83", DATUM["North American Datum 1983", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4269]]], METHOD["Geographic2D offsets", ID["EPSG",9619]], PARAMETER["Latitude offset",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8601]], PARAMETER["Longitude offset",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8602]], USAGE[ SCOPE["unknown"], AREA["World"], BBOX[-90,-180,90,180]]] - args: -s EPSG:4230 -t EPSG:4258 --bbox 8,54.51,15.24,57.8 --summary out: | Candidate operations found: 1 Note: using '--spatial-test intersects' would bring more results (2) EPSG:1626, ED50 to ETRS89 (4), 1.0 m, Denmark - onshore. - args: -s EPSG:23031 -t EPSG:4326 --bbox -13.87,34.91,-7.24,41.88 --crs-extent-use none --summary out: | Candidate operations found: 1 Note: using '--spatial-test intersects' would bring more results (9) unknown id, Inverse of UTM zone 31N + ED50 to WGS 84 (42), 5 m, Portugal - mainland - offshore. - args: -s EPSG:4230 -t EPSG:4258 --area EPSG:3237 --summary out: | Candidate operations found: 1 EPSG:1626, ED50 to ETRS89 (4), 1.0 m, Denmark - onshore. - args: -s EPSG:4230 -t EPSG:4258 --area 'Denmark - onshore' --summary out: | Candidate operations found: 1 EPSG:1626, ED50 to ETRS89 (4), 1.0 m, Denmark - onshore. - comment: several match args: -s EPSG:4230 -t EPSG:4258 --area 'Denmark -' --summary stderr: | Several candidates area of use matching provided name : EPSG:2531 : Denmark - Jutland and Funen - onshore. EPSG:2532 : Denmark - Zealand and Lolland (onshore). EPSG:2533 : Denmark - Bornholm onshore. EPSG:3237 : Denmark - onshore. EPSG:3471 : Denmark - onshore west of 12°E - Zealand, Jutland, Fuen and Lolland. EPSG:3472 : Denmark - onshore east of 12°E - Zealand and Falster, Bornholm. EPSG:3631 : Denmark - Jutland onshore west of 10°E. EPSG:3632 : Denmark - onshore - Jutland east of 9°E and Funen. EPSG:4575 : Denmark - onshore Jutland, Funen, Zealand and Lolland. EPSG:4693 : Denmark - onshore - Copenhagen and surrounding area. EPSG:4694 : Denmark - onshore northern Schleswig and surrounding islands (i.e. Jutland south of the pre-1920 border near the Kongea river). EPSG:4756 : Denmark - offshore. exitcode: 1 - args: -s EPSG:4230 -t EPSG:4258 --area no_match --summary stderr: No area of use matching provided name exitcode: 1 - args: -s EPSG:4230 -t EPSG:4258 --area WRONG:CODE --summary stderr: "Area of use retrieval failed: extent not found" exitcode: 1 - comment: Testing deprecated CRS args: EPSG:26591 out: | Warning: object is deprecated Alternative non-deprecated CRS: EPSG:3003 PROJ.4 string: +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +pm=rome +units=m +no_defs +type=crs WKT2:2019 string: PROJCRS["Monte Mario (Rome) / Italy zone 1", BASEGEOGCRS["Monte Mario (Rome)", DATUM["Monte Mario (Rome)", ELLIPSOID["International 1924",6378388,297, LENGTHUNIT["metre",1]]], PRIMEM["Rome",12.4523333333333, ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4806]], CONVERSION["Italy zone 1", METHOD["Transverse Mercator", ID["EPSG",9807]], PARAMETER["Latitude of natural origin",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8801]], PARAMETER["Longitude of natural origin",9, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8802]], PARAMETER["Scale factor at natural origin",0.9996, SCALEUNIT["unity",1], ID["EPSG",8805]], PARAMETER["False easting",1500000, LENGTHUNIT["metre",1], ID["EPSG",8806]], PARAMETER["False northing",0, LENGTHUNIT["metre",1], ID["EPSG",8807]]], CS[Cartesian,2], AXIS["easting (X)",east, ORDER[1], LENGTHUNIT["metre",1]], AXIS["northing (Y)",north, ORDER[2], LENGTHUNIT["metre",1]], USAGE[ SCOPE["Engineering survey, topographic mapping."], AREA["Italy - onshore and offshore - west of 12°E."], BBOX[36.53,5.93,47.04,12]], ID["EPSG",26591]] - comment: Testing non compliant WKT1 args: | 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],UNIT["degree",0.0174532925199433]]' out: | Warning: GEOGCS should have a PRIMEM node Grammar error: Parsing error : syntax error, unexpected UNIT, expecting PRIMEM. Error occurred around: HEROID["WGS 84",6378137,298.257223563]],UNIT["degree",0.0174532925199433]] ^ PROJ.4 string: +proj=longlat +datum=WGS84 +no_defs +type=crs WKT2:2019 string: GEOGCRS["WGS 84", DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1, ID["EPSG",9001]]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8901]], CS[ellipsoidal,2], AXIS["longitude",east, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["latitude",north, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]]] - comment: Testing CRS with towgs84 args: -o PROJ EPSG:25832 out: | PROJ.4 string: +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs - comment: Testing RH2000 height to SWEREF99 args: -s EPSG:5613 -t EPSG:4977 out: | Candidate operations found: 2 ------------------------------------- Operation No. 1: INVERSE(DERIVED_FROM(PROJ)):EPSG_4977_TO_EPSG_5613, Inverse of SWEREF99 to RH2000 height, unknown accuracy, Sweden - onshore. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=se_lantmateriet_SWEN17_RH2000.tif +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 WKT2:2019 string: COORDINATEOPERATION["Inverse of SWEREF99 to RH2000 height", SOURCECRS[ VERTCRS["RH2000 height", DYNAMIC[ FRAMEEPOCH[2000]], VDATUM["Rikets hojdsystem 2000"], CS[vertical,1], AXIS["gravity-related height (H)",up, LENGTHUNIT["metre",1]], ID["EPSG",5613]]], TARGETCRS[ GEOGCRS["SWEREF99", DATUM["SWEREF99", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,3], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], AXIS["ellipsoidal height (h)",up, ORDER[3], LENGTHUNIT["metre",1]], ID["EPSG",4977]]], METHOD["Inverse of Geographic3D to GravityRelatedHeight (gtx)", ID["INVERSE(EPSG)",9665]], PARAMETERFILE["Geoid (height correction) model file","se_lantmateriet_SWEN17_RH2000.tif"], USAGE[ SCOPE["Not known."], AREA["Sweden - onshore."], BBOX[55.28,10.93,69.07,24.17]], ID["INVERSE(DERIVED_FROM(PROJ))","EPSG_4977_TO_EPSG_5613"]] ------------------------------------- Operation No. 2: unknown id, Transformation from RH2000 height to SWEREF99 (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World, has ballpark transformation PROJ string: +proj=noop WKT2:2019 string: COORDINATEOPERATION["Transformation from RH2000 height to SWEREF99 (ballpark vertical transformation, without ellipsoid height to vertical height correction)", SOURCECRS[ VERTCRS["RH2000 height", DYNAMIC[ FRAMEEPOCH[2000]], VDATUM["Rikets hojdsystem 2000"], CS[vertical,1], AXIS["gravity-related height (H)",up, LENGTHUNIT["metre",1]], ID["EPSG",5613]]], TARGETCRS[ GEOGCRS["SWEREF99", DATUM["SWEREF99", ELLIPSOID["GRS 1980",6378137,298.257222101, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,3], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], AXIS["ellipsoidal height (h)",up, ORDER[3], LENGTHUNIT["metre",1]], ID["EPSG",4977]]], METHOD["Change of Vertical Unit", ID["EPSG",1069]], PARAMETER["Unit conversion scalar",1, SCALEUNIT["unity",1], ID["EPSG",1051]], USAGE[ SCOPE["unknown"], AREA["World"], BBOX[-90,-180,90,180]]] - comment: Testing NAD83(2011) + NAVD88 height -> NAD83(2011) args: -s EPSG:6349 -t EPSG:6319 --spatial-test intersects -o PROJ out: | Candidate operations found: 3 ------------------------------------- Operation No. 1: unknown id, Inverse of NAD83(2011) to NAVD88 height (3), 0.015 m, United States (USA) - CONUS onshore - Alabama; Arizona; Arkansas; California; Colorado; Connecticut; Delaware; Florida; Georgia; Idaho; Illinois; Indiana; Iowa; Kansas; Kentucky; Louisiana; Maine; Maryland; Massachusetts; Michigan; Minnesota; Mississippi; Missouri; Montana; Nebraska; Nevada; New Hampshire; New Jersey; New Mexico; New York; North Carolina; North Dakota; Ohio; Oklahoma; Oregon; Pennsylvania; Rhode Island; South Carolina; South Dakota; Tennessee; Texas; Utah; Vermont; Virginia; Washington; West Virginia; Wisconsin; Wyoming. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=us_noaa_g2018u0.tif +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 ------------------------------------- Operation No. 2: unknown id, Inverse of NAD83(2011) to NAVD88 height (2), 0.02 m, United States (USA) - Alaska. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=us_noaa_g2012ba0.tif +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 ------------------------------------- Operation No. 3: unknown id, Transformation from NAVD88 height to NAD83(2011) (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World, has ballpark transformation PROJ string: +proj=noop - comment: Testing NGF IGN69 height to RGF93 args: -s EPSG:5720 -t EPSG:4965 -o PROJ out: | Candidate operations found: 2 ------------------------------------- Operation No. 1: INVERSE(DERIVED_FROM(EPSG)):10000, Inverse of RGF93 v1 to NGF-IGN69 height (1), 0.5 m, France - mainland onshore. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=fr_ign_RAF18.tif +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 ------------------------------------- Operation No. 2: unknown id, Transformation from NGF-IGN69 height to RGF93 v1 (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World, has ballpark transformation PROJ string: +proj=noop - args: EPSG:32631 --3d out: | PROJ.4 string: +proj=utm +zone=31 +datum=WGS84 +units=m +no_defs +type=crs WKT2:2019 string: PROJCRS["WGS 84 / UTM zone 31N", BASEGEOGCRS["WGS 84", ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)"], MEMBER["World Geodetic System 1984 (G730)"], MEMBER["World Geodetic System 1984 (G873)"], MEMBER["World Geodetic System 1984 (G1150)"], MEMBER["World Geodetic System 1984 (G1674)"], MEMBER["World Geodetic System 1984 (G1762)"], MEMBER["World Geodetic System 1984 (G2139)"], MEMBER["World Geodetic System 1984 (G2296)"], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ENSEMBLEACCURACY[2.0]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4979]], CONVERSION["UTM zone 31N", METHOD["Transverse Mercator", ID["EPSG",9807]], PARAMETER["Latitude of natural origin",0, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8801]], PARAMETER["Longitude of natural origin",3, ANGLEUNIT["degree",0.0174532925199433], ID["EPSG",8802]], PARAMETER["Scale factor at natural origin",0.9996, SCALEUNIT["unity",1], ID["EPSG",8805]], PARAMETER["False easting",500000, LENGTHUNIT["metre",1], ID["EPSG",8806]], PARAMETER["False northing",0, LENGTHUNIT["metre",1], ID["EPSG",8807]], ID["EPSG",16031]], CS[Cartesian,3], AXIS["(E)",east, ORDER[1], LENGTHUNIT["metre",1, ID["EPSG",9001]]], AXIS["(N)",north, ORDER[2], LENGTHUNIT["metre",1, ID["EPSG",9001]]], AXIS["ellipsoidal height (h)",up, ORDER[3], LENGTHUNIT["metre",1, ID["EPSG",9001]]], USAGE[ SCOPE["unknown"], AREA["Between 0°E and 6°E, northern hemisphere between equator and 84°N, onshore and offshore. Algeria. Andorra. Belgium. Benin. Burkina Faso. Denmark - North Sea. France. Germany - North Sea. Ghana. Luxembourg. Mali. Netherlands. Niger. Nigeria. Norway. Spain. Togo. United Kingdom (UK) - North Sea."], BBOX[0,0,84,6]], REMARK["Promoted to 3D from EPSG:32631"]] - args: -s "WGS 84" -t "WGS 84 + EGM96 height" --hide-ballpark --summary out: | Candidate operations found: 1 unknown id, WGS 84 to EGM96 height (1), 1 m, World. - args: -s "WGS 84 + EGM96 height" -t "WGS 84" --hide-ballpark --summary out: | Candidate operations found: 1 unknown id, Inverse of WGS 84 to EGM96 height (1), 1 m, World. - args: -s EPSG:32631 -t EPSG:4326+3855 --summary out: | Candidate operations found: 1 unknown id, Inverse of UTM zone 31N + Inverse of Null geographic offset from WGS 84 to WGS 84, 0 m, World. - args: -s EPSG:32631 -t EPSG:4326+3855 --3d --summary out: | Candidate operations found: 3 unknown id, Inverse of UTM zone 31N + WGS 84 to EGM2008 height (1), 0.113 m, World. unknown id, Inverse of UTM zone 31N + WGS 84 to EGM2008 height (2), 0.11 m, World. unknown id, Inverse of UTM zone 31N + Inverse of Transformation from EGM2008 height to WGS 84 (ballpark vertical transformation, without ellipsoid height to vertical height correction), unknown accuracy, World., has ballpark transformation - args: -s EPSG:4326 -t EPSG:32661 --normalize-axis-order -o PROJ -q --single-line comment: "Undocumented option: --normalize-axis-order" out: | +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere +lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 +ellps=WGS84 - args: -s EPSG:4936 -t EPSG:4978 --spatial-test intersects --summary --bbox 5,54,6,55 comment: "WGS 84 to ETRS89 (2) uses a transformation method not supported by PROJ currently (time-specific Helmert), and thus must be sorted last" out: | Candidate operations found: 2 unknown id, Ballpark geocentric translation from ETRS89 to WGS 84, unknown accuracy, World, has ballpark transformation INVERSE(EPSG):9225, Inverse of WGS 84 to ETRS89 (2), 0.1 m, Germany - offshore North Sea. Netherlands - offshore east of 5E. - args: -s "+proj=longlat +datum=WGS84 +geoidgrids=@foo.gtx +type=crs" -t EPSG:4979 -o PROJ -q out: | +proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=@foo.gtx +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s "AGD66" -t "WGS 84 (G1762)" --spatial-test intersects --summary out: | Candidate operations found: 19 unknown id, AGD66 to WGS 84 (18) + WGS 84 to WGS 84 (G1762), 5 m, Australia - offshore including EEZ. unknown id, AGD66 to WGS 84 (16) + WGS 84 to WGS 84 (G1762), 7 m, Australia - Australian Capital Territory; New South Wales; Northern Territory; Queensland; South Australia; Tasmania; Western Australia; Victoria. unknown id, AGD66 to WGS 84 (20) + WGS 84 to WGS 84 (G1762), 11 m, Australia - Australian Capital Territory; New South Wales; Northern Territory; Queensland; South Australia; Tasmania; Western Australia; Victoria. unknown id, AGD66 to WGS 84 (15) + WGS 84 to WGS 84 (G1762), 5 m, Australia - Northern Territory. unknown id, AGD66 to WGS 84 (13) + WGS 84 to WGS 84 (G1762), 5 m, Australia - New South Wales and Victoria. unknown id, AGD66 to WGS 84 (21) + WGS 84 to WGS 84 (G1762), 7 m, Papua New Guinea - mainland onshore. unknown id, AGD66 to WGS 84 (14) + WGS 84 to WGS 84 (G1762), 5 m, Australia - Tasmania including islands - onshore. unknown id, AGD66 to WGS 84 (19) + WGS 84 to WGS 84 (G1762), 4 m, Papua New Guinea - Papuan fold and thrust belt. unknown id, AGD66 to WGS 84 (22) + WGS 84 to WGS 84 (G1762), 6 m, Papua New Guinea - Papuan fold and thrust belt. unknown id, AGD66 to WGS 84 (23) + WGS 84 to WGS 84 (G1762), 6 m, Papua New Guinea - North Fly area (between 5°04'S and 6°36'S and west of 141°32'E). unknown id, AGD66 to WGS 84 (12) + WGS 84 to WGS 84 (G1762), 5 m, Australia - Australian Capital Territory. unknown id, AGD66 to GDA94 (11) + GDA94 to GDA2020 (2) + Conversion from GDA2020 (geog2D) to GDA2020 (geocentric) + GDA2020 to WGS 84 (G1762) (1) + Conversion from WGS 84 (G1762) (geocentric) to WGS 84 (G1762) (geog2D), 0.75 m, Australia - Australian Capital Territory; New South Wales; Northern Territory; Queensland; South Australia; Tasmania; Western Australia; Victoria., time-dependent operation unknown id, AGD66 to GDA94 (12) + GDA94 to GDA2020 (2) + Conversion from GDA2020 (geog2D) to GDA2020 (geocentric) + GDA2020 to WGS 84 (G1762) (1) + Conversion from WGS 84 (G1762) (geocentric) to WGS 84 (G1762) (geog2D), 3.25 m, Australia - Australian Capital Territory; New South Wales; Northern Territory; Queensland; South Australia; Tasmania; Western Australia; Victoria., time-dependent operation unknown id, AGD66 to WGS 84 (17) + WGS 84 to WGS 84 (G1762), 4.9 m, Australia - Australian Capital Territory; New South Wales; Northern Territory; Queensland; South Australia; Tasmania; Western Australia; Victoria. unknown id, AGD66 to GDA94 (9) + GDA94 to GDA2020 (2) + Conversion from GDA2020 (geog2D) to GDA2020 (geocentric) + GDA2020 to WGS 84 (G1762) (1) + Conversion from WGS 84 (G1762) (geocentric) to WGS 84 (G1762) (geog2D), 1.25 m, Australia - Northern Territory., time-dependent operation unknown id, AGD66 to GDA94 (4) + GDA94 to GDA2020 (2) + Conversion from GDA2020 (geog2D) to GDA2020 (geocentric) + GDA2020 to WGS 84 (G1762) (1) + Conversion from WGS 84 (G1762) (geocentric) to WGS 84 (G1762) (geog2D), 1.25 m, Australia - New South Wales and Victoria., time-dependent operation unknown id, AGD66 to GDA94 (8) + GDA94 to GDA2020 (2) + Conversion from GDA2020 (geog2D) to GDA2020 (geocentric) + GDA2020 to WGS 84 (G1762) (1) + Conversion from WGS 84 (G1762) (geocentric) to WGS 84 (G1762) (geog2D), 1.25 m, Australia - Tasmania including islands - onshore., time-dependent operation unknown id, AGD66 to GDA94 (19) + GDA94 to GDA2020 (2) + Conversion from GDA2020 (geog2D) to GDA2020 (geocentric) + GDA2020 to WGS 84 (G1762) (1) + Conversion from WGS 84 (G1762) (geocentric) to WGS 84 (G1762) (geog2D), 0.75 m, Australia - Australian Capital Territory., time-dependent operation unknown id, Ballpark geographic offset from AGD66 to WGS 84 (G1762), unknown accuracy, World, has ballpark transformation - args: -s EPSG:31467 -t ETRS89 --spatial-test intersects --grid-check none --bbox 8,48,9,49 --summary out: | Candidate operations found: 4 unknown id, Inverse of 3-degree Gauss-Kruger zone 3 + DHDN to ETRS89 (9), 0.1 m, Germany - Baden-Wurttemberg. unknown id, Inverse of 3-degree Gauss-Kruger zone 3 + DHDN to ETRS89 (8), 0.9 m, Germany - onshore - states of Baden-Wurtemberg, Bayern, Berlin, Brandenburg, Bremen, Hamburg, Hessen, Mecklenburg-Vorpommern, Niedersachsen, Nordrhein-Westfalen, Rheinland-Pfalz, Saarland, Sachsen, Sachsen-Anhalt, Schleswig-Holstein, Thuringen. unknown id, Inverse of 3-degree Gauss-Kruger zone 3 + DHDN to ETRS89 (3), 1 m, Germany - states of former West Germany - south of 50°20'N. unknown id, Inverse of 3-degree Gauss-Kruger zone 3 + DHDN to ETRS89 (2), 3 m, Germany - states of former West Germany onshore - Baden-Wurtemberg, Bayern, Bremen, Hamburg, Hessen, Niedersachsen, Nordrhein-Westfalen, Rheinland-Pfalz, Saarland, Schleswig-Holstein. - args: -s "GDA94" -t "AHD height" --grid-check none -o PROJ --spatial-test intersects out: | Candidate operations found: 1 ------------------------------------- Operation No. 1: DERIVED_FROM(EPSG):5656, GDA94 to AHD height (49), 0.15 m, Australia - Australian Capital Territory; New South Wales; Northern Territory; Queensland; South Australia; Western Australia; Victoria. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +inv +proj=vgridshift +grids=au_ga_AUSGeoid09_V1.01.tif +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s "GDA2020" -t "AHD height" --grid-check none -o PROJ --spatial-test intersects out: | Candidate operations found: 1 ------------------------------------- Operation No. 1: DERIVED_FROM(EPSG):8451, GDA2020 to AHD height (1), 0.15 m, Australia - Australian Capital Territory, New South Wales, Northern Territory, Queensland, South Australia, Tasmania, Western Australia and Victoria - onshore. Christmas Island - onshore. Cocos and Keeling Islands - onshore. PROJ string: +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +inv +proj=vgridshift +grids=au_ga_AUSGeoid2020_20180201.tif +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -k ellipsoid WGS84 out: | PROJ string: +ellps=WGS84 WKT2:2019 string: ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1], ID["EPSG",7030]] - args: -k ellipsoid EPSG:7030 out: | PROJ string: +ellps=WGS84 WKT2:2019 string: ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1], ID["EPSG",7030]] - args: -k datum WGS84 out: | WKT2:2019 string: DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ID["EPSG",6326]] - args: -k datum EPSG:6326 out: | WKT2:2019 string: DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ID["EPSG",6326]] - args: -k ensemble WGS84 out: | WKT2:2019 string: ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)", ID["EPSG",1166]], MEMBER["World Geodetic System 1984 (G730)", ID["EPSG",1152]], MEMBER["World Geodetic System 1984 (G873)", ID["EPSG",1153]], MEMBER["World Geodetic System 1984 (G1150)", ID["EPSG",1154]], MEMBER["World Geodetic System 1984 (G1674)", ID["EPSG",1155]], MEMBER["World Geodetic System 1984 (G1762)", ID["EPSG",1156]], MEMBER["World Geodetic System 1984 (G2139)", ID["EPSG",1309]], MEMBER["World Geodetic System 1984 (G2296)", ID["EPSG",1383]], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1], ID["EPSG",7030]], ENSEMBLEACCURACY[2.0], ID["EPSG",6326]] - args: -k operation EPSG:8457 -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=bessel +step +proj=helmert +x=674.374 +y=15.056 +z=405.346 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: D_WGS_1984 out: | WKT2:2019 string: ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)", ID["EPSG",1166]], MEMBER["World Geodetic System 1984 (G730)", ID["EPSG",1152]], MEMBER["World Geodetic System 1984 (G873)", ID["EPSG",1153]], MEMBER["World Geodetic System 1984 (G1150)", ID["EPSG",1154]], MEMBER["World Geodetic System 1984 (G1674)", ID["EPSG",1155]], MEMBER["World Geodetic System 1984 (G1762)", ID["EPSG",1156]], MEMBER["World Geodetic System 1984 (G2139)", ID["EPSG",1309]], MEMBER["World Geodetic System 1984 (G2296)", ID["EPSG",1383]], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1], ID["EPSG",7030]], ENSEMBLEACCURACY[2.0], ID["EPSG",6326]] - args: -k datum D_WGS_1984 out: | WKT2:2019 string: DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ID["EPSG",6326]] - args: --searchpaths comment: Hard to test content - args: --remote-data env: PROJ_NETWORK: OFF out: | Status: disabled Reason: not enabled in proj.ini or PROJ_NETWORK=ON not specified - args: --remote-data env: PROJ_NETWORK: ON comment: Hard to test content - args: --accuracy 0.05 -s EPSG:4326 -t EPSG:4258 out: | Candidate operations found: 0 ###################### # NZGD2000 -> ITRFxx # ###################### - args: -s NZGD2000 -t ITRF96 -o PROJ -q --spatial-test intersects --hide-ballpark out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=defmodel +model=nz_linz_nzgd2000-20180701.json +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s NZGD2000 -t ITRF97 -o PROJ -q --spatial-test intersects --hide-ballpark out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=defmodel +model=nz_linz_nzgd2000-20000101.json +step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=0 +y=-0.00051 +z=0.01553 +rx=-0.00016508 +ry=0.00026897 +rz=5.984e-05 +s=-0.00151099 +dx=0.00069 +dy=-0.0001 +dz=0.00186 +drx=-1.347e-05 +dry=1.514e-05 +drz=-2.7e-07 +ds=-0.00019201 +t_epoch=2000 +convention=position_vector +step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s NZGD2000 -t ITRF2000 -o PROJ -q --spatial-test intersects --hide-ballpark out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=defmodel +model=nz_linz_nzgd2000-20000101.json +step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=0.0067 +y=0.00379 +z=-0.00717 +rx=-0.00016508 +ry=0.00026897 +rz=0.00011984 +s=6.901e-05 +dx=0.00069 +dy=-0.0007 +dz=0.00046 +drx=-1.347e-05 +dry=1.514e-05 +drz=1.973e-05 +ds=-0.00018201 +t_epoch=2000 +convention=position_vector +step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s NZGD2000 -t ITRF2005 -o PROJ -q --spatial-test intersects --hide-ballpark out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=defmodel +model=nz_linz_nzgd2000-20000101.json +step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=0.0068 +y=0.00299 +z=-0.01297 +rx=-0.00016508 +ry=0.00026897 +rz=0.00011984 +s=0.00046901 +dx=0.00049 +dy=-0.0006 +dz=-0.00134 +drx=-1.347e-05 +dry=1.514e-05 +drz=1.973e-05 +ds=-0.00010201 +t_epoch=2000 +convention=position_vector +step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s NZGD2000 -t ITRF2008 -o PROJ -q --spatial-test intersects --hide-ballpark out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=defmodel +model=nz_linz_nzgd2000-20171201.json +step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=0.0048 +y=0.00209 +z=-0.01767 +rx=-0.00016508 +ry=0.00026897 +rz=0.00011984 +s=0.00140901 +dx=0.00079 +dy=-0.0006 +dz=-0.00134 +drx=-1.347e-05 +dry=1.514e-05 +drz=1.973e-05 +ds=-0.00010201 +t_epoch=2000 +convention=position_vector +step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s NZGD2000 -t ITRF2014 -o PROJ -q --spatial-test intersects --hide-ballpark out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=defmodel +model=nz_linz_nzgd2000-20180701.json +step +proj=cart +ellps=GRS80 +step +inv +proj=helmert +x=0.0064 +y=0.00399 +z=-0.01427 +rx=-0.00016508 +ry=0.00026897 +rz=0.00011984 +s=0.00108901 +dx=0.00079 +dy=-0.0006 +dz=-0.00144 +drx=-1.347e-05 +dry=1.514e-05 +drz=1.973e-05 +ds=-7.201e-05 +t_epoch=2000 +convention=position_vector +step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 ###################### # Finland TINs ###################### - args: -s "KKJ / Finland Uniform Coordinate System" -t "ETRS89 / TM35FIN(E,N)" --grid-check none -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=tinshift +file=fi_nls_ykj_etrs35fin.json - args: -s KKJ -t ETRS89 -o PROJ --grid-check none -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +step +proj=tinshift +file=fi_nls_ykj_etrs35fin.json +step +inv +proj=utm +zone=35 +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s "KKJ + N43 height" -t "KKJ + N60 height" --grid-check none -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +step +proj=tinshift +file=fi_nls_n43_n60.json +step +inv +proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - args: -s "KKJ + N60 height" -t "KKJ + N2000 height" --grid-check none -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +step +proj=tinshift +file=fi_nls_n60_n2000.json +step +inv +proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 # Advanced ! - args: -s "KKJ + N43 height" -t "ETRS89 + N2000 height" --grid-check none -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +step +proj=tinshift +file=fi_nls_n43_n60.json +step +proj=tinshift +file=fi_nls_n60_n2000.json +step +proj=tinshift +file=fi_nls_ykj_etrs35fin.json +step +inv +proj=utm +zone=35 +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 # Advanced ! - args: -s "KKJ / Finland Uniform Coordinate System + N43 height" -t "ETRS89 / TM35FIN(E,N) + N2000 height" --grid-check none -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=tinshift +file=fi_nls_n43_n60.json +step +proj=tinshift +file=fi_nls_n60_n2000.json +step +proj=tinshift +file=fi_nls_ykj_etrs35fin.json # Advanced ! - args: -s "ETRS89 / TM35FIN(E,N) + N2000 height" -t "KKJ / Finland Uniform Coordinate System + N43 height" --grid-check none -o PROJ -q out: | +proj=pipeline +step +inv +proj=tinshift +file=fi_nls_ykj_etrs35fin.json +step +inv +proj=tinshift +file=fi_nls_n60_n2000.json +step +inv +proj=tinshift +file=fi_nls_n43_n60.json +step +proj=axisswap +order=2,1 - comment: Quick check of NKG transformations args: -s EPSG:7789 -t EPSG:4936 --area EPSG:1080 --summary --hide-ballpark out: | Candidate operations found: 1 NKG:ITRF2014_TO_DK, ITRF2014 to ETRS89(DK), 0.01 m, Denmark - onshore and offshore., time-dependent operation - args: --dump-db-structure head: 5 out: | CREATE TABLE metadata( key TEXT NOT NULL PRIMARY KEY CHECK (length(key) >= 1), value TEXT NOT NULL ) WITHOUT ROWID; CREATE TABLE unit_of_measure( - args: --dump-db-structure --output-id HOBU:XXXX EPSG:4326 tail: 4 out: | INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR',1); INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR',6); INSERT INTO geodetic_crs VALUES('HOBU','XXXX','WGS 84','','geographic 2D','EPSG','6422','EPSG','6326',NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_GEODETIC_CRS_XXXX','geodetic_crs','HOBU','XXXX','EPSG','1262','EPSG','1183'); - args: -s EPSG:23030 -t EPSG:25830 --bbox -6,40,-5,41 --grid-check known_available --hide-ballpark --summary comment: > Checks that ED50 to ETRS89 (12) is in the output (superseded transformation, but replacements has unknown grid) out: | Candidate operations found: 2 unknown id, Inverse of UTM zone 30N + ED50 to ETRS89 (12) + UTM zone 30N, 0.2 m, Spain - mainland, Balearic Islands, Ceuta and Melila - onshore. unknown id, Inverse of UTM zone 30N + ED50 to ETRS89 (7) + UTM zone 30N, 1.5 m, Spain - onshore mainland except northwest (north of 41°30'N and west of 4°30'W). - args: --list-crs grep: EPSG:32632|ESRI:103668|OGC out: | EPSG:32632 "WGS 84 / UTM zone 32N" ESRI:103668 "NAD_1983_HARN_Adj_MN_Ramsey_Meters" OGC:CRS27 "NAD27 (CRS27)" OGC:CRS83 "NAD83 (CRS83)" OGC:CRS84 "WGS 84 (CRS84)" OGC:CRS84h "WGS 84 longitude-latitude-height" - args: --list-crs --authority OGC,EPSG grep: EPSG:4326|OGC out: | OGC:CRS27 "NAD27 (CRS27)" OGC:CRS83 "NAD83 (CRS83)" OGC:CRS84 "WGS 84 (CRS84)" OGC:CRS84h "WGS 84 longitude-latitude-height" EPSG:4326 "WGS 84" - args: --list-crs grep: deprecated sort: out: - args: --list-crs vertical --bbox 0,40,1,41 --spatial-test intersects grep: Alicante|NAVD88 sort: out: EPSG:5782 "Alicante height" - args: --list-crs vertical --bbox -10,35,5,45 --spatial-test contains grep: Alicante|NAVD88 sort: out: EPSG:5782 "Alicante height" - args: --list-crs --area Spain --spatial-test intersects grep: EPSG:9505|EPSG:9398|EPSG:4258|EPSG:5703 sort: out: | EPSG:4258 "ETRS89" EPSG:9398 "Tenerife height" EPSG:9505 "ETRS89 + Alicante height" - args: --list-crs --area Spain --spatial-test contains grep: EPSG:9505|EPSG:9398|EPSG:4258|EPSG:5703 sort: out: | EPSG:9398 "Tenerife height" EPSG:9505 "ETRS89 + Alicante height" - args: --list-crs --area Spain grep: EPSG:9505|EPSG:9398|EPSG:4258|EPSG:5703 sort: out: | EPSG:9398 "Tenerife height" EPSG:9505 "ETRS89 + Alicante height" - args: --list-crs geodetic grep: EPSG:4326|EPSG:4979|EPSG:4978 sort: out: | EPSG:4326 "WGS 84" EPSG:4978 "WGS 84" EPSG:4979 "WGS 84" - args: --list-crs geographic grep: EPSG:4326|EPSG:4979|EPSG:4978 sort: out: | EPSG:4326 "WGS 84" EPSG:4979 "WGS 84" - args: --list-crs geocentric,geographic_3d grep: EPSG:4326|EPSG:4979|EPSG:4978 sort: out: | EPSG:4978 "WGS 84" EPSG:4979 "WGS 84" - args: --list-crs geographic_2d,allow_deprecated --bbox -100,40,-90,41 --spatial-test intersects grep: - deprecated - NAD83\(FBN\) sort: out: | EPSG:8449 "NAD83(FBN)" [deprecated] - args: --list-crs projected --bbox -100,40,-90,41 --spatial-test intersects grep: (2011).*Missouri East|York sort: out: EPSG:6512 "NAD83(2011) / Missouri East" - comment: Test case where --area has several matches args: --list-crs projected --area France grep: RGF93 v1 / Lambert-93|UTM zone 11N sort: out: EPSG:2154 "RGF93 v1 / Lambert-93" - comment: > Test North-Macedonia CRS for which there are several MGI 1901 -> WGS 84 Helmert transformations but only one matches the extent of the CRS args: EPSG:9945 -o PROJ -q out: +proj=tmerc +lat_0=0 +lon_0=21 +k=0.9999 +x_0=500000 +y_0=-4000000 +ellps=bessel +towgs84=521.748,229.489,590.921,4.029,4.488,-15.521,-9.78 +units=m +no_defs +type=crs - args: -s "NAD83(CSRS)v7" --s_epoch 1997 -t "NAD83(CSRS)v7" --t_epoch 2010 --summary --hide-ballpark --spatial-test intersects out: | Candidate operations found: 1 unknown id, Null geographic offset from NAD83(CSRS)v7 (geog2D) to NAD83(CSRS)v7 (geog3D) + Canada velocity grid v7 from epoch 1997 to epoch 2010 + Null geographic offset from NAD83(CSRS)v7 (geog3D) to NAD83(CSRS)v7 (geog2D), 0.01 m, Canada - onshore - Alberta; British Columbia (BC); Manitoba; New Brunswick (NB); Newfoundland and Labrador; Northwest Territories (NWT); Nova Scotia (NS); Nunavut; Ontario; Prince Edward Island (PEI); Quebec; Saskatchewan; Yukon. - comment: > Test that "CGVD28 height" to "CGVD28(HTv2.0) height" is a no-op args: -s "CGVD28 height" -t "CGVD28(HTv2.0) height" -o PROJ -q out: +proj=noop - comment: > Test custom records for WGS 84 realizations to EGM2008 (https://github.com/OSGeo/PROJ/issues/4362) args: -s "WGS 84 (G1150) + EGM2008 height" -t "WGS 84 (G1674)" --3d -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=us_nga_egm08_25.tif +multiplier=1 +step +proj=cart +ellps=WGS84 +step +proj=helmert +x=-0.0024 +y=0.0016 +z=0.0232 +rx=-0.00027 +ry=0.00027 +rz=-0.00038 +s=0.00208 +dx=-0.0001 +dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 +t_epoch=2005 +convention=coordinate_frame +step +inv +proj=cart +ellps=WGS84 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - comment: > Test 'WGS 84 (Gxxx) + EGM2008 height' to 'WGS 84 (Gyyy) + EGM2008 height' args: -s "WGS 84 (G1150) + EGM2008 height" -t "WGS 84 (G1674) + EGM2008 height" -o PROJ -q out: | +proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart +ellps=WGS84 +step +proj=helmert +x=-0.0024 +y=0.0016 +z=0.0232 +rx=-0.00027 +ry=0.00027 +rz=-0.00038 +s=0.00208 +dx=-0.0001 +dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 +t_epoch=2005 +convention=coordinate_frame +step +inv +proj=cart +ellps=WGS84 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1 - comment: > Test that ETRF2000 to ETRF2014 only goes through ITRF2000, ITRF2008 or ITRF2014, and not ITRF9x or ITRF>2020 args: -s ETRF2005 -t ETRF2014 --3d --summary --authority EPSG out: | Candidate operations found: 1 unknown id, Conversion from ETRF2005 (geog3D) to ETRF2005 (geocentric) + Inverse of ITRF2005 to ETRF2005 (1) + ITRF2005 to ETRF2014 (1) + Conversion from ETRF2014 (geocentric) to ETRF2014 (geog3D), 0 m, Europe - onshore and offshore - ETRF extent - approximately 16°W to 33°E and 33°N to 84°N., time-dependent operation - comment: > Test that ETRF2000-PL to ETRF2014 is not negatively affected by https://github.com/OSGeo/PROJ/pull/4364 changes (the current actual result is not golden in any way, using ETRF2000 to ETRF2014 could probably make sense) args: -s ETRF2000-PL -t ETRF2014 --summary out: | Candidate operations found: 1 unknown id, ETRF2000-PL to ETRS89 (1) + ETRS89 to ETRF2014, 0.1 m, Poland - onshore and offshore. - comment: > Test WGS 84 (G1150) to WGS 84 (G2296) args: -s "WGS 84 (G1150)" -t "WGS 84 (G2296)" --summary out: | Candidate operations found: 1 unknown id, Conversion from WGS 84 (G1150) (geog2D) to WGS 84 (G1150) (geocentric) + WGS 84 (G1150) to WGS 84 (G1762) (1) + WGS 84 (G1762) to WGS 84 (G2139) (1) + WGS 84 (G2139) to WGS 84 (G2296) (1) + Conversion from WGS 84 (G2296) (geocentric) to WGS 84 (G2296) (geog2D), 0.04 m, World - args: completion projinfo - out: | -o -k --summary -q --area --bbox --spatial-test --crs-extent-use --grid-check --pivot-crs --show-superseded --hide-ballpark --accuracy --allow-ellipsoidal-height-as-vertical-crs --boundcrs-to-wgs84 --authority --main-db-path --aux-db-path --identify --3d --output-id --c-ify --single-line --searchpaths --remote-data --list-crs --dump-db-structure -s --s_epoch -t --t_epoch - args: completion projinfo -o out: all PROJ WKT2:2019 WKT2:2015 WKT1:GDAL WKT1:ESRI PROJJSON SQL - args: completion projinfo -o "WKT2:" out: 2019 2015 - args: completion projinfo -o "WKT1:" out: GDAL ESRI - args: completion projinfo --pivot-crs out: | always if_no_direct_transformation never EPSG: ESRI: IAU_2015: IGNF: NKG: NRCAN: OGC: PROJ: - args: completion projinfo out: | EPSG: ESRI: IAU_2015: IGNF: NKG: NRCAN: OGC: PROJ: - args: completion projinfo NKG out: "NKG: NKG_ETRF14 NKG_ETRF14 NKG_ETRF14" - args: completion projinfo "OGC:" out: | CRS27\ --\ NAD27\ (CRS27) CRS83\ --\ NAD83\ (CRS83) CRS84\ --\ WGS\ 84\ (CRS84) CRS84h\ --\ WGS\ 84\ longitude-latitude-height - args: completion projinfo EPSG:432 out: | 4322\ --\ WGS\ 72 4324\ --\ WGS\ 72BE 4326\ --\ WGS\ 84 - args: completion projinfo EPSG:4326 out: | 4326 - args: completion projinfo EGM out: | EGM2008\ height EGM96\ height EGM84\ height - args: completion projinfo "\"RGF93" v1 "/" out: | Lambert-93 CC42 CC43 CC44 CC45 CC46 CC47 CC48 CC49 CC50 Lambert-93\ +\ NGF-IGN69\ height Lambert-93\ +\ NGF-IGN78\ height - args: completion projinfo "\"NAD83(HARN) / California Albers" "+" out: | NGVD29\ height\ (ftUS) NAVD88\ depth\ (ftUS) NGVD29\ depth\ (ftUS) NAVD88\ height\ (ftUS) NGVD29\ height\ (m) MSL\ height\ (ftUS) MSL\ depth\ (ftUS) NAVD88\ height\ (ft) COH88\ 2025\ (NAVD88)\ height COH88\ 2025\ (NAVD88)\ height\ (ftUS) - args: completion projinfo "\"Mauritania 1999" "/" UTM zone 29N "+" out: | EGM2008\ height MSL\ height MSL\ depth EGM96\ height EGM84\ height Instantaneous\ Water\ Level\ height Instantaneous\ Water\ Level\ depth LAT\ depth LLWLT\ depth ISLW\ depth MLLWS\ depth MLWS\ depth MLLW\ depth MLW\ depth MHW\ height MHHW\ height MHWS\ height HHWLT\ height HAT\ height Low\ Water\ depth High\ Water\ height MSL\ height\ (ft) MSL\ depth\ (ft) - args: -s "Christmas Island Grid 1985" -t "GDA94 / MGA zone 48" -o PROJ -q out: | +proj=affine +xoff=550015 +yoff=8780001 - args: -s "Christmas Island Grid 1985" -t "Christmas Island Grid 1985" -o PROJ -q out: | +proj=noop - comment: Test bugfix of https://github.com/OSGeo/PROJ/issues/4464 (NAD83(CSRS) to NAD83(CSRS)v2 should not go through NAD83) args: -s "NAD83(CSRS)" -t "NAD83(CSRS)v2" --spatial-test intersects --summary out: | Candidate operations found: 1 unknown id, Ballpark geographic offset from NAD83(CSRS) to NAD83(CSRS)v2, unknown accuracy, Canada - onshore and offshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon., has ballpark transformation - comment: Test that EPSG:4979 to EPSG:10645 does not include a non-sensical 'Inverse of Inverse of BES2020 Saba to Saba height (1) + Inverse of Saba to WGS 84 (1) + Saba Transverse Mercator 2020' args: -s EPSG:4979 -t EPSG:10645 --hide-ballpark --summary out: | Candidate operations found: 1 unknown id, Inverse of Saba to WGS 84 (1) + Saba to Saba height (1) + Saba Transverse Mercator 2020, 1.2 m, Bonaire, Sint Eustatius and Saba (BES Islands or Caribbean Netherlands) - Saba - onshore. - comment: Test that 'projinfo EGM2008' by default resolves to the datum args: EGM2008 out: | WKT2:2019 string: VDATUM["EGM2008 geoid", ID["EPSG",1027]] - comment: Test that 'projinfo -k crs EGM2008' resolves to the CRS args: -k crs EGM2008 out: | PROJ.4 string: +geoidgrids=us_nga_egm08_25.tif +geoid_crs=WGS84 +vunits=m +no_defs +type=crs WKT2:2019 string: VERTCRS["EGM2008 height", VDATUM["EGM2008 geoid"], CS[vertical,1], AXIS["gravity-related height (H)",up, LENGTHUNIT["metre",1]], USAGE[ SCOPE["Geodesy."], AREA["World."], BBOX[-90,-180,90,180]], ID["EPSG",3855]] proj-9.8.1/test/cli/test_cs2cs_various.yaml000664 001750 001750 00000147362 15166171715 020726 0ustar00eveneven000000 000000 comment: > Test various transformations that do not depend on datum files. exe: cs2cs tests: - comment: Test raw ellipse to raw ellipse args: +proj=latlong +ellps=clrk66 +to +proj=latlong +ellps=bessel in: | 79d58'00.000"W 37d02'00.000"N 0.0 79d58'00.000"W 36d58'00.000"N 0.0 out: | 79d58'W 37d2'N 0.000 79d58'W 36d58'N 0.000 - comment: Test NAD27 to raw ellipse args: +proj=latlong +datum=NAD27 +to +proj=latlong +ellps=bessel in: 79d00'00.000"W 35d00'00.000"N 0.0 out: | 79dW 35dN 0.000 - comment: Between two 3parameter approximations on same ellipsoid args: +proj=latlong +ellps=bessel +towgs84=5,0,0 +to +proj=latlong +ellps=bessel +towgs84=1,0,0 in: | 0d00'00.000"W 0d00'00.000"N 0.0 79d00'00.000"W 45d00'00.000"N 0.0 out: | 0dE 0dN 0.000 78d59'59.821"W 44d59'59.983"N 0.000 - comment: 3param to raw ellipsoid on same ellipsoid args: +proj=latlong +ellps=bessel +towgs84=5,0,0 +to +proj=latlong +ellps=bessel in: | 0d00'00.000"W 0d00'00.000"N 0.0 79d00'00.000"W 45d00'00.000"N 0.0 out: | 0dE 0dN 0.000 79dW 45dN 0.000 - comment: Test simple prime meridian handling args: +proj=latlong +datum=WGS84 +pm=greenwich +to +proj=latlong +datum=WGS84 +pm=1 in: | 0d00'00.000"W 0d00'00.000"N 0.0 79d00'00.000"W 45d00'00.000"N 0.0 out: | 1dW 0dN 0.000 80dW 45dN 0.000 - comment: Test support for the lon_wrap switch args: +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 +lon_wrap=180 in: | 1d00'00.000"W 10d00'00.000"N 0.0 0d00'00.000"W 10d00'00.000"N 0.0 0d00'00.000"E 10d00'00.000"N 0.0 1d00'00.000"E 45d00'00.000"N 0.0 179d00'00.000"E 45d00'00.000"N 0.0 181d00'00.000"E 45d00'00.000"N 0.0 350d00'00.000"E 45d00'00.000"N 0.0 370d00'00.000"E 45d00'00.000"N 0.0 out: | 359dE 10dN 0.000 0dE 10dN 0.000 0dE 10dN 0.000 1dE 45dN 0.000 179dE 45dN 0.000 181dE 45dN 0.000 350dE 45dN 0.000 10dE 45dN 0.000 - comment: Test simple prime meridian handling within a projection args: +proj=utm +zone=11 +datum=WGS84 +pm=3 +to +proj=latlong +datum=WGS84 +pm=1w in: 500000 3000000 out: | 113dW 27d7'20.891"N 0.000 - comment: Test input in grad args: EPSG:4807 EPSG:27572 in: 64.44444444 2.9586342556 out: | 760724.02 3457334.86 0.00 - comment: Test geocentric x/y/z generation args: +proj=latlong +datum=WGS84 +to +proj=geocent +datum=WGS84 in: | 0d00'00.001"W 0d00'00.001"N 0.0 0d00'00.001"W 0d00'00.001"N 10.0 79d00'00.000"W 45d00'00.000"N 0.0 45d00'00.000"W 89d59'59.990"N 0.0 out: | 6378137.00 -0.03 0.03 6378147.00 -0.03 0.03 861996.98 -4434590.01 4487348.41 0.22 -0.22 6356752.31 - comment: Test geocentric x/y/z consumption args: +proj=geocent +datum=WGS84 +to +proj=latlong +datum=WGS84 in: | 6378137.00 -0.00 0.00 6378147.00 -0.00 0.00 861996.98 -4434590.01 4487348.41 0.00 -0.00 6356752.31 out: | 0dE 0dN 0.000 0dE 0dN 10.000 79dW 45dN 0.001 0dE 90dN -0.004 - comment: Test conversion from geodetic latlong to geocentric latlong args: +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 +geoc in: | 0d00'00.000"W 0d00'00.000"N 0.0 79d00'00.000"W 45d00'00.000"N 0.0 12d00'00.000"W 45d00'00.000"N 0.0 0d00'00.000"W 90d00'00.000"N 0.0 out: | 0dE 0dN 0.000 79dW 44d48'27.276"N 0.000 12dW 44d48'27.276"N 0.000 0dE 90dN 0.000 - comment: Test conversion from geocentric latlong to geodetic latlong args: +proj=latlong +datum=WGS84 +geoc +to +proj=latlong +datum=WGS84 in: | 0d00'00.000"W 0d00'00.000"N 0.0 79d00'00.000"W 44d48'27.276"N 0.000 12d00'00.000"W 44d48'27.276"N 0.0 0d00'00.000"W 90d00'00.000"N 0.0 out: | 0dE 0dN 0.000 79dW 45dN 0.000 12dW 45dN 0.000 0dE 90dN 0.000 - comment: "Test stere projection (re: win32 ticket 12)" args: +proj=latlong +datum=WGS84 +to +proj=stere +lat_0=90 +lon_0=0 +lat_ts=70 +datum=WGS84 in: 105 40 out: | 5577808.93 1494569.40 0.00 - comment: Test stere without lat_ts (#147) args: +proj=latlong +datum=WGS84 +to +proj=stere +lat_0=40 +lon_0=10 +datum=WGS84 in: 20 45 out: | 789468.08 602385.33 0.00 - comment: "Test sts projection (re: ticket 12)" args: +proj=latlong +ellps=WGS84 +to +proj=kav5 +ellps=WGS84 +units=m in: 4.897000 52.371000 out: | 383646.09 5997047.89 0.00 - comment: "Test sts projection (re: ticket 12) -- inverse" args: +proj=kav5 +ellps=WGS84 +units=m +to +proj=latlong +ellps=WGS84 in: 383646.088858 5997047.888175 out: | 4d53'49.2"E 52d22'15.6"N 0.000 - comment: "Test RSO Borneo projection (re: ticket 62)" args: > +proj=latlong +a=6377298.556 +rf=300.8017 +to +proj=omerc +a=6377298.556 +rf=300.8017 +lat_0=4 +lonc=115 +alpha=53d18\'56.9537 +gamma=53d7\'48.3685 +k_0=0.99984 +x_0=590476.87 +y_0=442857.65 in: 116d2'11.12630 5d54'19.90183 out: | 704570.40 653979.68 0.00 - comment: Test extended transverse mercator (#97) args: > +proj=etmerc +k=0.998 +lon_0=-20 +datum=WGS84 +x_0=10000 +y_0=20000 +to +proj=latlong +datum=WGS84 in: | 10000 20000 500000 2000000 1000000 2000000 2000000 2000000 4000000 2000000 out: | 20dW 0dN 0.000 15d22'16.108"W 17d52'53.478"N 0.000 10d40'55.532"W 17d42'48.526"N 0.000 1d32'21.33"W 17d3'47.233"N 0.000 15d4'42.357"E 14d48'56.372"N 0.000 - comment: Test extended transverse mercator inverse (#97) args: > +proj=latlong +datum=WGS84 +to +proj=etmerc +k=0.998 +lon_0=-20 +datum=WGS84 +x_0=10000 +y_0=20000 in: | 0dN 0.000 15d22'16.108"W 17d52'53.478"N 0.000 10d40'55.532"W 17d42'48.526"N 0.000 1d32'21.33"W 17d3'47.233"N 0.000 15d4'42.357"E 14d48'56.372"N 0.000 - comment: Test transverse mercator (#97) args: > +proj=tmerc +approx +k=0.998 +lon_0=-20 +datum=WGS84 +x_0=10000 +y_0=20000 +to +proj=latlong +datum=WGS84 in: | 10000 20000 500000 2000000 1000000 2000000 2000000 2000000 4000000 2000000 out: | 20dW 0dN 0.000 15d22'16.108"W 17d52'53.478"N 0.000 10d40'55.532"W 17d42'48.526"N 0.000 1d32'21.399"W 17d3'47.244"N 0.000 15d4'6.539"E 14d49'7.331"N 0.000 - comment: Test transverse mercator inverse (#97) args: > +proj=latlong +datum=WGS84 +to +proj=tmerc +approx +k=0.998 +lon_0=-20 +datum=WGS84 +x_0=10000 +y_0=20000 in: | 0dN 0.000 15d22'16.108"W 17d52'53.478"N 0.000 10d40'55.532"W 17d42'48.526"N 0.000 1d32'21.33"W 17d3'47.233"N 0.000 15d4'42.357"E 14d48'56.372"N 0.000 out: | 2278812.96 20000.00 0.00 499999.99 2000000.01 0.00 999999.99 1999999.99 0.00 2000000.03 1999999.62 0.00 3999967.33 1999855.31 0.00 - comment: Test robinson projection (#113) args: +proj=latlong +datum=WGS84 +to +proj=robin +datum=WGS84 in: | -30 40 -35 45 20 40 out: | -2612095.95 4276351.58 0.00 -2963455.42 4805073.65 0.00 1741397.30 4276351.58 0.00 - comment: Test robinson inverse projection (#113) args: +proj=robin +datum=WGS84 +to +proj=latlong +datum=WGS84 in: | -2612095.95 4276351.58 0.00 -2963455.42 4805073.65 0.00 1741397.30 4276351.58 0.00 out: | 30d0'0.004"W 40d0'0.066"N 0.000 35dW 45dN 0.000 20d0'0.002"E 40d0'0.066"N 0.000 - comment: "Test hammer projection (pull request #329)" args: +proj=latlong +datum=WGS84 +to +proj=hammer +datum=WGS84 in: | -30 40 -35 45 20 40 out: | -2711575.08 4395506.62 0.00 -2964412.70 4929091.33 0.00 1811748.54 4377349.50 0.00 - comment: "Test hammer inverse projection (pull request #329)" args: +proj=hammer +datum=WGS84 +to +proj=latlong +datum=WGS84 in: | -2711575.08 4395506.62 0.00 -2964412.70 4929091.33 0.00 1811748.54 4377349.50 0.00 out: | 30dW 40dN 0.000 35dW 45dN 0.000 20dE 40dN 0.000 - comment: Test healpix forward projection on sphere args: +proj=latlong +R=1 +lon_0=0 +to +proj=healpix +R=1 +lon_0=0 -f %.5f in: | 0 41.81031 -90 0 out: | 0.00000 0.78540 0.00000 -1.57080 0.00000 0.00000 - args: +proj=latlong +R=5 +to +proj=healpix +R=5 -f %.5f in: | 0 0 0 41.810314895778596 0 -41.810314895778596 90.0 0 -90.0 0 -180 0 -180 90.0 -180 -90.0 0 60.0 0 -60.0 out: | 0.00000 0.00000 0.00000 0.00000 3.92699 0.00000 0.00000 -3.92699 0.00000 7.85398 0.00000 0.00000 -7.85398 0.00000 0.00000 -15.70796 0.00000 0.00000 -11.78097 7.85398 0.00000 -11.78097 -7.85398 0.00000 1.43738 5.36437 0.00000 1.43738 -5.36437 0.00000 - comment: Test healpix forward projection on ellipsoid args: > +proj=latlong +a=1 +lon_0=0 +ellps=WGS84 +to +proj=healpix +a=1 +lon_0=0 +ellps=WGS84 -f %.5f in: | 0 41.937853904844985 -90 0 out: | 0.00000 0.78452 0.00000 -1.56904 0.00000 0.00000 - args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=healpix +a=5 +e=0.8 +r_a=4.3220011711888882 -f %.5f in: | 0 0 0 41.810314895778596 0 -41.810314895778596 90.0 0 -90.0 0 -180 0 -180 90.0 -180 -90.0 0 60.0 0 -60.0 out: | 0.00000 0.00000 0.00000 0.00000 2.05479 0.00000 0.00000 -2.05479 0.00000 6.78898 0.00000 0.00000 -6.78898 0.00000 0.00000 -13.57797 0.00000 0.00000 -10.18348 6.78898 0.00000 -10.18348 -6.78898 0.00000 0.00000 3.35128 0.00000 0.00000 -3.35128 0.00000 - comment: Test healpix inverse projection on ellipsoid args: > +proj=latlong +a=1 +lon_0=0 +ellps=WGS84 +to +proj=healpix +a=1 +lon_0=0 +ellps=WGS84 -f %.5f -I in: | 0 0.7853981633974483 -1.5707963267948966 0 out: | * * inf -90.10072 0.00000 0.00000 - args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=healpix +a=5 +e=0.8 +r_a=4.3220011711888882 -f %.5f -I in: | 0.0 0.0 0.0 2.0547874222147415 0.0 -2.0547874222147415 6.788983564106746 0.0 -6.788983564106746 0.0 -13.577967128213492 0.0 0.0 3.351278550178025 0.0 -3.351278550178025 out: | 0.00000 0.00000 0.00000 0.00000 41.81031 0.00000 0.00000 -41.81031 0.00000 90.00000 0.00000 0.00000 -90.00000 0.00000 0.00000 -180.00000 0.00000 0.00000 0.00000 60.00000 0.00000 0.00000 -60.00000 0.00000 - comment: Those points used to be in the previous test, but precision requirement reduced here to 1 decimal because of i386 failures (cf https://github.com/OSGeo/PROJ/pull/4441#issuecomment-2744141103) args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=healpix +a=5 +e=0.8 +r_a=4.3220011711888882 -f %.1f -I in: | -10.183475346160119 6.788983564106746 -10.183475346160119 -6.788983564106746 out: | -180.0 90.0 0.0 -180.0 -90.0 0.0 - comment: Test rHEALPix forward projection on sphere north=0 south=0 args: > +proj=latlong +R=5 +to +proj=rhealpix +R=5 +north_square=0 +south_square=0 -f %.5f in: | -180 30.0 -180 -25.714285714285715 0 0 60.0 41.809314895778598 out: | -15.70796 2.94524 0.00000 -15.70796 -2.55579 0.00000 0.00000 0.00000 0.00000 5.23599 3.92691 0.00000 - comment: Test rHEALPix forward projection on sphere north=1 south=1 args: > +proj=latlong +R=5 +to +proj=rhealpix +R=5 +north_square=1 +south_square=1 -f %.5f in: | -180 30.0 -180 -25.714285714285715 0 0 60.0 41.809314895778598 out: | -15.70796 2.94524 0.00000 -15.70796 -2.55579 0.00000 0.00000 0.00000 0.00000 5.23599 3.92691 0.00000 - comment: Test rHEALPix inverse projection on sphere north=0 south=0 args: > +proj=latlong +R=5 +to +proj=rhealpix +R=5 +north_square=0 +south_square=0 -f %.5f -I in: | 0.0 0.0 0.0 3.9269908169872414 0.0 -3.9269908169872414 7.853981633974483 0.0 -7.853981633974483 0.0 out: | 0.00000 0.00000 0.00000 0.00000 41.81031 0.00000 0.00000 -41.81031 0.00000 90.00000 0.00000 0.00000 -90.00000 0.00000 0.00000 - comment: Test rHEALPix inverse projection on sphere north=1 south=1 args: > +proj=latlong +R=5 +to +proj=rhealpix +R=5 +north_square=1 +south_square=1 -f %.5f -I in: | 0.0 0.0 0.0 3.9269908169872414 0.0 -3.9269908169872414 7.853981633974483 0.0 -7.853981633974483 0.0 out: | 0.00000 0.00000 0.00000 0.00000 41.81031 0.00000 0.00000 -41.81031 0.00000 90.00000 0.00000 0.00000 -90.00000 0.00000 0.00000 - comment: Test rHEALPix forward projection on ellipsoid north=0 south=0 args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=rhealpix +a=5 +e=0.8 +r_a=4.3220011711888882 +north_square=0 +south_square=0 -f %.5f in: | 0 0 0 41.810314895778596 0 -41.810314895778596 90.0 0 -90.0 0 out: | 0.00000 0.00000 0.00000 0.00000 2.05479 0.00000 0.00000 -2.05479 0.00000 6.78898 0.00000 0.00000 -6.78898 0.00000 0.00000 - comment: Test rHEALPix forward projection on ellipsoid north=1 south=1 args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=rhealpix +a=5 +e=0.8 +r_a=4.3220011711888882 +north_square=1 +south_square=1 -f %.5f in: | 0 0 0 41.810314895778596 0 -41.810314895778596 90.0 0 -90.0 0 out: | 0.00000 0.00000 0.00000 0.00000 2.05479 0.00000 0.00000 -2.05479 0.00000 6.78898 0.00000 0.00000 -6.78898 0.00000 0.00000 - comment: Test rHEALPix inverse projection on ellipsoid north=0 south=0 args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=rhealpix +a=5 -I +e=0.8 +r_a=4.3220011711888882 +north_square=0 +south_square=0 -f %.5f in: | 0.0 0.0 0.0 2.0547874222147415 0.0 -2.0547874222147415 6.788983564106746 0.0 -6.788983564106746 0.0 out: | 0.00000 0.00000 0.00000 0.00000 41.81031 0.00000 0.00000 -41.81031 0.00000 90.00000 0.00000 0.00000 -90.00000 0.00000 0.00000 - comment: Test rHEALPix inverse projection on ellipsoid north=1 south=1 args: > +proj=latlong +a=5 +e=0.8 +r_a=4.3220011711888882 +to +proj=rhealpix +a=5 -I +e=0.8 +r_a=4.3220011711888882 +north_square=1 +south_square=1 -f %.5f in: | 0.0 0.0 0.0 2.0547874222147415 0.0 -2.0547874222147415 6.788983564106746 0.0 -6.788983564106746 0.0 out: | 0.00000 0.00000 0.00000 0.00000 41.81031 0.00000 0.00000 -41.81031 0.00000 90.00000 0.00000 0.00000 -90.00000 0.00000 0.00000 - comment: Test geos on a sphere args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=sphere in: | 16d11'8" 58d35'31" -43d11'47" -22d54'30" 18d25'26" -33d55'31" 47d58'42" 29d22'11" out: | 849736.77 4960015.43 0.00 -3780930.93 -2326595.36 0.00 1608689.65 -3412115.56 0.00 3825202.59 2885980.79 0.00 - comment: Test geos on a ellipsoid args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=WGS84 in: | 16d11'8" 58d35'31" -43d11'47" -22d54'30" 18d25'26" -33d55'31" 47d58'42" 29d22'11" out: | 852862.53 4945122.70 0.00 -3787026.57 -2314765.32 0.00 1612331.00 -3397031.37 0.00 3832522.65 2872185.29 0.00 - comment: Test inv geos on a sphere args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=sphere -I in: | 849736.77 4960015.43 -3780930.93 -2326595.36 1608689.65 -3412115.56 3825202.59 2885980.79 out: | 16d11'8"E 58d35'31"N 0.000 43d11'47"W 22d54'30"S 0.000 18d25'26"E 33d55'31"S 0.000 47d58'42"E 29d22'11"N 0.000 - comment: Test inv geos on a ellipsoid args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=WGS84 -I in: | 852862.53 4945122.70 -3787026.57 -2314765.32 1612331.00 -3397031.37 3832522.65 2872185.29 out: | 16d11'8"E 58d35'31"N 0.000 43d11'47"W 22d54'30"S 0.000 18d25'26"E 33d55'31"S 0.000 47d58'42"E 29d22'11"N 0.000 - comment: Test geos on a sphere with alternate sweep args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=sphere +sweep=x in: | 16d11'8" 58d35'31" -43d11'47" -22d54'30" 18d25'26" -33d55'31" 47d58'42" 29d22'11" out: | 841586.28 4961396.21 0.00 -3772913.22 -2339604.71 0.00 1601377.77 -3415545.15 0.00 3812722.89 2902474.62 0.00 - comment: Test geos on a ellipsoid with alternate sweep args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=WGS84 +sweep=x in: | 16d11'8" 58d35'31" -43d11'47" -22d54'30" 18d25'26" -33d55'31" 47d58'42" 29d22'11" out: | 844731.03 4946509.59 0.00 -3779077.27 -2327750.87 0.00 1605067.15 -3400461.47 0.00 3820138.08 2888664.15 0.00 - comment: Test inv geos on a sphere with alternate sweep args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=sphere +sweep=x -I in: | 841586.28 4961396.21 -3772913.22 -2339604.71 1601377.77 -3415545.15 3812722.89 2902474.62 out: | 16d11'8"E 58d35'31"N 0.000 43d11'47"W 22d54'30"S 0.000 18d25'26"E 33d55'31"S 0.000 47d58'42"E 29d22'11"N 0.000 - comment: Test inv geos on a ellipsoid with alternate sweep args: > +proj=latlong +ellps=sphere +to +proj=geos +h=35785831.0 +lon_0=0 +ellps=WGS84 +sweep=x -I in: | 844731.03 4946509.59 -3779077.27 -2327750.87 1605067.15 -3400461.47 3820138.08 2888664.15 out: | 16d11'8"E 58d35'31"N 0.000 43d11'47"W 22d54'30"S 0.000 18d25'26"E 33d55'31"S 0.000 47d58'42"E 29d22'11"N 0.000 - comment: Test the Natural Earth Projection args: > +proj=latlong +a=6371008.7714 +b=6371008.7714 +to +proj=natearth +a=6371008.7714 +b=6371008.7714 -f %.7f in: | 0.0 0.0 0 0.0 0.0 0.0 22.5 0 0.0 2525419.569383768 0.0 45.0 0 0.0 5052537.389973222 0.0 67.5 0 0.0 7400065.6562573705 0.0 90.0 0 0.0 9062062.394736718 45.0 0.0 0 4356790.016612169 0.0 45.0 22.5 0 4253309.544984069 2525419.569383768 45.0 45.0 0 3924521.5829515466 5052537.389973222 45.0 67.5 0 3354937.47115583 7400065.6562573705 45.0 90.0 0 2397978.2448443635 9062062.394736718 90.0 0.0 0 8713580.033224339 0.0 90.0 22.5 0 8506619.089968137 2525419.569383768 90.0 45.0 0 7849043.165903093 5052537.389973222 90.0 67.5 0 6709874.94231166 7400065.6562573705 90.0 90.0 0 4795956.489688727 9062062.394736718 135.0 0.0 0 1.3070370049836507E7 0.0 135.0 22.5 0 1.2759928634952208E7 2525419.569383768 135.0 45.0 0 1.177356474885464E7 5052537.389973222 135.0 67.5 0 1.0064812413467491E7 7400065.6562573705 135.0 90.0 0 7193934.734533091 9062062.394736718 180.0 0.0 0 1.7427160066448677E7 0.0 180.0 22.5 0 1.7013238179936275E7 2525419.569383768 180.0 45.0 0 1.5698086331806187E7 5052537.389973222 180.0 67.5 0 1.341974988462332E7 7400065.6562573705 180.0 90.0 0 9591912.979377454 9062062.394736718 out: | 0.0000000 0.0000000 0.0000000 0.0 0.0 0.0000000 2525419.5693838 0.0000000 0.0 2525419.569383768 0.0000000 5052537.3899732 0.0000000 0.0 5052537.389973222 0.0000000 7400065.6562574 0.0000000 0.0 7400065.6562573705 0.0000000 9062062.3947367 0.0000000 0.0 9062062.394736718 4356790.0166122 0.0000000 0.0000000 4356790.016612169 0.0 4253309.5449841 2525419.5693838 0.0000000 4253309.544984069 2525419.569383768 3924521.5829515 5052537.3899732 0.0000000 3924521.5829515466 5052537.389973222 3354937.4711558 7400065.6562574 0.0000000 3354937.47115583 7400065.6562573705 2397978.2448444 9062062.3947367 0.0000000 2397978.2448443635 9062062.394736718 8713580.0332243 0.0000000 0.0000000 8713580.033224339 0.0 8506619.0899681 2525419.5693838 0.0000000 8506619.089968137 2525419.569383768 7849043.1659031 5052537.3899732 0.0000000 7849043.165903093 5052537.389973222 6709874.9423117 7400065.6562574 0.0000000 6709874.94231166 7400065.6562573705 4795956.4896887 9062062.3947367 0.0000000 4795956.489688727 9062062.394736718 13070370.0498365 0.0000000 0.0000000 1.3070370049836507E7 0.0 12759928.6349522 2525419.5693838 0.0000000 1.2759928634952208E7 2525419.569383768 11773564.7488546 5052537.3899732 0.0000000 1.177356474885464E7 5052537.389973222 10064812.4134675 7400065.6562574 0.0000000 1.0064812413467491E7 7400065.6562573705 7193934.7345331 9062062.3947367 0.0000000 7193934.734533091 9062062.394736718 17427160.0664487 0.0000000 0.0000000 1.7427160066448677E7 0.0 17013238.1799363 2525419.5693838 0.0000000 1.7013238179936275E7 2525419.569383768 15698086.3318062 5052537.3899732 0.0000000 1.5698086331806187E7 5052537.389973222 13419749.8846233 7400065.6562574 0.0000000 1.341974988462332E7 7400065.6562573705 9591912.9793775 9062062.3947367 0.0000000 9591912.979377454 9062062.394736718 - comment: Test the Natural Earth II Projection args: > +proj=latlong +a=6371008.7714 +b=6371008.7714 +to +proj=natearth2 +a=6371008.7714 +b=6371008.7714 -f %.7f in: | 0.0 0.0 0 0.00000000 0.00000000 0.0 22.5 0 0.00000000 2531453.57080958 0.0 45.0 0 0.00000000 5051471.50086845 0.0 67.5 0 0.00000000 7395411.22478983 0.0 90.0 0 0.00000000 9073776.52662810 45.0 0.0 0 4239151.18200719 0.00000000 45.0 22.5 0 4138348.61904244 2531453.57080958 45.0 45.0 0 3830621.33880773 5051471.50086845 45.0 67.5 0 3158326.32836996 7395411.22478983 45.0 90.0 0 957973.37034235 9073776.52662810 90.0 0.0 0 8478302.36401439 0.00000000 90.0 22.5 0 8276697.23808488 2531453.57080958 90.0 45.0 0 7661242.67761547 5051471.50086845 90.0 67.5 0 6316652.65673992 7395411.22478983 90.0 90.0 0 1915946.74068470 9073776.52662810 135.0 0.0 0 12717453.54602160 0.00000000 135.0 22.5 0 12415045.85712730 2531453.57080958 135.0 45.0 0 11491864.01642320 5051471.50086845 135.0 67.5 0 9474978.98510988 7395411.22478983 135.0 90.0 0 2873920.11102705 9073776.52662810 180.0 0.0 0 16956604.72802880 0.00000000 180.0 22.5 0 16553394.47616980 2531453.57080958 180.0 45.0 0 15322485.35523090 5051471.50086845 180.0 67.5 0 12633305.31347990 7395411.22478983 180.0 90.0 0 3831893.48136940 9073776.52662810 out: | 0.0000000 0.0000000 0.0000000 0.00000000 0.00000000 0.0000000 2531453.5708096 0.0000000 0.00000000 2531453.57080958 0.0000000 5051471.5008684 0.0000000 0.00000000 5051471.50086845 0.0000000 7395411.2247898 0.0000000 0.00000000 7395411.22478983 0.0000000 9073776.5266281 0.0000000 0.00000000 9073776.52662810 4239151.1820072 0.0000000 0.0000000 4239151.18200719 0.00000000 4138348.6190424 2531453.5708096 0.0000000 4138348.61904244 2531453.57080958 3830621.3388077 5051471.5008684 0.0000000 3830621.33880773 5051471.50086845 3158326.3283700 7395411.2247898 0.0000000 3158326.32836996 7395411.22478983 957973.3703423 9073776.5266281 0.0000000 957973.37034235 9073776.52662810 8478302.3640144 0.0000000 0.0000000 8478302.36401439 0.00000000 8276697.2380849 2531453.5708096 0.0000000 8276697.23808488 2531453.57080958 7661242.6776155 5051471.5008684 0.0000000 7661242.67761547 5051471.50086845 6316652.6567399 7395411.2247898 0.0000000 6316652.65673992 7395411.22478983 1915946.7406847 9073776.5266281 0.0000000 1915946.74068470 9073776.52662810 12717453.5460216 0.0000000 0.0000000 12717453.54602160 0.00000000 12415045.8571273 2531453.5708096 0.0000000 12415045.85712730 2531453.57080958 11491864.0164232 5051471.5008684 0.0000000 11491864.01642320 5051471.50086845 9474978.9851099 7395411.2247898 0.0000000 9474978.98510988 7395411.22478983 2873920.1110270 9073776.5266281 0.0000000 2873920.11102705 9073776.52662810 16956604.7280288 0.0000000 0.0000000 16956604.72802880 0.00000000 16553394.4761698 2531453.5708096 0.0000000 16553394.47616980 2531453.57080958 15322485.3552309 5051471.5008684 0.0000000 15322485.35523090 5051471.50086845 12633305.3134798 7395411.2247898 0.0000000 12633305.31347990 7395411.22478983 3831893.4813694 9073776.5266281 0.0000000 3831893.48136940 9073776.52662810 - comment: Test the Compact Miller projection args: > +proj=latlong +a=6371008.7714 +b=6371008.7714 +to +proj=comill +a=6371008.7714 +b=6371008.7714 -f %.7f in: | 0.0 0.0 0 0.0 0.0 0.0 22.5 0 0.0 2537439.6610749415 0.0 45.0 0 0.0 5391682.432264133 0.0 67.5 0 0.0 8661480.510260897 0.0 90.0 0 0.0 12009484.264916677 45.0 0.0 0 5003778.588046594 0.0 45.0 22.5 0 5003778.588046594 2537439.6610749415 45.0 45.0 0 5003778.588046594 5391682.432264133 45.0 67.5 0 5003778.588046594 8661480.510260897 45.0 90.0 0 5003778.588046594 12009484.264916677 90.0 0.0 0 10007557.176093187 0.0 90.0 22.5 0 10007557.176093187 2537439.6610749415 90.0 45.0 0 10007557.176093187 5391682.432264133 90.0 67.5 0 10007557.176093187 8661480.510260897 90.0 90.0 0 10007557.176093187 12009484.264916677 135.0 0.0 0 15011335.76413978 0.0 135.0 22.5 0 15011335.76413978 2537439.6610749415 135.0 45.0 0 15011335.76413978 5391682.432264133 135.0 67.5 0 15011335.76413978 8661480.510260897 135.0 90.0 0 15011335.76413978 12009484.264916677 180.0 0.0 0 20015114.352186374 0.0 180.0 22.5 0 20015114.352186374 2537439.6610749415 180.0 45.0 0 20015114.352186374 5391682.432264133 180.0 67.5 0 20015114.352186374 8661480.510260897 180.0 90.0 0 20015114.352186374 12009484.264916677 out: | 0.0000000 0.0000000 0.0000000 0.0 0.0 0.0000000 2537439.6610749 0.0000000 0.0 2537439.6610749415 0.0000000 5391682.4322641 0.0000000 0.0 5391682.432264133 0.0000000 8661480.5102609 0.0000000 0.0 8661480.510260897 0.0000000 12009484.2649167 0.0000000 0.0 12009484.264916677 5003778.5880466 0.0000000 0.0000000 5003778.588046594 0.0 5003778.5880466 2537439.6610749 0.0000000 5003778.588046594 2537439.6610749415 5003778.5880466 5391682.4322641 0.0000000 5003778.588046594 5391682.432264133 5003778.5880466 8661480.5102609 0.0000000 5003778.588046594 8661480.510260897 5003778.5880466 12009484.2649167 0.0000000 5003778.588046594 12009484.264916677 10007557.1760932 0.0000000 0.0000000 10007557.176093187 0.0 10007557.1760932 2537439.6610749 0.0000000 10007557.176093187 2537439.6610749415 10007557.1760932 5391682.4322641 0.0000000 10007557.176093187 5391682.432264133 10007557.1760932 8661480.5102609 0.0000000 10007557.176093187 8661480.510260897 10007557.1760932 12009484.2649167 0.0000000 10007557.176093187 12009484.264916677 15011335.7641398 0.0000000 0.0000000 15011335.76413978 0.0 15011335.7641398 2537439.6610749 0.0000000 15011335.76413978 2537439.6610749415 15011335.7641398 5391682.4322641 0.0000000 15011335.76413978 5391682.432264133 15011335.7641398 8661480.5102609 0.0000000 15011335.76413978 8661480.510260897 15011335.7641398 12009484.2649167 0.0000000 15011335.76413978 12009484.264916677 20015114.3521864 0.0000000 0.0000000 20015114.352186374 0.0 20015114.3521864 2537439.6610749 0.0000000 20015114.352186374 2537439.6610749415 20015114.3521864 5391682.4322641 0.0000000 20015114.352186374 5391682.432264133 20015114.3521864 8661480.5102609 0.0000000 20015114.352186374 8661480.510260897 20015114.3521864 12009484.2649167 0.0000000 20015114.352186374 12009484.264916677 - comment: Test pconic (#148) args: > +proj=latlong +datum=WGS84 +to +proj=pconic +units=m +lat_1=20n +lat_2=60n +lon_0=60W +datum=WGS84 in: -70.4 -23.65 out: | -2240096.40 -6940342.15 0.00 - args: > +proj=pconic +units=m +lat_1=20n +lat_2=60n +lon_0=60W +datum=WGS84 +to +proj=latlong +datum=WGS84 in: -2240096.40 -6940342.15 out: | 70d24'W 23d39'S 0.000 - comment: Test laea args: > +proj=laea +lat_0=45 +lon_0=-100 +units=m +datum=WGS84 +to +proj=latlong +datum=WGS84 -f %.12f in: -6086629.0 4488761.0 out: | 156.058637988599 37.765458306622 0.000000000000 - comment: Test forward calcofi projection args: +proj=latlong +ellps=clrk66 +to +proj=calcofi +ellps=clrk66 in: | 120d40'42.273"W 38d56'50.766"N 121d9'W 34d9'N 123d59'56.066"W 30d25'4.617"N out: | 60.00 20.00 0.00 80.00 60.00 0.00 90.00 120.00 0.00 - comment: Test inverse calcofi projection args: +proj=calcofi +ellps=clrk66 +to +proj=longlat +ellps=clrk66 in: | 60 20 80 60 90 120 out: | 120d40'42.273"W 38d56'50.766"N 0.000 121d9'W 34d9'N 0.000 123d59'56.066"W 30d25'4.617"N 0.000 - comment: Check inverse error handling with ob_tran (#225) args: +proj=ob_tran +o_proj=moll +a=6378137 +es=0 +o_lon_p=0 +o_lat_p=0 +lon_0=180 in: | 300000 400000 20000000 30000000 out: | 42d45'22.377"W 85d35'28.083"N 0.000 * * inf - comment: Test inverse handling args: -I +proj=ob_tran +o_proj=moll +a=6378137 +es=0 +o_lon_p=0 +o_lat_p=0 +lon_0=180 in: 10 20 out: | -1384841.19 7581707.88 0.00 - comment: Test MGI datum gives expected results (#207) args: +proj=latlong +datum=WGS84 +to +init=epsg:31284 -f %.7f in: 16.33 48.20 out: | 595710.3731286 5357598.4645652 0.0000000 - comment: Test omerc sensitivity with locations 90d from origin (#114) args: > +proj=latlong +ellps=WGS84 +to +proj=omerc +ellps=WGS84 +lon_1=62.581150 +lat_1=74.856102 +lon_2=53.942810 +lat_2=74.905884 +units=km +no_rot -f %.8f in: | 56.958381652832 72.8798 56.9584 72.8798 out: | -9985.16336453 -227.67701050 0.00000000 9985.16263662 -227.67701050 0.00000000 - comment: Test omerc differences between poles (#190) - north pole args: > +proj=latlong +ellps=WGS84 +to +proj=omerc +ellps=WGS84 +datum=WGS84 +no_rot +lon_1=-27 +lat_1=70 +lon_2=-38 +lat_2=80 +lat_0=70 -f %.3f in: | -27 70 -27 80 -27 89.9 163 89.9 163 80 out: | 7846957.203 0.000 0.000 8944338.041 204911.652 0.000 10033520.737 402158.063 0.000 10055728.173 404099.799 0.000 11163496.121 397796.828 0.000 - comment: Test omerc differences between poles (#190) - south pole args: > +proj=latlong +ellps=WGS84 +to +proj=omerc +ellps=WGS84 +datum=WGS84 +no_rot +lon_1=-27 +lat_1=-70 +lon_2=-38 +lat_2=-80 +lat_0=-70 -f %.3f in: | -27 -70 -27 -80 -27 -89.9 163 -89.9 163 -80 out: | -7846957.203 0.000 0.000 -8944338.041 204911.652 0.000 -10033520.737 402158.063 0.000 -10055728.173 404099.799 0.000 -11163496.121 397796.828 0.000 - comment: Test qsc args: > +proj=latlong +datum=WGS84 +to +proj=qsc +datum=WGS84 -f %.7f in: 13 -10 out: | 2073986.9490881 -1680858.2722243 0.0000000 - comment: Test inv qsc args: > +proj=qsc +datum=WGS84 +to +proj=latlong +datum=WGS84 -f %.13f in: | 2073986.94908809568733 -1680858.27222427958623 out: | 13.0000000000000 -10.0000000000000 0.0000000000000 - comment: Test bug 229 args: > +init=epsg:4326 +proj=longlat +ellps=WGS84 +datum=WGS84 +towgs84=0,0,0 +to +proj=latlong +datum=WGS84 -f %.13f in: 13 -10 out: | 13.0000000000000 -10.0000000000000 0.0000000000000 - comment: Test bug 229 (2) args: +init=epsg:4326 +to +init=epsg:4326 -f %.13f in: 13 -10 out: | 13.0000000000000 -10.0000000000000 0.0000000000000 - comment: Test bug 244 args: > +init=epsg:4326 +to +proj=aeqd +lon_0=130.0 +lat_0=40.0 +a=6378137 +b=6378137 +units=m -f %.8f in: -140.100000 -87.000000 out: | 987122.41833028 -14429896.53953091 0.00000000 - comment: Test bug 244 (2) args: > +proj=aeqd +lon_0=130.0 +lat_0=40.0 +a=6378137 +b=6378137 +units=m +to +init=epsg:4326 -f %.11f in: 987122.418330284 -14429896.539530909 out: | -140.10000000000 -87.00000000000 0.00000000000 - comment: Test bug 245 (use +datum=carthage) args: > +proj=longlat +datum=WGS84 +to +proj=utm +zone=32 +datum=carthage -f %.7f in: 10 34 out: | 592302.9819461 3762148.7340609 0.0000000 - comment: Test bug 245 (use expansion of +datum=carthage) args: > +proj=longlat +datum=WGS84 +to +proj=utm +zone=32 +a=6378249.2 +b=6356515 +towgs84=-263.0,6.0,431.0 -f %.7f in: 10 34 out: | 592302.9819461 3762148.7340609 0.0000000 - comment: Test SCH forward projection args: > +proj=latlong +datum=WGS84 +to +proj=sch +datum=WGS84 +plat_0=30.0 +plon_0=45.0 +phdg_0=-12.0 +nodefs -f %.7f in: | 0.0 0.0 0.0 90.0 45.0 45.0 45.1 44.9 44.9 45.1 30.0 45.0 out: | -1977112.0305592 5551475.1418378 6595.7256583 6618337.9734775 -1152927.4060894 10055.1157181 1630035.5650122 -342353.6396475 128.3445654 1617547.4295637 -347855.9734973 125.4645102 1642526.7453121 -336878.8571851 131.3265616 1974596.2356203 787409.8217445 773.0028577 - comment: Test SCH inverse projection args: > +proj=sch +datum=WGS84 +plat_0=30.0 +plon_0=45.0 +phdg_0=-12.0 +nodefs +to +proj=latlong +datum=WGS84 -f %.6f in: | 0. 0. 2. 0. 1000. 1000. 0. 1000. 1000. out: | 45.000000 30.000000 2.000000 44.989863 29.998124 -0.000362 44.997845 30.008824 -0.000000 44.987707 30.006948 -0.000523 - comment: "Test issue #316 (switch utm to use etmerc)" args: > +proj=latlong +datum=WGS84 +to +proj=utm +zone=35 +datum=WGS84 -f %.6f in: 0 83 out: | 145723.870553 9300924.845226 0.000000 - comment: "Test issue #316 (switch utm to use etmerc)" args: > +proj=latlong +datum=WGS84 +to +proj=etmerc +datum=WGS84 +k=0.9996 +lon_0=27 +x_0=500000 -f %.6f in: 0 83 out: | 145723.870553 9300924.845226 0.000000 - comment: Test nzmg forward projection args: > +proj=latlong +datum=WGS84 +to +proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=WGS84 +units=m -f %.7f in: 175. -40. 0. out: | 2680778.5726797 6132228.0764513 0.0000000 - comment: Test nzmg inverse projection args: > +proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=WGS84 +units=m +to +proj=latlong +datum=WGS84 -f %.7f in: 2680778.57267967 6132228.07645127 0. out: | 175.0000000 -40.0000000 0.0000000 - comment: Test patterson forward projection args: > +proj=latlong +datum=WGS84 +to +proj=patterson +a=6371008.7714 +b=6371008.7714 +units=m -f %0.8f in: | -180 90 -135 67.5 -90 45 -45 22.5 0 0 45 -22.5 90 -45 135 -67.5 180 -90 out: | -20015114.35218637 11409566.82283130 0.00000000 -15011335.76413978 8729502.05411184 0.00000000 -10007557.17609319 5366413.42115378 0.00000000 -5003778.58804659 2551415.72966934 0.00000000 0.00000000 0.00000000 0.00000000 5003778.58804659 -2551415.72966934 0.00000000 10007557.17609319 -5366413.42115378 0.00000000 15011335.76413978 -8729502.05411184 0.00000000 20015114.35218637 -11409566.82283130 0.00000000 - comment: Test patterson inverse projection args: > +proj=patterson +a=6371008.7714 +b=6371008.7714 +units=m +to +proj=latlong +datum=WGS84 -f %0.3f in: | -20015114.352186374 11409566.822831295 -15011335.76413978 8729502.054111844 -10007557.176093187 5366413.421153781 -5003778.588046594 2551415.729669344 0.0 0.0 5003778.588046594 -2551415.729669344 10007557.176093187 -5366413.421153781 15011335.76413978 -8729502.054111844 20015114.352186374 -11409566.822831295 out: | -180.000 90.000 0.000 -135.000 67.500 0.000 -90.000 45.000 0.000 -45.000 22.500 0.000 0.000 0.000 0.000 45.000 -22.500 0.000 90.000 -45.000 0.000 135.000 -67.500 0.000 180.000 -90.000 0.000 - comment: "Test Web Mercator to avoid issue #834 in the future" args: > +proj=utm +zone=15 +datum=NAD83 +to +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 -f %0.3f in: 487147.594520173 4934316.46263998 out: | -10370728.796 5552839.742 0.000 - comment: Test vto_meter args: > +proj=longlat +a=1 +b=1 +vto_meter=1000 +to +proj=longlat +a=1 +b=1 -f %0.3f in: 0 0 1 out: | 0.000 0.000 1000.000 - args: > +proj=merc +a=1 +b=1 +vto_meter=1000 +to +proj=longlat +a=1 +b=1 -f %0.3f in: 0 0 1 out: | 0.000 0.000 1000.000 - args: > +proj=longlat +a=1 +b=1 +to +proj=longlat +a=1 +b=1 +vto_meter=1000 -f %0.3f in: 0 0 1000 out: | 0.000 0.000 1.000 - args: > +proj=longlat +a=1 +b=1 +to +proj=merc +a=1 +b=1 +vto_meter=1000 -f %0.3f in: 0 0 1000 out: | 0.000 0.000 1.000 - comment: "Test EPSG:4326 to EPSG:32631" # Input is latitude, longitude order args: EPSG:4326 +to EPSG:32631 in: 49 3 0 out: | 500000.00 5427455.78 0.00 - comment: "Test EPSG:32631 to EPSG:4326" # Output is latitude, longitude order args: EPSG:32631 EPSG:4326 in: 400000 5000000 0 out: | 45d8'47.014"N 1d43'40.681"E 0.000 - comment: Check we allow ellipsoids up to a 0.59% relative difference in size args: +proj=longlat +R=3376200 +to IAU_2015:49935 in: 0 -90 out: | 0.00 0.00 0.00 - comment: "Test EPSG:4896 to EPSG:7930" # Here we test that 4D coordinates are handled by cs2cs. Due to backwards # compatibility, the t-component is not written to STDOUT as part of the # coordinate data, but rather as part of the string that follows the xyz # components. This is only seen by users when the -E option is used. Which # means that this test also experience that behavior. args: EPSG:4896 EPSG:7930 -f %.4f -E in: | 3496737.2679 743254.4507 5264462.9620 2019.0 3496737.2679 743254.4507 5264462.9620 2029.0 out: | 3496737.2679 743254.4507 5264462.9620 3496737.7857 743254.0394 5264462.6437 2019.0 3496737.2679 743254.4507 5264462.9620 3496737.9401 743253.8861 5264462.5497 2029.0 - comment: Test ITRF2000 to ITRF1993 # Here we test that HUGE_VAL is passed as the time value if not explicitly # specified args: ITRF2000 ITRF1993 -f %.7f -E in: | 59.4967 -117.61748 329.396 59.4967 -117.61748 329.396 1988 out: | 59.4967 -117.61748 329.396 59.4967002 -117.6174799 329.3845529 59.4967 -117.61748 329.396 59.4967002 -117.6174799 329.3845529 1988 - comment: Check ob_tran with o_proj=longlat (#1525) args: > +proj=longlat +ellps=GRS80 +to +proj=ob_tran +o_proj=longlat +lon_0=10 +o_lat_p=90 +ellps=GRS80 -f %.7f in: -122 46 out: | -132.0000000 46.0000000 0.0000000 - comment: Test inverse handling args: > +proj=longlat +ellps=GRS80 +to +proj=ob_tran +o_proj=longlat +lon_0=10 +o_lat_p=90 +ellps=GRS80 -I -f %.7f in: -122 46 out: | -112.0000000 46.0000000 0.0000000 - args: +init=epsg:4326 +over +to +init=epsg:3857 +over -f %.7f in: -181 49 out: | -20148827.8335825 6274861.3940066 0.0000000 - args: > +proj=longlat +over +datum=WGS84 +to proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +over -f %.7f in: -181 49 out: | -20148827.8335825 6274861.3940066 0.0000000 - comment: Test EPSG:xxxx EPSG:yyyy filename file: name: tmp.txt content: 2 49 args: EPSG:4326 EPSG:4326 tmp.txt -E out: | 2 49 2dN 49dE 0.000 - comment: Test Colombia Urban args: EPSG:4686 EPSG:6247 -f %.3f in: 4.8 -74.25 out: | 122543.174 80859.033 0.000 - comment: Test effect of --authority (GH-2442) - use the EPSG:8076 operation args: ITRF96 ITRF2014 in: 49 2 out: | 49d0'0.002"N 2dE 0.018 - comment: Test effect of --authority (GH-2442) - a no-op args: --authority PROJ ITRF96 ITRF2014 in: 49 2 out: | 49dN 2dE 0.000 - comment: Test effect of --accuracy env: PROJ_DISPLAY_PROGRAM_NAME: NO args: --accuracy 0.05 EPSG:4326 EPSG:4258 exitcode: 3 stderr: |2 cannot initialize transformation cause: (null) program abnormally terminated - comment: Test effect of --no-ballpark env: PROJ_DISPLAY_PROGRAM_NAME: NO args: --no-ballpark EPSG:4267 EPSG:4258 exitcode: 3 stderr: |2 cannot initialize transformation cause: (null) program abnormally terminated - comment: Check that we can use a transformation spanning the antimeridian (should use Pulkovo 1942 to WGS 84 (20)) args: '"Pulkovo 1942" "WGS 84"' in: | 50 179.999999999 50 -179.999999999 out: | 49d59'59.36"N 179d59'52.133"W 0.000 49d59'59.36"N 179d59'52.133"W 0.000 - args: 'EPSG:2636 "WGS 84"' in: | 5540944.47 499999.999 5540944.47 500000.001 out: | 49d59'59.36"N 179d59'52.133"W 0.000 49d59'59.36"N 179d59'52.133"W 0.000 - comment: Check that we select the operation that has the smallest area of use, when 2 have the same accuracy args: 'EPSG:4326 "NAD83(HARN)"' in: 34 -120 out: | 33d59'59.983"N 119d59'59.955"W 0.000 - comment: Check that we promote CRS specified by name to 3D when the other one is 3D env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: '"WGS 84" "WGS 84 + EGM96 height" -d 3' in: 49 2 50 out: | 49.000 2.000 4.936 - args: '"WGS 84 + EGM96 height" "WGS 84" -d 3' env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES in: 49 2 0 out: | 49.000 2.000 45.064 - comment: Test input file with UTF-8 BOM marker file: name: input_file2_with_utf8_bom.txt content: "\uFEFF0 3 0\n" args: EPSG:4326 EPSG:32631 input_file2_with_utf8_bom.txt -d 3 # no BOM with output out: "500000.000\t0.000 0.000" - comment: Test cs2cs -W0 args: -W0 +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 in: 0 0 out: | 0d00'00"E 0d00'00"N 0.000 - comment: Test cs2cs -W8 args: -W8 +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 in: 0 0 out: | 0d00'00.00000000"E 0d00'00.00000000"N 0.000 - comment: Test cs2cs -W env: PROJ_DISPLAY_PROGRAM_NAME: NO args: -W +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 exitcode: 1 stderr: |2 -W argument missing or not in range [0,8] program abnormally terminated - comment: Test cs2cs -W9 env: PROJ_DISPLAY_PROGRAM_NAME: NO args: -W9 +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 exitcode: 1 stderr: |2 -W argument missing or not in range [0,8] program abnormally terminated - comment: Test cs2cs -W10 env: PROJ_DISPLAY_PROGRAM_NAME: NO args: -W10 +proj=latlong +datum=WGS84 +to +proj=latlong +datum=WGS84 exitcode: 1 stderr: |2 -W argument missing or not in range [0,8] program abnormally terminated - comment: Test cs2cs --only-best (grid missing) for NTF to RGF93 args: --only-best NTF RGF93 -E in: 49 2 0 stderr: | Attempt to use coordinate operation NTF to RGF93 v1 (1) failed. Grid fr_ign_gr3df97a.tif is not available. Consult https://proj.org/resource_files.html for guidance. stdout: | 49 2 0 * * inf - comment: Test cs2cs --only-best (working) for EPSG:4326+5773 to EPSG:4979 env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best EPSG:4326+5773 EPSG:4979 in: 49 2 0 out: | 49dN 2dE 45.064 - comment: Test cs2cs --only-best (working) for EPSG:4326 to GDA94 args: --only-best EPSG:4326 GDA94 in: -30 152 0 out: | 30dS 152dE 0.000 - comment: Test cs2cs --only-best (working) for GDA94 to EPSG:4326 args: --only-best GDA94 EPSG:4326 in: -30 152 0 out: | 30dS 152dE 0.000 - comment: Test cs2cs --only-best (working) for EPSG:4326 to GDA2020 args: --only-best EPSG:4326 GDA2020 in: -30 152 0 out: | 30dS 152dE 0.000 - comment: Test cs2cs --only-best (working) for GDA2020 to EPSG:4326 args: --only-best GDA2020 EPSG:4326 in: -30 152 0 out: | 30dS 152dE 0.000 - comment: Test cs2cs (grid missing) for EPSG:4326+3855 to EPSG:4979 env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: EPSG:4326+3855 EPSG:4979 in: 49 2 0 out: | 49dN 2dE 0.000 - comment: Test cs2cs --only-best=no (grid missing) for EPSG:4326+3855 to EPSG:4979 env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best=no EPSG:4326+3855 EPSG:4979 in: 49 2 0 out: | 49dN 2dE 0.000 - comment: Test cs2cs --only-best (grid missing) for EPSG:4326+3855 to EPSG:4979 env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best EPSG:4326+3855 EPSG:4979 in: 49 2 0 stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM2008 height (1) failed. Grid us_nga_egm08_25.tif is not available. Consult https://proj.org/resource_files.html for guidance. stdout: | * * inf - comment: Test cs2cs --only-best=yes (grid missing) for EPSG:4326+3855 to EPSG:4979 env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best=yes EPSG:4326+3855 EPSG:4979 in: 49 2 0 stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM2008 height (1) failed. Grid us_nga_egm08_25.tif is not available. Consult https://proj.org/resource_files.html for guidance. stdout: | * * inf - comment: Test cs2cs (grid missing) with PROJ_ONLY_BEST_DEFAULT=YES for EPSG:4326+3855 to EPSG:4979 env: PROJ_ONLY_BEST_DEFAULT: YES PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: EPSG:4326+3855 EPSG:4979 in: 49 2 0 stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM2008 height (1) failed. Grid us_nga_egm08_25.tif is not available. Consult https://proj.org/resource_files.html for guidance. stdout: | * * inf - comment: Test cs2cs (grid missing) with only_best_default=on in proj.ini file: name: proj.ini content: only_best_default=on env: PROJ_DATA: $PWD:$PROJ_DATA PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: EPSG:4326+3855 EPSG:4979 in: 49 2 0 stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM2008 height (1) failed. Grid us_nga_egm08_25.tif is not available. Consult https://proj.org/resource_files.html for guidance. stdout: | * * inf - comment: Test cs2cs --only-best (grid missing) env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best NAD27 NAD83 in: 40 -100 0 stderr: | Attempt to use coordinate operation NAD27 to NAD83 (7) failed. Grid us_noaa_nadcon5_nad27_nad83_1986_conus.tif is not available. Consult https://proj.org/resource_files.html for guidance. stdout: | * * inf - comment: Test cs2cs --only-best --no-ballpark (grid missing) env: PROJ_DISPLAY_PROGRAM_NAME: NO PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best --no-ballpark EPSG:4326+3855 EPSG:4979 exitcode: 3 stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM2008 height (1) failed. Grid us_nga_egm08_25.tif is not available. Consult https://proj.org/resource_files.html for guidance. cannot initialize transformation cause: File not found or invalid program abnormally terminated - comment: Test cs2cs (grid missing) with scenario of GH-3607 that we are using the 'NAD27 to WGS 84 (6)' DMA-ConusW transformation tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir args: +proj=latlong +datum=WGS84 +to +proj=utm +zone=10 +datum=NAD27 in: | -111.5 45.25919444444 -111.5 45.25919444444 out: | 1402288.54 5076296.64 0.00 1402288.54 5076296.64 0.00 - comment: Test cs2cs (grid missing) with scenario of GH-3613 that we are using a WGS 84 intermediate to do NAD27 to NAD83 tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: EPSG:26915 EPSG:26715 in: | 569704.5660295591 4269024.671083651 569704.5660295591 4269024.671083651 out: | 569720.46 4268813.88 0.00 569720.46 4268813.88 0.00 - comment: Test cs2cs (grid missing) with scenario of https://lists.osgeo.org/pipermail/proj/2023-April/011003.html tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir PROJ_DEBUG: 2 PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES PROJ_ONLY_BEST_DEFAULT: "" args: EPSG:4326+5773 EPSG:4326+5782 in: 39 -3 0 grep-v: pj_open_lib stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM96 height (1) + ETRS89 to Alicante height (1) using ETRS89 to WGS 84 (1) failed. Grid es_ign_egm08-rednap.tif is not available. Consult https://proj.org/resource_files.html for guidance. Grid us_nga_egm96_15.tif is not available. Consult https://proj.org/resource_files.html for guidance. This might become an error in a future PROJ major release. Set the ONLY_BEST option to YES or NO. This warning will no longer be emitted (for the current transformation instance). Using coordinate operation Transformation from EGM96 height to Alicante height (ballpark vertical transformation) stdout: | 39.00 -3.00 0.00 - comment: Test cs2cs (grid missing) --only-best=no with scenario of https://lists.osgeo.org/pipermail/proj/2023-April/011003.html tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir PROJ_DEBUG: 2 PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best=no EPSG:4326+5773 EPSG:4326+5782 in: 39 -3 0 grep-v: pj_open_lib out: | 39.00 -3.00 0.00 - comment: Test cs2cs (grid missing) --only-best with scenario of https://lists.osgeo.org/pipermail/proj/2023-April/011003.html tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir PROJ_DEBUG: 2 PROJ_DISPLAY_PROGRAM_NAME: NO PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES args: --only-best EPSG:4326+5773 EPSG:4326+5782 in: 39 -3 0 grep-v: pj_open_lib stderr: | Attempt to use coordinate operation Inverse of WGS 84 to EGM96 height (1) + ETRS89 to Alicante height (1) using ETRS89 to WGS 84 (1) failed. Grid es_ign_egm08-rednap.tif is not available. Consult https://proj.org/resource_files.html for guidance. Grid us_nga_egm96_15.tif is not available. Consult https://proj.org/resource_files.html for guidance. cannot initialize transformation cause: File not found or invalid program abnormally terminated stdout: "" exitcode: 3 - comment: Test cs2cs grid missing with @gridname args: +proj=longlat +datum=WGS84 +units=m +geoidgrids=@i_dont_exist.tif +vunits=m +no_defs +type=crs +to EPSG:4979 in: 2 49 0 out: | 49dN 2dE 0.000 - comment: Test cs2cs geographic -> geocentric using BETA2007.gsb grid args: EPSG:4746 EPSG:4978 in: 50.5 10 0 out: | 4003461.55 705832.08 4898267.79 - comment: Test cs2cs geocentric -> geographic using BETA2007.gsb grid args: EPSG:4978 EPSG:4746 in: 4003461.55 705832.08 4898267.79 out: | 50d30'N 10dE -0.001 - comment: Test cs2cs geographic -> geocentric, normally using BETA2007.gsb grid but missing tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir args: EPSG:4746 EPSG:4978 in: 50.5 10 0 out: | 4003461.37 705832.04 4898267.94 - comment: Test cs2cs geocentric -> geographic, normally using BETA2007.gsb grid but missing tmpdir: copy: - $PROJ_DATA/proj.db env: PROJ_DATA: $tmpdir args: EPSG:4978 EPSG:4746 in: 4003461.37 705832.04 4898267.94 out: | 50d30'N 10dE -0.002 - comment: Test cs2cs geographic -> geocentric outside BETA2007.gsb grid args: EPSG:4746 EPSG:4978 in: 49 2 0 out: | 4189881.02 146313.87 4790558.75 - comment: Test cs2cs geocentric -> geographic outside BETA2007.gsb grid args: EPSG:4978 EPSG:4746 in: 4189881.02 146313.87 4790558.75 out: | 49dN 2dE 0.002 - comment: Test Similarity Transformation (example from EPSG Guidance Note 7.2) args: -d 3 EPSG:23031 EPSG:25831 in: 300000 4500000 out: | 299905.060 4499796.515 0.000 - comment: Test inverse of Similarity Transformation (example from EPSG Guidance Note 7.2) args: -d 3 EPSG:25831 EPSG:23031 in: 299905.060 4499796.515 out: | 300000.000 4500000.000 0.000 - comment: Test Similarity Transformation through CompoundCRS # Cf https://github.com/OSGeo/PROJ/issues/3854#issuecomment-1689964773 args: -d 3 EPSG:3912 EPSG:3794 in: 477134.28 95134.21 out: | 476763.303 95620.222 0.000 - args: -d 3 EPSG:3912+EPSG:5779 EPSG:3794+EPSG:8690 in: 477134.28 95134.21 5 out: | 476763.303 95620.222 5.000 - comment: Test --s_epoch args: -d 8 --s_epoch 2022 "ITRF2014" "GDA2020" in: -30 150 out: | -30.00000099 149.99999956 0.00031880 - comment: Test --t_epoch args: -d 8 --t_epoch 2022 "GDA2020" "ITRF2014" -E in: -30 150 out: | -30 150 -29.99999901 150.00000044 -0.00031879 - args: > +proj=topocentric +datum=WGS84 +X_0=-3982059.42 +Y_0=3331314.88 +Z_0=3692463.58 +no_defs +type=crs +to EPSG:4978 -d 3 -E in: 0 0 0 out: | 0 0 0 -3982059.420 3331314.880 3692463.580 - comment: > Test cs2cs EPSG:5488 (RGAF09) to EPSG:4559+5757 (RRAF 1991 / UTM zone 20N + Guadeloupe 1988 height) Check that we use the horizontal transformation for Guadeloupe (RRAF 1991 to RGAF09 (2)), and not the one for Martinique skipif: env.get("TIFF_ENABLED") == "NO" env: PROJ_DATA: ${PROJ_DATA}:${PROJ_DATA}/tests args: -d 3 EPSG:5488 EPSG:4559+5757 in: 16.248285304 -61.484212843 53.073 out: | 661991.318 1796999.201 93.846 proj-9.8.1/test/cli/test_cs2cs_ntv2.yaml000664 001750 001750 00000004712 15166171715 020116 0ustar00eveneven000000 000000 comment: > Test NTv2 support The expected output is modified to use a downsampled version of ntv2_0.gsb found in PROJ's data/tests directory. exe: cs2cs env: PROJ_NETWORK: NO PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES tests: - comment: Point in the ONwinsor subgrid args: +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb +to +proj=latlong +datum=NAD83 -w4 in: | 82d00'00.000"W 42d00'00.000"N 0.0 82d00'01.000"W 42d00'00.000"N 0.0 82d00'02.000"W 42d00'00.000"N 0.0 84d00'00.000"W 42d00'00.000"N 0.0 out: | 81d59'59.6104"W 42d0'0.1602"N 0.000 82d0'0.6104"W 42d0'0.1602"N 0.000 82d0'1.6104"W 42d0'0.1602"N 0.000 83d59'59.8623"W 42d0'0.1807"N 0.000 - comment: Try with NTv2 and NTv1 together ... falls back to NTv1 args: +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb,ntv1_can.dat,conus +to +proj=latlong +datum=NAD83 -w4 in: | 99d00'00.000"W 65d00'00.000"N 0.0 111d00'00.000"W 46d00'00.000"N 0.0 111d00'00.000"W 47d30'00.000"N 0.0 out: | 99d0'1.5926"W 65d0'1.3478"N 0.000 111d0'3.1897"W 45d59'59.7489"N 0.000 111d0'2.8054"W 47d29'59.9899"N 0.000 - comment: Switching between NTv2 subgrids # Initial guess is in ALraymnd, going to parent CAwest afterwards args: +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb -d 8 in: -112.5839956 49.4914451 0 out: "-112.58307621\t49.49144267 0.00000000" - comment: Interpolating very close (and sometimes a bit outside) to the edges a NTv2 subgrid (#209) args: +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb -d 8 in: | -115.58333333 51.25000000 0 -115.58333333 51.25000010 0 -115.58333334 51.25000000 0 -115.49166667 51.07500000 0 -115.49166668 51.07500000 0 -115.49166667 51.07499990 0 out: | -115.58228512 51.24997866 0.00000000 -115.58228512 51.24997876 0.00000000 -115.58228513 51.24997866 0.00000000 -115.49063575 51.07497568 0.00000000 -115.49063576 51.07497568 0.00000000 -115.49063575 51.07497558 0.00000000 - comment: Attempt first with ntv2_0.gsb and then conus args: +proj=longlat +datum=NAD27 +to +proj=longlat +datum=WGS84 -d 8 in: -111.5 45.26 out: "-111.50079772\t45.25992835 0.00000000" - comment: "NAD27 -> NAD83: 1st through ntv2, 2nd through conus" args: NAD27 NAD83 in: | 55d00'00.000"N 111d00'00.000"W 0.0 39d00'00.000"N 111d00'00.000"W 0.0 out: | 55d0'0.367"N 111d0'3.231"W 0.000 38d59'59.912"N 111d0'2.604"W 0.000 proj-9.8.1/test/cli/test_projinfo_out.dist000664 001750 001750 00000002410 15166171715 020637 0ustar00eveneven000000 000000 ############################################################## Testing PROJ_AUX_DB environment variable PROJ.4 string: +proj=longlat +datum=WGS84 +no_defs +type=crs WKT2:2019 string: GEOGCRS["WGS 84", ENSEMBLE["World Geodetic System 1984 ensemble", MEMBER["World Geodetic System 1984 (Transit)"], MEMBER["World Geodetic System 1984 (G730)"], MEMBER["World Geodetic System 1984 (G873)"], MEMBER["World Geodetic System 1984 (G1150)"], MEMBER["World Geodetic System 1984 (G1674)"], MEMBER["World Geodetic System 1984 (G1762)"], MEMBER["World Geodetic System 1984 (G2139)"], MEMBER["World Geodetic System 1984 (G2296)"], ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]], ENSEMBLEACCURACY[2.0]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["geodetic latitude (Lat)",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["geodetic longitude (Lon)",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], USAGE[ SCOPE["Horizontal component of 3D system."], AREA["World."], BBOX[-90,-180,90,180]], ID["HOBU","XXXX"]] proj-9.8.1/test/cli/test_invproj.yaml000664 001750 001750 00000000300 15166171715 017604 0ustar00eveneven000000 000000 comment: Test basic capabilities of the invproj command exe: invproj tests: - args: +proj=tmerc +ellps=GRS80 -E -f %.3f in: 146339.48 5431555.61 out: "146339.48 5431555.61\t2.000\t49.000" proj-9.8.1/test/cli/run_cli_test.py000775 001750 001750 00000147175 15166171715 017267 0ustar00eveneven000000 000000 #!/usr/bin/env python3 """Run a CLI test provided as a YAML file. Requirements ------------ - Python 3.7 or later - Either ruamel.yaml (preferred) or pyyaml, from pip, conda or similar - Self-testing needs pytest (`python3 -m pytest`) Usage ----- A minimal example to run all tests defined in `test_proj.yaml` is: ``` $ python3 run_cli_test.py test_proj.yaml ``` To see full usages, run `python3 run_cli_test.py --help` Test file --------- Test files are defined in YAML format, and are designed to run with one executable. The top-level keys are: - `exe`: (required) executable name, e.g. `proj`. This can be overridden from the command-line using (e.g.) `--exe /path/to/build/bin/proj` - `env`: (optional) mapping of environment variables common to all tests - `comment`: (optional) comment to describe purpose of tests - `tests`: (required) list of tests, described next Each test item have the optional keys: - `args`: command-line arguments passed as a single string, or as a list of individual string arguments - `comment`: comment to describe the test - `env`: mapping of environment variables - `input`: standard input for program, either as string or bytes - `stdout`: expected standard output from program, either as string or bytes - `stderr`: expected standard error from program as a string - `out`: expected combined `stdout` and `stderr` as a string; this option can't be used with `stdout` and/or `stderr` - `exitcode`: expected exit code (or return code) from the program, default 0 - `skipif`: a Python expression to evaluate if test should be skipped; to access the environment variable mapping, use (e.g.) `env.get('VAR')`; other special variables include `byteorder` (either "big" or "little") and `platform` (usually "linux", "darwin", or "win32") Each test may have additional features: - `file` - either a mapping or list of mappings with required keys `name` (str) and `content` (str or bytes), used to specify create one or more files, then removed after test. The `name` can specify a file to be written to the current working directory, or an absolult path to another location. The path may have a special `$tmpdir/file.txt` to write into a temporary directory, described next. - `tmpdir` - create a temporary directory, and copy zero or more files listed in `copy`. Lastly, test results can be post-processed using several commands, processed in the same order they are specified: - `grep`: capture lines that match pattern - `grep-v`: capture lines that don't match pattern - `sub`: substitute pattern with replacement, similar to `sed s/pat/repl/` - `head`: select the first *n* lines - `tail`: select the last *n* lines - `sort`: sort the lines Development ----------- Running self-tests with pytest: ``` python3 -m pytest run_cli_test.py -v ``` Running coverage scan: ``` python3 -m coverage run --source=. -m pytest run_cli_test.py python3 -m coverage html ``` """ ############################################################################### # # Project: PROJ # Purpose: Test command-line interface # Author: Mike Taves # ############################################################################### # Copyright (c) 2024, Mike Taves # # 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. ############################################################################### __author__ = "Mike Taves" __version__ = "0.1" import difflib import functools import os import re import shlex import shutil import subprocess import sys import tempfile from collections.abc import Mapping from copy import deepcopy from dataclasses import dataclass, field from pathlib import Path from textwrap import dedent from typing import List, Optional, Union # at runtime, import either ruamel.yaml or pyyaml lib # always show unbuffered output print = functools.partial(print, flush=True) def sub_env_var(txt: str, env: dict) -> str: """Substitute environment variables into string, if found. Also change pathsep for Windows from ':' to ';'. """ if os.pathsep != ":" and ":" in txt: txt = txt.replace(":", os.pathsep) if "$" not in txt: return txt pat_subvar = [ (r"\$([a-zA-Z_]\w*)", "${var}"), # applies to $VAR (r"\$\{([a-zA-Z_]\w*)\}", "${{{var}}}"), # and to ${VAR} ] for pat, subvar in pat_subvar: for var in set(re.findall(pat, txt)): if var in env: txt = txt.replace(subvar.format(var=var), env[var]) else: print(f"env var {var} not found", file=sys.stderr) if "$" not in txt: return txt return txt def eval_skipif(expr: str, env: dict) -> bool: """Evaluate an expression to determine if test should be skipped.""" import ast if not expr: return False # Add a few useful system constants eval_env = {"env": env} if "byteorder" in expr: eval_env["byteorder"] = sys.byteorder # type: ignore if "platform" in expr: eval_env["platform"] = sys.platform # type: ignore # Declare "safe" ast node types # fmt: off safe_nodes = [ "Expression", "Load", "Name", "Compare", "Subscript", "FormattedValue", "Index", "Dict", "Set", "List", "Tuple", "Constant", "JoinedStr", "Num", "Str", "NameConstant", "BoolOp", "BinOp", "UnaryOp", "IfExp", "And", "Or", "Eq", "NotEq", "Lt", "LtE", "Gt", "GtE", "Is", "IsNot", "In", "NotIn", "Add", "Sub", "Mult", "Div", "Mod", "FloorDiv", "Pow", "Not", "BitOr", "BitAnd", "BitXor", "LShift", "RShift", "Invert", ] # fmt: on expr_tree = ast.parse(expr, mode="eval") # First scan to find acceptable nodes to ignore ignore_ids = set() for node in ast.walk(expr_tree): if isinstance(node, ast.Call): child_nodes = list(ast.iter_child_nodes(node)) nnode = child_nodes[0] if len(child_nodes) > 0 else None if isinstance(nnode, ast.Attribute) and 1 <= len(child_nodes) <= 3: if nnode.value.id == "env" and nnode.attr == "get": # type: ignore # Allow: env.get("key", "default") ignore_ids.add(id(node)) for child in child_nodes: ignore_ids.add(id(child)) # Second scan to raise issues with expression for node in ast.walk(expr_tree): if isinstance(node, ast.Name) and node.id not in eval_env: raise ValueError(f"undefined name {node.id!r}") elif id(node) in ignore_ids: continue node_name = type(node).__name__ if node_name not in safe_nodes: raise ValueError(f"unsafe expression {expr!r}, contains {node_name!r}") res = eval(expr, eval_env) return bool(res) @dataclass class TmpDir: """Create a temporary directory, and optionally copy files.""" copy: list = field(default_factory=list) def __post_init__(self): self._prepared = False if not isinstance(self.copy, list): raise ValueError("tmpdir copy must be a list") def prepare(self, env): """Create temporary directory and copy files into it.""" if self._prepared: return self.strpth = tempfile.mkdtemp() env["tmpdir"] = self.strpth # used by sub_env_var self.pth = Path(self.strpth) self.copy_files = [] for idx, pth in enumerate(self.copy): sub_pth = sub_env_var(pth, env) if sub_pth != pth: self.copy[idx] = sub_pth src_pth = Path(sub_pth) if not src_pth.exists(): raise FileNotFoundError(f"cannot find {src_pth}") dst_pth = self.pth / src_pth.name if dst_pth.exists(): raise FileExistsError(f"file exists {dst_pth}") dst_pth.write_bytes(src_pth.read_bytes()) self.copy_files.append(src_pth) self._prepared = True def sub_var(self, txt) -> str: """Substitute string with $tmpdir into this temporary path.""" if not self._prepared: raise ValueError(f"{self.__class__} not prepared") if "$tmpdir" in txt: txt = re.sub(r"(\$tmpdir)\b", self.strpth, txt) return txt def cleanup(self): if self._prepared: shutil.rmtree(self.strpth) self._prepared = False def __del__(self): if hasattr(self, "cleanup"): self.cleanup() @dataclass class File: """Write a named file with content. File name should not already exist in filesystem. Path can include "$tmpdir" if already using TmpDir. """ name: str content: Union[str, bytes] def __post_init__(self): self._prepared = False if not isinstance(self.name, (str, os.PathLike)): raise ValueError("file name must be str or path-like") if isinstance(self.content, str): if len(self.content) > 0 and not self.content.endswith("\n"): self.content += "\n" elif not isinstance(self.content, bytes): raise ValueError("file content must be str or bytes") def prepare(self, env): if self._prepared: return self.name = sub_env_var(self.name, env) self.pth = Path(self.name) if self.pth.exists(): raise FileExistsError(f"{self.name} exists!") file_contents = self.content if isinstance(file_contents, str): self.pth.write_text(file_contents, encoding="utf-8") else: self.pth.write_bytes(file_contents) self._prepared = True def cleanup(self): if self._prepared: if self.pth.exists(): self.pth.unlink() self._prepared = False def __del__(self): if hasattr(self, "cleanup"): self.cleanup() @dataclass class Test: args: List[str] = field(default_factory=list) comment: Optional[str] = None env: dict = field(default_factory=dict) input: Union[None, str, bytes] = None # or "in" stdout: Union[None, List[str], bytes] = None stderr: Union[None, List[str]] = None out: Union[None, str, List[str]] = None out_cmds: dict = field(default_factory=dict) exitcode: int = 0 skipif: Optional[str] = None file: List[File] = field(default_factory=list) tmpdir: Optional[TmpDir] = None __test__ = False # prevent pytest from autodiscover expected_out_cmds = ["grep", "grep-v", "sub", "head", "tail", "sort"] @classmethod def from_item(cls, verbose=0, **item): cls_kwargs = {} out_cmds = {} # keep order for key in item.keys(): value = item[key] if key in cls.expected_out_cmds: out_cmds[key] = value else: if key == "in": # rename reserved Python name key = "input" cls_kwargs[key] = value return cls(**cls_kwargs, out_cmds=out_cmds) def __post_init__(self): """Do some checks and modify data, but hold-off creating files etc.""" self._prepared = False self.skip = False if isinstance(self.args, str): self.args = shlex.split(self.args) if isinstance(self.input, str) and not self.input.endswith("\n"): self.input += "\n" def text2list(text): if not text: return [] if not text.endswith("\n"): text += "\n" return text.splitlines(keepends=True) self.check_stdout = self.stdout is not None if self.check_stdout and isinstance(self.stdout, str): self.stdout = text2list(self.stdout) self.check_stderr = self.stderr is not None if self.check_stderr and isinstance(self.stderr, str): self.stderr = text2list(self.stderr) self.check_out = self.out is not None if self.check_out and isinstance(self.out, str): if self.check_stdout or self.check_stderr: raise ValueError("out cannot be used with stdout or stderr") self.stdout = text2list(self.out) for key in self.env.keys(): value = self.env[key] if not isinstance(value, str): self.env[key] = str(value) if self.tmpdir is not None and not isinstance(self.tmpdir, TmpDir): self.tmpdir = TmpDir(**self.tmpdir) if self.file: # make into list of File if not isinstance(self.file, list): self.file = [self.file] for idx, file in enumerate(self.file): if not isinstance(file, File): self.file[idx] = File(**file) for key in list(self.out_cmds): value = self.out_cmds[key] if key in {"grep", "grep-v"}: if isinstance(value, str): self.out_cmds[key] = [value] elif not isinstance(value, list): raise ValueError( f"unsupported grep type {type(value)} for {value}", ) elif key == "sub": if ( not isinstance(value, list) or len(value) != 2 or not all(isinstance(val, str) for val in value) ): raise ValueError("sub should be a list of 2 str") def prepare(self, exe: str, global_env: dict): if not self._prepared: self.exe = exe self.global_env = global_env.copy() if self.skipif is not None: self.skip = eval_skipif(self.skipif, self.global_env) if self.skip: return if self.tmpdir is not None: self.tmpdir.prepare(self.global_env) for idx, arg in enumerate(self.args): arg2 = self.tmpdir.sub_var(arg) if arg != arg2: self.args[idx] = arg2 for key in self.env.keys(): self.env[key] = sub_env_var(str(self.env[key]), self.global_env) self.global_env.update(self.env) for file in self.file: file.prepare(self.global_env) self._prepared = True def get_cmd(self) -> str: """Get an approximate POSIX shell command to replicate test.""" if self.skip: raise ValueError("test is skipped") elif not self._prepared: raise ValueError("test not prepared") cmd = "" if self.tmpdir is not None: cmd += f"mkdir {shlex.quote(self.tmpdir.strpth)}\n" for pth in self.tmpdir.copy_files: cmd += f"cp {shlex.quote(str(pth))} {shlex.quote(self.tmpdir.strpth)}\n" for file in self.file: efile_name = shlex.quote(file.name) file_content = file.content if len(file_content) == 0: cmd += f"touch {efile_name}\n" elif isinstance(file_content, str): num_file_lines = sum( 1 if len(line) > 0 else 0 for line in file_content.splitlines() ) if num_file_lines == 1: cmd += "echo " file_content_rstrip = file_content.rstrip() if file_content_rstrip.isprintable(): cmd += shlex.quote(file_content_rstrip) else: # best attempt to capture Unicode and other cmd += "-e " + repr(file_content_rstrip) cmd += f" > {efile_name}\n" else: cmd += f"cat << EOF > {efile_name}\n{file_content}EOF\n" elif isinstance(file_content, bytes): cmd += f"printf {file_content.decode('utf-8')!r} > {efile_name}\n" num_input = 0 if isinstance(self.input, str): num_input = sum( 1 if len(line) > 0 else 0 for line in self.input.splitlines() ) if num_input == 1: cmd += "echo " input_rstrip = self.input.rstrip() if input_rstrip.isprintable(): cmd += shlex.quote(input_rstrip) else: # best attempt to capture Unicode and other cmd += f"-e {input_rstrip!r}" cmd += " | " elif isinstance(self.input, bytes): cmd += f"printf {repr(self.input)[1:]} | " for key, value in self.env.items(): cmd += f"{key}={shlex.quote(value)} " cmd += shlex.quote(self.exe) for arg in self.args: cmd += " " + shlex.quote(arg) if isinstance(self.input, str) and num_input > 1: cmd += " < int: """Run all tests.""" failures = [] num_fail = 0 num_pass = 0 num_skip = 0 glob_env = glob_env.copy() glob_env.update(self.env) for num, test in enumerate(self.tests): test.prepare(self.exe, glob_env) if self.verbose > 1: print("-" * 28) print(f"Test {num + 1}" + (f": {test.comment}" if test.comment else "")) print(test.get_cmd()) if test.skip: num_skip += 1 if self.verbose == 1: print("x", end="") elif self.verbose > 1: print(f"Test skipped: {test.skipif}") continue elif self.verbose > 1 and test.skipif is not None: print(f"Test not skipped: {test.skipif}") result = test.run() if result.failed: num_fail += 1 result.num = num if self.verbose == 1: print("F", end="") result.cmd = test.get_cmd() failures.append(result) elif self.verbose > 1: print("Test failed", file=sys.stderr) result.describe_nonfail() result.describe_fail() if exitfirst: test.cleanup() break else: num_pass += 1 if self.verbose == 1: print(".", end="") elif self.verbose > 1: result.describe_nonfail() print("Test passed") test.cleanup() if self.verbose == 1: print() # show/update failures after all tests have run for result in failures: if self.verbose == 1: print("-" * 28) print(f"Test {result.num + 1} failed", file=sys.stderr) if result.expected.comment is not None: print(f"comment: {result.expected.comment}") if result.expected.skipif is not None: print(f"Test not skipped: {result.expected.skipif}") print(result.cmd) result.describe_nonfail() result.describe_fail() if self.update: result.update_expected(self) if self.update: filename, data, yaml = self.filename, self.data, self.yaml # type: ignore with open(filename, "w", encoding="utf-8") as fp: yaml.dump(data, fp) if self.verbose: print(f"Updated {filename}") if self.verbose: if self.verbose > 1 or num_fail > 0: print("-" * 28) msg_list = [] if num_pass: plr = "" if num_pass == 1 else "s" msg_list.append(f"{num_pass} test{plr} passed") if num_fail: plr = "" if num_fail == 1 else "s" msg_list.append(f"{num_fail} test{plr} failed") if num_skip: plr = "" if num_skip == 1 else "s" msg_list.append(f"{num_skip} test{plr} skipped") if len(self) > 1 and len(msg_list) == 1: msg = f"All {msg_list[0]}" else: msg = ", ".join(msg_list) if num_fail: if self.verbose: print(msg, file=sys.stderr) return 1 if self.verbose: print(msg) return 0 def main(filename, exe=None, env=None, exitfirst=False, update=False, verbose=1): tester = Tester.from_yaml(filename, update=update, verbose=verbose) if exe is not None: tester.exe = exe glob_env = os.environ.copy() # Add/update PWD, which may be out-of-sync glob_env["PWD"] = str(Path.cwd()) glob_env.update(tester.env) if env is not None: glob_env.update(env) if verbose: print("Relevant environment variables:") for key, value in glob_env.items(): if key.startswith("PROJ") or "NABLED" in key: print(f" {key}={value}") # Check exe exe_path = Path(tester.exe) full_exe_path = str(exe_path) if exe_path.is_dir(): raise FileNotFoundError(f"'{exe_path}' is a directory, not a path to an EXE") elif tester.exe[1:3] == ":/": # CMake passes paths with forward slashes, which breaks D:/path/to/invproj.exe if full_exe_path[1:3] == ":/": # on MSYS2 force to :\ full_exe_path = full_exe_path.replace("/", "\\") tester.exe = full_exe_path elif exe_path.is_absolute(): if not exe_path.exists(): raise FileNotFoundError(f"cannot find '{tester.exe}' (does not exist)") else: full_exe_path = shutil.which(tester.exe) if full_exe_path is None: raise FileNotFoundError(f"cannot find '{tester.exe}' (not found on PATH)") if verbose: print(f"Testing '{full_exe_path}'") return tester.run(glob_env, exitfirst=exitfirst) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("filename") parser.add_argument( "--exe", help="path to executable; overrides exe from test file (if defined)" ) parser.add_argument( "--env", action="append", help="environment variable, e.g. 'PROJ_NETWORK=ON'" ) parser.add_argument( "-x", "--exitfirst", action="store_true", help="exit instantly on first failed test", ) parser.add_argument( "--update", action="store_true", help="update filename with new results", ) parser.add_argument( "--verbose", type=int, default=1, help="0 silent (except errors), 1 (default) some messages, 2 more more messages", ) args = parser.parse_args() if args.env is not None: env_d = {} for item in args.env: if item.count("=") != 1: raise ValueError(f"env {item!r} does not use KEY=VALUE style") key, value = item.split("=") env_d[key] = value args.env = env_d sys.exit(main(**vars(args))) # %% Tests for this utility def test_sub_env_var(): env = {"foo": "baz"} assert sub_env_var("foo=bar", env) == "foo=bar" assert sub_env_var("$foo/bar", env) == "baz/bar" assert sub_env_var("line with ${foo}. Or $foo.", env) == "line with baz. Or baz." assert sub_env_var("what $food this?", env) == "what $food this?" def test_eval_skipif(): import pytest assert eval_skipif("", {}) is False assert eval_skipif("0", {}) is False assert eval_skipif("not True is False", {}) is True assert eval_skipif("None is not True", {}) is True env = {"foo": "bar"} assert eval_skipif("env['foo'] == 'bar'", env) is True assert eval_skipif("env['foo'][1] == 'a'", env) is True assert eval_skipif("env.get('foo') == 'bar'", env) is True assert eval_skipif("env.get('no') == 'bar'", env) is False assert eval_skipif("env.get('no', 'bar') == 'bar'", env) is True assert eval_skipif("""f"re{env['foo']}" == 'rebar'""", env) is True assert eval_skipif("[1] != [2]", {}) is True assert eval_skipif("1 in (1,)", {}) is True assert eval_skipif("1 in {1, 2}", {}) is True assert eval_skipif("1 not in {3: 4}", {}) is True assert eval_skipif("2 - 1 < 2 + 1 * 2 and 1 / 2 <= 2 // 2", {}) is True assert eval_skipif("1 > 2 ** 2 or 1 >= 2", {}) is False assert eval_skipif("1 | 4 >> 1 if 4 % 1 << 2 else 2 & ~3 ^ 1", {}) is True # special values assert eval_skipif("byteorder == 'little'", {}) is (sys.byteorder == "little") assert eval_skipif("platform in ('linux', 'darwin')", {}) is ( sys.platform in ("linux", "darwin") ) # errors with pytest.raises(ValueError, match="undefined name 'abs'"): eval_skipif("abs == 'bar'", {}) # missing variable with pytest.raises(ValueError, match="unsafe expression"): eval_skipif("sys.exit()", {}) with pytest.raises(ValueError, match="unsafe expression"): eval_skipif("eval('sys.exit()')", {}) with pytest.raises(ValueError, match="unsafe expression"): eval_skipif("exec('sys.exit()')", {}) with pytest.raises(ValueError, match="unsafe expression"): eval_skipif("foo.upper() == 'X'", {"foo": "x"}) # Call, Attribute def test_tmpdir(): import pytest td = TmpDir() assert td.copy == [] env = {} td.prepare(env) td.prepare(env) # check subsequent call assert list(env.keys()) == ["tmpdir"] assert td.strpth == env["tmpdir"] assert td.pth.exists() td.cleanup() assert not td.pth.exists() prevdir = Path().cwd() with tempfile.TemporaryDirectory() as otd: os.chdir(otd) text_pth = Path("my file.txt") text_pth.write_text("some\nlines\n") bin_pth = Path("file.bits") bin_pth.write_bytes(b"\x02\x03\x04\x05") td = TmpDir(["my file.txt", "file.bits"]) assert td.copy == ["my file.txt", "file.bits"] td.prepare(env) assert td.pth.exists() assert set(pth.name for pth in td.pth.iterdir()) == {"my file.txt", "file.bits"} assert text_pth.read_text() == (td.pth / "my file.txt").read_text() assert bin_pth.read_bytes() == (td.pth / "file.bits").read_bytes() assert td.sub_var("$tmpdir/my file.txt") == f"{td.strpth}/my file.txt" assert td.sub_var("${tmpdir}/my file.txt") == "${tmpdir}/my file.txt" assert td.sub_var("$tmpdirs/my file.txt") == "$tmpdirs/my file.txt" td.cleanup() assert not td.pth.exists() os.chdir(prevdir) # errors with pytest.raises(ValueError, match="copy must be a list"): TmpDir("not a list") td = TmpDir(["/not/a/file.txt"]) with pytest.raises(ValueError, match="not prepared"): td.sub_var("xyz") with pytest.raises(FileNotFoundError, match=r"cannot find /not/a/file\.txt"): td.prepare({}) td = TmpDir(["$DIR/file.txt"]) with pytest.raises(FileNotFoundError, match=r"cannot find /nope/file\.txt"): td.prepare({"DIR": "/nope"}) def test_file(): import pytest prevdir = Path().cwd() with tempfile.TemporaryDirectory() as otd: os.chdir(otd) td_pth = Path(otd) text_file = File("my file.txt", "some\nlines\n") text_pth = td_pth / "my file.txt" assert not text_pth.exists() env = {} text_file.prepare(env) text_file.prepare(env) # check subsequent call assert text_pth != text_file.pth assert env == {} assert text_pth.read_text() == "some\nlines\n" text_file.cleanup() assert not text_pth.exists() bin_file = File("file.bits", b"\x02\x03\x04\x05") bin_file.prepare(env) assert (td_pth / "file.bits").read_bytes() == b"\x02\x03\x04\x05" bin_file.cleanup() os.chdir(prevdir) # errors with pytest.raises(ValueError, match="file name must be str or path-like"): File(None, "some\nlines\n") with pytest.raises(ValueError, match="file content must be str or bytes"): File("my file.txt", None) def test_tester_from_yaml(): fd, fname = tempfile.mkstemp() with os.fdopen(fd, "w") as fp: fp.write( dedent( """\ exe: cat env: FOO: 1 tests: - in: one stdout: one sort: - env: FOO: 2 in: two sub: ["w", "RrR"] out: tRrRo """ ) ) tester = Tester.from_yaml(fname) os.unlink(fname) assert tester.exe == "cat" assert tester.env == {"FOO": "1"} assert len(tester) == 2 assert tester[0] == tester[-2] assert tester[0] != tester[-1] tests = [ Test(input="one", stdout="one", out_cmds={"sort": None}), Test( input="two", out="tRrRo", env={"FOO": "2"}, out_cmds={"sub": ["w", "RrR"]}, ), ] assert tester == Tester("cat", env={"FOO": 1}, tests=tests) assert tester.run({}) == 0 def test_tester_from_yaml_with_update(): import pytest pytest.importorskip("ruamel.yaml") fd, fname = tempfile.mkstemp() with os.fdopen(fd, "w") as fp: fp.write( dedent( """\ exe: cat env: FOO: '1' tests: - in: | this is the new expected result over two lines stdout: |- replace me! # some comment - in: a b c out: | d e f exitcode: 8 - in: here it is stderr: erase me - in: "tab\\tline" out: tabline - in: !!binary | AAAAAAAA4D8AAAAAAADQPw== # base64.b64encode(np.array([0.5, 0.25]).tobytes()) stdout: !!binary "" """ ) ) tester = Tester.from_yaml(fname, preferred="ruamel.yaml", update=True) tester.run({}) with open(fname, "r") as fp: new = fp.read() assert new == dedent( """\ exe: cat env: FOO: '1' tests: - in: | this is the new expected result over two lines stdout: | this is the new expected result over two lines # some comment - in: a b c out: | a b c exitcode: 0 - in: here it is stderr: '' - in: "tab\\tline" out: "tab\\tline" - in: !!binary | AAAAAAAA4D8AAAAAAADQPw== # base64.b64encode(np.array([0.5, 0.25]).tobytes()) stdout: !!binary | AAAAAAAA4D8AAAAAAADQPw== """ ) os.unlink(fname) def test_test(): import pytest # simple text pass test = Test(input="text", stdout="text", env={"FOO": 1}) test.prepare("cat", {}) assert not test.skip assert test.env == {"FOO": "1"} assert test.get_cmd() == "echo text | FOO=1 cat" result = test.run() assert result, result.describe_fail() assert result.failed == set() assert result == TestResult(["text\n"], [], 0, test) # simple text fail test = Test(input="\text", out="text") test.prepare("cat", {}) assert not test.skip assert test.get_cmd() == r"echo -e '\text' | cat" result = test.run() assert not result assert result.failed == {"out"} assert result == TestResult(["\text\n"], [], 0, test) # multiple fails test = Test("notafile", stdout="should be nothing", stderr="good file") test.prepare("ls", {}) assert test.get_cmd() == "ls notafile" result = test.run() assert not result assert result.failed == {"exitcode", "stdout", "stderr"} # multi-lines test = Test(input="one\ntwo", stdout="text") test.prepare("cat", {}) assert test.get_cmd() == dedent( """\ cat < file1.txt printf 'two' > file2.txt echo -e '\\ufeff02 1' > file3_with_bom.txt cat << EOF > 'last file.txt' 3 4 5 6 EOF ls -1 rm file1.txt file2.txt file3_with_bom.txt 'last file.txt'""" ) result = test.run() assert result, result.describe_fail() test.cleanup() os.chdir(prevdir) def test_out_cmds(): import pytest fd, fname = tempfile.mkstemp(suffix=".txt") with os.fdopen(fd, "w") as fp: fp.write("one\ntwo\nthree\nfour\n") test = Test(fname, out_cmds={"sort": None}, out="four\none\nthree\ntwo\n") test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} 2>&1 | sort" result = test.run() assert result, result.describe_fail() test = Test(fname, out_cmds={"grep": "o"}, stdout="one\ntwo\nfour\n") test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} | grep o" result = test.run() assert result, result.describe_fail() test = Test(fname, out_cmds={"grep": "none"}, stdout="") test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} | grep none" result = test.run() assert result, result.describe_fail() test = Test(fname, out_cmds={"grep-v": "o"}, stdout="three") test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} | grep -v o" result = test.run() assert result, result.describe_fail() test = Test( fname, out_cmds={"sub": ["o", "OO"]}, stdout="OOne\ntwOO\nthree\nfOOur\n" ) test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} | sed s/o/OO/" result = test.run() assert result, result.describe_fail() test = Test(fname, out_cmds={"head": 3, "sort": None}, stdout="one\nthree\ntwo\n") test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} | head -n 3 | sort" result = test.run() assert result, result.describe_fail() test = Test(fname, out_cmds={"tail": 1}, stdout="four") test.prepare("cat", {}) assert test.get_cmd() == f"cat {fname} | tail -n 1" result = test.run() assert result, result.describe_fail() # errors with pytest.raises(ValueError, match="unsupported grep type"): Test(fname, out_cmds={"grep": 1}) with pytest.raises(ValueError, match="sub should be a list of 2 str"): Test(fname, out_cmds={"sub": ["o"]}) with pytest.raises(ValueError, match="sub should be a list of 2 str"): Test(fname, out_cmds={"sub": [1, 2]}) test = Test(fname, out_cmds={"silly": None}) test.prepare("cat", {}) with pytest.raises(NotImplementedError, match="unsupported out cmd: silly=None"): test.get_cmd() with pytest.raises(NotImplementedError, match="unsupported out cmd: silly=None"): test.run() os.unlink(fname) proj-9.8.1/test/cli/test_proj.yaml000664 001750 001750 00000001574 15166171715 017105 0ustar00eveneven000000 000000 comment: Test basic capabilities of the proj command exe: proj tests: - args: +ellps=WGS84 +proj=ob_tran +o_proj=latlon +o_lon_p=0.0 +o_lat_p=90.0 +lon_0=360.0 +to_meter=0.0174532925199433 +no_defs -E -f %.3f in: 2 49 out: "2 49\t2.000\t49.000" - comment: Test CRS option args: EPSG:32620 -S in: -63 0 out: "500000.00\t0.00\t<0.9996 0.9996 0.9992 0 0.9996 0.9996>" - comment: Test projection factors on projected CRS with non-Greenwhich prime meridian args: EPSG:27571 -S in: 2.33722917 49.5 # On some architectures the angular distortion (omega) of EPSG:27571 is # not exactly 0, but 8.53878e-07 sub: ["8.53878e-07", "0"] out: "600000.00\t1200000.00\t<0.999877 0.999877 0.999755 0 0.999877 0.999877>" - comment: Test projection factors on compound CRS with a projected CRS args: EPSG:5972 -S in: 9 0 out: "500000.00\t0.00\t<0.9996 0.9996 0.9992 0 0.9996 0.9996>" proj-9.8.1/test/cli/test_cs2cs_datumfile.yaml000664 001750 001750 00000007313 15166171715 021177 0ustar00eveneven000000 000000 comment: > Test various transformations depending on datum files that are not always available exe: cs2cs env: PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY: YES tests: - comment: 1st through ntv1, 2nd through conus args: +proj=latlong +ellps=clrk66 +nadgrids=ntv1_can.dat,conus +to +proj=latlong +datum=NAD83 in: | 111d00'00.000"W 44d00'00.000"N 0.0 111d00'00.000"W 39d00'00.000"N 0.0 out: |- 111d0'3.208"W 43d59'59.732"N 0.000 111d0'2.604"W 38d59'59.912"N 0.000 - comment: As above, but without ntv1 everything goes through conus file # see data/CMakeLists.txt for "dir with space/myconus" args: +proj=latlong +ellps=clrk66 '+nadgrids="dir with space/myconus"' +to +proj=latlong +datum=NAD83 in: | 111d00'00.000"W 44d00'00.000"N 0.0 111d00'00.000"W 39d00'00.000"N 0.0 out: |- 111d0'2.788"W 43d59'59.725"N 0.000 111d0'2.604"W 38d59'59.912"N 0.000 - comment: Test --bbox -141.01,40.04,-47.74,83.17 NAD27 NAD83 (using NTv2) args: --bbox -141.01,40.04,-47.74,83.17 NAD27 NAD83 in: 40.5 -60 out: | 40d30'0.368"N 59d59'56.617"W 0.000 - comment: Test --area "USA - CONUS including EEZ" NAD27 NAD83 (using conus) args: --area "USA - CONUS including EEZ" NAD27 NAD83 in: | 44 -111 40.5 -60 out: | 43d59'59.725"N 111d0'2.788"W 0.000 * * inf - comment: Test MD used where available args: +proj=latlong +ellps=clrk66 +nadgrids=MD,conus +to +proj=latlong +datum=NAD83 in: | 79d58'00.000"W 37d02'00.000"N 0.0 79d58'00.000"W 36d58'00.000"N 0.0 out: |- 79d58'0.005"W 37d1'59.998"N 0.000 79d57'59.128"W 36d58'0.501"N 0.000 - comment: Similar to previous test, but using only conus args: +proj=latlong +ellps=clrk66 +nadgrids=conus +to +proj=latlong +datum=NAD83 in: | 79d58'00.000"W 37d02'00.000"N 0.0 79d58'00.000"W 36d58'00.000"N 0.0 out: |- 79d57'59.126"W 37d2'0.501"N 0.000 79d57'59.128"W 36d58'0.501"N 0.000 - comment: > Test that we use grid shift files even if we are right on the edge or even a wee bit outside (#141). # Our test points are (1) right on mesh corner, (2) outside but within # epsilon (3) inside a bit (4) outside by more than epsilon args: +proj=latlong +ellps=WGS84 +nadgrids=ntf_r93.gsb,null +to +proj=latlong +datum=WGS84 -f %.12f in: | -5.5 52.0 -5.5000000000001 52.0000000000001 -5.4999 51.9999 -5.5001 52.0 out: |- -5.501106465528 51.999890470284 0.000000000000 -5.501106465529 51.999890470284 0.000000000000 -5.501006458305 51.999790470257 0.000000000000 -5.500100000000 52.000000000000 0.000000000000 - comment: Inverted version of previous test args: +proj=latlong +datum=WGS84 +to +proj=latlong +ellps=WGS84 +nadgrids=ntf_r93.gsb,null -f %.12f in: | -5.5 52.0 -5.5000000000001 52.0000000000001 -5.4999 51.9999 -5.5001 52.0 out: |- -5.498893534472 52.000109529716 0.000000000000 -5.498893534472 52.000109529717 0.000000000000 -5.498793593803 52.000009531513 0.000000000000 -5.500100000000 52.000000000000 0.000000000000 - comment: "NAD27 -> NAD83: 1st through ntv1 or ntv2, 2nd through conus" args: NAD27 NAD83 -f %.4f in: | 55d00'00.000"N 111d00'00.000"W 0.0 39d00'00.000"N 111d00'00.000"W 0.0 out: |- 55.0001 -111.0009 0.0000 39.0000 -111.0007 0.0000 - comment: WGS84 (2D) -> WGS84+EGM96 args: +init=epsg:4326 +to +init=epsg:4326 +geoidgrids=egm96_15.gtx in: 2dE 49dN 0 out: |- 2.00 49.00 0.00 - comment: WGS84 (3D) -> WGS84+EGM96 args: +init=epsg:4979 +to +init=epsg:4326 +geoidgrids=egm96_15.gtx in: 2dE 49dN 0 out: |- 2.00 49.00 -45.06 - comment: WGS84 (2D), promoted to 3D -> WGS84+EGM96 args: --3d +init=epsg:4326 +to +init=epsg:4326 +geoidgrids=egm96_15.gtx in: 2dE 49dN 0 out: |- 2.00 49.00 -45.06 proj-9.8.1/test/cli/test_proj_nad27.yaml000664 001750 001750 00000141522 15166171715 020076 0ustar00eveneven000000 000000 comment: > These tests are from an old script "test27" from the 1990s used to test nad27 init files with most of the SPCS zones. It was originally generated from execution of NMD's program l176, where the second pair of numbers are respective easting and northing output. Original versions of proj varied in the .001ft range with projections using Transverse Mercator due to greater precision of meridional distance function. exe: proj tests: - args: +units=us-ft +init=nad27:5001 -E -f %.3f in: -134d00'00.000 55d00'00.000 2615716.535 1156768.938 AK 1 GP1 out: "-134d00'00.000 55d00'00.000\t2615716.535\t1156768.938 2615716.535 1156768.938 AK 1 GP1" - args: +units=us-ft +init=nad27:5001 -f %.3f in: -133d40'00.000 57d00'00.000 2685642.815 1887198.473 AK 1 GP2 out: "2685642.815\t1887198.473 2685642.815 1887198.473 AK 1 GP2" - args: +units=us-ft +init=nad27:5001 -f %.3f in: -131d35'45.432 54d39'02.654 3124247.971 1035731.647 AK 1 GP3 out: "3124247.971\t1035731.647 3124247.971 1035731.647 AK 1 GP3" - args: +units=us-ft +init=nad27:5001 -f %.3f in: -129d32'30.000 54d32'30.000 3561180.429 1015414.284 AK 1 GP4 out: "3561180.429\t1015414.284 3561180.429 1015414.284 AK 1 GP4" - args: +units=us-ft +init=nad27:5001 -f %.3f in: -141d30'00.000 60d30'00.000 1275974.313 3248584.184 AK 1 GP6 out: "1275974.313\t3248584.184 1275974.313 3248584.184 AK 1 GP6" - args: +units=us-ft +init=nad27:5002 -f %.3f in: -142d00'00.000 56d30'30.000 500000.000 916085.508 AK 2 GP1 out: "500000.000\t916085.508 500000.000 916085.508 AK 2 GP1" - args: +units=us-ft +init=nad27:5003 -f %.3f in: -146d00'00.000 56d30'30.000 500000.000 916085.508 AK 3 GP1 out: "500000.000\t916085.508 500000.000 916085.508 AK 3 GP1" - args: +units=us-ft +init=nad27:5004 -f %.3f in: -150d00'00.000 56d30'30.000 500000.000 916085.508 AK 4 GP1 out: "500000.000\t916085.508 500000.000 916085.508 AK 4 GP1" - args: +units=us-ft +init=nad27:5005 -f %.3f in: -152d28'56.134 60d53'28.765 770312.640 2520850.031 AK 5 GP1 out: "770312.640\t2520850.030 770312.640 2520850.031 AK 5 GP1" - args: +units=us-ft +init=nad27:5005 -f %.3f in: -154d00'00.000 56d30'30.000 500000.000 916085.508 AK 5 GP2 out: "500000.000\t916085.508 500000.000 916085.508 AK 5 GP2" - args: +units=us-ft +init=nad27:5006 -f %.3f in: -155d00'00.000 71d00'00.000 857636.168 6224356.320 AK 6 GP1 out: "857636.168\t6224356.319 857636.168 6224356.320 AK 6 GP1" - args: +units=us-ft +init=nad27:5006 -f %.3f in: -158d00'00.000 71d00'00.000 500000.000 6215501.078 AK 6 GP1 out: "500000.000\t6215501.077 500000.000 6215501.078 AK 6 GP1" - args: +units=us-ft +init=nad27:5007 -f %.3f in: -162d00'00.000 65d15'00.000 700000.000 4111525.687 AK 7 GP1 out: "700000.000\t4111525.685 700000.000 4111525.687 AK 7 GP1" - args: +units=us-ft +init=nad27:5008 -f %.3f in: -166d00'00.000 65d15'00.000 500000.000 4111525.687 AK 8 GP1 out: "500000.000\t4111525.685 500000.000 4111525.687 AK 8 GP1" - args: +units=us-ft +init=nad27:5009 -f %.3f in: -170d00'00.000 63d20'00.000 600000.000 3410550.008 AK 9 GP1 out: "600000.000\t3410550.007 600000.000 3410550.008 AK 9 GP1" - args: +units=us-ft +init=nad27:5010 -f %.3f in: -164d02'30.000 54d27'30.000 5533424.392 1473805.123 AK10 GP1 out: "5533424.392\t1473805.123 5533424.392 1473805.123 AK10 GP1" - args: +units=us-ft +init=nad27:5010 -f %.3f in: -176d00'00.000 52d30'00.000 3000000.000 547583.333 AK10 GP2 out: "3000000.000\t547583.333 3000000.000 547583.333 AK10 GP2" - args: +units=us-ft +init=nad27:101 -f %.3f in: -85d50'00.000 31d20'00.000 500000.000 303093.746 AL E GP1 out: "500000.000\t303093.746 500000.000 303093.746 AL E GP1" - args: +units=us-ft +init=nad27:101 -f %.3f in: -85d12'41.738 32d38'57.737 691376.573 782394.791 AL E GP2 out: "691376.573\t782394.791 691376.573 782394.791 AL E GP2" - args: +units=us-ft +init=nad27:101 -f %.3f in: -86d36'58.670 34d48'58.708 264979.900 1571249.667 AL E GP3 out: "264979.900\t1571249.667 264979.900 1571249.667 AL E GP3" - args: +units=us-ft +init=nad27:102 -f %.3f in: -87d30'00.000 33d20'00.000 500000.000 1212487.425 AL W GP1 out: "500000.000\t1212487.425 500000.000 1212487.425 AL W GP1" - args: +units=us-ft +init=nad27:102 -f %.3f in: -87d30'00.000 33d20'30.000 500000.000 1215519.455 AL W GP2 out: "500000.000\t1215519.455 500000.000 1215519.455 AL W GP2" - args: +units=us-ft +init=nad27:301 -f %.3f in: -91d34'46.321 35d18'37.443 2125448.748 355890.988 AR N GP1 out: "2125448.748\t355890.988 2125448.748 355890.988 AR N GP1" - args: +units=us-ft +init=nad27:301 -f %.3f in: -92d04'11.625 35d19'34.269 1979150.162 361375.766 AR N GP2 out: "1979150.162\t361375.766 1979150.162 361375.766 AR N GP2" - args: +units=us-ft +init=nad27:302 -f %.3f in: -92d00'00.000 34d45'00.000 2000000.000 758096.040 AR S GP1 out: "2000000.000\t758096.040 2000000.000 758096.040 AR S GP1" - args: +units=us-ft +init=nad27:302 -f %.3f in: -92d00'00.000 33d15'00.000 2000000.000 212263.845 AR S GP2 out: "2000000.000\t212263.845 2000000.000 212263.845 AR S GP2" - args: +units=us-ft +init=nad27:5300 -f %.3f in: -170d00'00.000 -14d16'00.000 500000.000 312234.650 AS GP1 out: "500000.000\t312234.650 500000.000 312234.650 AS GP1" - args: +units=us-ft +init=nad27:5300 -f %.3f in: -166d50'38.406 -9d34'41.556 1640416.676 2007870.029 AS GP2 out: "1640416.676\t2007870.029 1640416.676 2007870.029 AS GP2" - args: +units=us-ft +init=nad27:202 -f %.3f in: -111d55'00.000 34d45'00.000 500000.000 1364267.386 AZ C GP1 out: "500000.000\t1364267.386 500000.000 1364267.386 AZ C GP1" - args: +units=us-ft +init=nad27:202 -f %.3f in: -111d55'00.000 32d20'00.000 500000.000 484978.270 AZ C GP2 out: "500000.000\t484978.270 500000.000 484978.270 AZ C GP2" - args: +units=us-ft +init=nad27:201 -f %.3f in: -110d24'59.771 35d09'58.568 425301.125 1515853.426 AZ E GP1 out: "425301.125\t1515853.425 425301.125 1515853.426 AZ E GP1" - args: +units=us-ft +init=nad27:201 -f %.3f in: -109d34'33.127 31d59'53.103 683147.830 363527.538 AZ E GP2 out: "683147.830\t363527.538 683147.830 363527.538 AZ E GP2" - args: +units=us-ft +init=nad27:201 -f %.3f in: -110d30'34.948 35d07'28.243 397422.297 1500739.241 AZ E GP3 out: "397422.297\t1500739.241 397422.297 1500739.241 AZ E GP3" - args: +units=us-ft +init=nad27:201 -f %.3f in: -109d45'13.226 32d08'41.778 627823.092 416691.532 AZ E GP4 out: "627823.092\t416691.532 627823.092 416691.532 AZ E GP4" - args: +units=us-ft +init=nad27:203 -f %.3f in: -113d45'00.000 34d45'00.000 500000.000 1364312.866 AZ W GP1 out: "500000.000\t1364312.866 500000.000 1364312.866 AZ W GP1" - args: +units=us-ft +init=nad27:203 -f %.3f in: -113d45'00.000 34d45'30.000 500000.000 1367345.603 AZ W GP2 out: "500000.000\t1367345.603 500000.000 1367345.603 AZ W GP2" - args: +units=us-ft +init=nad27:203 -f %.3f in: -113d45'00.000 34d46'00.000 500000.000 1370378.345 AZ W GP3 out: "500000.000\t1370378.345 500000.000 1370378.345 AZ W GP3" - args: +units=us-ft +init=nad27:401 -f %.3f in: -122d00'00.000 41d30'00.000 2000000.000 789314.699 CA 1 GP1 out: "2000000.000\t789314.699 2000000.000 789314.699 CA 1 GP1" - args: +units=us-ft +init=nad27:401 -f %.3f in: -122d00'00.000 41d30'30.000 2000000.000 792351.052 CA 1 GP2 out: "2000000.000\t792351.052 2000000.000 792351.052 CA 1 GP2" - args: +units=us-ft +init=nad27:402 -f %.3f in: -122d00'00.000 39d20'00.000 2000000.000 606975.074 CA 2 GP1 out: "2000000.000\t606975.074 2000000.000 606975.074 CA 2 GP1" - args: +units=us-ft +init=nad27:402 -f %.3f in: -122d00'00.000 39d20'30.000 2000000.000 610010.158 CA 2 GP2 out: "2000000.000\t610010.158 2000000.000 610010.158 CA 2 GP2" - args: +units=us-ft +init=nad27:403 -f %.3f in: -120d30'00.000 37d05'00.000 2000000.000 212394.029 CA 3 GP1 out: "2000000.000\t212394.029 2000000.000 212394.029 CA 3 GP1" - args: +units=us-ft +init=nad27:403 -f %.3f in: -121d22'26.019 37d30'30.324 1746516.910 368350.900 CA 3 GP2 out: "1746516.910\t368350.900 1746516.910 368350.900 CA 3 GP2" - args: +units=us-ft +init=nad27:403 -f %.3f in: -119d46'32.733 37d07'41.470 2211146.746 229541.692 CA 3 GP3 out: "2211146.746\t229541.692 2211146.746 229541.692 CA 3 GP3" - args: +units=us-ft +init=nad27:403 -f %.3f in: -119d38'26.434 36d55'48.009 2251190.696 157720.169 CA 3 GP4 out: "2251190.696\t157720.169 2251190.696 157720.169 CA 3 GP4" - args: +units=us-ft +init=nad27:403 -f %.3f in: -120d42'59.779 38d06'52.815 1937681.203 587984.757 CA 3 GP5 out: "1937681.203\t587984.757 1937681.203 587984.757 CA 3 GP5" - args: +units=us-ft +init=nad27:404 -f %.3f in: -119d00'00.000 36d20'00.000 2000000.000 364036.106 CA 4 GP1 out: "2000000.000\t364036.106 2000000.000 364036.106 CA 4 GP1" - args: +units=us-ft +init=nad27:404 -f %.3f in: -119d00'00.000 36d20'30.000 2000000.000 367069.711 CA 4 GP2 out: "2000000.000\t367069.711 2000000.000 367069.711 CA 4 GP2" - args: +units=us-ft +init=nad27:405 -f %.3f in: -118d00'00.000 34d45'00.000 2000000.000 454894.032 CA 5 GP1 out: "2000000.000\t454894.032 2000000.000 454894.032 CA 5 GP1" - args: +units=us-ft +init=nad27:405 -f %.3f in: -118d00'00.000 34d45'30.000 2000000.000 457926.735 CA 5 GP2 out: "2000000.000\t457926.735 2000000.000 457926.735 CA 5 GP2" - args: +units=us-ft +init=nad27:406 -f %.3f in: -116d15'00.000 33d20'00.000 2000000.000 424481.703 CA 6 GP1 out: "2000000.000\t424481.703 2000000.000 424481.703 CA 6 GP1" - args: +units=us-ft +init=nad27:406 -f %.3f in: -116d15'00.000 33d20'30.000 2000000.000 427513.796 CA 6 GP2 out: "2000000.000\t427513.796 2000000.000 427513.796 CA 6 GP2" - args: +units=us-ft +init=nad27:407 -f %.3f in: -118d20'00.000 34d30'00.000 4186692.580 4294365.712 CA 7 GP1 out: "4186692.580\t4294365.712 4186692.580 4294365.712 CA 7 GP1" - args: +units=us-ft +init=nad27:502 -f %.3f in: -105d30'00.000 39d15'00.000 2000000.000 515936.228 CO C GP1 out: "2000000.000\t515936.228 2000000.000 515936.228 CO C GP1" - args: +units=us-ft +init=nad27:502 -f %.3f in: -105d30'00.000 39d15'30.000 2000000.000 518971.313 CO C GP2 out: "2000000.000\t518971.313 2000000.000 518971.313 CO C GP2" - args: +units=us-ft +init=nad27:501 -f %.3f in: -108d45'55.378 40d25'33.504 1091086.832 414752.176 CO N GP1 out: "1091086.832\t414752.176 1091086.832 414752.176 CO N GP1" - args: +units=us-ft +init=nad27:501 -f %.3f in: -105d14'45.588 40d12'42.711 2070940.652 320120.166 CO N GP2 out: "2070940.652\t320120.166 2070940.652 320120.166 CO N GP2" - args: +units=us-ft +init=nad27:503 -f %.3f in: -105d30'00.000 37d30'00.000 2000000.000 303425.100 CO S GP1 out: "2000000.000\t303425.100 2000000.000 303425.100 CO S GP1" - args: +units=us-ft +init=nad27:503 -f %.3f in: -105d30'00.000 37d30'30.000 2000000.000 306459.335 CO S GP2 out: "2000000.000\t306459.335 2000000.000 306459.335 CO S GP2" - args: +units=us-ft +init=nad27:600 -f %.3f in: -72d43'30.515 41d16'55.847 606832.139 163540.219 CT GP1 out: "606832.139\t163540.219 606832.139 163540.219 CT GP1" - args: +units=us-ft +init=nad27:600 -f %.3f in: -73d01'15.609 41d13'25.985 525446.203 142415.891 CT GP2 out: "525446.203\t142415.891 525446.203 142415.891 CT GP2" - args: +units=us-ft +init=nad27:700 -f %.3f in: -75d33'00.748 39d21'15.214 462235.881 493228.846 DE GP1 out: "462235.881\t493228.846 462235.881 493228.846 DE GP1" - args: +units=us-ft +init=nad27:700 -f %.3f in: -75d19'01.889 39d45'14.765 527969.596 638870.822 DE GP2 out: "527969.596\t638870.822 527969.596 638870.822 DE GP2" - args: +units=us-ft +init=nad27:901 -f %.3f in: -80d11'00.000 25d45'00.000 768810.056 515637.939 FL E GP1 out: "768810.056\t515637.939 768810.056 515637.939 FL E GP1" - args: +units=us-ft +init=nad27:903 -f %.3f in: -82d45'52.412 29d39'06.589 2551254.254 241240.008 FL N GP1 out: "2551254.254\t241240.008 2551254.254 241240.008 FL N GP1" - args: +units=us-ft +init=nad27:903 -f %.3f in: -84d55'11.533 29d38'51.982 1866620.008 235814.655 FL N GP2 out: "1866620.008\t235814.655 1866620.008 235814.655 FL N GP2" - args: +units=us-ft +init=nad27:902 -f %.3f in: -82d38'00.000 27d47'00.000 295216.148 1254408.638 FL W GP1 out: "295216.148\t1254408.638 295216.148 1254408.638 FL W GP1" - args: +units=us-ft +init=nad27:1001 -f %.3f in: -81d27'15.592 32d38'03.003 719287.314 958818.262 GA E GP1 out: "719287.314\t958818.262 719287.314 958818.262 GA E GP1" - args: +units=us-ft +init=nad27:1001 -f %.3f in: -83d15'39.990 33d29'58.626 166361.311 1274706.363 GA E GP2 out: "166361.311\t1274706.363 166361.311 1274706.363 GA E GP2" - args: +units=us-ft +init=nad27:1002 -f %.3f in: -84d23'00.000 33d45'00.000 434141.824 1364117.672 GA W GP1 out: "434141.824\t1364117.672 434141.824 1364117.672 GA W GP1" - args: +units=us-ft +init=nad27:5400 -f %.3f in: 144d44'55.503 13d28'20.879 164041.712 164041.680 GU GP1 out: "164041.712\t164041.680 164041.712 164041.680 GU GP1" - args: +units=us-ft +init=nad27:5400 -f %.3f in: 144d38'07.193 13d20'20.538 123728.401 115623.086 GU GP2 out: "123728.401\t115623.086 123728.401 115623.086 GU GP2" - args: +units=us-ft +init=nad27:5101 -f %.3f in: -155d59'16.911 19d37'23.477 332050.939 287068.342 HI 1 GP1 out: "332050.939\t287068.342 332050.939 287068.342 HI 1 GP1" - args: +units=us-ft +init=nad27:5101 -f %.3f in: -155d18'06.262 19d31'24.578 568270.061 250663.241 HI 1 GP2 out: "568270.061\t250663.241 568270.061 250663.241 HI 1 GP2" - args: +units=us-ft +init=nad27:5101 -f %.3f in: -155d30'00.000 19d42'00.000 500000.000 314722.985 HI 1 GP3 out: "500000.000\t314722.985 500000.000 314722.985 HI 1 GP3" - args: +units=us-ft +init=nad27:5101 -f %.3f in: -155d30'00.000 19d42'30.000 500000.000 317749.315 HI 1 GP4 out: "500000.000\t317749.315 500000.000 317749.315 HI 1 GP4" - args: +units=us-ft +init=nad27:5102 -f %.3f in: -156d40'00.000 20d42'00.000 500000.000 133170.903 HI 2 GP1 out: "500000.000\t133170.903 500000.000 133170.903 HI 2 GP1" - args: +units=us-ft +init=nad27:5102 -f %.3f in: -156d40'00.000 20d42'30.000 500000.000 136197.580 HI 2 GP2 out: "500000.000\t136197.580 500000.000 136197.580 HI 2 GP2" - args: +units=us-ft +init=nad27:5103 -f %.3f in: -158d00'00.000 21d30'00.000 500000.000 121078.981 HI 3 GP1 out: "500000.000\t121078.981 500000.000 121078.981 HI 3 GP1" - args: +units=us-ft +init=nad27:5103 -f %.3f in: -158d01'30.000 21d37'30.000 491508.215 166485.537 HI 3 GP2 out: "491508.215\t166485.537 491508.215 166485.537 HI 3 GP2" - args: +units=us-ft +init=nad27:5104 -f %.3f in: -159d30'00.000 22d05'00.000 500000.000 90816.138 HI 4 GP1 out: "500000.000\t90816.138 500000.000 90816.138 HI 4 GP1" - args: +units=us-ft +init=nad27:5105 -f %.3f in: -160d10'00.000 21d42'00.000 500000.000 12108.532 HI 5 GP1 out: "500000.000\t12108.532 500000.000 12108.532 HI 5 GP1" - args: +units=us-ft +init=nad27:1401 -f %.3f in: -93d28'33.966 42d44'50.101 2006419.316 454523.076 IA N GP1 out: "2006419.316\t454523.076 2006419.316 454523.076 IA N GP1" - args: +units=us-ft +init=nad27:1401 -f %.3f in: -93d54'22.084 42d40'23.699 1890779.351 427816.212 IA N GP2 out: "1890779.351\t427816.212 1890779.351 427816.212 IA N GP2" - args: +units=us-ft +init=nad27:1402 -f %.3f in: -93d37'00.000 41d35'00.000 1968081.762 576880.709 IA S GP1 out: "1968081.762\t576880.709 1968081.762 576880.709 IA S GP1" - args: +units=us-ft +init=nad27:1102 -f %.3f in: -114d24'00.000 42d56'00.000 392878.009 461838.231 ID C GP1 out: "392878.009\t461838.231 392878.009 461838.231 ID C GP1" - args: +units=us-ft +init=nad27:1101 -f %.3f in: -111d42'29.824 43d48'07.616 621017.480 778569.749 ID E GP1 out: "621017.480\t778569.749 621017.480 778569.749 ID E GP1" - args: +units=us-ft +init=nad27:1101 -f %.3f in: -112d22'35.516 43d35'26.260 444398.356 701217.958 ID E GP2 out: "444398.356\t701217.958 444398.356 701217.958 ID E GP2" - args: +units=us-ft +init=nad27:1103 -f %.3f in: -116d22'02.592 48d07'50.941 349231.302 2357247.272 ID W GP1 out: "349231.302\t2357247.272 349231.302 2357247.272 ID W GP1" - args: +units=us-ft +init=nad27:1201 -f %.3f in: -88d07'06.790 41d46'11.855 558591.507 1858801.531 IL E GP1 out: "558591.507\t1858801.531 558591.507 1858801.531 IL E GP1" - args: +units=us-ft +init=nad27:1201 -f %.3f in: -88d41'35.208 40d43'37.202 400279.755 1478930.010 IL E GP2 out: "400279.755\t1478930.010 400279.755 1478930.010 IL E GP2" - args: +units=us-ft +init=nad27:1202 -f %.3f in: -90d10'00.000 38d30'00.000 500000.000 667527.020 IL W GP1 out: "500000.000\t667527.020 500000.000 667527.020 IL W GP1" - args: +units=us-ft +init=nad27:1301 -f %.3f in: -85d40'00.000 40d00'00.000 500000.000 910470.786 IN E GP1 out: "500000.000\t910470.785 500000.000 910470.786 IN E GP1" - args: +units=us-ft +init=nad27:1301 -f %.3f in: -85d40'00.000 40d00'30.000 500000.000 913506.351 IN E GP2 out: "500000.000\t913506.350 500000.000 913506.351 IN E GP2" - args: +units=us-ft +init=nad27:1301 -f %.3f in: -86d14'27.780 40d00'12.690 339087.973 912273.325 IN E GP3 out: "339087.973\t912273.324 339087.973 912273.325 IN E GP3" - args: +units=us-ft +init=nad27:1301 -f %.3f in: -86d14'27.790 40d00'31.660 339099.565 914192.836 IN E GP4 out: "339099.565\t914192.836 339099.565 914192.836 IN E GP4" - args: +units=us-ft +init=nad27:1301 -f %.3f in: -86d14'28.103 40d00'47.412 339085.485 915786.883 IN E GP6 out: "339085.485\t915786.883 339085.485 915786.883 IN E GP6" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -87d05'00.000 40d00'00.000 500000.000 910470.786 IN W GP1 out: "500000.000\t910470.785 500000.000 910470.786 IN W GP1" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -87d05'00.000 40d00'30.000 500000.000 913506.351 IN W GP2 out: "500000.000\t913506.350 500000.000 913506.351 IN W GP2" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -86d45'10.717 39d41'24.840 592969.921 797807.077 IN W GP3 out: "592969.921\t797807.077 592969.921 797807.077 IN W GP3" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -87d41'44.075 37d54'24.755 323351.583 148732.658 IN W GP4 out: "323351.583\t148732.658 323351.583 148732.658 IN W GP4" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -86d32'13.179 39d32'46.419 654071.692 745650.467 IN W GP5 out: "654071.692\t745650.467 654071.692 745650.467 IN W GP5" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -87d25'26.675 38d26'17.646 402398.078 341828.410 IN W GP6 out: "402398.078\t341828.410 402398.078 341828.410 IN W GP6" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -86d14'28.103 40d00'47.412 735905.989 916383.007 IN W GP7 out: "735905.989\t916383.007 735905.989 916383.007 IN W GP7" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -86d14'27.780 40d00'12.690 735964.329 912869.812 IN W GP8 out: "735964.329\t912869.812 735964.329 912869.812 IN W GP8" - args: +units=us-ft +init=nad27:1302 -f %.3f in: -86d14'27.790 40d00'31.660 735945.409 914789.331 IN W GP9 out: "735945.409\t914789.331 735945.409 914789.331 IN W GP9" - args: +units=us-ft +init=nad27:1501 -f %.3f in: -96d47'54.567 38d58'52.096 2341555.463 238196.375 KS N GP1 out: "2341555.463\t238196.375 2341555.463 238196.375 KS N GP1" - args: +units=us-ft +init=nad27:1501 -f %.3f in: -98d35'23.954 39d58'41.967 1834645.786 599682.614 KS N GP2 out: "1834645.786\t599682.614 1834645.786 599682.614 KS N GP2" - args: +units=us-ft +init=nad27:1502 -f %.3f in: -97d21'00.000 37d42'00.000 2332714.529 378302.303 KS S GP1 out: "2332714.529\t378302.303 2332714.529 378302.303 KS S GP1" - args: +units=us-ft +init=nad27:1601 -f %.3f in: -84d05'43.283 38d14'35.963 2044414.776 270720.831 KY N GP1 out: "2044414.776\t270720.831 2044414.776 270720.831 KY N GP1" - args: +units=us-ft +init=nad27:1601 -f %.3f in: -84d26'49.265 39d04'03.099 1944057.054 570906.807 KY N GP2 out: "1944057.054\t570906.807 1944057.054 570906.807 KY N GP2" - args: +units=us-ft +init=nad27:1602 -f %.3f in: -86d05'00.000 37d10'00.000 1902871.440 303569.007 KY S GP1 out: "1902871.440\t303569.007 1902871.440 303569.007 KY S GP1" - args: +units=us-ft +init=nad27:1701 -f %.3f in: -91d34'46.483 31d57'26.243 2285456.159 470671.781 LA N GP1 out: "2285456.159\t470671.781 2285456.159 470671.781 LA N GP1" - args: +units=us-ft +init=nad27:1701 -f %.3f in: -92d52'46.615 32d54'52.264 1883486.181 817905.853 LA N GP2 out: "1883486.181\t817905.853 1883486.181 817905.853 LA N GP2" - args: +units=us-ft +init=nad27:1701 -f %.3f in: -91d29'09.480 31d56'44.721 2314527.078 466735.568 LA N GP3 out: "2314527.078\t466735.568 2314527.078 466735.568 LA N GP3" - args: +units=us-ft +init=nad27:1701 -f %.3f in: -93d59'38.241 32d48'43.467 1540965.776 783590.902 LA N GP4 out: "1540965.776\t783590.902 1540965.776 783590.902 LA N GP4" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d00'00.000 28d50'00.000 2747176.527 68218.410 LA S GP1 out: "2747176.527\t68218.410 2747176.527 68218.410 LA S GP1" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d30'00.000 28d50'00.000 2587082.796 65307.429 LA S GP2 out: "2587082.796\t65307.429 2587082.796 65307.429 LA S GP2" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d29'59.999 29d19'59.994 2584173.994 247106.020 LA S GP3 out: "2584173.994\t247106.020 2584173.994 247106.020 LA S GP3" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d00'00.004 29d19'59.998 2743474.038 250002.972 LA S GP4 out: "2743474.038\t250002.972 2743474.038 250002.972 LA S GP4" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d10'23.487 29d20'32.615 2688234.966 252215.035 LA S GP5 out: "2688234.966\t252215.035 2688234.966 252215.035 LA S GP5" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d06'34.632 29d15'19.642 2709099.980 220994.973 LA S GP6 out: "2709099.980\t220994.973 2709099.980 220994.973 LA S GP6" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d01'33.803 29d07'47.918 2736661.987 175901.967 LA S GP7 out: "2736661.987\t175901.967 2736661.987 175901.967 LA S GP7" - args: +units=us-ft +init=nad27:1702 -f %.3f in: -89d08'45.781 28d58'27.979 2699434.976 118600.021 LA S GP9 out: "2699434.976\t118600.021 2699434.976 118600.021 LA S GP9" - args: +units=us-ft +init=nad27:2002 -f %.3f in: -70d30'00.000 41d30'00.000 200000.000 182180.613 MA I GP1 out: "200000.000\t182180.613 200000.000 182180.613 MA I GP1" - args: +units=us-ft +init=nad27:2001 -f %.3f in: -70d27'00.716 41d40'15.808 886823.958 246295.510 MA M GP1 out: "886823.958\t246295.510 886823.958 246295.510 MA M GP1" - args: +units=us-ft +init=nad27:2001 -f %.3f in: -73d25'59.173 42d06'06.860 75432.106 407473.253 MA M GP2 out: "75432.106\t407473.253 75432.106 407473.253 MA M GP2" - args: +units=us-ft +init=nad27:1900 -f %.3f in: -76d11'27.492 39d12'06.132 1029272.677 499353.154 MD GP1 out: "1029272.677\t499353.154 1029272.677 499353.154 MD GP1" - args: +units=us-ft +init=nad27:1900 -f %.3f in: -77d02'30.406 38d26'37.492 788033.549 222300.512 MD GP2 out: "788033.549\t222300.512 788033.549 222300.512 MD GP2" - args: +units=us-ft +init=nad27:1900 -f %.3f in: -77d30'10.460 38d59'25.903 657055.715 421819.661 MD GP3 out: "657055.715\t421819.661 657055.715 421819.661 MD GP3" - args: +units=us-ft +init=nad27:1801 -f %.3f in: -68d24'25.489 46d32'46.920 523379.868 989125.403 ME E GP1 out: "523379.868\t989125.403 523379.868 989125.403 ME E GP1" - args: +units=us-ft +init=nad27:1801 -f %.3f in: -68d37'29.366 47d02'12.659 468876.638 1168006.571 ME E GP2 out: "468876.638\t1168006.571 468876.638 1168006.571 ME E GP2" - args: +units=us-ft +init=nad27:1802 -f %.3f in: -70d16'00.000 43d40'00.000 473538.933 303746.300 ME W GP1 out: "473538.933\t303746.300 473538.933 303746.300 ME W GP1" - args: +units=us-ft +init=nad27:2112 -f %.3f in: -85d40'00.000 44d45'00.000 1653612.784 525406.529 MI C GP1 out: "1653612.784\t525406.529 1653612.784 525406.529 MI C GP1" - args: +units=us-ft +init=nad27:2113 -f %.3f in: -83d29'17.919 42d19'19.299 2228532.810 300724.433 MI S GP1 out: "2228532.810\t300724.433 2228532.810 300724.433 MI S GP1" - args: +units=us-ft +init=nad27:2113 -f %.3f in: -83d35'24.656 42d20'02.682 2200944.119 304856.048 MI S GP2 out: "2200944.119\t304856.048 2200944.119 304856.048 MI S GP2" - args: +units=us-ft +init=nad27:2113 -f %.3f in: -85d55'26.569 41d50'10.236 1566471.427 126614.633 MI S GP3 out: "1566471.427\t126614.633 1566471.427 126614.633 MI S GP3" - args: +units=us-ft +init=nad27:2113 -f %.3f in: -85d45'59.490 41d49'22.346 1609315.113 120996.336 MI S GP4 out: "1609315.113\t120996.336 1609315.113 120996.336 MI S GP4" - args: +units=us-ft +init=nad27:2103 -f %.3f in: -89d20'00.000 46d50'00.000 353999.488 1944621.410 MI W GP1 out: "353999.488\t1944621.410 353999.488 1944621.410 MI W GP1" - args: +units=us-ft +init=nad27:2201 -f %.3f in: -91d27'51.183 47d08'19.177 2407087.310 237254.364 MN N GP1 out: "2407087.310\t237254.364 2407087.310 237254.364 MN N GP1" - args: +units=us-ft +init=nad27:2201 -f %.3f in: -95d51'05.998 48d19'26.552 1330690.998 677229.560 MN N GP2 out: "1330690.998\t677229.560 1330690.998 677229.560 MN N GP2" - args: +units=us-ft +init=nad27:2402 -f %.3f in: -92d30'00.000 38d15'00.000 500000.000 879833.618 MO C GP1 out: "500000.000\t879833.618 500000.000 879833.618 MO C GP1" - args: +units=us-ft +init=nad27:2402 -f %.3f in: -92d30'00.000 38d15'30.000 500000.000 882868.158 MO C GP2 out: "500000.000\t882868.158 500000.000 882868.158 MO C GP2" - args: +units=us-ft +init=nad27:2401 -f %.3f in: -91d42'04.297 37d22'05.932 150919.587 561018.127 MO E GP1 out: "150919.587\t561018.126 150919.587 561018.127 MO E GP1" - args: +units=us-ft +init=nad27:2401 -f %.3f in: -90d08'08.896 36d53'44.124 606497.861 386893.306 MO E GP2 out: "606497.861\t386893.306 606497.861 386893.306 MO E GP2" - args: +units=us-ft +init=nad27:2403 -f %.3f in: -94d30'00.000 38d15'00.000 500000.000 758504.732 MO W GP1 out: "500000.000\t758504.732 500000.000 758504.732 MO W GP1" - args: +units=us-ft +init=nad27:2403 -f %.3f in: -94d30'00.000 38d15'30.000 500000.000 761539.296 MO W GP2 out: "500000.000\t761539.296 500000.000 761539.296 MO W GP2" - args: +units=us-ft +init=nad27:2301 -f %.3f in: -89d10'14.013 30d30'51.338 393805.810 308399.629 MS E GP1 out: "393805.810\t308399.629 393805.810 308399.629 MS E GP1" - args: +units=us-ft +init=nad27:2301 -f %.3f in: -88d26'04.338 30d43'01.454 625321.316 382224.788 MS E GP2 out: "625321.316\t382224.788 625321.316 382224.788 MS E GP2" - args: +units=us-ft +init=nad27:2302 -f %.3f in: -90d10'00.000 32d17'00.000 551507.962 648697.041 MS W GP1 out: "551507.962\t648697.041 551507.962 648697.041 MS W GP1" - args: +units=us-ft +init=nad27:2502 -f %.3f in: -109d25'00.000 47d05'00.000 2020760.609 455889.692 MT C GP1 out: "2020760.609\t455889.692 2020760.609 455889.692 MT C GP1" - args: +units=us-ft +init=nad27:2501 -f %.3f in: -106d29'11.521 47d52'21.103 2739443.845 332808.759 MT N GP1 out: "2739443.845\t332808.759 2739443.845 332808.759 MT N GP1" - args: +units=us-ft +init=nad27:2501 -f %.3f in: -114d30'43.122 48d52'46.764 794693.447 725072.329 MT N GP2 out: "794693.447\t725072.329 794693.447 725072.329 MT N GP2" - args: +units=us-ft +init=nad27:2503 -f %.3f in: -109d15'00.000 45d39'00.000 2063931.561 601700.560 MT S GP1 out: "2063931.561\t601700.560 2063931.561 601700.560 MT S GP1" - args: +units=us-ft +init=nad27:3200 -f %.3f in: -81d12'31.790 35d09'31.049 1339854.041 519988.737 NC GP1 out: "1339854.041\t519988.737 1339854.041 519988.737 NC GP1" - args: +units=us-ft +init=nad27:3200 -f %.3f in: -76d31'54.918 35d33'51.452 2733941.071 669408.798 NC GP2 out: "2733941.071\t669408.798 2733941.071 669408.798 NC GP2" - args: +units=us-ft +init=nad27:3200 -f %.3f in: -78d28'26.580 36d15'15.480 2155088.262 911860.343 NC GP3 out: "2155088.262\t911860.343 2155088.262 911860.343 NC GP3" - args: +units=us-ft +init=nad27:3301 -f %.3f in: -98d46'03.232 48d08'13.483 2422983.823 419525.823 ND N GP1 out: "2422983.823\t419525.823 2422983.823 419525.823 ND N GP1" - args: +units=us-ft +init=nad27:3301 -f %.3f in: -101d18'21.456 47d39'18.935 1801367.700 240053.997 ND N GP2 out: "1801367.700\t240053.997 1801367.700 240053.997 ND N GP2" - args: +units=us-ft +init=nad27:3302 -f %.3f in: -100d46'00.000 46d48'00.000 1933213.911 413422.204 ND S GP1 out: "1933213.911\t413422.204 1933213.911 413422.204 ND S GP1" - args: +units=us-ft +init=nad27:2601 -f %.3f in: -96d17'52.930 42d04'48.305 3004688.243 293978.208 NE N GP1 out: "3004688.243\t293978.208 3004688.243 293978.208 NE N GP1" - args: +units=us-ft +init=nad27:2601 -f %.3f in: -100d49'26.949 41d58'54.025 1775916.042 237340.591 NE N GP2 out: "1775916.042\t237340.591 1775916.042 237340.591 NE N GP2" - args: +units=us-ft +init=nad27:2602 -f %.3f in: -96d43'00.000 40d49'00.000 2770252.364 431225.617 NE S GP1 out: "2770252.364\t431225.617 2770252.364 431225.617 NE S GP1" - args: +units=us-ft +init=nad27:2800 -f %.3f in: -70d56'11.287 43d08'15.006 694907.496 233185.793 NH GP1 out: "694907.496\t233185.793 694907.496 233185.793 NH GP1" - args: +units=us-ft +init=nad27:2800 -f %.3f in: -72d32'32.197 42d51'25.984 265213.564 131404.574 NH GP2 out: "265213.564\t131404.574 265213.564 131404.574 NH GP2" - args: +units=us-ft +init=nad27:2900 -f %.3f in: -74d13'55.737 39d52'02.095 2121971.499 376878.657 NJ GP1 out: "2121971.499\t376878.657 2121971.499 376878.657 NJ GP1" - args: +units=us-ft +init=nad27:2900 -f %.3f in: -74d51'24.058 41d12'07.401 1947709.569 862915.876 NJ GP2 out: "1947709.569\t862915.876 1947709.569 862915.876 NJ GP2" - args: +units=us-ft +init=nad27:3002 -f %.3f in: -106d15'00.000 33d30'00.000 500000.000 909419.295 NM C GP1 out: "500000.000\t909419.295 500000.000 909419.295 NM C GP1" - args: +units=us-ft +init=nad27:3002 -f %.3f in: -106d15'00.000 33d30'30.000 500000.000 912451.306 NM C GP2 out: "500000.000\t912451.306 500000.000 912451.306 NM C GP2" - args: +units=us-ft +init=nad27:3001 -f %.3f in: -104d11'42.410 33d17'21.732 542236.924 832820.301 NM E GP1 out: "542236.924\t832820.301 542236.924 832820.301 NM E GP1" - args: +units=us-ft +init=nad27:3001 -f %.3f in: -104d47'37.948 33d22'32.349 359406.535 864495.732 NM E GP2 out: "359406.535\t864495.731 359406.535 864495.732 NM E GP2" - args: +units=us-ft +init=nad27:3003 -f %.3f in: -107d50'00.000 32d30'00.000 500000.000 545616.897 NM W GP1 out: "500000.000\t545616.897 500000.000 545616.897 NM W GP1" - args: +units=us-ft +init=nad27:3003 -f %.3f in: -107d50'00.000 32d30'30.000 500000.000 548648.466 NM W GP2 out: "500000.000\t548648.466 500000.000 548648.466 NM W GP2" - args: +units=us-ft +init=nad27:2702 -f %.3f in: -116d48'00.000 36d58'00.000 461048.286 806858.042 NV C GP1 out: "461048.286\t806858.042 461048.286 806858.042 NV C GP1" - args: +units=us-ft +init=nad27:2701 -f %.3f in: -114d49'09.337 35d43'09.299 726805.224 353637.053 NV E GP1 out: "726805.224\t353637.053 726805.224 353637.053 NV E GP1" - args: +units=us-ft +init=nad27:2701 -f %.3f in: -116d50'32.766 41d30'37.869 155162.931 2464191.579 NV E GP2 out: "155162.931\t2464191.578 155162.931 2464191.579 NV E GP2" - args: +units=us-ft +init=nad27:2703 -f %.3f in: -119d49'00.000 39d32'00.000 152145.548 1743820.924 NV W GP1 out: "152145.548\t1743820.923 152145.548 1743820.924 NV W GP1" - args: +units=us-ft +init=nad27:3102 -f %.3f in: -76d10'00.000 43d05'00.000 611313.134 1123706.621 NY C GP1 out: "611313.134\t1123706.620 611313.134 1123706.621 NY C GP1" - args: +units=us-ft +init=nad27:3101 -f %.3f in: -74d02'53.671 42d17'01.775 577147.690 832219.885 NY E GP1 out: "577147.690\t832219.885 577147.690 832219.885 NY E GP1" - args: +units=us-ft +init=nad27:3101 -f %.3f in: -74d44'39.818 42d30'07.382 389148.814 911884.889 NY E GP2 out: "389148.814\t911884.889 389148.814 911884.889 NY E GP2" - args: +units=us-ft +init=nad27:3104 -f %.3f in: -73d02'36.247 40d47'50.624 2264860.626 209793.919 NY L GP1 out: "2264860.626\t209793.919 2264860.626 209793.919 NY L GP1" - args: +units=us-ft +init=nad27:3104 -f %.3f in: -74d06'58.125 40d36'07.281 1967746.807 137190.013 NY L GP2 out: "1967746.807\t137190.013 1967746.807 137190.013 NY L GP2" - args: +units=us-ft +init=nad27:3104 -f %.3f in: -74d00'00.000 40d45'00.000 2000000.000 191080.202 NY L GP3 out: "2000000.000\t191080.202 2000000.000 191080.202 NY L GP3" - args: +units=us-ft +init=nad27:3104 -f %.3f in: -73d15'00.000 40d37'30.000 2208197.581 146431.086 NY L GP4 out: "2208197.581\t146431.086 2208197.581 146431.086 NY L GP4" - args: +units=us-ft +init=nad27:3104 -f %.3f in: -73d22'30.000 40d45'00.000 2173173.707 191697.996 NY L GP5 out: "2173173.707\t191697.996 2173173.707 191697.996 NY L GP5" - args: +units=us-ft +init=nad27:3103 -f %.3f in: -78d51'00.000 42d54'00.000 428547.567 1056727.674 NY W GP1 out: "428547.567\t1056727.674 428547.567 1056727.674 NY W GP1" - args: +units=us-ft +init=nad27:3401 -f %.3f in: -80d49'28.238 40d17'50.894 2467363.986 234305.751 OH N GP1 out: "2467363.986\t234305.751 2467363.986 234305.751 OH N GP1" - args: +units=us-ft +init=nad27:3401 -f %.3f in: -82d37'31.021 40d20'14.678 1965071.932 244391.910 OH N GP2 out: "1965071.932\t244391.910 1965071.932 244391.910 OH N GP2" - args: +units=us-ft +init=nad27:3402 -f %.3f in: -84d15'00.000 39d45'00.000 1507970.925 642141.152 OH S GP1 out: "1507970.925\t642141.152 1507970.925 642141.152 OH S GP1" - args: +units=us-ft +init=nad27:3501 -f %.3f in: -98d42'45.414 36d50'19.568 1791448.615 670119.442 OK N GP1 out: "1791448.615\t670119.442 1791448.615 670119.442 OK N GP1" - args: +units=us-ft +init=nad27:3501 -f %.3f in: -95d38'44.046 35d20'36.925 2702176.218 133585.952 OK N GP2 out: "2702176.218\t133585.952 2702176.218 133585.952 OK N GP2" - args: +units=us-ft +init=nad27:3502 -f %.3f in: -97d08'00.000 34d34'00.000 2260914.787 449942.599 OK S GP1 out: "2260914.787\t449942.599 2260914.787 449942.599 OK S GP1" - args: +units=us-ft +init=nad27:3601 -f %.3f in: -123d41'00.000 45d31'00.000 1184216.898 690530.257 OR N GP1 out: "1184216.898\t690530.257 1184216.898 690530.257 OR N GP1" - args: +units=us-ft +init=nad27:3602 -f %.3f in: -119d46'26.562 44d24'25.943 2189746.353 999672.239 OR S GP1 out: "2189746.353\t999672.239 2189746.353 999672.239 OR S GP1" - args: +units=us-ft +init=nad27:3602 -f %.3f in: -121d09'56.105 44d23'08.924 1825970.576 991740.899 OR S GP2 out: "1825970.576\t991740.899 1825970.576 991740.899 OR S GP2" - args: +units=us-ft +init=nad27:3701 -f %.3f in: -74d33'20.644 41d23'48.566 2876202.339 464358.775 PA N GP1 out: "2876202.339\t464358.775 2876202.339 464358.775 PA N GP1" - args: +units=us-ft +init=nad27:3701 -f %.3f in: -78d09'48.121 40d51'35.455 1885652.438 252829.477 PA N GP2 out: "1885652.438\t252829.477 1885652.438 252829.477 PA N GP2" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -67d08'56.930 18d29'56.972 251990.753 242253.319 PR F GP1 out: "251990.753\t242253.319 251990.753 242253.319 PR F GP1" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -66d52'30.000 18d15'00.000 346756.548 151479.295 PR F GP2 out: "346756.548\t151479.295 346756.548 151479.295 PR F GP2" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -66d26'00.000 18d15'00.000 500000.000 151294.491 PR F GP3 out: "500000.000\t151294.491 500000.000 151294.491 PR F GP3" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -66d26'00.000 18d30'00.000 500000.000 242074.012 PR F GP4 out: "500000.000\t242074.012 500000.000 242074.012 PR F GP4" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -67d08'56.930 18d29'56.972 251990.753 242253.319 PR M GP1 out: "251990.753\t242253.319 251990.753 242253.319 PR M GP1" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -66d52'30.000 18d15'00.000 346756.548 151479.295 PR M GP2 out: "346756.548\t151479.295 346756.548 151479.295 PR M GP2" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -66d26'00.000 18d15'00.000 500000.000 151294.491 PR M GP3 out: "500000.000\t151294.491 500000.000 151294.491 PR M GP3" - args: +units=us-ft +init=nad27:5201 -f %.3f in: -66d26'00.000 18d30'00.000 500000.000 242074.012 PR M GP4 out: "500000.000\t242074.012 500000.000 242074.012 PR M GP4" - args: +units=us-ft +init=nad27:5202 -f %.3f in: -64d43'00.000 17d40'00.000 1097602.972 42283.509 PS S GP1 out: "1097602.972\t42283.509 1097602.972 42283.509 PS S GP1" - args: +units=us-ft +init=nad27:3800 -f %.3f in: -71d16'00.833 41d32'24.848 563817.074 166563.592 RI GP1 out: "563817.074\t166563.592 563817.074 166563.592 RI GP1" - args: +units=us-ft +init=nad27:3800 -f %.3f in: -71d37'13.730 41d23'53.266 466943.554 114721.079 RI GP2 out: "466943.554\t114721.079 466943.554 114721.079 RI GP2" - args: +units=us-ft +init=nad27:3901 -f %.3f in: -80d32'30.000 34d32'30.000 2138028.224 561330.721 SC N GP1 out: "2138028.224\t561330.721 2138028.224 561330.721 SC N GP1" - args: +units=us-ft +init=nad27:3901 -f %.3f in: -81d00'00.000 34d32'30.000 2000000.000 561019.077 SC N GP2 out: "2000000.000\t561019.077 2000000.000 561019.077 SC N GP2" - args: +units=us-ft +init=nad27:3902 -f %.3f in: -80d32'30.000 33d32'30.000 2139661.529 621836.603 SC S GP1 out: "2139661.529\t621836.603 2139661.529 621836.603 SC S GP1" - args: +units=us-ft +init=nad27:3902 -f %.3f in: -81d00'00.000 33d32'30.000 2000000.000 621532.356 SC S GP2 out: "2000000.000\t621532.356 2000000.000 621532.356 SC S GP2" - args: +units=us-ft +init=nad27:4001 -f %.3f in: -99d12'21.983 44d06'08.121 2208566.880 99065.808 SD N GP1 out: "2208566.880\t99065.808 2208566.880 99065.808 SD N GP1" - args: +units=us-ft +init=nad27:4001 -f %.3f in: -100d32'28.873 44d32'34.917 1858852.206 259207.243 SD N GP2 out: "1858852.206\t259207.243 1858852.206 259207.243 SD N GP2" - args: +units=us-ft +init=nad27:4002 -f %.3f in: -103d14'00.000 44d06'00.000 1238344.555 657205.595 SD S GP1 out: "1238344.555\t657205.595 1238344.555 657205.595 SD S GP1" - args: +units=us-ft +init=nad27:4100 -f %.3f in: -85d13'55.967 36d21'48.503 2226074.895 718522.870 TN GP1 out: "2226074.895\t718522.870 2226074.895 718522.870 TN GP1" - args: +units=us-ft +init=nad27:4100 -f %.3f in: -88d43'05.849 36d30'08.410 1201097.659 779285.593 TN GP2 out: "1201097.659\t779285.593 1201097.659 779285.593 TN GP2" - args: +units=us-ft +init=nad27:4203 -f %.3f in: -97d06'00.000 31d35'00.000 3006704.541 711708.204 TX C GP1 out: "3006704.541\t711708.204 3006704.541 711708.204 TX C GP1" - args: +units=us-ft +init=nad27:4201 -f %.3f in: -100d33'06.303 34d39'35.684 2285173.373 241550.390 TX N GP1 out: "2285173.373\t241550.390 2285173.373 241550.390 TX N GP1" - args: +units=us-ft +init=nad27:4201 -f %.3f in: -102d48'50.949 34d43'39.249 1605118.921 267430.718 TX N GP2 out: "1605118.921\t267430.718 1605118.921 267430.718 TX N GP2" - args: +units=us-ft +init=nad27:4205 -f %.3f in: -97d30'00.000 25d55'00.000 2328727.194 92175.721 TX S GP1 out: "2328727.194\t92175.721 2328727.194 92175.721 TX S GP1" - args: +units=us-ft +init=nad27:4202 -f %.3f in: -96d48'00.000 32d45'00.000 2215204.973 394833.169 TXNC GP1 out: "2215204.973\t394833.169 2215204.973 394833.169 TXNC GP1" - args: +units=us-ft +init=nad27:4204 -f %.3f in: -98d30'00.000 29d25'00.000 2159176.237 576022.948 TXSC GP1 out: "2159176.237\t576022.948 2159176.237 576022.948 TXSC GP1" - args: +units=us-ft +init=nad27:4302 -f %.3f in: -111d30'00.000 38d40'00.000 2000000.000 121415.345 UT C GP1 out: "2000000.000\t121415.345 2000000.000 121415.345 UT C GP1" - args: +units=us-ft +init=nad27:4302 -f %.3f in: -111d30'00.000 38d40'30.000 2000000.000 124450.619 UT C GP2 out: "2000000.000\t124450.619 2000000.000 124450.619 UT C GP2" - args: +units=us-ft +init=nad27:4301 -f %.3f in: -111d30'00.000 41d30'00.000 2000000.000 425057.445 UT N GP1 out: "2000000.000\t425057.445 2000000.000 425057.445 UT N GP1" - args: +units=us-ft +init=nad27:4301 -f %.3f in: -111d30'00.000 41d30'30.000 2000000.000 428093.810 UT N GP2 out: "2000000.000\t428093.810 2000000.000 428093.810 UT N GP2" - args: +units=us-ft +init=nad27:4303 -f %.3f in: -109d48'37.967 38d29'30.877 2483568.472 668988.098 UT S GP1 out: "2483568.472\t668988.098 2483568.472 668988.098 UT S GP1" - args: +units=us-ft +init=nad27:4303 -f %.3f in: -113d52'56.922 37d09'18.788 1305706.243 186731.606 UT S GP2 out: "1305706.243\t186731.606 1305706.243 186731.606 UT S GP2" - args: +units=us-ft +init=nad27:4501 -f %.3f in: -77d13'46.945 38d55'12.407 2361415.621 458962.786 VA N GP1 out: "2361415.621\t458962.786 2361415.621 458962.786 VA N GP1" - args: +units=us-ft +init=nad27:4501 -f %.3f in: -79d18'51.557 38d09'59.020 1765875.433 183017.881 VA N GP2 out: "1765875.433\t183017.881 1765875.433 183017.881 VA N GP2" - args: +units=us-ft +init=nad27:4501 -f %.3f in: -77d38'10.823 37d49'23.964 2249484.834 58221.695 VA N GP3 out: "2249484.834\t58221.695 2249484.834 58221.695 VA N GP3" - args: +units=us-ft +init=nad27:4501 -f %.3f in: -79d26'19.475 37d47'25.852 1728704.621 46487.604 VA N GP4 out: "1728704.621\t46487.604 1728704.621 46487.604 VA N GP4" - args: +units=us-ft +init=nad27:4501 -f %.3f in: -77d44'30.336 39d00'06.804 2215488.016 487135.448 VA N GP6 out: "2215488.016\t487135.448 2215488.016 487135.448 VA N GP6" - args: +units=us-ft +init=nad27:4501 -f %.3f in: -77d43'47.013 38d59'55.454 2218917.620 486015.701 VA N GP9 out: "2218917.620\t486015.701 2218917.620 486015.701 VA N GP9" - args: +units=us-ft +init=nad27:4502 -f %.3f in: -78d30'00.000 37d30'00.000 2000000.000 424763.516 VA S GP1 out: "2000000.000\t424763.516 2000000.000 424763.516 VA S GP1" - args: +units=us-ft +init=nad27:4502 -f %.3f in: -78d30'00.000 37d30'30.000 2000000.000 427797.710 VA S GP2 out: "2000000.000\t427797.710 2000000.000 427797.710 VA S GP2" - args: +units=us-ft +init=nad27:4502 -f %.3f in: -77d32'33.000 36d54'42.507 2279939.213 212030.192 VA S GP3 out: "2279939.213\t212030.192 2279939.213 212030.192 VA S GP3" - args: +units=us-ft +init=nad27:4502 -f %.3f in: -77d21'55.732 38d04'53.901 2326572.191 638519.064 VA S GP4 out: "2326572.191\t638519.064 2326572.191 638519.064 VA S GP4" - args: +units=us-ft +init=nad27:5202 -f %.3f in: -64d45'30.000 17d45'30.000 1082794.001 75432.552 VI F GP1 out: "1082794.001\t75432.552 1082794.001 75432.552 VI F GP1" - args: +units=us-ft +init=nad27:5202 -f %.3f in: -66d26'00.000 17d45'56.426 500000.000 75432.505 VI F GP2 out: "500000.000\t75432.505 500000.000 75432.505 VI F GP2" - args: +units=us-ft +init=nad27:5202 -f %.3f in: -64d45'30.000 17d45'30.000 1082794.001 75432.552 VI M GP1 out: "1082794.001\t75432.552 1082794.001 75432.552 VI M GP1" - args: +units=us-ft +init=nad27:5202 -f %.3f in: -66d26'00.000 17d45'56.426 500000.000 75432.505 VI M GP2 out: "500000.000\t75432.505 500000.000 75432.505 VI M GP2" - args: +units=us-ft +init=nad27:4400 -f %.3f in: -72d29'31.418 43d09'58.526 502118.227 242816.621 VT GP1 out: "502118.227\t242816.621 502118.227 242816.621 VT GP1" - args: +units=us-ft +init=nad27:4400 -f %.3f in: -73d12'06.978 44d22'22.810 316451.963 683472.660 VT GP2 out: "316451.963\t683472.660 316451.963 683472.660 VT GP2" - args: +units=us-ft +init=nad27:4601 -f %.3f in: -119d51'37.006 47d50'51.069 2238927.196 310658.148 WA N GP1 out: "2238927.196\t310658.148 2238927.196 310658.148 WA N GP1" - args: +units=us-ft +init=nad27:4601 -f %.3f in: -123d59'49.087 48d09'29.131 1228043.506 438306.777 WA N GP2 out: "1228043.506\t438306.777 1228043.506 438306.777 WA N GP2" - args: +units=us-ft +init=nad27:4602 -f %.3f in: -122d54'00.000 46d09'00.000 1391814.257 307059.945 WA S GP1 out: "1391814.257\t307059.945 1391814.257 307059.945 WA S GP1" - args: +units=us-ft +init=nad27:4802 -f %.3f in: -88d04'00.000 44d30'00.000 2504399.560 249042.105 WI C GP1 out: "2504399.560\t249042.105 2504399.560 249042.105 WI C GP1" - args: +units=us-ft +init=nad27:4801 -f %.3f in: -88d44'40.778 45d22'21.598 2322632.765 77666.151 WI N GP1 out: "2322632.765\t77666.151 2322632.765 77666.151 WI N GP1" - args: +units=us-ft +init=nad27:4801 -f %.3f in: -92d12'19.275 45d48'35.812 1437681.450 242373.846 WI N GP2 out: "1437681.450\t242373.846 1437681.450 242373.846 WI N GP2" - args: +units=us-ft +init=nad27:4803 -f %.3f in: -89d23'00.000 43d05'00.000 2164743.544 395445.420 WI S GP1 out: "2164743.544\t395445.420 2164743.544 395445.420 WI S GP1" - args: +units=us-ft +init=nad27:4701 -f %.3f in: -77d53'39.269 39d14'39.339 2454764.840 275139.246 WV N GP1 out: "2454764.840\t275139.246 2454764.840 275139.246 WV N GP1" - args: +units=us-ft +init=nad27:4701 -f %.3f in: -81d33'23.549 39d18'08.535 1418073.862 298900.611 WV N GP2 out: "1418073.862\t298900.611 1418073.862 298900.611 WV N GP2" - args: +units=us-ft +init=nad27:4701 -f %.3f in: -77d30'10.460 38d59'25.903 2567632.286 184970.946 WV N GP3 out: "2567632.286\t184970.946 2567632.286 184970.946 WV N GP3" - args: +units=us-ft +init=nad27:4901 -f %.3f in: -105d07'00.000 44d38'00.000 513016.009 1445570.355 WY E GP1 out: "513016.009\t1445570.354 513016.009 1445570.355 WY E GP1" - args: +units=us-ft +init=nad27:4901 -f %.3f in: -105d31'02.882 43d30'40.600 406937.677 1036750.418 WY E GP1 out: "406937.677\t1036750.417 406937.677 1036750.418 WY E GP1" - args: +units=us-ft +init=nad27:4901 -f %.3f in: -105d22'42.856 43d30'14.685 443778.141 1034002.062 WY E GP2 out: "443778.141\t1034002.062 443778.141 1034002.062 WY E GP2" - args: +units=us-ft +init=nad27:4901 -f %.3f in: -105d28'42.827 43d36'33.391 417392.389 1072428.186 WY E GP3 out: "417392.389\t1072428.186 417392.389 1072428.186 WY E GP3" - args: +units=us-ft +init=nad27:4901 -f %.3f in: -105d23'43.223 42d00'59.422 437860.186 491889.060 WY E GP4 out: "437860.186\t491889.060 437860.186 491889.060 WY E GP4" - args: +units=us-ft +init=nad27:4901 -f %.3f in: -104d35'06.686 42d34'50.366 656606.905 697923.643 WY E GP5 out: "656606.905\t697923.643 656606.905 697923.643 WY E GP5" - args: +units=us-ft +init=nad27:4904 -f %.3f in: -110d36'00.000 41d48'00.000 359125.204 413338.815 WY W GP1 out: "359125.204\t413338.815 359125.204 413338.815 WY W GP1" - args: +units=us-ft +init=nad27:4902 -f %.3f in: -106d13'03.224 41d36'14.640 805153.891 343496.746 WYEC GP1 out: "805153.891\t343496.745 805153.891 343496.746 WYEC GP1" - args: +units=us-ft +init=nad27:4902 -f %.3f in: -108d01'56.720 41d51'57.518 309581.204 437731.262 WYEC GP2 out: "309581.204\t437731.262 309581.204 437731.262 WYEC GP2" - args: +units=us-ft +init=nad27:4903 -f %.3f in: -108d24'00.000 43d02'00.000 593579.361 862553.590 WYWC GP1 out: "593579.361\t862553.590 593579.361 862553.590 WYWC GP1" proj-9.8.1/test/cli/test_projsync.sh000775 001750 001750 00000022250 15166171715 017447 0ustar00eveneven000000 000000 #!/bin/bash # Test projsync set -e TEST_CLI_DIR=$(dirname $0) EXE=$1 if test -z "${EXE}"; then echo "Usage: ${0} " exit 1 fi if test ! -x ${EXE}; then echo "*** ERROR: Can not find '${EXE}' program!" exit 1 fi echo "============================================" echo "Running ${0} using ${EXE}:" echo "============================================" if ! curl -s https://cdn.proj.org/files.geojson >/dev/null 2>/dev/null; then if ! wget https://cdn.proj.org/files.geojson -O /dev/null 2>/dev/null; then echo "Cannot download https://cdn.proj.org/files.geojson. Skipping test" exit 0 fi fi TMP_OUT=test_projsync_out.txt rm -rf tmp_user_writable_directory mkdir -p tmp_user_writable_directory echo "Testing $EXE --list-files --target-dir tmp_user_writable_directory" if ! $EXE --list-files --target-dir tmp_user_writable_directory > ${TMP_OUT}; then echo "--list-files failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "at_bev_README.txt,,at_bev" >/dev/null || (cat ${TMP_OUT}; exit 100) export PROJ_USER_WRITABLE_DIRECTORY=tmp_user_writable_directory if test ! -f ${PROJ_USER_WRITABLE_DIRECTORY}/cache.db; then echo "*** ERROR: Can not find ${PROJ_USER_WRITABLE_DIRECTORY}/cache.db!" exit 100 fi if test ! -f ${PROJ_USER_WRITABLE_DIRECTORY}/files.geojson; then echo "*** ERROR: Can not find ${PROJ_USER_WRITABLE_DIRECTORY}/files.geojson!" exit 100 fi echo "Testing $EXE --all" if ! $EXE --all --dry-run > ${TMP_OUT}; then echo "--all failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "Would download https://cdn.proj.org/at_bev_README.txt" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --source-id fr_ign" if ! $EXE --source-id fr_ign --dry-run > ${TMP_OUT}; then echo "--source-id fr_ign failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "fr_ign_README.txt" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --source-id non_existing" if $EXE --source-id non_existing >${TMP_OUT} 2>&1 ; then echo "--source-id non_existing failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "Warning: 'non_existing' is a unknown value for --source-id" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "fr_ign" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --area-of-use France" if ! $EXE --area-of-use France --dry-run > ${TMP_OUT}; then echo "--area-of-use France failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --area-of-use non_existing" if $EXE --area-of-use non_existing >${TMP_OUT} 2>&1 ; then echo "--area-of-use non_existing failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "Warning: 'non_existing' is a unknown value for --area-of-use." >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "Australia" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --file ntf_r93" if ! $EXE --file ntf_r93 > ${TMP_OUT}; then echo "--file ntf_r93 failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "Downloading https://cdn.proj.org/fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) if test ! -f ${PROJ_USER_WRITABLE_DIRECTORY}/fr_ign_ntf_r93.tif; then echo "*** ERROR: Can not find ${PROJ_USER_WRITABLE_DIRECTORY}/fr_ign_ntf_r93.tif!" exit 100 fi echo "Testing $EXE --file ntf_r93 a second time" if ! $EXE --file ntf_r93 > ${TMP_OUT}; then echo "--file ntf_r93 failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "https://cdn.proj.org/fr_ign_ntf_r93.tif already downloaded" >/dev/null || (cat ${TMP_OUT}; exit 100) rm -f ${PROJ_USER_WRITABLE_DIRECTORY}/fr_ign_ntf_r93.tif echo "Testing $EXE --file non_existing" if $EXE --file non_existing >${TMP_OUT} 2>&1 ; then echo "--file non_existing failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "Warning: 'non_existing' is a unknown value for --file." >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --bbox 2,49,3,50" if ! $EXE --bbox 2,49,3,50 --dry-run > ${TMP_OUT}; then echo "--bbox 2,49,3,50 failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "us_nga_egm96_15.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --bbox 2,49,3,50 --exclude-world-coverage" if ! $EXE --bbox 2,49,3,50 --exclude-world-coverage --dry-run > ${TMP_OUT}; then echo "--bbox 2,49,3,50 --exclude-world-coverage failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "fr_ign_ntf_r93.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) if cat ${TMP_OUT} | grep "us_nga_egm96_15.tif" >/dev/null; then cat ${TMP_OUT} exit 100 fi echo "Testing $EXE --bbox 170,-90,-170,90" if ! $EXE --bbox 170,-90,-170,90 --dry-run > ${TMP_OUT}; then echo "--bbox 170,-90,-170,90 failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "nz_linz_nzgeoid2009.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "us_noaa_alaska.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --bbox 170,-90,190,90" if ! $EXE --bbox 170,-90,190,90 --dry-run > ${TMP_OUT}; then echo "--bbox 170,-90,190,90 failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "nz_linz_nzgeoid2009.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "us_noaa_alaska.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) echo "Testing $EXE --bbox -190,-90,-170,90" if ! $EXE --bbox -190,-90,-170,90 --dry-run > ${TMP_OUT}; then echo "--bbox -190,-90,-170,90 failed" cat ${TMP_OUT} exit 100 fi cat ${TMP_OUT} | grep "nz_linz_nzgeoid2009.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "us_noaa_alaska.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) cat > dummy.geojson < ${TMP_OUT}; then echo "--source-id au_ga --verbose --dry-run --local-geojson-file dummy.geojson" cat ${TMP_OUT} exit 100 fi rm dummy.geojson cat ${TMP_OUT} | grep "Skipping removed_in_1.7 as it is no longer useful starting with PROJ-data 1.7" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "Skipping added_in_99_99.tif as it is only useful starting with PROJ-data 99.99" >/dev/null || (cat ${TMP_OUT}; exit 100) cat ${TMP_OUT} | grep "Would download https://cdn.proj.org/without_version.tif" >/dev/null || (cat ${TMP_OUT}; exit 100) # Cleanup rm -rf ${PROJ_USER_WRITABLE_DIRECTORY} rm ${TMP_OUT} exit 0 proj-9.8.1/test/cli/test_cs2cs_ignf.yaml000664 001750 001750 00000011461 15166171715 020147 0ustar00eveneven000000 000000 comment: > Test some points in IGNF managed SRS. The expected coordinates have been calculated by other means. exe: cs2cs env: PROJ_NETWORK: ON tests: - args: +init=IGNF:NTFG +to +init=IGNF:RGF93G in: 3.300866856 43.4477976569 0.0000 out: | 3d18'0.915"E 43d26'52.077"N 0.000 - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 600000.0000 2600545.4523 0.0000 out: "652760.737\t7033791.243 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 135638.3592 2418760.4094 0.0000 out: "187194.062\t6855928.882 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 998137.3947 2413822.2844 0.0000 out: "1049052.258\t6843776.562 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 600000.0000 2200000.0000 0.0000 out: "649398.872\t6633524.191 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 311552.5340 1906457.4840 0.0000 out: "358799.172\t6342652.486 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 960488.4138 1910172.8812 0.0000 out: "1007068.686\t6340907.237 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 600000.0000 1699510.8340 0.0000 out: "645204.279\t6133556.746 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:LAMB93 -f %.3f in: 1203792.5981 626873.17210 0.0000 out: "1238837.253\t5057451.037 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 600000.0000 2600545.4523 0.0000 out: "179356.306\t5585333.153 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 135638.3592 2418760.4094 0.0000 out: "-304265.704\t5385136.401 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 998137.3947 2413822.2844 0.0000 out: "593889.664\t5385138.596 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 600000.0000 2200000.0000 0.0000 out: "179357.831\t5185007.149 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 311552.5340 1906457.4840 0.0000 out: "-96996.444\t4884928.293 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 960488.4138 1910172.8812 0.0000 out: "524805.113\t4884935.286 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 600000.0000 1699510.8340 0.0000 out: "179363.804\t4684962.279 0.000" - args: +init=IGNF:LAMBE +to +init=IGNF:GEOPORTALFXX -f %.3f in: 1203792.5981 626873.17210 0.0000 out: "659421.855\t3603178.891 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: 2d20'11.4239243" 50d23'59.7718445" 0.0 out: "179356.309\t5585333.158 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: -3d57'49.4051448" 48d35'59.7121716" 0.0 out: "-304265.706\t5385136.397 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: 7d44'12.1439796" 48d35'59.7832558" 0.0 out: "593889.666\t5385138.593 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: 2d20'11.4951975" 46d47'59.8029841" 0.0 out: "179357.829\t5185007.147 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: -1d15'48.9240599" 44d05'59.8251878" 0.0 out: "-96996.446\t4884928.296 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: 6d50'12.2276489" 44d06'00.0517019" 0.0 out: "524805.116\t4884935.287 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: 2d20'11.7754730" 42d18'00.0824436" 0.0 out: "179363.805\t4684962.283 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:GEOPORTALFXX -f %.3f in: 9d32'12.6680218" 41d24'00.3542556" 0.0 out: "732073.508\t4585007.331 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: 2d20'11.4239243" 50d23'59.7718445" 0.0 out: "260098.730\t6140682.441 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: -3d57'49.4051448" 48d35'59.7121716" 0.0 out: "-441239.699\t5880610.004 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: 7d44'12.1439796" 48d35'59.7832558" 0.0 out: "861246.246\t5880612.827 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: 2d20'11.4951975" 46d47'59.8029841" 0.0 out: "260100.934\t5625762.156 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: -1d15'48.9240599" 44d05'59.8251878" 0.0 out: "-140662.197\t5252490.165 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: 6d50'12.2276489" 44d06'00.0517019" 0.0 out: "761061.291\t5252498.745 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: 2d20'11.7754730" 42d18'00.0824436" 0.0 out: "260109.601\t5009175.714 0.000" - args: +init=IGNF:RGF93G +to +init=IGNF:MILLER -f %.3f in: 9d32'12.6680218" 41d24'00.3542556" 0.0 out: "1061637.534\t4889066.592 0.000" - args: +init=IGNF:RGR92 +to +init=IGNF:REUN47 -f %.3f in: 3356123.5400 1303218.3090 5247430.6050 out: "3353420.949\t1304075.021 5248935.144" proj-9.8.1/test/cli/test_cs2cs_locale.sh000775 001750 001750 00000006644 15166171715 020145 0ustar00eveneven000000 000000 #!/bin/bash # Script to do some locale testing. # See test_cs2cs_various.yaml for general CLI testing. set -e TEST_CLI_DIR=$(dirname $0) EXE=$1 if test -z "${EXE}"; then echo "Usage: ${0} " exit 1 fi if test ! -x ${EXE}; then echo "*** ERROR: Can not find '${EXE}' program!" exit 1 fi # Would be great to have a universale way of selecting a locale with # a decimal separator that is not '.' if command locale >/dev/null 2>/dev/null; then if test `locale -a | grep fr_FR.utf8`; then echo "Using locale with comma as decimal separator" export LC_ALL=fr_FR.UTF-8 export PROJ_USE_ENV_LOCALE=1 fi fi echo "============================================" echo "Running ${0} using ${EXE}:" echo "============================================" OUT=$(basename $0 .sh)_out # echo "doing tests into file ${OUT}, please wait" rm -f ${OUT} # echo "##############################################################" >> ${OUT} echo Between two 3parameter approximations on same ellipsoid >> ${OUT} # $EXE +proj=latlong +ellps=bessel +towgs84=5,0,0 \ +to +proj=latlong +ellps=bessel +towgs84=1,0,0 \ -E >>${OUT} <> ${OUT} echo Test input in grad >> ${OUT} # $EXE EPSG:4807 EPSG:27572 -E >>${OUT} <> ${OUT} echo Test geocentric x/y/z generation. >> ${OUT} # $EXE +proj=latlong +datum=WGS84 \ +to +proj=geocent +datum=WGS84 \ -E >>${OUT} <> ${OUT} echo Test geocentric x/y/z consumption. >> ${OUT} # $EXE +proj=geocent +datum=WGS84 \ +to +proj=latlong +datum=WGS84 \ -E >>${OUT} <> ${OUT} echo Test conversion from geodetic latlong to geocentric latlong >> ${OUT} # $EXE +proj=latlong +datum=WGS84 \ +to +proj=latlong +datum=WGS84 +geoc \ -E >>${OUT} <> ${OUT} echo Test conversion from geocentric latlong to geodetic latlong >> ${OUT} # $EXE +proj=latlong +datum=WGS84 +geoc \ +to +proj=latlong +datum=WGS84 \ -E >>${OUT} <" exit 1 fi if test ! -x ${EXE}; then echo "*** ERROR: Can not find '${EXE}' program!" exit 1 fi echo "============================================" echo "Running ${0} using ${EXE}:" echo "============================================" OUT=$(basename $0 .sh)_out echo "doing tests into file ${OUT}, please wait" rm -f ${OUT} # echo "##############################################################" >> ${OUT} echo "Testing PROJ_AUX_DB environment variable" >> ${OUT} rm -f tmp_projinfo_aux.db $EXE --dump-db-structure --output-id HOBU:XXXX EPSG:4326 | sqlite3 tmp_projinfo_aux.db export PROJ_AUX_DB=tmp_projinfo_aux.db $EXE HOBU:XXXX >>${OUT} unset PROJ_AUX_DB rm -f tmp_projinfo_aux.db echo "" >>${OUT} ############################################################################## # Done! # do 'diff' with distribution results echo "diff ${OUT} with ${OUT}.dist" diff -u -b ${OUT} ${TEST_CLI_DIR}/${OUT}.dist if [ $? -ne 0 ] ; then echo "" echo "PROBLEMS HAVE OCCURRED" echo "test file ${OUT} saved" exit 100 else echo "TEST OK" echo "test file ${OUT} removed" rm -f ${OUT} exit 0 fi proj-9.8.1/README.md000664 001750 001750 00000007120 15166171715 013732 0ustar00eveneven000000 000000 # PROJ [![Coveralls Status](https://coveralls.io/repos/github/OSGeo/PROJ/badge.svg?branch=master)](https://coveralls.io/github/OSGeo/PROJ?branch=master) [![CodeQL](https://github.com/OSGeo/PROJ/actions/workflows/codeql.yml/badge.svg)](https://github.com/OSGeo/PROJ/actions/workflows/codeql.yml) [![Gitter](https://badges.gitter.im/OSGeo/proj.4.svg)](https://gitter.im/OSGeo/proj.4) [![Mailing List](https://img.shields.io/badge/PROJ-mailing%20list-4eb899.svg)](http://lists.osgeo.org/mailman/listinfo/proj) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) [![Release](https://img.shields.io/github/v/release/OSGeo/PROJ)](https://github.com/OSGeo/PROJ/releases) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5884394.svg)](https://doi.org/10.5281/zenodo.5884394) PROJ is a generic coordinate transformation software, that transforms coordinates from one coordinate reference system (CRS) to another. This includes cartographic projections as well as geodetic transformations. For more information on the PROJ project please see the web page at: The PROJ mailing list can be found at: See the NEWS.md file for changes between versions. The following command line utilities are included in the PROJ package: - `proj`: for cartographic projection of geodetic coordinates. - `cs2cs`: for transformation from one CRS to another CRS. - `geod`: for geodesic (great circle) computations. - `cct`: for generic Coordinate Conversions and Transformations. - `gie`: the Geospatial Integrity Investigation Environment. - `projinfo`: for geodetic object and coordinate operation queries. - `projsync`: for synchronizing PROJ datum and transformation support data. > More information on the utilities can be found on the [PROJ website](https://proj.org/apps). ## Installation Consult the [Installation](https://proj.org/install.html) page of the official documentation. For builds on the master branch, [install.rst](https://github.com/OSGeo/PROJ/blob/master/docs/source/install.rst) might be more up-to-date. ## Distribution files and format Sources are distributed in one or more files. The principle elements of the system are stored in a compressed tar file named `proj-x.y.z.tar.gz` where "x" will indicate the major release number, "y" indicates the minor release number, and "z" indicates the patch number of the release. In addition to the PROJ software package, distributions of datum conversion grid files and PROJ parameter files are also available. The grid package is distributed under the name `proj-data-x.y.zip`, where "x" is the major release version and "y" is the minor release version numbers. The resource packages can be downloaded from the [PROJ website](https://proj.org/download.html). More info on the contents of the proj-data package can be found at the [PROJ-data GitHub repository](https://github.com/OSGeo/PROJ-data). The resource file packages should be extracted to `PROJ_LIB` where PROJ will find them after installation. The default location of `PROJ_LIB` on UNIX-based systems is `/usr/local/share/proj` but it may be changed to a different directory. On Windows you have to define `PROJ_LIB` yourself. As an alternative to installing the data package on the local system, the resource files can be retrieved on-the-fly from the [PROJ CDN](https://cdn.proj.org/). A [network-enabled](https://proj.org/usage/network.html) PROJ build, will automatically fetch resource files that are not present locally from the CDN. ## Citing PROJ in publications See [CITATION](CITATION) proj-9.8.1/src/000775 001750 001750 00000000000 15166171735 013244 5ustar00eveneven000000 000000 proj-9.8.1/src/memvfs.h000664 001750 001750 00000000532 15166171715 014710 0ustar00eveneven000000 000000 #ifndef MEMVFS_H #define MEMVFS_H #include #ifdef __cplusplus extern "C" { #endif int pj_sqlite3_memvfs_init(sqlite3_vfs *vfs, const char *vfs_name, const void *buffer, size_t bufferSize); void pj_sqlite3_memvfs_deallocate_user_data(sqlite3_vfs *vfs); #ifdef __cplusplus } #endif #endif /* MEMVFS_H */ proj-9.8.1/src/pr_list.cpp000664 001750 001750 00000005355 15166171715 015432 0ustar00eveneven000000 000000 /* print projection's list of parameters */ #include #include #include #include "proj.h" #include "proj_internal.h" #define LINE_LEN 72 static int pr_list(PJ *P, int not_used) { paralist *t; int l, n = 1, flag = 0; (void)putchar('#'); for (t = P->params; t; t = t->next) if ((!not_used && t->used) || (not_used && !t->used)) { l = (int)strlen(t->param) + 1; if (n + l > LINE_LEN) { (void)fputs("\n#", stdout); n = 2; } (void)putchar(' '); if (*(t->param) != '+') (void)putchar('+'); (void)fputs(t->param, stdout); n += l; } else flag = 1; if (n > 1) (void)putchar('\n'); return flag; } void /* print link list of projection parameters */ pj_pr_list(PJ *P) { char const *s; (void)putchar('#'); for (s = P->descr; *s; ++s) { (void)putchar(*s); if (*s == '\n') (void)putchar('#'); } (void)putchar('\n'); if (pr_list(P, 0)) { (void)fputs("#--- following specified but NOT used\n", stdout); (void)pr_list(P, 1); } } /************************************************************************/ /* pj_get_def() */ /* */ /* Returns the PROJ.4 command string that would produce this */ /* definition expanded as much as possible. For instance, */ /* +init= calls and +datum= definitions would be expanded. */ /************************************************************************/ char *pj_get_def(const PJ *P, int options) { paralist *t; int l; char *definition; size_t def_max = 10; (void)options; definition = (char *)malloc(def_max); if (!definition) return nullptr; definition[0] = '\0'; for (t = P->params; t; t = t->next) { /* skip unused parameters ... mostly appended defaults and stuff */ if (!t->used) continue; /* grow the resulting string if needed */ l = (int)strlen(t->param) + 1; if (strlen(definition) + l + 5 > def_max) { char *def2; def_max = def_max * 2 + l + 5; def2 = (char *)malloc(def_max); if (def2) { strcpy(def2, definition); free(definition); definition = def2; } else { free(definition); return nullptr; } } /* append this parameter */ strcat(definition, " +"); strcat(definition, t->param); } return definition; } proj-9.8.1/src/wkt1_parser.cpp000664 001750 001750 00000016261 15166171715 016216 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: WKT1 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2013, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/internal/internal.hpp" #include #include #include #include "wkt1_parser.h" #include "wkt_parser.hpp" using namespace NS_PROJ::internal; //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- struct pj_wkt1_parse_context : public pj_wkt_parse_context {}; // --------------------------------------------------------------------------- void pj_wkt1_error(pj_wkt1_parse_context *context, const char *msg) { pj_wkt_error(context, msg); } // --------------------------------------------------------------------------- std::string pj_wkt1_parse(const std::string &wkt) { pj_wkt1_parse_context context; context.pszInput = wkt.c_str(); context.pszLastSuccess = wkt.c_str(); context.pszNext = wkt.c_str(); if (pj_wkt1_parse(&context) != 0) { return context.errorMsg; } return std::string(); } // --------------------------------------------------------------------------- typedef struct { const char *pszToken; int nTokenVal; } osr_cs_wkt_tokens; #define PAIR(X) \ { \ #X, T_##X \ } static const osr_cs_wkt_tokens tokens[] = { PAIR(PARAM_MT), PAIR(PARAMETER), PAIR(CONCAT_MT), PAIR(INVERSE_MT), PAIR(PASSTHROUGH_MT), PAIR(PROJCS), PAIR(PROJECTION), PAIR(GEOGCS), PAIR(DATUM), PAIR(SPHEROID), PAIR(PRIMEM), PAIR(UNIT), PAIR(GEOCCS), PAIR(AUTHORITY), PAIR(VERT_CS), PAIR(VERTCS), PAIR(VERT_DATUM), PAIR(VDATUM), PAIR(COMPD_CS), PAIR(AXIS), PAIR(TOWGS84), PAIR(FITTED_CS), PAIR(LOCAL_CS), PAIR(LOCAL_DATUM), PAIR(LINUNIT), PAIR(EXTENSION)}; // --------------------------------------------------------------------------- int pj_wkt1_lex(YYSTYPE * /*pNode */, pj_wkt1_parse_context *context) { size_t i; const char *pszInput = context->pszNext; /* -------------------------------------------------------------------- */ /* Skip white space. */ /* -------------------------------------------------------------------- */ while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == 10 || *pszInput == 13) pszInput++; context->pszLastSuccess = pszInput; if (*pszInput == '\0') { context->pszNext = pszInput; return EOF; } /* -------------------------------------------------------------------- */ /* Recognize node names. */ /* -------------------------------------------------------------------- */ if (isalpha(*pszInput)) { for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++) { if (ci_starts_with(pszInput, tokens[i].pszToken) && !isalpha(pszInput[strlen(tokens[i].pszToken)])) { context->pszNext = pszInput + strlen(tokens[i].pszToken); return tokens[i].nTokenVal; } } } /* -------------------------------------------------------------------- */ /* Recognize double quoted strings. */ /* -------------------------------------------------------------------- */ if (*pszInput == '"') { pszInput++; while (*pszInput != '\0' && *pszInput != '"') pszInput++; if (*pszInput == '\0') { context->pszNext = pszInput; return EOF; } context->pszNext = pszInput + 1; return T_STRING; } /* -------------------------------------------------------------------- */ /* Recognize numerical values. */ /* -------------------------------------------------------------------- */ if (((*pszInput == '-' || *pszInput == '+') && pszInput[1] >= '0' && pszInput[1] <= '9') || (*pszInput >= '0' && *pszInput <= '9')) { if (*pszInput == '-' || *pszInput == '+') pszInput++; // collect non-decimal part of number while (*pszInput >= '0' && *pszInput <= '9') pszInput++; // collect decimal places. if (*pszInput == '.') { pszInput++; while (*pszInput >= '0' && *pszInput <= '9') pszInput++; } // collect exponent if (*pszInput == 'e' || *pszInput == 'E') { pszInput++; if (*pszInput == '-' || *pszInput == '+') pszInput++; while (*pszInput >= '0' && *pszInput <= '9') pszInput++; } context->pszNext = pszInput; return T_NUMBER; } /* -------------------------------------------------------------------- */ /* Recognize identifiers. */ /* -------------------------------------------------------------------- */ if ((*pszInput >= 'A' && *pszInput <= 'Z') || (*pszInput >= 'a' && *pszInput <= 'z')) { pszInput++; while ((*pszInput >= 'A' && *pszInput <= 'Z') || (*pszInput >= 'a' && *pszInput <= 'z')) pszInput++; context->pszNext = pszInput; return T_IDENTIFIER; } /* -------------------------------------------------------------------- */ /* Handle special tokens. */ /* -------------------------------------------------------------------- */ context->pszNext = pszInput + 1; return *pszInput; } //! @endcond proj-9.8.1/src/phi2.cpp000664 001750 001750 00000013736 15166171715 014622 0ustar00eveneven000000 000000 /* Determine latitude angle phi-2. */ #include #include #include #include "proj.h" #include "proj_internal.h" double pj_sinhpsi2tanphi(PJ_CONTEXT *ctx, const double taup, const double e) { /*************************************************************************** * Convert tau' = sinh(psi) = tan(chi) to tau = tan(phi). The code is taken * from GeographicLib::Math::tauf(taup, e). * * Here * phi = geographic latitude (radians) * psi is the isometric latitude * psi = asinh(tan(phi)) - e * atanh(e * sin(phi)) * = asinh(tan(chi)) * chi is the conformal latitude * * The representation of latitudes via their tangents, tan(phi) and * tan(chi), maintains full *relative* accuracy close to latitude = 0 and * +/- pi/2. This is sometimes important, e.g., to compute the scale of the * transverse Mercator projection which involves cos(phi)/cos(chi) tan(phi) * * From Karney (2011), Eq. 7, * * tau' = sinh(psi) = sinh(asinh(tan(phi)) - e * atanh(e * sin(phi))) * = tan(phi) * cosh(e * atanh(e * sin(phi))) - * sec(phi) * sinh(e * atanh(e * sin(phi))) * = tau * sqrt(1 + sigma^2) - sqrt(1 + tau^2) * sigma * where * sigma = sinh(e * atanh( e * tau / sqrt(1 + tau^2) )) * * For e small, * * tau' = (1 - e^2) * tau * * The relation tau'(tau) can therefore by reliably inverted by Newton's * method with * * tau = tau' / (1 - e^2) * * as an initial guess. Newton's method requires dtau'/dtau. Noting that * * dsigma/dtau = e^2 * sqrt(1 + sigma^2) / * (sqrt(1 + tau^2) * (1 + (1 - e^2) * tau^2)) * d(sqrt(1 + tau^2))/dtau = tau / sqrt(1 + tau^2) * * we have * * dtau'/dtau = (1 - e^2) * sqrt(1 + tau'^2) * sqrt(1 + tau^2) / * (1 + (1 - e^2) * tau^2) * * This works fine unless tau^2 and tau'^2 overflows. This may be partially * cured by writing, e.g., sqrt(1 + tau^2) as hypot(1, tau). However, nan * will still be generated with tau' = inf, since (inf - inf) will appear in * the Newton iteration. * * If we note that for sufficiently large |tau|, i.e., |tau| >= 2/sqrt(eps), * sqrt(1 + tau^2) = |tau| and * * tau' = exp(- e * atanh(e)) * tau * * So * * tau = exp(e * atanh(e)) * tau' * * can be returned unless |tau| >= 2/sqrt(eps); this then avoids overflow * problems for large tau' and returns the correct result for tau' = +/-inf * and nan. * * Newton's method usually take 2 iterations to converge to double precision * accuracy (for WGS84 flattening). However only 1 iteration is needed for * |chi| < 3.35 deg. In addition, only 1 iteration is needed for |chi| > * 89.18 deg (tau' > 70), if tau = exp(e * atanh(e)) * tau' is used as the * starting guess. * * For small flattening, |f| <= 1/50, the series expansion in n can be * used: * * Assuming n = e^2 / (1 + sqrt(1 - e^2))^2 is passed as an argument * * double F[int(AuxLat::AUXORDER)]; * pj_auxlat_coeffs(n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, F); * double sphi, cphi; * // schi cchi * pj_auxlat_convert(taup/hypot(1.0, taup), 1/hypot(1.0, taup), * sphi, cphi, F); * return sphi/cphi; **************************************************************************/ constexpr int numit = 5; // min iterations = 1, max iterations = 2; mean = 1.954 static const double rooteps = sqrt(std::numeric_limits::epsilon()); static const double tol = rooteps / 10; // the criterion for Newton's method static const double tmax = 2 / rooteps; // threshold for large arg limit exact const double e2m = 1 - e * e; const double stol = tol * std::max(1.0, fabs(taup)); // The initial guess. 70 corresponds to chi = 89.18 deg (see above) double tau = fabs(taup) > 70 ? taup * exp(e * atanh(e)) : taup / e2m; if (!(fabs(tau) < tmax)) // handles +/-inf and nan and e = 1 return tau; // If we need to deal with e > 1, then we could include: // if (e2m < 0) return std::numeric_limits::quiet_NaN(); int i = numit; for (; i; --i) { double tau1 = sqrt(1 + tau * tau); double sig = sinh(e * atanh(e * tau / tau1)); double taupa = sqrt(1 + sig * sig) * tau - sig * tau1; double dtau = ((taup - taupa) * (1 + e2m * (tau * tau)) / (e2m * tau1 * sqrt(1 + taupa * taupa))); tau += dtau; if (!(fabs(dtau) >= stol)) // backwards test to allow nans to succeed. break; } if (i == 0) proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return tau; } /*****************************************************************************/ double pj_phi2(PJ_CONTEXT *ctx, const double ts0, const double e) { /**************************************************************************** * Determine latitude angle phi-2. * Inputs: * ts = exp(-psi) where psi is the isometric latitude (dimensionless) * this variable is defined in Snyder (1987), Eq. (7-10) * e = eccentricity of the ellipsoid (dimensionless) * Output: * phi = geographic latitude (radians) * Here isometric latitude is defined by * psi = log( tan(pi/4 + phi/2) * * ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) * = asinh(tan(phi)) - e * atanh(e * sin(phi)) * = asinh(tan(chi)) * chi = conformal latitude * * This routine converts t = exp(-psi) to * * tau' = tan(chi) = sinh(psi) = (1/t - t)/2 * * returns atan(sinpsi2tanphi(tau')) ***************************************************************************/ return atan(pj_sinhpsi2tanphi(ctx, (1 / ts0 - ts0) / 2, e)); } proj-9.8.1/src/zpoly1.cpp000664 001750 001750 00000002257 15166171715 015212 0ustar00eveneven000000 000000 /* evaluate complex polynomial */ #include "proj.h" #include "proj_internal.h" /* note: coefficients are always from C_1 to C_n ** i.e. C_0 == (0., 0) ** n should always be >= 1 though no checks are made */ COMPLEX pj_zpoly1(COMPLEX z, const COMPLEX *C, int n) { COMPLEX a; double t; a = *(C += n); while (n-- > 0) { a.r = (--C)->r + z.r * (t = a.r) - z.i * a.i; a.i = C->i + z.r * a.i + z.i * t; } a.r = z.r * (t = a.r) - z.i * a.i; a.i = z.r * a.i + z.i * t; return a; } /* evaluate complex polynomial and derivative */ COMPLEX pj_zpolyd1(COMPLEX z, const COMPLEX *C, int n, COMPLEX *der) { COMPLEX a, b; double t; int first = 1; a = *(C += n); b = a; while (n-- > 0) { if (first) { first = 0; } else { b.r = a.r + z.r * (t = b.r) - z.i * b.i; b.i = a.i + z.r * b.i + z.i * t; } a.r = (--C)->r + z.r * (t = a.r) - z.i * a.i; a.i = C->i + z.r * a.i + z.i * t; } b.r = a.r + z.r * (t = b.r) - z.i * b.i; b.i = a.i + z.r * b.i + z.i * t; a.r = z.r * (t = a.r) - z.i * a.i; a.i = z.r * a.i + z.i * t; *der = b; return a; } proj-9.8.1/src/proj_json_streaming_writer.hpp000664 001750 001750 00000012422 15166171715 021424 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: CPL - Common Portability Library * Purpose: JSon streaming writer * Author: Even Rouault, even.rouault at spatialys.com * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PROJ_JSON_STREAMING_WRITER_H #define PROJ_JSON_STREAMING_WRITER_H /*! @cond Doxygen_Suppress */ #include #include #include #define CPL_DLL #include "proj/util.hpp" NS_PROJ_START typedef std::int64_t GIntBig; typedef std::uint64_t GUInt64; class CPL_DLL CPLJSonStreamingWriter { public: typedef void (*SerializationFuncType)(const char *pszTxt, void *pUserData); private: CPLJSonStreamingWriter(const CPLJSonStreamingWriter &) = delete; CPLJSonStreamingWriter &operator=(const CPLJSonStreamingWriter &) = delete; std::string m_osStr{}; SerializationFuncType m_pfnSerializationFunc = nullptr; void *m_pUserData = nullptr; bool m_bPretty = true; std::string m_osIndent = std::string(" "); std::string m_osIndentAcc{}; int m_nLevel = 0; bool m_bNewLineEnabled = true; struct State { bool bIsObj = false; bool bFirstChild = true; explicit State(bool bIsObjIn) : bIsObj(bIsObjIn) {} }; std::vector m_states{}; bool m_bWaitForValue = false; void Print(const std::string &text); void IncIndent(); void DecIndent(); static std::string FormatString(const std::string &str); void EmitCommaIfNeeded(); public: CPLJSonStreamingWriter(SerializationFuncType pfnSerializationFunc, void *pUserData); ~CPLJSonStreamingWriter(); void SetPrettyFormatting(bool bPretty) { m_bPretty = bPretty; } void SetIndentationSize(int nSpaces); // cppcheck-suppress functionStatic const std::string &GetString() const { return m_osStr; } void Add(const std::string &str); void Add(const char *pszStr); void AddUnquoted(const char *pszStr); void Add(bool bVal); void Add(int nVal) { Add(static_cast(nVal)); } void Add(unsigned int nVal) { Add(static_cast(nVal)); } void Add(GIntBig nVal); void Add(GUInt64 nVal); void Add(float fVal, int nPrecision = 9); void Add(double dfVal, int nPrecision = 18); void AddNull(); void StartObj(); void EndObj(); void AddObjKey(const std::string &key); struct CPL_DLL ObjectContext { CPLJSonStreamingWriter &m_serializer; ObjectContext(const ObjectContext &) = delete; ObjectContext(ObjectContext &&) = default; explicit inline ObjectContext(CPLJSonStreamingWriter &serializer) : m_serializer(serializer) { m_serializer.StartObj(); } ~ObjectContext() { m_serializer.EndObj(); } }; inline ObjectContext MakeObjectContext() { return ObjectContext(*this); } void StartArray(); void EndArray(); struct CPL_DLL ArrayContext { CPLJSonStreamingWriter &m_serializer; bool m_bForceSingleLine; bool m_bNewLineEnabledBackup; ArrayContext(const ArrayContext &) = delete; ArrayContext(ArrayContext &&) = default; inline explicit ArrayContext(CPLJSonStreamingWriter &serializer, bool bForceSingleLine = false) : m_serializer(serializer), m_bForceSingleLine(bForceSingleLine), m_bNewLineEnabledBackup(serializer.GetNewLine()) { if (m_bForceSingleLine) serializer.SetNewline(false); m_serializer.StartArray(); } ~ArrayContext() { m_serializer.EndArray(); if (m_bForceSingleLine) m_serializer.SetNewline(m_bNewLineEnabledBackup); } }; inline ArrayContext MakeArrayContext(bool bForceSingleLine = false) { return ArrayContext(*this, bForceSingleLine); } bool GetNewLine() const { return m_bNewLineEnabled; } void SetNewline(bool bEnabled) { m_bNewLineEnabled = bEnabled; } }; NS_PROJ_END /*! @endcond */ #endif // PROJ_JSON_STREAMING_WRITER_H proj-9.8.1/src/quadtree.hpp000664 001750 001750 00000022256 15166171715 015574 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Implementation of quadtree building and searching functions. * Derived from shapelib, mapserver and GDAL implementations * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 1999-2008, Frank Warmerdam * Copyright (c) 2008-2020, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef QUADTREE_HPP #define QUADTREE_HPP #include "proj/util.hpp" #include #include //! @cond Doxygen_Suppress NS_PROJ_START namespace QuadTree { /* -------------------------------------------------------------------- */ /* If the following is 0.5, psNodes will be split in half. If it */ /* is 0.6 then each apSubNode will contain 60% of the parent */ /* psNode, with 20% representing overlap. This can be help to */ /* prevent small objects on a boundary from shifting too high */ /* up the hQuadTree. */ /* -------------------------------------------------------------------- */ constexpr double DEFAULT_SPLIT_RATIO = 0.55; /** Describe a rectangle */ struct RectObj { double minx = 0; /**< Minimum x */ double miny = 0; /**< Minimum y */ double maxx = 0; /**< Maximum x */ double maxy = 0; /**< Maximum y */ /* Returns whether this rectangle is contained by other */ inline bool isContainedBy(const RectObj &other) const { return minx >= other.minx && maxx <= other.maxx && miny >= other.miny && maxy <= other.maxy; } /* Returns whether this rectangles overlaps other */ inline bool overlaps(const RectObj &other) const { return minx <= other.maxx && maxx >= other.minx && miny <= other.maxy && maxy >= other.miny; } /* Returns whether this rectangles contains the specified point */ inline bool contains(double x, double y) const { return minx <= x && maxx >= x && miny <= y && maxy >= y; } /* Return whether this rectangles is different from other */ inline bool operator!=(const RectObj &other) const { return minx != other.minx || miny != other.miny || maxx != other.maxx || maxy != other.maxy; } }; /** Quadtree */ template class QuadTree { struct Node { RectObj rect{}; /* area covered by this psNode */ /* list of shapes stored at this node. */ std::vector> features{}; std::vector subnodes{}; explicit Node(const RectObj &rectIn) : rect(rectIn) {} }; Node root{}; unsigned nBucketCapacity = 8; double dfSplitRatio = DEFAULT_SPLIT_RATIO; public: /** Construct a new quadtree with the global bounds of all objects to be * inserted */ explicit QuadTree(const RectObj &globalBounds) : root(globalBounds) {} /** Add a new feature, with its bounds specified in featureBounds */ void insert(const Feature &feature, const RectObj &featureBounds) { insert(root, feature, featureBounds); } #ifdef UNUSED /** Retrieve all features whose bounds intersects aoiRect */ void search(const RectObj &aoiRect, std::vector> &features) const { search(root, aoiRect, features); } #endif /** Retrieve all features whose bounds contains (x,y) */ void search(double x, double y, std::vector &features) const { search(root, x, y, features); } private: void splitBounds(const RectObj &in, RectObj &out1, RectObj &out2) { // The output bounds will be very similar to the input bounds, // so just copy over to start. out1 = in; out2 = in; // Split in X direction. if ((in.maxx - in.minx) > (in.maxy - in.miny)) { const double range = in.maxx - in.minx; out1.maxx = in.minx + range * dfSplitRatio; out2.minx = in.maxx - range * dfSplitRatio; } // Otherwise split in Y direction. else { const double range = in.maxy - in.miny; out1.maxy = in.miny + range * dfSplitRatio; out2.miny = in.maxy - range * dfSplitRatio; } } void insert(Node &node, const Feature &feature, const RectObj &featureBounds) { if (node.subnodes.empty()) { // If we have reached the max bucket capacity, try to insert // in a subnode if possible. if (node.features.size() >= nBucketCapacity) { RectObj half1; RectObj half2; RectObj quad1; RectObj quad2; RectObj quad3; RectObj quad4; splitBounds(node.rect, half1, half2); splitBounds(half1, quad1, quad2); splitBounds(half2, quad3, quad4); if (node.rect != quad1 && node.rect != quad2 && node.rect != quad3 && node.rect != quad4 && (featureBounds.isContainedBy(quad1) || featureBounds.isContainedBy(quad2) || featureBounds.isContainedBy(quad3) || featureBounds.isContainedBy(quad4))) { node.subnodes.reserve(4); node.subnodes.emplace_back(Node(quad1)); node.subnodes.emplace_back(Node(quad2)); node.subnodes.emplace_back(Node(quad3)); node.subnodes.emplace_back(Node(quad4)); auto features = std::move(node.features); node.features.clear(); for (auto &pair : features) { insert(node, pair.first, pair.second); } /* recurse back on this psNode now that it has apSubNodes */ insert(node, feature, featureBounds); return; } } } else { // If we have sub nodes, then consider whether this object will // fit in them. for (auto &subnode : node.subnodes) { if (featureBounds.isContainedBy(subnode.rect)) { insert(subnode, feature, featureBounds); return; } } } // If none of that worked, just add it to this nodes list. node.features.push_back( std::pair(feature, featureBounds)); } #ifdef UNUSED void search(const Node &node, const RectObj &aoiRect, std::vector> &features) const { // Does this node overlap the area of interest at all? If not, // return without adding to the list at all. if (!node.rect.overlaps(aoiRect)) return; // Add the local features to the list. for (const auto &pair : node.features) { if (pair.second.overlaps(aoiRect)) { features.push_back( std::reference_wrapper(pair.first)); } } // Recurse to subnodes if they exist. for (const auto &subnode : node.subnodes) { search(subnode, aoiRect, features); } } #endif static void search(const Node &node, double x, double y, std::vector &features) { // Does this node overlap the area of interest at all? If not, // return without adding to the list at all. if (!node.rect.contains(x, y)) return; // Add the local features to the list. for (const auto &pair : node.features) { if (pair.second.contains(x, y)) { features.push_back(pair.first); } } // Recurse to subnodes if they exist. for (const auto &subnode : node.subnodes) { search(subnode, x, y, features); } } }; } // namespace QuadTree NS_PROJ_END //! @endcond #endif // QUADTREE_HPP proj-9.8.1/src/adjlon.cpp000664 001750 001750 00000001045 15166171715 015215 0ustar00eveneven000000 000000 /* reduce argument to range +/- PI */ #include #include "proj.h" #include "proj_internal.h" double adjlon(double longitude) { /* Let longitude slightly overshoot, to avoid spurious sign switching at the * date line */ if (fabs(longitude) < M_PI + 1e-12) return longitude; /* adjust to 0..2pi range */ longitude += M_PI; /* remove integral # of 'revolutions'*/ longitude -= M_TWOPI * floor(longitude / M_TWOPI); /* adjust back to -pi..pi range */ longitude -= M_PI; return longitude; } proj-9.8.1/src/factors.cpp000664 001750 001750 00000027467 15166171715 015427 0ustar00eveneven000000 000000 /* projection scale factors */ #include "proj.h" #include "proj_internal.h" #include #include #ifndef DEFAULT_H #define DEFAULT_H 1e-5 /* radian default for numeric h */ #endif #define EPS 1.0e-12 static int pj_factors(PJ_LP lp, PJ *toplevel, const PJ *internal, double h, struct FACTORS *fac) { double cosphi, t, n, r; int err; PJ_COORD coo = {{0, 0, 0, 0}}; coo.lp = lp; if (HUGE_VAL == lp.lam) return 1; /* But from here, we're ready to make our own mistakes */ err = proj_errno_reset(toplevel); /* Indicate that all factors are numerical approximations */ fac->code = 0; /* Check for latitude or longitude overange */ if ((fabs(lp.phi) - M_HALFPI) > EPS) { proj_log_error(toplevel, _("Invalid latitude")); proj_errno_set(toplevel, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); return 1; } if (fabs(lp.lam) > 10.) { proj_log_error(toplevel, _("Invalid longitude")); proj_errno_set(toplevel, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); return 1; } /* Set a reasonable step size for the numerical derivatives */ h = fabs(h); if (h < EPS) h = DEFAULT_H; /* If input latitudes are geocentric, convert to geographic */ if (internal->geoc) lp = pj_geocentric_latitude(internal, PJ_INV, coo).lp; /* If latitude + one step overshoots the pole, move it slightly inside, */ /* so the numerical derivative still exists */ if (fabs(lp.phi) > (M_HALFPI - h)) lp.phi = lp.phi < 0. ? -(M_HALFPI - h) : (M_HALFPI - h); /* Longitudinal distance from central meridian */ /* Only apply that correction for a non-pipeline transformation, otherwise * it will be applied twice (here and by the fwd_prepare() method of the * first step) */ const bool bIsPipeline = internal->short_name && strcmp(internal->short_name, "pipeline") == 0; if (!bIsPipeline) { lp.lam -= internal->lam0; if (!internal->over) lp.lam = adjlon(lp.lam); } /* Derivatives */ if (pj_deriv(lp, h, internal, &(fac->der))) { proj_log_error(toplevel, _("Invalid latitude or longitude")); proj_errno_set(toplevel, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); return 1; } /* This code path is intended to be taken for a derived projected CRS, * with first step being a map projection, and second step some other * transformation. We must undo the scaling from the unit sphere to the * actual semi major radius done in the fwd_finalize() method of the * first step, otherwise most factors will be wrong. */ if (bIsPipeline) { fac->der.x_l *= internal->ra * internal->fr_meter; fac->der.x_p *= internal->ra * internal->fr_meter; fac->der.y_l *= internal->ra * internal->fr_meter; fac->der.y_p *= internal->ra * internal->fr_meter; } /* Scale factors */ cosphi = cos(lp.phi); fac->h = hypot(fac->der.x_p, fac->der.y_p); fac->k = hypot(fac->der.x_l, fac->der.y_l) / cosphi; if (internal->es != 0.0) { t = sin(lp.phi); t = 1. - internal->es * t * t; n = sqrt(t); fac->h *= t * n / internal->one_es; fac->k *= n; r = t * t / internal->one_es; } else r = 1.; /* Convergence */ fac->conv = -atan2(fac->der.x_p, fac->der.y_p); /* Areal scale factor */ fac->s = (fac->der.y_p * fac->der.x_l - fac->der.x_p * fac->der.y_l) * r / cosphi; /* Meridian-parallel angle (theta prime) */ fac->thetap = aasin(internal->ctx, fac->s / (fac->h * fac->k)); /* Tissot ellipse axis */ t = fac->k * fac->k + fac->h * fac->h; fac->a = sqrt(t + 2. * fac->s); t = t - 2. * fac->s; t = t > 0 ? sqrt(t) : 0; fac->b = 0.5 * (fac->a - t); fac->a = 0.5 * (fac->a + t); /* Angular distortion */ fac->omega = 2. * aasin(internal->ctx, (fac->a - fac->b) / (fac->a + fac->b)); proj_errno_restore(toplevel, err); return 0; } /*****************************************************************************/ PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) { /****************************************************************************** Cartographic characteristics at point lp. Characteristics include meridian, parallel and areal scales, angular distortion, meridian/parallel, meridian convergence and scale error. returns PJ_FACTORS. If unsuccessful, error number is set and the struct returned contains NULL data. ******************************************************************************/ PJ_FACTORS factors = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; struct FACTORS f; if (nullptr == P) return factors; auto pj = P; auto type = proj_get_type(pj); PJ *horiz = nullptr; if (pj->cached_op_for_proj_factors) { pj = pj->cached_op_for_proj_factors; } else { if (type == PJ_TYPE_COMPOUND_CRS) { horiz = proj_crs_get_sub_crs(pj->ctx, pj, 0); pj = horiz; type = proj_get_type(pj); } const auto createGeogCRSLonLatRad = [](PJ *base_geodetic_crs) { auto ctx = base_geodetic_crs->ctx; auto pm = proj_get_prime_meridian(ctx, base_geodetic_crs); double pm_longitude = 0; proj_prime_meridian_get_parameters(ctx, pm, &pm_longitude, nullptr, nullptr); proj_destroy(pm); PJ *geogCRSNormalized; auto cs = proj_create_ellipsoidal_2D_cs( ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Radian", 1.0); if (pm_longitude != 0) { auto ellipsoid = proj_get_ellipsoid(ctx, base_geodetic_crs); double semi_major_metre = 0; double inv_flattening = 0; proj_ellipsoid_get_parameters(ctx, ellipsoid, &semi_major_metre, nullptr, nullptr, &inv_flattening); geogCRSNormalized = proj_create_geographic_crs( ctx, "unname crs", "unnamed datum", proj_get_name(ellipsoid), semi_major_metre, inv_flattening, "reference prime meridian", 0, nullptr, 0, cs); proj_destroy(ellipsoid); } else { auto datum = proj_crs_get_datum(ctx, base_geodetic_crs); auto datum_ensemble = proj_crs_get_datum_ensemble(ctx, base_geodetic_crs); geogCRSNormalized = proj_create_geographic_crs_from_datum( ctx, "unnamed crs", datum ? datum : datum_ensemble, cs); proj_destroy(datum); proj_destroy(datum_ensemble); } proj_destroy(cs); return geogCRSNormalized; }; double semiMajorToDivide = 1; if (type == PJ_TYPE_PROJECTED_CRS) { // If it is a projected CRS, then compute the factors on the // conversion associated to it. We need to start from a temporary // geographic CRS using the same datum as the one of the projected // CRS, and with input coordinates being in longitude, latitude // order in radian, to be consistent with the expectations of the lp // input parameter. We also need to create a modified projected CRS // with a normalized easting/northing axis order in metre, so the // resulting operation is just a single step pipeline with no // axisswap or unitconvert steps. auto ctx = pj->ctx; auto geodetic_crs = proj_get_source_crs(ctx, pj); assert(geodetic_crs); auto geogCRSNormalized = createGeogCRSLonLatRad(geodetic_crs); auto conversion = proj_crs_get_coordoperation(ctx, pj); auto projCS = proj_create_cartesian_2D_cs( ctx, PJ_CART2D_EASTING_NORTHING, "metre", 1.0); auto projCRSNormalized = proj_create_projected_crs( ctx, nullptr, geodetic_crs, conversion, projCS); assert(projCRSNormalized); proj_destroy(geodetic_crs); proj_destroy(conversion); proj_destroy(projCS); auto newOp = proj_create_crs_to_crs_from_pj( ctx, geogCRSNormalized, projCRSNormalized, nullptr, nullptr); proj_destroy(geogCRSNormalized); proj_destroy(projCRSNormalized); assert(newOp); // For debugging: // printf("%s\n", proj_as_proj_string(ctx, newOp, PJ_PROJ_5, // nullptr)); P->cached_op_for_proj_factors = newOp; pj = newOp; } else if (type == PJ_TYPE_DERIVED_PROJECTED_CRS) { auto ctx = pj->ctx; auto base_proj_crs = proj_get_source_crs(ctx, pj); assert(base_proj_crs); auto geodetic_crs = proj_get_source_crs(ctx, base_proj_crs); proj_destroy(base_proj_crs); assert(geodetic_crs); auto ellipsoid = proj_get_ellipsoid(ctx, geodetic_crs); proj_ellipsoid_get_parameters(ctx, ellipsoid, &semiMajorToDivide, nullptr, nullptr, nullptr); proj_destroy(ellipsoid); auto geogCRSNormalized = createGeogCRSLonLatRad(geodetic_crs); auto newOp = proj_create_crs_to_crs_from_pj(ctx, geogCRSNormalized, pj, nullptr, nullptr); proj_destroy(geodetic_crs); proj_destroy(geogCRSNormalized); assert(newOp); PJ *cs = proj_crs_get_coordinate_system(ctx, pj); double cs_unit_conv_factor = 1.0; proj_cs_get_axis_info(ctx, cs, 0, nullptr, nullptr, nullptr, &cs_unit_conv_factor, nullptr, nullptr, nullptr); proj_destroy(cs); // Remove trailing unit conversion and axis swap std::string osProjStr = proj_as_proj_string(ctx, newOp, PJ_PROJ_5, nullptr); bool resized = false; for (const char *suffix : {" +step +proj=unitconvert", " +step +proj=axiswap"}) { auto nPos = osProjStr.rfind(suffix); if (nPos != std::string::npos) { resized = true; osProjStr.resize(nPos); } } if (resized) { proj_destroy(newOp); newOp = proj_create(ctx, osProjStr.c_str()); assert(newOp); } newOp->fr_meter = cs_unit_conv_factor; P->cached_op_for_proj_factors = newOp; pj = newOp; } else if (type != PJ_TYPE_CONVERSION && type != PJ_TYPE_TRANSFORMATION && type != PJ_TYPE_CONCATENATED_OPERATION && type != PJ_TYPE_OTHER_COORDINATE_OPERATION) { proj_log_error(P, _("Invalid type for P object")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); if (horiz) proj_destroy(horiz); return factors; } } const int ret = pj_factors(lp.lp, P, pj, 0.0, &f); if (horiz) proj_destroy(horiz); if (ret) return factors; factors.meridional_scale = f.h; factors.parallel_scale = f.k; factors.areal_scale = f.s; factors.angular_distortion = f.omega; factors.meridian_parallel_angle = f.thetap; factors.meridian_convergence = f.conv; factors.tissot_semimajor = f.a; factors.tissot_semiminor = f.b; /* Raw derivatives, for completeness's sake */ factors.dx_dlam = f.der.x_l; factors.dx_dphi = f.der.x_p; factors.dy_dlam = f.der.y_l; factors.dy_dphi = f.der.y_p; return factors; } proj-9.8.1/src/tsfn.cpp000664 001750 001750 00000002345 15166171715 014724 0ustar00eveneven000000 000000 /* determine small t */ #include "proj.h" #include "proj_internal.h" #include double pj_tsfn(double phi, double sinphi, double e) { /**************************************************************************** * Determine function ts(phi) defined in Snyder (1987), Eq. (7-10) * Inputs: * phi = geographic latitude (radians) * e = eccentricity of the ellipsoid (dimensionless) * Output: * ts = exp(-psi) where psi is the isometric latitude (dimensionless) * = 1 / (tan(chi) + sec(chi)) * Here isometric latitude is defined by * psi = log( tan(pi/4 + phi/2) * * ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) * = asinh(tan(phi)) - e * atanh(e * sin(phi)) * = asinh(tan(chi)) * chi = conformal latitude ***************************************************************************/ double cosphi = cos(phi); // exp(-asinh(tan(phi))) = 1 / (tan(phi) + sec(phi)) // = cos(phi) / (1 + sin(phi)) good for phi > 0 // = (1 - sin(phi)) / cos(phi) good for phi < 0 return exp(e * atanh(e * sinphi)) * (sinphi > 0 ? cosphi / (1 + sinphi) : (1 - sinphi) / cosphi); } proj-9.8.1/src/CMakeLists.txt000664 001750 001750 00000001026 15166171715 016001 0ustar00eveneven000000 000000 # Global compile options for current directory and subdirectories add_compile_options("$<$:${PROJ_C_WARN_FLAGS}>") add_compile_options("$<$:${PROJ_CXX_WARN_FLAGS}>") # 0: include the lib files for the apps. include(apps/lib_projapps.cmake) # First configure proj library include(lib_proj.cmake) add_subdirectory(apps) if(BUILD_TESTING) add_subdirectory(tests) endif() if(APPLE) # Directory name for installed targets set(CMAKE_INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR}) endif() proj-9.8.1/src/wkt_parser.hpp000664 001750 001750 00000004051 15166171715 016134 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: WKT parser common routines * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018 Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PJ_WKT_PARSER_H_INCLUDED #define PJ_WKT_PARSER_H_INCLUDED //! @cond Doxygen_Suppress #include struct pj_wkt_parse_context { const char *pszInput = nullptr; const char *pszLastSuccess = nullptr; const char *pszNext = nullptr; std::string errorMsg{}; pj_wkt_parse_context() = default; pj_wkt_parse_context(const pj_wkt_parse_context &) = delete; pj_wkt_parse_context &operator=(const pj_wkt_parse_context &) = delete; }; void pj_wkt_error(pj_wkt_parse_context *context, const char *msg); //! @endcond #endif // PJ_WKT_PARSER_H_INCLUDED proj-9.8.1/src/generic_inverse.cpp000664 001750 001750 00000011050 15166171715 017112 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Generic method to compute inverse projection from forward method * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "proj_internal.h" #include #include /** Compute (lam, phi) corresponding to input (xy.x, xy.y) for projection P. * * Uses Newton-Raphson method, extended to 2D variables, that is using * inversion of the Jacobian 2D matrix of partial derivatives. The derivatives * are estimated numerically from the P->fwd method evaluated at close points. * * Note: thresholds used have been verified to work with adams_ws2 and wink2 * * Starts with initial guess provided by user in lpInitial */ PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial, double deltaXYTolerance) { PJ_LP lp = lpInitial; double deriv_lam_X = 0; double deriv_lam_Y = 0; double deriv_phi_X = 0; double deriv_phi_Y = 0; for (int i = 0; i < 15; i++) { PJ_XY xyApprox = P->fwd(lp, P); const double deltaX = xyApprox.x - xy.x; const double deltaY = xyApprox.y - xy.y; if (fabs(deltaX) < deltaXYTolerance && fabs(deltaY) < deltaXYTolerance) { return lp; } if (i == 0 || fabs(deltaX) > 1e-6 || fabs(deltaY) > 1e-6) { // Compute Jacobian matrix (only if we aren't close to the final // result to speed things a bit) PJ_LP lp2; PJ_XY xy2; const double dLam = lp.lam > 0 ? -1e-6 : 1e-6; lp2.lam = lp.lam + dLam; lp2.phi = lp.phi; xy2 = P->fwd(lp2, P); const double deriv_X_lam = (xy2.x - xyApprox.x) / dLam; const double deriv_Y_lam = (xy2.y - xyApprox.y) / dLam; const double dPhi = lp.phi > 0 ? -1e-6 : 1e-6; lp2.lam = lp.lam; lp2.phi = lp.phi + dPhi; xy2 = P->fwd(lp2, P); const double deriv_X_phi = (xy2.x - xyApprox.x) / dPhi; const double deriv_Y_phi = (xy2.y - xyApprox.y) / dPhi; // Inverse of Jacobian matrix const double det = deriv_X_lam * deriv_Y_phi - deriv_X_phi * deriv_Y_lam; if (det != 0) { deriv_lam_X = deriv_Y_phi / det; deriv_lam_Y = -deriv_X_phi / det; deriv_phi_X = -deriv_Y_lam / det; deriv_phi_Y = deriv_X_lam / det; } } // Limit the amplitude of correction to avoid overshoots due to // bad initial guess const double delta_lam = std::max( std::min(deltaX * deriv_lam_X + deltaY * deriv_lam_Y, 0.3), -0.3); lp.lam -= delta_lam; if (lp.lam < -M_PI) lp.lam = -M_PI; else if (lp.lam > M_PI) lp.lam = M_PI; const double delta_phi = std::max( std::min(deltaX * deriv_phi_X + deltaY * deriv_phi_Y, 0.3), -0.3); lp.phi -= delta_phi; if (lp.phi < -M_HALFPI) lp.phi = -M_HALFPI; else if (lp.phi > M_HALFPI) lp.phi = M_HALFPI; } proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } proj-9.8.1/src/list.cpp000664 001750 001750 00000001553 15166171715 014725 0ustar00eveneven000000 000000 /* Projection System: default list of projections */ #define DO_NOT_DEFINE_PROJ_HEAD #include "proj.h" #include "proj_internal.h" /* Generate prototypes for projection functions */ #define PROJ_HEAD(id, name) \ extern "C" struct PJconsts *pj_##id(struct PJconsts *); #include "pj_list.h" #undef PROJ_HEAD /* Generate extern declarations for description strings */ #define PROJ_HEAD(id, name) extern "C" const char *const pj_s_##id; #include "pj_list.h" #undef PROJ_HEAD /* Generate the null-terminated list of projection functions with associated * mnemonics and descriptions */ #define PROJ_HEAD(id, name) {#id, pj_##id, &pj_s_##id}, const struct PJ_LIST pj_list[] = { #include "pj_list.h" {nullptr, nullptr, nullptr}, }; #undef PROJ_HEAD const PJ_OPERATIONS *proj_list_operations(void) { return pj_list; } proj-9.8.1/src/general_doc.dox000664 001750 001750 00000014152 15166171715 016223 0ustar00eveneven000000 000000 /*! \page general_doc General documentation \section general_api_design General API design The design of the class hierarchy is strongly derived from \ref ISO_19111_2019. Classes for which the constructors are not directly accessible have their instances constructed with create() methods. The returned object is a non-null shared pointer. Such objects are immutable, and thread-safe. TODO \section general_properties General properties All classes deriving from IdentifiedObject have general properties that can be defined at creation time. Those properties are:
  • osgeo::proj::metadata::Identifier::DESCRIPTION_KEY ("description"): the natural language description of the meaning of the code value, provided a a string.
  • osgeo::proj::metadata::Identifier::CODE_KEY ("code"): a numeric or alphanumeric code, provided as a integer or a string. For example 4326, for the EPSG:4326 "WGS84" GeographicalCRS
  • osgeo::proj::metadata::Identifier::CODESPACE_KEY ("codespace"): the organization responsible for definition and maintenance of the code., provided a a string. For example "EPSG".
  • osgeo::proj::metadata::Identifier::VERSION_KEY ("version"): the version identifier for the namespace, provided a a string.
  • osgeo::proj::metadata::Identifier::AUTHORITY_KEY ("authority"): a citation for the authority, provided as a string or a osgeo::proj::metadata::Citation object. Often unused
  • osgeo::proj::metadata::Identifier::URI_KEY ("uri"): the URI of the identifier, provided as a string. Often unused
  • osgeo::proj::common::IdentifiedObject::NAME_KEY ("name"): the name of a osgeo::proj::common::IdentifiedObject, provided as a string or osgeo::proj::metadata::IdentifierNNPtr.
  • osgeo::proj::common::IdentifiedObject::IDENTIFIERS_KEY ("identifiers"): the identifier(s) of a osgeo::proj::common::IdentifiedObject, provided as a osgeo::proj::common::IdentifierNNPtr or a osgeo::proj::util::ArrayOfBaseObjectNNPtr of osgeo::proj::metadata::IdentifierNNPtr.
  • osgeo::proj::common::IdentifiedObject::ALIAS_KEY ("alias"): the alias(es) of a osgeo::proj::common::IdentifiedObject, provided as string, a osgeo::proj::util::GenericNameNNPtr or a osgeo::proj::util::ArrayOfBaseObjectNNPtr of osgeo::proj::util::GenericNameNNPtr.
  • osgeo::proj::common::IdentifiedObject::REMARKS_KEY ("remarks"): the remarks of a osgeo::proj::common::IdentifiedObject, provided as a string.
  • osgeo::proj::common::IdentifiedObject::DEPRECATED_KEY ("deprecated"): the deprecation flag of a osgeo::proj::common::IdentifiedObject, provided as a boolean.
  • osgeo::proj::common::ObjectUsage::SCOPE_KEY ("scope"): the scope of a osgeo::proj::common::ObjectUsage, provided as a string.
  • osgeo::proj::common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY ("domainOfValidity"): the domain of validity of a osgeo::proj::common::ObjectUsage, provided as a osgeo::proj::metadata::ExtentNNPtr.
  • osgeo::proj::common::ObjectUsage::OBJECT_DOMAIN_KEY ("objectDomain"): the object domain(s) of a osgeo::proj::common::ObjectUsage, provided as a osgeo::proj::common::ObjectDomainNNPtr or a osgeo::proj::util::ArrayOfBaseObjectNNPtr of osgeo::proj::common::ObjectDomainNNPtr.
\section standards Applicable standards \subsection ISO_19111 ISO:19111 / OGC Topic 2 standard Topic 2 - Spatial referencing by coordinates. This is an Abstract Specification describes the data elements, relationships and associated metadata required for spatial referencing by coordinates. It describes Coordinate Reference Systems (CRS), coordinate systems (CS) and coordinate transformation or coordinate conversion between two different coordinate reference systems. \subsubsection ISO_19111_2019 ISO 19111:2019 This is the revision mostly used for PROJ C++ modelling. [OGC 18-005r5, 2021-07-02, ISO 19111:2019] (http://docs.opengeospatial.org/as/18-005r5/18-005r5.html) \subsubsection ISO_19111_2007 ISO 19111:2007 The precedent version of the specification was: [OGC 08-015r2, 2010-04-27, ISO 19111:2007] (http://portal.opengeospatial.org/files/?artifact_id=39049) \subsection WKT2 WKT2 standard Well-known text representation of coordinate reference systems. Well-known Text (WKT) offers a compact machine- and human-readable representation of the critical elements of coordinate reference system (CRS) definitions, and coordinate operations. This is an implementation of \ref ISO_19111 PROJ implements the two following revisions of the standard: \subsubsection WKT2_2019 WKT2:2019 [OGC 18-010r7, 2019-06-24, WKT2-2019] (http://docs.opengeospatial.org/is/18-010r7/18-010r7.html) \subsubsection WKT2_2015 WKT2:2015 [OGC 12-063r5, 2015-05-01, ISO 19162:2015(E), WKT2-2015] (http://docs.opengeospatial.org/is/12-063r5/12-063r5.html) \subsection WKT1 WKT1 specification Older specifications of well-known text representation of coordinate reference systems are also supported by PROJ, mostly for compatibility with legacy systems, or older versions of GDAL. GDAL v2.4 and earlier mostly implements: [OGC 01-009, 2001-01-12, OpenGIS Coordinate Transformation Service Implementation Specification] (http://portal.opengeospatial.org/files/?artifact_id=999) The [GDAL documentation, OGC WKT Coordinate System Issues] (https://gdal.org/tutorials/wktproblems.html) discusses issues, and GDAL implementation choices. An older specification of WKT1 is/was used by some software packages: [OGC 99-049, 1999-05-05, OpenGIS Simple Features Specification For SQL v1.1] (http://portal.opengeospatial.org/files/?artifact_id=829) \subsection ISO_19115 ISO 19115 (Metadata) Defines the schema required for describing geographic information and services. It provides information about the identification, the extent, the quality, the spatial and temporal schema, spatial reference, and distribution of digital geographic data. PROJ implements a simplified subset of ISO 19115. \subsection GeoAPI GeoAPI A set of Java and Python language programming interfaces for geospatial applications. [GeoAPI main page](http://www.geoapi.org/) [GeoAPI Javadoc](http://www.geoapi.org/3.0/javadoc/index.html) [OGC GeoAPI Implementation Specification] (http://www.opengeospatial.org/standards/geoapi) */ proj-9.8.1/src/latitudes.cpp000664 001750 001750 00000042770 15166171715 015756 0ustar00eveneven000000 000000 /* * Project: PROJ * * Helper functions to compute latitudes * * Some from Map Projections - A Working Manual. 1987. John P. Snyder * https://neacsu.net/docs/geodesy/snyder/2-general/sect_3/ * * Copyright (c) 2025 Javier Jimenez Shaw */ #include #include #include "proj_internal.h" /*****************************************************************************/ double pj_conformal_lat(double phi, const PJ *P) { /*********************************** * The conformal latitude chi, in terms of the geodetic latitude, phi. * * phi: geodetic latitude, in radians * returns: conformal latitude, chi, in radians * Copied from merc.cpp ***********************************/ if (P->e == 0.0) return phi; // Instead of calling tan and sin, call sin and cos which the compiler // optimizes to a single call to sincos. double sphi = sin(phi), cphi = cos(phi); return atan(sinh(asinh(sphi / cphi) - P->e * atanh(P->e * sphi))); } /*****************************************************************************/ double pj_conformal_lat_inverse(double chi, const PJ *P) { /*********************************** * The geodetic latitude, phi, in terms of the conformal latitude, chi. * * chi: conformal latitude, in radians * returns: geodetic latitude, phi, in radians * Copied from merc.cpp ***********************************/ if (P->e == 0.0) return chi; return atan(pj_sinhpsi2tanphi(P->ctx, tan(chi), P->e)); } /*****************************************************************************/ // Computes coefficient q such that authalic_latitude = beta = asin(q / qp) // where qp is q at phi=90deg, i.e. qp = pj_authalic_lat_q(1, e, one_es) // Cf Snyder (3-11) and (3-12) double pj_authalic_lat_q(double sinphi, const PJ *P) { constexpr double EPSILON = 1e-7; if (P->e >= EPSILON) { const double e_sinphi = P->e * sinphi; const double one_minus_e_sinphi_sq = 1.0 - e_sinphi * e_sinphi; /* avoid zero division, fail gracefully */ if (one_minus_e_sinphi_sq == 0.0) return HUGE_VAL; // Snyder uses 0.5 * ln((1-e*sinphi)/(1+e*sinphi) which is // -atanh(e*sinphi) return P->one_es * (sinphi / one_minus_e_sinphi_sq + atanh(e_sinphi) / P->e); } else return 2 * sinphi; } /*****************************************************************************/ // Computes coefficients needed for conversions between geographic and authalic // latitude. These are preferred over the analytical expressions for |n| < // 0.01. However the inverse series is used to start the inverse method for // large |n|. // Ensure we use the cutoff in n consistently #define PROJ_AUTHALIC_SERIES_VALID(n) (fabs(n) < 0.01) double *pj_authalic_lat_compute_coeffs(double n) { double *APA; constexpr int Lmax = int(AuxLat::ORDER); const int APA_SIZE = Lmax * (PROJ_AUTHALIC_SERIES_VALID(n) ? 2 : 1); if ((APA = (double *)malloc(APA_SIZE * sizeof(double))) != nullptr) { pj_auxlat_coeffs(n, AuxLat::AUTHALIC, AuxLat::GEOGRAPHIC, APA); if (PROJ_AUTHALIC_SERIES_VALID(n)) pj_auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::AUTHALIC, APA + Lmax); } return APA; } /*****************************************************************************/ // Computes authalic latitude from the geographic latitude. // qp is q at phi=90deg, i.e. qp = pj_authalic_lat_q(1, e, one_es) double pj_authalic_lat(double phi, double sinphi, double cosphi, const double *APA, const PJ *P, double qp) { if (PROJ_AUTHALIC_SERIES_VALID(P->n)) { constexpr int Lmax = int(AuxLat::ORDER); return pj_auxlat_convert(phi, sinphi, cosphi, APA + Lmax); } else { // This result is ill-conditioned near the poles. So don't use this if // the series are accurate. const double q = pj_authalic_lat_q(sinphi, P); double ratio = q / qp; if (fabs(ratio) > 1) { /* Rounding error. */ ratio = ratio > 0 ? 1 : -1; } return asin(ratio); } } /*****************************************************************************/ // Compute the geographic latitude from beta = authalic_latitude // where APA = pj_compute_coefficients_for_inverse_authalic_lat() and // qp = pj_authalic_lat_q(1, P->e, P->one_es) double pj_authalic_lat_inverse(double beta, const double *APA, const PJ *P, double qp) { double phi = pj_auxlat_convert(beta, APA); if (PROJ_AUTHALIC_SERIES_VALID(P->n)) return phi; // If the flattening is large, solve // f(phi) = qp*sin(beta)/(1-e^2) - q(phi)/(1-e^2) = 0 // for phi, using Newton's method, where // q(phi)/(1-e^2) = sin(phi)/(1 - e^2*sin(phi)^2) + atanh(e*sin(phi))/e // and // df(phi)/dphi = - dq(phi)/dphi / (1-e^2) // = - 2 * (1-e^2) * cos(phi) / (1 - e^2*sinphi^2)^2 // This is subject to large roundoff errors near the poles, so only use // this if the series isn't accurate. const double q = sin(beta) * qp; const double q_div_one_minus_es = q / P->one_es; for (int i = 0; i < 10; ++i) { const double sinphi = sin(phi); const double cosphi = cos(phi); const double one_minus_es_sin2phi = 1 - P->es * (sinphi * sinphi); // Snyder uses 0.5 * ln((1-e*sinphi)/(1+e*sinphi) which is // -atanh(e*sinphi) const double dphi = (one_minus_es_sin2phi * one_minus_es_sin2phi) / (2 * cosphi) * (q_div_one_minus_es - sinphi / one_minus_es_sin2phi - atanh(P->e * sinphi) / P->e); if (!(fabs(dphi) >= 1e-15)) break; phi += dphi; } return phi; } #undef PROJ_AUTHALIC_SERIES_VALID // The following routines pj_auxlat_coeffs, pj_polyvol, pj_clenshaw, // pj_auxlat_convert (3 signatures) provide a uniform interface for converting // between any pair of auxiliary latitudes using series expansions in the third // flattening, n. There are 6 (= AuxLat::NUMBER) auxiliary latitudes // supported labeled by // // AuxLat::GEOGRAPHIC for geographic latitude, phi // AuxLat::PARAMETRIC for parametric latitude, beta // AuxLat::GEOCENTRIC for geocentric latitude, theta // AuxLat::RECTIFYING for rectifying latitude, mu // AuxLat::CONFORMAL for conformal latitude, chi // AuxLat::AUTHALIC for authlatic latitude, xi // // This is adapted from // // C. F. F. Karney, On auxiliary latitudes, // Survey Review 56, 165-180 (2024) // https://doi.org/10.1080/00396265.2023.2217604 // Preprint: https://arxiv.org/abs/2212.05818 // // The typical calling sequence is // // constexpr int L = int(AuxLat::ORDER); // // Managing the memory for the coefficient array is the // // responibility of the calling routine. // double F[L]; // // Fill F[] with coefficients to convert conformal to geographic // pj_auxlat_coeffs(P->n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, F); // ... // double chi = 1; // known conformal latitude // // compute corresponding geographic latitude // double phi = pj_auxlat_convert(chi, F); // // The conversions are Fourier series in the auxiliary latitude where each // coefficient is given as a Taylor series in n truncated at order 6 (= // AuxLat::ORDER). This suffices to give full double precision accuracy for // |f| <= 1/150 and probably provide satisfactory results for |f| <= 1/50. The // coefficients for these Taylor series are given by matrics listed in // Eqs. (A1-A28) of this paper. // // These coefficients are bundled up into a single array coeffs in // pj_auxlat_coeffs. Only the upper triangular portion of the matrices are // included. Furthermore, half the coefficients for the conversions between // any of phi, bete, theta, and mu are zero (the Taylor series are expansions // in n^2), these zero elements are excluded. // // The coefficient array, coeffs, is machine-generated by the Maxima code // auxlat.mac bundled with GeographicLib. To use // // * Ensure that Lmax (set near the top of the file) is set to 6 (= // AuxLat::ORDER). // * run // $ maxima // Maxima 5.47.0 https://maxima.sourceforge.io // (%i1) load("auxlat.mac")$ // (%i2) writecppproj()$ // .... // # // * The results are in the file auxvalsproj6.cpp // // Only a subset of the conversion matrices are written out. To add others, // include them in the list "required" in writecppproj(). The conversions // currently supported are // // phi <-> mu for meridian distance // phi <-> chi for tmerc // phi <-> xi for authalic latitude conversions // chi <-> mu for tmerc // // Because all the matrices are concatenated together into a single array, // coeff, an auxiliary array, ptrs, or length 37 = AUXNUMBER^2 + 1, is written // out to give the starting point of any particular matrix. // // Input: // n -- the third flattening (a-b)/(a+b) // auxin, auxout -- compute the coefficients for converting auxin (zeta) to // auxout (eta). // Output: // F -- F[eta,zeta] = C[eta,zeta] . P(n), where C is a matrix of constants // and P(n) = [n, n^2, n^3, ...]^T; the first AuxLat::ORDER elements of F // are filled. void pj_auxlat_coeffs(double n, AuxLat auxin, AuxLat auxout, double F[]) { // Generated by Maxima on 2025-03-23 19:13:00-04:00 constexpr int Lmax = 6; static_assert(Lmax == int(AuxLat::ORDER), "Mismatch between maxima and AuxLat::ORDER"); static const double coeffs[] = { // C[phi,phi] skipped // C[phi,beta] skipped // C[phi,theta] skipped // C[phi,mu]; even coeffs only 3.0 / 2.0, -27.0 / 32.0, 269.0 / 512.0, 21.0 / 16.0, -55.0 / 32.0, 6759.0 / 4096.0, 151.0 / 96.0, -417.0 / 128.0, 1097.0 / 512.0, -15543.0 / 2560.0, 8011.0 / 2560.0, 293393.0 / 61440.0, // C[phi,chi] 2.0, -2.0 / 3.0, -2.0, 116.0 / 45.0, 26.0 / 45.0, -2854.0 / 675.0, 7.0 / 3.0, -8.0 / 5.0, -227.0 / 45.0, 2704.0 / 315.0, 2323.0 / 945.0, 56.0 / 15.0, -136.0 / 35.0, -1262.0 / 105.0, 73814.0 / 2835.0, 4279.0 / 630.0, -332.0 / 35.0, -399572.0 / 14175.0, 4174.0 / 315.0, -144838.0 / 6237.0, 601676.0 / 22275.0, // C[phi,xi] 4.0 / 3.0, 4.0 / 45.0, -16.0 / 35.0, -2582.0 / 14175.0, 60136.0 / 467775.0, 28112932.0 / 212837625.0, 46.0 / 45.0, 152.0 / 945.0, -11966.0 / 14175.0, -21016.0 / 51975.0, 251310128.0 / 638512875.0, 3044.0 / 2835.0, 3802.0 / 14175.0, -94388.0 / 66825.0, -8797648.0 / 10945935.0, 6059.0 / 4725.0, 41072.0 / 93555.0, -1472637812.0 / 638512875.0, 768272.0 / 467775.0, 455935736.0 / 638512875.0, 4210684958.0 / 1915538625.0, // C[beta,phi] skipped // C[beta,beta] skipped // C[beta,theta] skipped // C[beta,mu] skipped // C[beta,chi] skipped // C[beta,xi] skipped // C[theta,phi] skipped // C[theta,beta] skipped // C[theta,theta] skipped // C[theta,mu] skipped // C[theta,chi] skipped // C[theta,xi] skipped // C[mu,phi]; even coeffs only -3.0 / 2.0, 9.0 / 16.0, -3.0 / 32.0, 15.0 / 16.0, -15.0 / 32.0, 135.0 / 2048.0, -35.0 / 48.0, 105.0 / 256.0, 315.0 / 512.0, -189.0 / 512.0, -693.0 / 1280.0, 1001.0 / 2048.0, // C[mu,beta] skipped // C[mu,theta] skipped // C[mu,mu] skipped // C[mu,chi] 1.0 / 2.0, -2.0 / 3.0, 5.0 / 16.0, 41.0 / 180.0, -127.0 / 288.0, 7891.0 / 37800.0, 13.0 / 48.0, -3.0 / 5.0, 557.0 / 1440.0, 281.0 / 630.0, -1983433.0 / 1935360.0, 61.0 / 240.0, -103.0 / 140.0, 15061.0 / 26880.0, 167603.0 / 181440.0, 49561.0 / 161280.0, -179.0 / 168.0, 6601661.0 / 7257600.0, 34729.0 / 80640.0, -3418889.0 / 1995840.0, 212378941.0 / 319334400.0, // C[mu,xi] skipped // C[chi,phi] -2.0, 2.0 / 3.0, 4.0 / 3.0, -82.0 / 45.0, 32.0 / 45.0, 4642.0 / 4725.0, 5.0 / 3.0, -16.0 / 15.0, -13.0 / 9.0, 904.0 / 315.0, -1522.0 / 945.0, -26.0 / 15.0, 34.0 / 21.0, 8.0 / 5.0, -12686.0 / 2835.0, 1237.0 / 630.0, -12.0 / 5.0, -24832.0 / 14175.0, -734.0 / 315.0, 109598.0 / 31185.0, 444337.0 / 155925.0, // C[chi,beta] skipped // C[chi,theta] skipped // C[chi,mu] -1.0 / 2.0, 2.0 / 3.0, -37.0 / 96.0, 1.0 / 360.0, 81.0 / 512.0, -96199.0 / 604800.0, -1.0 / 48.0, -1.0 / 15.0, 437.0 / 1440.0, -46.0 / 105.0, 1118711.0 / 3870720.0, -17.0 / 480.0, 37.0 / 840.0, 209.0 / 4480.0, -5569.0 / 90720.0, -4397.0 / 161280.0, 11.0 / 504.0, 830251.0 / 7257600.0, -4583.0 / 161280.0, 108847.0 / 3991680.0, -20648693.0 / 638668800.0, // C[chi,chi] skipped // C[chi,xi] skipped // C[xi,phi] -4.0 / 3.0, -4.0 / 45.0, 88.0 / 315.0, 538.0 / 4725.0, 20824.0 / 467775.0, -44732.0 / 2837835.0, 34.0 / 45.0, 8.0 / 105.0, -2482.0 / 14175.0, -37192.0 / 467775.0, -12467764.0 / 212837625.0, -1532.0 / 2835.0, -898.0 / 14175.0, 54968.0 / 467775.0, 100320856.0 / 1915538625.0, 6007.0 / 14175.0, 24496.0 / 467775.0, -5884124.0 / 70945875.0, -23356.0 / 66825.0, -839792.0 / 19348875.0, 570284222.0 / 1915538625.0, // C[xi,beta] skipped // C[xi,theta] skipped // C[xi,mu] skipped // C[xi,chi] skipped // C[xi,xi] skipped }; static constexpr int ptrs[] = { 0, 0, 0, 0, 12, 33, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 66, 66, 66, 66, 87, 87, 108, 108, 108, 129, 129, 129, 150, 150, 150, 150, 150, 150, }; static_assert(sizeof(ptrs) / sizeof(int) == int(AuxLat::NUMBER) * int(AuxLat::NUMBER) + 1, "Mismatch in size of ptrs array"); static_assert(sizeof(coeffs) / sizeof(double) == ptrs[int(AuxLat::NUMBER) * int(AuxLat::NUMBER)], "Mismatch in size of coeffs array"); if (!(auxin >= AuxLat(0) && auxin < AuxLat::NUMBER && auxout >= AuxLat(0) && auxout < AuxLat::NUMBER)) throw std::out_of_range("Bad specification for auxiliary latitude"); int k = int(AuxLat::NUMBER) * int(auxout) + int(auxin), o = ptrs[k]; if (o == ptrs[k + 1]) throw std::out_of_range( "Unsupported conversion between auxiliary latitudes"); double d = n, n2 = n * n; if (auxin <= AuxLat::RECTIFYING && auxout <= AuxLat::RECTIFYING) { for (int l = 0; l < Lmax; ++l) { int m = (Lmax - l - 1) / 2; // order of polynomial in n^2 F[l] = d * pj_polyval(n2, coeffs + o, m); o += m + 1; // coverity[copy_paste_error] d *= n; } } else { for (int l = 0; l < Lmax; ++l) { int m = (Lmax - l - 1); // order of polynomial in n F[l] = d * pj_polyval(n, coeffs + o, m); o += m + 1; d *= n; } } // assert (o == ptrs[k+1]); } // Evaluation sum(p[i] * x^i, i, 0, N) via Horner's method. N.B. p is of // length N+1. double pj_polyval(double x, const double p[], int N) { double y = N < 0 ? 0 : p[N]; for (; N > 0;) y = y * x + p[--N]; return y; } // Evaluate y = sum(F[k] * sin((2*k+2) * zeta), k, 0, K-1) by Clenshaw // summation. zeta is specify by its sine and cosine, szeta and czeta. double pj_clenshaw(double szeta, double czeta, const double F[], int K) { // Approx operation count = (K + 5) mult and (2 * K + 2) add double u0 = 0, u1 = 0, // accumulators for sum X = 2 * (czeta - szeta) * (czeta + szeta); // 2 * cos(2*zeta) for (; K > 0;) { double t = X * u0 - u1 + F[--K]; u1 = u0; u0 = t; } return 2 * szeta * czeta * u0; // sin(2*zeta) * u0 } // Convert auxiliary latitude zeta to eta, given coefficients F produced by // pj_auxlats_coeffs(n, zeta, eta, F). K is the size of F (defaults to // AuxLat::ORDER). double pj_auxlat_convert(double zeta, const double F[], int K) { return pj_auxlat_convert(zeta, sin(zeta), cos(zeta), F, K); } // Convert auxiliary latitude zeta to eta, given coefficients F produced by // pj_auxlats_coeffs(n, zeta, eta, F). In this signature, szeta and czeta (the // sine and cosine of zeta) are given as inputs. K is the size of F (defaults // to AuxLat::ORDER). double pj_auxlat_convert(double zeta, double szeta, double czeta, const double F[], int K) { return zeta + pj_clenshaw(szeta, czeta, F, K); } // Convert auxiliary latitude zeta to eta, given coefficients F produced by // pj_auxlats_coeffs(n, zeta, eta, F). K is the size of F (defaults to // AuxLat::ORDER). In this signature, the sine and cosine of eta are returned. // This provides high relative accuracy near the poles. void pj_auxlat_convert(double szeta, double czeta, double &seta, double &ceta, const double F[], int K) { double delta = pj_clenshaw(szeta, czeta, F, K), sdelta = sin(delta), cdelta = cos(delta); seta = szeta * cdelta + czeta * sdelta; ceta = czeta * cdelta - szeta * sdelta; } // Compute the rectifying radius = quarter meridian / (pi/2 * a). The accuracy // of this series is the same as those used for the computation of the // auxiliary latitudes. double pj_rectifying_radius(double n) { // Expansion of (quarter meridian) / ((a+b)/2 * pi/2) as series in n^2; // these coefficients are ( (2*k - 3)!! / (2*k)!! )^2 for k = 0..3 static const double coeff_rad[] = {1, 1.0 / 4, 1.0 / 64, 1.0 / 256}; return pj_polyval(n * n, coeff_rad, 3) / (1 + n); } proj-9.8.1/src/wkt2_parser.cpp000664 001750 001750 00000023132 15166171715 016212 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: WKT2 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018 Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/internal/internal.hpp" #include #include #include #include #include "proj_constants.h" #include "wkt2_parser.h" #include "wkt_parser.hpp" using namespace NS_PROJ::internal; //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- struct pj_wkt2_parse_context : public pj_wkt_parse_context {}; // --------------------------------------------------------------------------- void pj_wkt2_error(pj_wkt2_parse_context *context, const char *msg) { pj_wkt_error(context, msg); } // --------------------------------------------------------------------------- std::string pj_wkt2_parse(const std::string &wkt) { pj_wkt2_parse_context context; context.pszInput = wkt.c_str(); context.pszLastSuccess = wkt.c_str(); context.pszNext = wkt.c_str(); if (pj_wkt2_parse(&context) != 0) { return context.errorMsg; } return std::string(); } // --------------------------------------------------------------------------- typedef struct { const char *pszToken; int nTokenVal; } wkt2_tokens; #define PAIR(X) \ { \ #X, T_##X \ } static const wkt2_tokens tokens[] = { PAIR(PARAMETER), PAIR(PROJECTION), PAIR(DATUM), PAIR(SPHEROID), PAIR(PRIMEM), PAIR(UNIT), PAIR(AXIS), PAIR(GEODCRS), PAIR(LENGTHUNIT), PAIR(ANGLEUNIT), PAIR(SCALEUNIT), PAIR(TIMEUNIT), PAIR(ELLIPSOID), PAIR(CS), PAIR(ID), PAIR(PROJCRS), PAIR(BASEGEODCRS), PAIR(MERIDIAN), PAIR(BEARING), PAIR(ORDER), PAIR(ANCHOR), PAIR(ANCHOREPOCH), PAIR(CONVERSION), PAIR(METHOD), PAIR(REMARK), PAIR(GEOGCRS), PAIR(BASEGEOGCRS), PAIR(SCOPE), PAIR(AREA), PAIR(BBOX), PAIR(CITATION), PAIR(URI), PAIR(VERTCRS), PAIR(VDATUM), PAIR(GEOIDMODEL), PAIR(COMPOUNDCRS), PAIR(PARAMETERFILE), PAIR(COORDINATEOPERATION), PAIR(SOURCECRS), PAIR(TARGETCRS), PAIR(INTERPOLATIONCRS), PAIR(OPERATIONACCURACY), PAIR(CONCATENATEDOPERATION), PAIR(STEP), PAIR(BOUNDCRS), PAIR(ABRIDGEDTRANSFORMATION), PAIR(DERIVINGCONVERSION), PAIR(TDATUM), PAIR(CALENDAR), PAIR(TIMEORIGIN), PAIR(TIMECRS), PAIR(VERTICALEXTENT), PAIR(TIMEEXTENT), PAIR(USAGE), PAIR(DYNAMIC), PAIR(FRAMEEPOCH), PAIR(MODEL), PAIR(VELOCITYGRID), PAIR(ENSEMBLE), PAIR(MEMBER), PAIR(ENSEMBLEACCURACY), PAIR(DERIVEDPROJCRS), PAIR(BASEPROJCRS), PAIR(EDATUM), PAIR(ENGCRS), PAIR(PDATUM), PAIR(PARAMETRICCRS), PAIR(PARAMETRICUNIT), PAIR(BASEVERTCRS), PAIR(BASEENGCRS), PAIR(BASEPARAMCRS), PAIR(BASETIMECRS), PAIR(GEODETICCRS), PAIR(GEODETICDATUM), PAIR(PROJECTEDCRS), PAIR(PRIMEMERIDIAN), PAIR(GEOGRAPHICCRS), PAIR(TRF), PAIR(VERTICALCRS), PAIR(VERTICALDATUM), PAIR(VRF), PAIR(TIMEDATUM), PAIR(TEMPORALQUANTITY), PAIR(ENGINEERINGDATUM), PAIR(ENGINEERINGCRS), PAIR(PARAMETRICDATUM), PAIR(EPOCH), PAIR(COORDEPOCH), PAIR(COORDINATEMETADATA), PAIR(POINTMOTIONOPERATION), PAIR(VERSION), PAIR(AXISMINVALUE), PAIR(AXISMAXVALUE), PAIR(RANGEMEANING), PAIR(exact), PAIR(wraparound), PAIR(DEFININGTRANSFORMATION), // CS types PAIR(AFFINE), PAIR(CARTESIAN), PAIR(CYLINDRICAL), PAIR(ELLIPSOIDAL), PAIR(LINEAR), PAIR(PARAMETRIC), PAIR(POLAR), PAIR(SPHERICAL), PAIR(VERTICAL), PAIR(TEMPORAL), PAIR(TEMPORALCOUNT), PAIR(TEMPORALMEASURE), PAIR(ORDINAL), PAIR(TEMPORALDATETIME), // Axis directions PAIR(NORTH), PAIR(NORTHNORTHEAST), PAIR(NORTHEAST), PAIR(EASTNORTHEAST), PAIR(EAST), PAIR(EASTSOUTHEAST), PAIR(SOUTHEAST), PAIR(SOUTHSOUTHEAST), PAIR(SOUTH), PAIR(SOUTHSOUTHWEST), PAIR(SOUTHWEST), PAIR(WESTSOUTHWEST), PAIR(WEST), PAIR(WESTNORTHWEST), PAIR(NORTHWEST), PAIR(NORTHNORTHWEST), PAIR(UP), PAIR(DOWN), PAIR(GEOCENTRICX), PAIR(GEOCENTRICY), PAIR(GEOCENTRICZ), PAIR(COLUMNPOSITIVE), PAIR(COLUMNNEGATIVE), PAIR(ROWPOSITIVE), PAIR(ROWNEGATIVE), PAIR(DISPLAYRIGHT), PAIR(DISPLAYLEFT), PAIR(DISPLAYUP), PAIR(DISPLAYDOWN), PAIR(FORWARD), PAIR(AFT), PAIR(PORT), PAIR(STARBOARD), PAIR(CLOCKWISE), PAIR(COUNTERCLOCKWISE), PAIR(TOWARDS), PAIR(AWAYFROM), PAIR(FUTURE), PAIR(PAST), PAIR(UNSPECIFIED), }; // --------------------------------------------------------------------------- int pj_wkt2_lex(YYSTYPE * /*pNode */, pj_wkt2_parse_context *context) { size_t i; const char *pszInput = context->pszNext; /* -------------------------------------------------------------------- */ /* Skip white space. */ /* -------------------------------------------------------------------- */ while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == 10 || *pszInput == 13) pszInput++; context->pszLastSuccess = pszInput; if (*pszInput == '\0') { context->pszNext = pszInput; return EOF; } /* -------------------------------------------------------------------- */ /* Recognize node names. */ /* -------------------------------------------------------------------- */ if (isalpha(*pszInput)) { for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++) { if (ci_starts_with(pszInput, tokens[i].pszToken) && !isalpha(pszInput[strlen(tokens[i].pszToken)])) { context->pszNext = pszInput + strlen(tokens[i].pszToken); return tokens[i].nTokenVal; } } } /* -------------------------------------------------------------------- */ /* Recognize unsigned integer */ /* -------------------------------------------------------------------- */ if (*pszInput >= '0' && *pszInput <= '9') { // Special case for 1, 2, 3 if ((*pszInput == '1' || *pszInput == '2' || *pszInput == '3') && !(pszInput[1] >= '0' && pszInput[1] <= '9')) { context->pszNext = pszInput + 1; return *pszInput; } pszInput++; while (*pszInput >= '0' && *pszInput <= '9') pszInput++; context->pszNext = pszInput; return T_UNSIGNED_INTEGER_DIFFERENT_ONE_TWO_THREE; } /* -------------------------------------------------------------------- */ /* Recognize double quoted strings. */ /* -------------------------------------------------------------------- */ if (*pszInput == '"') { pszInput++; while (*pszInput != '\0') { if (*pszInput == '"') { if (pszInput[1] == '"') pszInput++; else break; } pszInput++; } if (*pszInput == '\0') { context->pszNext = pszInput; return EOF; } context->pszNext = pszInput + 1; return T_STRING; } // As used in examples of OGC 12-063r5 const char *startPrintedQuote = "\xE2\x80\x9C"; const char *endPrintedQuote = "\xE2\x80\x9D"; if (strncmp(pszInput, startPrintedQuote, 3) == 0) { context->pszNext = strstr(pszInput, endPrintedQuote); if (context->pszNext == nullptr) { context->pszNext = pszInput + strlen(pszInput); return EOF; } context->pszNext += 3; return T_STRING; } /* -------------------------------------------------------------------- */ /* Handle special tokens. */ /* -------------------------------------------------------------------- */ context->pszNext = pszInput + 1; return *pszInput; } //! @endcond proj-9.8.1/src/wkt2_generated_parser.c000664 001750 001750 00000526656 15166171715 017713 0ustar00eveneven000000 000000 /* A Bison parser, made by GNU Bison 3.5.1. */ /* Bison implementation for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Undocumented macros, especially those whose name start with YY_, are private implementation details. Do not rely on them. */ /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ #define YYBISON_VERSION "3.5.1" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 1 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* Substitute the variable and function names. */ #define yyparse pj_wkt2_parse #define yylex pj_wkt2_lex #define yyerror pj_wkt2_error #define yydebug pj_wkt2_debug #define yynerrs pj_wkt2_nerrs /* First part of user prologue. */ /****************************************************************************** * Project: PROJ * Purpose: WKT2 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018 Even Rouault, * * 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 "wkt2_parser.h" # ifndef YY_CAST # ifdef __cplusplus # define YY_CAST(Type, Val) static_cast (Val) # define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) # else # define YY_CAST(Type, Val) ((Type) (Val)) # define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) # endif # endif # ifndef YY_NULLPTR # if defined __cplusplus # if 201103L <= __cplusplus # define YY_NULLPTR nullptr # else # define YY_NULLPTR 0 # endif # else # define YY_NULLPTR ((void*)0) # endif # endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE # undef YYERROR_VERBOSE # define YYERROR_VERBOSE 1 #else # define YYERROR_VERBOSE 1 #endif /* Use api.header.include to #include this header instead of duplicating it here. */ #ifndef YY_PJ_WKT2_WKT2_GENERATED_PARSER_H_INCLUDED # define YY_PJ_WKT2_WKT2_GENERATED_PARSER_H_INCLUDED /* Debug traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif #if YYDEBUG extern int pj_wkt2_debug; #endif /* Token type. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { END = 0, T_PROJECTION = 258, T_DATUM = 259, T_SPHEROID = 260, T_PRIMEM = 261, T_UNIT = 262, T_AXIS = 263, T_PARAMETER = 264, T_GEODCRS = 265, T_LENGTHUNIT = 266, T_ANGLEUNIT = 267, T_SCALEUNIT = 268, T_TIMEUNIT = 269, T_ELLIPSOID = 270, T_CS = 271, T_ID = 272, T_PROJCRS = 273, T_BASEGEODCRS = 274, T_MERIDIAN = 275, T_BEARING = 276, T_ORDER = 277, T_ANCHOR = 278, T_ANCHOREPOCH = 279, T_CONVERSION = 280, T_METHOD = 281, T_REMARK = 282, T_GEOGCRS = 283, T_BASEGEOGCRS = 284, T_SCOPE = 285, T_AREA = 286, T_BBOX = 287, T_CITATION = 288, T_URI = 289, T_VERTCRS = 290, T_VDATUM = 291, T_GEOIDMODEL = 292, T_COMPOUNDCRS = 293, T_PARAMETERFILE = 294, T_COORDINATEOPERATION = 295, T_SOURCECRS = 296, T_TARGETCRS = 297, T_INTERPOLATIONCRS = 298, T_OPERATIONACCURACY = 299, T_CONCATENATEDOPERATION = 300, T_STEP = 301, T_BOUNDCRS = 302, T_ABRIDGEDTRANSFORMATION = 303, T_DERIVINGCONVERSION = 304, T_TDATUM = 305, T_CALENDAR = 306, T_TIMEORIGIN = 307, T_TIMECRS = 308, T_VERTICALEXTENT = 309, T_TIMEEXTENT = 310, T_USAGE = 311, T_DYNAMIC = 312, T_FRAMEEPOCH = 313, T_MODEL = 314, T_VELOCITYGRID = 315, T_ENSEMBLE = 316, T_MEMBER = 317, T_ENSEMBLEACCURACY = 318, T_DERIVEDPROJCRS = 319, T_BASEPROJCRS = 320, T_EDATUM = 321, T_ENGCRS = 322, T_PDATUM = 323, T_PARAMETRICCRS = 324, T_PARAMETRICUNIT = 325, T_BASEVERTCRS = 326, T_BASEENGCRS = 327, T_BASEPARAMCRS = 328, T_BASETIMECRS = 329, T_EPOCH = 330, T_COORDEPOCH = 331, T_COORDINATEMETADATA = 332, T_POINTMOTIONOPERATION = 333, T_VERSION = 334, T_AXISMINVALUE = 335, T_AXISMAXVALUE = 336, T_RANGEMEANING = 337, T_exact = 338, T_wraparound = 339, T_DEFININGTRANSFORMATION = 340, T_GEODETICCRS = 341, T_GEODETICDATUM = 342, T_PROJECTEDCRS = 343, T_PRIMEMERIDIAN = 344, T_GEOGRAPHICCRS = 345, T_TRF = 346, T_VERTICALCRS = 347, T_VERTICALDATUM = 348, T_VRF = 349, T_TIMEDATUM = 350, T_TEMPORALQUANTITY = 351, T_ENGINEERINGDATUM = 352, T_ENGINEERINGCRS = 353, T_PARAMETRICDATUM = 354, T_AFFINE = 355, T_CARTESIAN = 356, T_CYLINDRICAL = 357, T_ELLIPSOIDAL = 358, T_LINEAR = 359, T_PARAMETRIC = 360, T_POLAR = 361, T_SPHERICAL = 362, T_VERTICAL = 363, T_TEMPORAL = 364, T_TEMPORALCOUNT = 365, T_TEMPORALMEASURE = 366, T_ORDINAL = 367, T_TEMPORALDATETIME = 368, T_NORTH = 369, T_NORTHNORTHEAST = 370, T_NORTHEAST = 371, T_EASTNORTHEAST = 372, T_EAST = 373, T_EASTSOUTHEAST = 374, T_SOUTHEAST = 375, T_SOUTHSOUTHEAST = 376, T_SOUTH = 377, T_SOUTHSOUTHWEST = 378, T_SOUTHWEST = 379, T_WESTSOUTHWEST = 380, T_WEST = 381, T_WESTNORTHWEST = 382, T_NORTHWEST = 383, T_NORTHNORTHWEST = 384, T_UP = 385, T_DOWN = 386, T_GEOCENTRICX = 387, T_GEOCENTRICY = 388, T_GEOCENTRICZ = 389, T_COLUMNPOSITIVE = 390, T_COLUMNNEGATIVE = 391, T_ROWPOSITIVE = 392, T_ROWNEGATIVE = 393, T_DISPLAYRIGHT = 394, T_DISPLAYLEFT = 395, T_DISPLAYUP = 396, T_DISPLAYDOWN = 397, T_FORWARD = 398, T_AFT = 399, T_PORT = 400, T_STARBOARD = 401, T_CLOCKWISE = 402, T_COUNTERCLOCKWISE = 403, T_TOWARDS = 404, T_AWAYFROM = 405, T_FUTURE = 406, T_PAST = 407, T_UNSPECIFIED = 408, T_STRING = 409, T_UNSIGNED_INTEGER_DIFFERENT_ONE_TWO_THREE = 410 }; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef int YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif int pj_wkt2_parse (pj_wkt2_parse_context *context); #endif /* !YY_PJ_WKT2_WKT2_GENERATED_PARSER_H_INCLUDED */ #ifdef short # undef short #endif /* On compilers that do not define __PTRDIFF_MAX__ etc., make sure and (if available) are included so that the code can choose integer types of a good width. */ #ifndef __PTRDIFF_MAX__ # include /* INFRINGES ON USER NAME SPACE */ # if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YY_STDINT_H # endif #endif /* Narrow types that promote to a signed type and that can represent a signed or unsigned integer of at least N bits. In tables they can save space and decrease cache pressure. Promoting to a signed type helps avoid bugs in integer arithmetic. */ #ifdef __INT_LEAST8_MAX__ typedef __INT_LEAST8_TYPE__ yytype_int8; #elif defined YY_STDINT_H typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif #ifdef __INT_LEAST16_MAX__ typedef __INT_LEAST16_TYPE__ yytype_int16; #elif defined YY_STDINT_H typedef int_least16_t yytype_int16; #else typedef short yytype_int16; #endif #if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ typedef __UINT_LEAST8_TYPE__ yytype_uint8; #elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ && UINT_LEAST8_MAX <= INT_MAX) typedef uint_least8_t yytype_uint8; #elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX typedef unsigned char yytype_uint8; #else typedef short yytype_uint8; #endif #if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ typedef __UINT_LEAST16_TYPE__ yytype_uint16; #elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ && UINT_LEAST16_MAX <= INT_MAX) typedef uint_least16_t yytype_uint16; #elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX typedef unsigned short yytype_uint16; #else typedef int yytype_uint16; #endif #ifndef YYPTRDIFF_T # if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ # define YYPTRDIFF_T __PTRDIFF_TYPE__ # define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ # elif defined PTRDIFF_MAX # ifndef ptrdiff_t # include /* INFRINGES ON USER NAME SPACE */ # endif # define YYPTRDIFF_T ptrdiff_t # define YYPTRDIFF_MAXIMUM PTRDIFF_MAX # else # define YYPTRDIFF_T long # define YYPTRDIFF_MAXIMUM LONG_MAX # endif #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif #define YYSIZE_MAXIMUM \ YY_CAST (YYPTRDIFF_T, \ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ ? YYPTRDIFF_MAXIMUM \ : YY_CAST (YYSIZE_T, -1))) #define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) /* Stored state numbers (used for stacks). */ typedef yytype_int16 yy_state_t; /* State numbers in computations. */ typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif #ifndef YY_ATTRIBUTE_PURE # if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) # else # define YY_ATTRIBUTE_PURE # endif #endif #ifndef YY_ATTRIBUTE_UNUSED # if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else # define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YYUSE(E) ((void) (E)) #else # define YYUSE(E) /* empty */ #endif #if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif #if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ # define YY_IGNORE_USELESS_CAST_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") # define YY_IGNORE_USELESS_CAST_END \ _Pragma ("GCC diagnostic pop") #endif #ifndef YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_END #endif #define YY_ASSERT(E) ((void) (0 && (E))) #if ! defined yyoverflow || YYERROR_VERBOSE /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ /* Use EXIT_SUCCESS as a witness for stdlib.h. */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's 'empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* ! defined yyoverflow || YYERROR_VERBOSE */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif # endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 106 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 3517 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 171 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 375 /* YYNRULES -- Number of rules. */ #define YYNRULES 750 /* YYNSTATES -- Number of states. */ #define YYNSTATES 1531 #define YYUNDEFTOK 2 #define YYMAXUTOK 410 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ (0 <= (YYX) && (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ static const yytype_uint8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 167, 169, 2, 161, 170, 162, 156, 2, 2, 158, 159, 160, 2, 2, 2, 2, 2, 2, 163, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 157, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 164, 2, 2, 2, 2, 2, 165, 166, 2, 168, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155 }; #if YYDEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { 0, 214, 214, 214, 214, 214, 214, 214, 215, 215, 215, 216, 219, 219, 220, 220, 220, 221, 223, 223, 227, 231, 231, 233, 235, 237, 237, 239, 239, 241, 243, 245, 247, 249, 249, 251, 251, 253, 253, 253, 253, 255, 255, 259, 261, 265, 266, 267, 269, 269, 271, 273, 275, 277, 281, 282, 285, 286, 288, 290, 292, 295, 296, 297, 299, 301, 303, 303, 305, 308, 309, 311, 311, 316, 316, 318, 318, 320, 322, 324, 328, 329, 332, 333, 334, 336, 336, 337, 340, 341, 345, 346, 347, 351, 352, 353, 354, 356, 360, 362, 365, 367, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 405, 407, 409, 413, 418, 420, 422, 424, 426, 430, 435, 436, 438, 440, 442, 446, 450, 452, 452, 454, 454, 459, 464, 465, 466, 467, 468, 469, 470, 472, 474, 476, 476, 478, 478, 480, 482, 484, 486, 488, 490, 494, 496, 500, 500, 503, 506, 511, 511, 511, 511, 511, 514, 519, 519, 519, 519, 522, 526, 527, 529, 545, 549, 550, 552, 552, 554, 554, 560, 560, 562, 564, 571, 571, 571, 571, 573, 575, 582, 589, 590, 591, 592, 594, 595, 598, 600, 603, 604, 605, 606, 607, 609, 610, 611, 612, 614, 621, 622, 623, 624, 626, 633, 640, 641, 642, 644, 646, 646, 646, 646, 646, 646, 646, 646, 646, 648, 648, 650, 650, 652, 652, 652, 654, 659, 665, 670, 673, 676, 677, 678, 679, 680, 681, 682, 683, 684, 687, 688, 689, 690, 691, 692, 693, 694, 697, 698, 699, 700, 701, 702, 703, 704, 707, 708, 711, 712, 713, 714, 715, 719, 720, 721, 722, 723, 724, 725, 726, 727, 730, 731, 732, 733, 736, 737, 738, 739, 742, 743, 746, 747, 748, 753, 754, 757, 758, 759, 760, 761, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 801, 804, 806, 808, 810, 812, 815, 816, 817, 818, 820, 821, 822, 823, 824, 826, 827, 829, 830, 832, 833, 834, 834, 836, 852, 852, 854, 861, 862, 864, 865, 867, 875, 876, 878, 880, 882, 887, 888, 890, 892, 894, 896, 898, 900, 902, 907, 911, 913, 916, 919, 920, 921, 923, 924, 926, 931, 932, 934, 934, 936, 940, 940, 940, 942, 942, 944, 952, 961, 969, 979, 980, 982, 984, 984, 986, 986, 989, 990, 994, 1000, 1001, 1002, 1004, 1004, 1006, 1008, 1010, 1014, 1019, 1019, 1021, 1024, 1025, 1030, 1031, 1033, 1038, 1038, 1038, 1040, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1051, 1054, 1056, 1058, 1061, 1063, 1063, 1063, 1067, 1073, 1073, 1077, 1077, 1078, 1078, 1080, 1085, 1086, 1087, 1088, 1089, 1091, 1097, 1102, 1108, 1110, 1112, 1114, 1118, 1124, 1125, 1126, 1128, 1130, 1132, 1136, 1136, 1138, 1140, 1145, 1146, 1147, 1149, 1151, 1153, 1155, 1159, 1159, 1161, 1167, 1174, 1174, 1177, 1184, 1185, 1186, 1187, 1188, 1190, 1191, 1192, 1194, 1198, 1200, 1202, 1202, 1206, 1211, 1211, 1211, 1215, 1220, 1220, 1222, 1226, 1226, 1228, 1229, 1230, 1231, 1235, 1240, 1242, 1246, 1246, 1250, 1255, 1257, 1261, 1262, 1263, 1264, 1265, 1267, 1267, 1269, 1272, 1274, 1274, 1276, 1278, 1280, 1284, 1290, 1291, 1292, 1293, 1295, 1297, 1301, 1306, 1308, 1311, 1317, 1318, 1319, 1321, 1325, 1331, 1331, 1331, 1331, 1331, 1331, 1335, 1340, 1342, 1347, 1347, 1348, 1350, 1350, 1352, 1359, 1359, 1361, 1368, 1368, 1370, 1377, 1384, 1389, 1390, 1391, 1393, 1399, 1404, 1412, 1418, 1420, 1422, 1433, 1434, 1435, 1437, 1439, 1439, 1440, 1440, 1444, 1450, 1450, 1452, 1457, 1463, 1468, 1474, 1479, 1484, 1490, 1495, 1500, 1506, 1511, 1516, 1522, 1522, 1523, 1523, 1524, 1524, 1525, 1525, 1526, 1526, 1527, 1527, 1530, 1530, 1532, 1533, 1534, 1536, 1538, 1542, 1545, 1545, 1548, 1549, 1550, 1552, 1556, 1557, 1559, 1561, 1561, 1562, 1562, 1563, 1563, 1563, 1564, 1565, 1565, 1566, 1566, 1567, 1567, 1569, 1569, 1570, 1570, 1571, 1572, 1572, 1576, 1580, 1581, 1584, 1589, 1590, 1591, 1592, 1593, 1594, 1595, 1597, 1599, 1601, 1604, 1606, 1608, 1610, 1612, 1614, 1616, 1618, 1620, 1622, 1627, 1631, 1632, 1635, 1640, 1641, 1642, 1643, 1644, 1646, 1651, 1656, 1657, 1660, 1666, 1666, 1666, 1666, 1668, 1669, 1670, 1671, 1673, 1675, 1680, 1686, 1688, 1693, 1694, 1697, 1705, 1706, 1707, 1708, 1710, 1712 }; #endif #if YYDEBUG || YYERROR_VERBOSE || 1 /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "\"end of string\"", "error", "$undefined", "\"PROJECTION\"", "\"DATUM\"", "\"SPHEROID\"", "\"PRIMEM\"", "\"UNIT\"", "\"AXIS\"", "\"PARAMETER\"", "\"GEODCRS\"", "\"LENGTHUNIT\"", "\"ANGLEUNIT\"", "\"SCALEUNIT\"", "\"TIMEUNIT\"", "\"ELLIPSOID\"", "\"CS\"", "\"ID\"", "\"PROJCRS\"", "\"BASEGEODCRS\"", "\"MERIDIAN\"", "\"BEARING\"", "\"ORDER\"", "\"ANCHOR\"", "\"ANCHOREPOCH\"", "\"CONVERSION\"", "\"METHOD\"", "\"REMARK\"", "\"GEOGCRS\"", "\"BASEGEOGCRS\"", "\"SCOPE\"", "\"AREA\"", "\"BBOX\"", "\"CITATION\"", "\"URI\"", "\"VERTCRS\"", "\"VDATUM\"", "\"GEOIDMODEL\"", "\"COMPOUNDCRS\"", "\"PARAMETERFILE\"", "\"COORDINATEOPERATION\"", "\"SOURCECRS\"", "\"TARGETCRS\"", "\"INTERPOLATIONCRS\"", "\"OPERATIONACCURACY\"", "\"CONCATENATEDOPERATION\"", "\"STEP\"", "\"BOUNDCRS\"", "\"ABRIDGEDTRANSFORMATION\"", "\"DERIVINGCONVERSION\"", "\"TDATUM\"", "\"CALENDAR\"", "\"TIMEORIGIN\"", "\"TIMECRS\"", "\"VERTICALEXTENT\"", "\"TIMEEXTENT\"", "\"USAGE\"", "\"DYNAMIC\"", "\"FRAMEEPOCH\"", "\"MODEL\"", "\"VELOCITYGRID\"", "\"ENSEMBLE\"", "\"MEMBER\"", "\"ENSEMBLEACCURACY\"", "\"DERIVEDPROJCRS\"", "\"BASEPROJCRS\"", "\"EDATUM\"", "\"ENGCRS\"", "\"PDATUM\"", "\"PARAMETRICCRS\"", "\"PARAMETRICUNIT\"", "\"BASEVERTCRS\"", "\"BASEENGCRS\"", "\"BASEPARAMCRS\"", "\"BASETIMECRS\"", "\"EPOCH\"", "\"COORDEPOCH\"", "\"COORDINATEMETADATA\"", "\"POINTMOTIONOPERATION\"", "\"VERSION\"", "\"AXISMINVALUE\"", "\"AXISMAXVALUE\"", "\"RANGEMEANING\"", "\"exact\"", "\"wraparound\"", "\"DEFININGTRANSFORMATION\"", "\"GEODETICCRS\"", "\"GEODETICDATUM\"", "\"PROJECTEDCRS\"", "\"PRIMEMERIDIAN\"", "\"GEOGRAPHICCRS\"", "\"TRF\"", "\"VERTICALCRS\"", "\"VERTICALDATUM\"", "\"VRF\"", "\"TIMEDATUM\"", "\"TEMPORALQUANTITY\"", "\"ENGINEERINGDATUM\"", "\"ENGINEERINGCRS\"", "\"PARAMETRICDATUM\"", "\"affine\"", "\"Cartesian\"", "\"cylindrical\"", "\"ellipsoidal\"", "\"linear\"", "\"parametric\"", "\"polar\"", "\"spherical\"", "\"vertical\"", "\"temporal\"", "\"temporalCount\"", "\"temporalMeasure\"", "\"ordinal\"", "\"temporalDateTime\"", "\"north\"", "\"northNorthEast\"", "\"northEast\"", "\"eastNorthEast\"", "\"east\"", "\"eastSouthEast\"", "\"southEast\"", "\"southSouthEast\"", "\"south\"", "\"southSouthWest\"", "\"southWest\"", "\"westSouthWest\"", "\"west\"", "\"westNorthWest\"", "\"northWest\"", "\"northNorthWest\"", "\"up\"", "\"down\"", "\"geocentricX\"", "\"geocentricY\"", "\"geocentricZ\"", "\"columnPositive\"", "\"columnNegative\"", "\"rowPositive\"", "\"rowNegative\"", "\"displayRight\"", "\"displayLeft\"", "\"displayUp\"", "\"displayDown\"", "\"forward\"", "\"aft\"", "\"port\"", "\"starboard\"", "\"clockwise\"", "\"counterClockwise\"", "\"towards\"", "\"awayFrom\"", "\"future\"", "\"part\"", "\"unspecified\"", "\"string\"", "\"unsigned integer\"", "'.'", "'E'", "'1'", "'2'", "'3'", "'+'", "'-'", "':'", "'T'", "'Z'", "'['", "'('", "']'", "')'", "','", "$accept", "input", "datum", "crs", "period", "number", "signed_numeric_literal_with_sign", "signed_numeric_literal", "unsigned_numeric_literal", "opt_sign", "approximate_numeric_literal", "mantissa", "exponent", "signed_integer", "exact_numeric_literal", "opt_period_unsigned_integer", "unsigned_integer", "sign", "colon", "hyphen", "datetime", "opt_24_hour_clock", "year", "month", "day", "_24_hour_clock", "opt_colon_minute_colon_second_time_zone_designator", "opt_colon_second_time_zone_designator", "time_designator", "hour", "minute", "second_time_zone_designator", "seconds_integer", "seconds_fraction", "time_zone_designator", "utc_designator", "local_time_zone_designator", "opt_colon_minute", "left_delimiter", "right_delimiter", "wkt_separator", "quoted_latin_text", "quoted_unicode_text", "opt_separator_scope_extent_identifier_remark", "no_opt_separator_scope_extent_identifier_remark", "opt_identifier_list_remark", "scope_extent_opt_identifier_list_opt_remark", "scope_extent_opt_identifier_list_remark", "usage_list_opt_identifier_list_remark", "usage", "usage_keyword", "scope", "scope_keyword", "scope_text_description", "extent", "extent_opt_identifier_list_remark", "area_description", "area_description_keyword", "area_text_description", "geographic_bounding_box", "geographic_bounding_box_keyword", "lower_left_latitude", "lower_left_longitude", "upper_right_latitude", "upper_right_longitude", "vertical_extent", "opt_separator_length_unit", "vertical_extent_keyword", "vertical_extent_minimum_height", "vertical_extent_maximum_height", "temporal_extent", "temporal_extent_keyword", "temporal_extent_start", "temporal_extent_end", "identifier", "opt_version_authority_citation_uri", "identifier_keyword", "authority_name", "authority_unique_identifier", "version", "authority_citation", "citation_keyword", "citation", "id_uri", "uri_keyword", "uri", "remark", "remark_keyword", "unit", "spatial_unit", "angle_or_length_or_parametric_or_scale_unit", "angle_or_length_or_parametric_or_scale_unit_keyword", "angle_or_length_or_scale_unit", "angle_or_length_or_scale_unit_keyword", "angle_unit", "opt_separator_identifier_list", "length_unit", "time_unit", "opt_separator_conversion_factor_identifier_list", "angle_unit_keyword", "length_unit_keyword", "time_unit_keyword", "unit_name", "conversion_factor", "coordinate_system_scope_extent_identifier_remark", "coordinate_system_defining_transformation_scope_extent_identifier_remark", "spatial_cs_scope_extent_identifier_remark", "spatial_cs_defining_transformation_scope_extent_identifier_remark", "opt_separator_spatial_axis_list_opt_separator_cs_unit_scope_extent_identifier_remark", "opt_defining_transformation_separator_scope_extent_identifier_remark", "defining_transformation", "defining_transformation_name", "no_opt_defining_transformation_separator_scope_extent_identifier_remark", "opt_separator_spatial_axis_list_opt_defining_transformation_opt_separator_cs_unit_scope_extent_identifier_remark", "wkt2015temporal_cs_scope_extent_identifier_remark", "opt_separator_cs_unit_scope_extent_identifier_remark", "temporalcountmeasure_cs_scope_extent_identifier_remark", "ordinaldatetime_cs_scope_extent_identifier_remark", "opt_separator_ordinaldatetime_axis_list_scope_extent_identifier_remark", "cs_keyword", "spatial_cs_type", "temporalcountmeasure_cs_type", "ordinaldatetime_cs_type", "dimension", "spatial_axis", "temporalcountmeasure_axis", "ordinaldatetime_axis", "axis_keyword", "axis_name_abbrev", "axis_direction_opt_axis_order_spatial_unit_identifier_list", "north_south_options_spatial_unit", "clockwise_counter_clockwise_options_spatial_unit", "axis_direction_except_n_s_cw_ccw_opt_axis_spatial_unit_identifier_list", "axis_direction_except_n_s_cw_ccw_opt_axis_spatial_unit_identifier_list_options", "axis_direction_opt_axis_order_identifier_list", "north_south_options", "clockwise_counter_clockwise_options", "axis_direction_except_n_s_cw_ccw_opt_axis_identifier_list", "axis_direction_except_n_s_cw_ccw_opt_axis_identifier_list_options", "opt_separator_axis_time_unit_identifier_list", "axis_direction_except_n_s_cw_ccw_opt_axis_time_unit_identifier_list_options", "axis_direction_except_n_s_cw_ccw", "meridian", "meridian_keyword", "bearing", "bearing_keyword", "axis_order", "axis_order_keyword", "axis_range_opt_separator_identifier_list", "opt_separator_axis_range_opt_separator_identifier_list", "axis_minimum_value", "axis_minimum_value_keyword", "axis_maximum_value", "axis_maximum_value_keyword", "axis_range_meaning", "axis_range_meaning_keyword", "axis_range_meaning_value", "cs_unit", "datum_ensemble", "geodetic_datum_ensemble_without_pm", "datum_ensemble_member_list_ellipsoid_accuracy_identifier_list", "opt_separator_datum_ensemble_identifier_list", "vertical_datum_ensemble", "datum_ensemble_member_list_accuracy_identifier_list", "datum_ensemble_keyword", "datum_ensemble_name", "datum_ensemble_member", "opt_datum_ensemble_member_identifier_list", "datum_ensemble_member_keyword", "datum_ensemble_member_name", "datum_ensemble_member_identifier", "datum_ensemble_accuracy", "datum_ensemble_accuracy_keyword", "accuracy", "datum_ensemble_identifier", "dynamic_crs", "dynamic_crs_keyword", "frame_reference_epoch", "frame_reference_epoch_keyword", "reference_epoch", "opt_separator_deformation_model_id", "deformation_model_id", "opt_separator_identifier", "deformation_model_id_keyword", "deformation_model_name", "geodetic_crs", "geographic_crs", "static_geodetic_crs", "dynamic_geodetic_crs", "static_geographic_crs", "dynamic_geographic_crs", "opt_prime_meridian_coordinate_system_defining_transformation_scope_extent_identifier_remark", "crs_name", "geodetic_crs_keyword", "geographic_crs_keyword", "geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm", "ellipsoid", "opt_separator_length_unit_identifier_list", "ellipsoid_keyword", "ellipsoid_name", "semi_major_axis", "inverse_flattening", "prime_meridian", "prime_meridian_keyword", "prime_meridian_name", "irm_longitude_opt_separator_identifier_list", "geodetic_reference_frame_with_opt_pm", "geodetic_reference_frame_without_pm", "geodetic_reference_frame_keyword", "datum_name", "opt_separator_datum_anchor_anchor_epoch_identifier_list", "datum_anchor", "datum_anchor_keyword", "datum_anchor_description", "datum_anchor_epoch", "datum_anchor_epoch_keyword", "anchor_epoch", "projected_crs", "projected_crs_keyword", "base_geodetic_crs", "base_static_geodetic_crs", "opt_separator_pm_ellipsoidal_cs_unit_opt_separator_identifier_list", "base_dynamic_geodetic_crs", "base_static_geographic_crs", "base_dynamic_geographic_crs", "base_geodetic_crs_keyword", "base_geographic_crs_keyword", "base_crs_name", "ellipsoidal_cs_unit", "map_projection", "opt_separator_parameter_list_identifier_list", "map_projection_keyword", "map_projection_name", "map_projection_method", "map_projection_method_keyword", "map_projection_method_name", "map_projection_parameter", "opt_separator_param_unit_identifier_list", "parameter_keyword", "parameter_name", "parameter_value", "map_projection_parameter_unit", "vertical_crs", "static_vertical_crs", "dynamic_vertical_crs", "vertical_reference_frame_or_vertical_datum_ensemble", "vertical_cs_opt_geoid_model_id_scope_extent_identifier_remark", "opt_separator_cs_unit_opt_geoid_model_id_scope_extent_identifier_remark", "opt_geoid_model_id_list_opt_separator_scope_extent_identifier_remark", "geoid_model_id", "geoid_model_keyword", "geoid_model_name", "vertical_crs_keyword", "vertical_reference_frame", "vertical_reference_frame_keyword", "engineering_crs", "engineering_crs_keyword", "engineering_datum", "engineering_datum_keyword", "opt_separator_datum_anchor_identifier_list", "parametric_crs", "parametric_crs_keyword", "parametric_datum", "parametric_datum_keyword", "temporal_crs", "temporal_crs_keyword", "temporal_datum", "opt_separator_temporal_datum_end", "temporal_datum_keyword", "temporal_origin", "temporal_origin_keyword", "temporal_origin_description", "calendar", "calendar_keyword", "calendar_identifier", "deriving_conversion", "opt_separator_parameter_or_parameter_file_identifier_list", "deriving_conversion_keyword", "deriving_conversion_name", "operation_method", "operation_method_keyword", "operation_method_name", "operation_parameter", "opt_separator_parameter_unit_identifier_list", "parameter_unit", "length_or_angle_or_scale_or_time_or_parametric_unit", "length_or_angle_or_scale_or_time_or_parametric_unit_keyword", "operation_parameter_file", "parameter_file_keyword", "parameter_file_name", "derived_geodetic_crs", "derived_geographic_crs", "derived_static_geod_crs", "base_static_geod_crs_or_base_static_geog_crs", "derived_dynamic_geod_crs", "base_dynamic_geod_crs_or_base_dynamic_geog_crs", "derived_static_geog_crs", "derived_dynamic_geog_crs", "base_static_geod_crs", "opt_separator_pm_opt_separator_identifier_list", "base_dynamic_geod_crs", "base_static_geog_crs", "base_dynamic_geog_crs", "derived_projected_crs", "derived_projected_crs_keyword", "derived_crs_name", "base_projected_crs", "base_projected_crs_opt_separator_cs_identifier", "base_projected_crs_keyword", "base_geodetic_geographic_crs", "derived_vertical_crs", "base_vertical_crs", "base_static_vertical_crs", "base_dynamic_vertical_crs", "base_vertical_crs_keyword", "derived_engineering_crs", "base_engineering_crs", "base_engineering_crs_keyword", "derived_parametric_crs", "base_parametric_crs", "base_parametric_crs_keyword", "derived_temporal_crs", "base_temporal_crs", "base_temporal_crs_keyword", "compound_crs", "single_crs", "single_crs_or_bound_crs", "opt_wkt_separator_single_crs_list_opt_separator_scope_extent_identifier_remark", "compound_crs_keyword", "compound_crs_name", "metadata_coordinate_epoch", "coordinate_epoch_keyword", "coordinate_epoch", "coordinate_metadata", "coordinate_metadata_crs", "coordinate_metadata_keyword", "static_crs_coordinate_metadata", "dynamic_crs_coordinate_metadata", "coordinate_operation", "coordinate_operation_next", "coordinate_operation_end", "opt_parameter_or_parameter_file_list_opt_interpolation_crs_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark", "operation_keyword", "operation_name", "operation_version", "operation_version_keyword", "operation_version_text", "source_crs", "source_crs_keyword", "target_crs", "target_crs_keyword", "interpolation_crs", "interpolation_crs_keyword", "operation_accuracy", "operation_accuracy_keyword", "point_motion_operation", "point_motion_operation_next", "point_motion_operation_end", "opt_parameter_or_parameter_file_list_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark", "point_motion_keyword", "concatenated_operation", "concatenated_operation_next", "concatenated_operation_end", "step", "opt_concatenated_operation_end", "concatenated_operation_keyword", "step_keyword", "bound_crs", "bound_crs_keyword", "abridged_coordinate_transformation", "abridged_coordinate_transformation_next", "abridged_coordinate_transformation_end", "opt_end_abridged_coordinate_transformation", "abridged_transformation_keyword", "abridged_transformation_parameter", YY_NULLPTR }; #endif # ifdef YYPRINT /* YYTOKNUM[NUM] -- (External) token number corresponding to the (internal) symbol number NUM (which must be that of a token). */ static const yytype_int16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 46, 69, 49, 50, 51, 43, 45, 58, 84, 90, 91, 40, 93, 41, 44 }; # endif #define YYPACT_NINF (-1291) #define yypact_value_is_default(Yyn) \ ((Yyn) == YYPACT_NINF) #define YYTABLE_NINF (-691) #define yytable_value_is_error(Yyn) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ static const yytype_int16 yypact[] = { 2165, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, 115, -1291, -1291, -1291, 157, -1291, -1291, -1291, 157, -1291, -1291, -1291, -1291, -1291, -1291, 157, 157, -1291, 157, -1291, -25, 157, -1291, 157, -1291, 157, -1291, -1291, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, -1291, -1291, -1291, -1291, -1291, -1291, 157, -1291, -1291, -1291, -1291, -1291, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, 157, -1291, -1291, -1291, 0, 0, 0, 0, 0, -1291, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1039, 0, 0, 0, 140, -1291, -1291, -25, -1291, -25, -1291, -25, -25, -1291, -25, -1291, -1291, -1291, 157, -1291, -25, -25, -1291, -25, -25, -25, -25, -25, -25, -25, -25, -25, -1291, -25, -1291, -25, -1291, -1291, -1291, -1291, -13, -1291, -1291, -1291, -1291, -1291, 66, 141, 179, -1291, -1291, -1291, -1291, 341, -1291, -25, -1291, -25, -25, -25, -1291, -25, 157, 1257, 331, 341, 298, 298, 895, 0, 367, 393, 136, 264, 447, 341, 83, 289, 341, 151, 341, 139, 315, 341, 226, 106, -1291, -1291, -1291, 497, 49, -1291, -1291, 49, -1291, -1291, 49, -1291, -1291, 314, 1039, -1291, -1291, -1291, -1291, -1291, -1291, -1291, 508, -1291, -1291, -1291, -1291, 211, 223, 228, 895, -1291, -25, -1291, -25, 157, -25, -1291, -1291, -1291, -1291, -1291, 157, -25, 157, -25, -1291, 157, 157, -25, -25, -1291, -1291, -1291, -1291, -25, -25, -25, -25, -1291, -25, -1291, -25, -25, -25, -1291, -1291, -1291, -1291, 157, 157, -1291, -1291, -25, 157, -1291, -1291, 157, -25, -25, -1291, -25, -1291, -1291, 157, -1291, -1291, -25, -25, 157, -25, 157, -1291, -1291, -25, -25, 157, -25, -25, -1291, -1291, -25, -25, 157, -1291, -1291, -25, -25, 157, -1291, -1291, -25, -25, 157, -25, 157, -1291, -1291, -25, 157, -1291, -25, -1291, -1291, -1291, -1291, 157, -1291, -25, 157, -25, -25, -25, -25, -25, -1291, -25, 157, 341, -1291, 420, 508, -1291, -1291, 409, 341, 121, -1291, 341, 0, 334, 0, 73, 355, 90, 0, 0, 339, 339, 73, 90, 339, 339, 895, 420, 341, 395, 0, 0, 198, 341, 0, 0, 116, 407, 339, 0, 413, -1291, 95, 0, 413, 508, 407, 339, 0, -1291, 413, 407, 339, 0, 407, 339, 0, -1291, -1291, 425, 65, -1291, 0, 339, 0, 106, 508, 140, -1291, 0, 314, 140, -1291, 410, 140, -1291, 314, 385, 1039, -1291, 508, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -25, -25, 157, -1291, 157, -1291, -1291, -25, -25, 157, -1291, -1291, -25, -25, -25, -25, -1291, -25, 157, -1291, -1291, -1291, 157, 341, -25, -1291, -25, -25, -1291, -25, 157, -25, -25, 341, -25, -25, -1291, -25, -25, 895, 341, -1291, -25, -25, -25, -1291, -25, -25, 157, -1291, -1291, -25, -25, -25, 157, 341, -25, -25, -25, -25, -25, -1291, 341, -25, 228, 341, 341, -1291, -1291, -1291, -1291, 157, -25, -25, -25, 341, -25, -25, 341, -25, -25, -1291, -1291, 285, -1291, 341, -25, -1291, 341, -25, -25, -25, 228, 341, -1291, 341, -25, -1291, -25, 157, -25, -1291, -25, 157, 341, -1291, 440, 446, 0, 0, -1291, 413, -1291, 1350, 413, 341, -1291, 331, 90, 612, 341, 508, 1113, -1291, 407, 81, 81, 407, 0, 407, 90, -1291, 407, 407, 336, 341, 353, -1291, -1291, -1291, 407, 81, 81, -1291, -1291, 0, 341, 426, 407, 1113, -1291, 407, 133, -1291, -1291, 413, -1291, -1291, 508, -1291, -1291, 1386, 407, 64, -1291, -1291, 407, 132, -1291, 407, 37, -1291, -1291, 508, -1291, -1291, 508, -1291, -1291, -1291, 407, 393, 1734, 341, 508, -1291, -1291, 410, 1568, 341, 0, 452, 734, 341, 0, -1291, -25, -1291, -1291, 341, -1291, 341, -1291, -25, -1291, 341, -1291, -25, -1291, -25, 341, -1291, -1291, -1291, 157, -1291, 228, 341, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -25, -1291, -25, -25, -25, -25, 341, -1291, -25, 341, 341, 341, 341, -1291, -1291, -25, -25, 157, -1291, -1291, -1291, -25, 157, 341, -25, -25, -25, -25, -1291, -25, -1291, -25, 341, -25, 341, -25, -25, -25, -1291, -25, -1291, -1291, -1291, -1291, -25, -25, -25, 341, -25, 341, -25, 341, -25, 330, 340, -1291, 1023, 341, -1291, -1291, -1291, -1291, -25, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -25, 157, -25, 157, -1291, -25, 157, -25, 157, -25, 157, -25, 157, -25, -1291, 157, -25, -1291, -1291, -25, -1291, -1291, -1291, 157, -25, -25, 157, -25, 157, -1291, -1291, -25, -1291, 157, -1291, -1291, -25, 446, -1291, -1291, -1291, -1291, -1291, -1291, 0, 508, -1291, 455, 73, 104, 341, 73, 341, -1291, 410, -1291, -1291, -1291, -1291, -1291, -1291, 0, -1291, 0, -1291, 73, 82, 341, 73, 341, 420, 628, -1291, 455, -1291, 116, 341, -1291, 455, 455, 455, 455, -1291, 341, -1291, 341, -1291, 341, -1291, 508, -1291, -1291, 508, 508, -1291, 358, -1291, -1291, -1291, -1291, 395, 325, 502, 377, -1291, 0, 442, -1291, 0, 449, -1291, 1350, 323, -1291, 1350, 379, -1291, 425, -1291, 386, -1291, 875, 341, 0, -1291, -1291, 0, -1291, 1350, 413, 341, 346, 99, -1291, -1291, -1291, -1291, -25, -1291, -1291, -1291, -1291, -25, -25, -25, -25, -1291, -25, -1291, -25, -1291, -25, -25, -25, -25, -1291, -25, -25, -1291, -25, -1291, -1291, -25, -25, -25, -25, -1291, -25, -25, -25, -25, -1291, -1291, -1291, -1291, 382, 358, -1291, 1023, 508, -1291, -25, -1291, -25, -1291, -25, -1291, -25, -1291, -1291, 341, -25, -25, -25, -1291, 341, -25, -25, -1291, -25, -25, -1291, -25, -1291, -1291, -25, -1291, 341, -1291, -1291, -25, -25, -25, 157, -25, -1291, -25, -25, 341, -1291, -1291, -1291, -1291, -1291, -1291, 341, -25, -25, 341, 341, 341, -1291, -1291, 341, 194, 341, 895, 895, 341, -1291, 353, -1291, -1291, 341, 825, 341, 341, 341, 341, 341, 341, 341, -1291, -1291, 508, -1291, -1291, -1291, 990, 341, -1291, 503, -1291, -1291, 449, -1291, 323, -1291, -1291, -1291, 323, -1291, -1291, 1350, -1291, 1350, 425, -1291, -1291, -1291, 1206, -1291, 1039, -1291, 420, 0, -1291, -25, 971, 341, 410, -1291, -1291, -25, -1291, -1291, -25, -25, -25, -1291, -1291, -25, -25, -1291, -25, -1291, -1291, -1291, -1291, -1291, -25, -1291, 157, -25, -1291, -25, -1291, -25, -25, -25, -25, -1291, -1291, 925, -1291, -1291, 157, -1291, 341, -25, -25, -25, -1291, -25, -25, -25, -25, -1291, -25, -1291, -25, -1291, -1291, 341, -25, 341, -25, -1291, -25, 452, -1291, 157, -25, -25, -1291, 545, -1291, -1291, -1291, 341, 341, -1291, -1291, 0, -1291, 545, 545, 545, 545, 545, 783, -1291, 1113, -1291, 436, 640, 535, 323, -1291, -1291, -1291, -1291, 1350, 241, 341, -1291, -1291, -1291, 704, 341, 341, 157, 0, -1291, -1291, -1291, -25, 157, -1291, -1291, -25, -25, -25, 157, -25, -25, -25, 157, 559, 783, -1291, -25, -25, -1291, -25, -1291, -1291, -25, -1291, -25, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -25, -25, -1291, 157, -1291, -1291, 346, -25, 1267, -1291, 0, 895, 1084, -1291, 1448, -1291, 0, 1318, -1291, -1291, 844, -1291, 0, -1291, 640, 535, 535, -1291, 1350, -1291, -1291, 0, 341, 420, -1291, -1291, -1291, -1291, -1291, -1291, 157, -1291, -1291, -25, -1291, -1291, -1291, -1291, 157, -1291, 157, -25, -1291, -25, -25, -1291, -25, -25, -1291, -1291, -25, -25, 157, -1291, -25, -25, -1291, -25, -25, -1291, -25, -25, -25, -1291, -1291, -1291, -1291, 341, -25, -25, -25, 0, -1291, 0, 0, 520, -1291, 520, -1291, 2271, 341, 1287, -1291, 1287, -1291, 0, 734, 2416, -1291, -1291, -1291, 2583, 535, -1291, 895, 341, 1354, 341, 341, -1291, -25, -25, -25, -1291, -1291, -25, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -25, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -25, -25, -1291, -1291, -1291, -1291, -1291, 341, -1291, -25, -1291, -25, -1291, -25, -1291, -25, -1291, -25, -25, -25, -25, -25, 341, -1291, -25, -1291, -25, -1291, -1291, -25, 157, -1291, -1291, 341, 895, 341, 611, 611, 843, 843, -1291, 595, -1291, -1291, 341, 281, 341, 443, 611, 521, 521, -1291, 96, -1291, -1291, 346, -1291, -25, -1291, -1291, -1291, -25, -25, -1291, -25, 157, -25, 157, -1291, -1291, -25, -25, -1291, -25, 157, -25, -1291, -1291, -1291, -25, -25, -1291, -25, -1291, -25, 157, -25, 157, -1291, -25, -25, -1291, -25, -1291, -1291, -25, -1291, -25, -25, -1291, -25, -1291, -25, -25, -1291, -25, -1291, -25, -1291, 341, 341, -1291, -1291, 726, -1291, 1350, 729, -1291, 508, -1291, -1291, 726, -1291, 1350, 729, -1291, -1291, 621, -1291, 549, -1291, 62, -1291, 1350, -1291, 1350, -1291, -1291, 283, -1291, -1291, 460, -1291, -1291, -1291, 460, -1291, -1291, -1291, -1291, -25, -1291, -25, -25, -25, -25, 341, -25, -25, 341, -25, -25, -25, -25, -25, 341, 341, -25, -25, -25, -1291, -1291, 729, -1291, 542, -1291, -1291, -1291, 729, -1291, -1291, -1291, 62, -1291, -1291, -1291, 69, -1291, -1291, -1291, -1291, -1291, -1291, -25, 341, -25, -25, -1291, -25, 157, -1291, -1291, -1291, 69, -1291, -1291, 630, -25, -1291, -1291, 341, -1291, -1291 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ static const yytype_int16 yydefact[] = { 0, 454, 441, 430, 440, 161, 475, 498, 432, 530, 533, 658, 702, 737, 740, 559, 552, 391, 614, 540, 537, 549, 547, 669, 724, 431, 456, 476, 433, 455, 531, 535, 534, 560, 541, 538, 550, 0, 4, 5, 2, 0, 13, 381, 382, 0, 641, 420, 418, 419, 421, 422, 0, 0, 3, 0, 12, 451, 0, 643, 0, 11, 0, 645, 512, 513, 0, 14, 0, 647, 0, 15, 0, 649, 0, 16, 0, 651, 0, 17, 0, 642, 595, 593, 594, 596, 597, 644, 0, 646, 648, 650, 652, 19, 18, 0, 7, 0, 8, 0, 9, 0, 10, 0, 6, 0, 1, 73, 74, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 162, 0, 392, 0, 429, 0, 0, 442, 0, 446, 447, 452, 0, 457, 0, 0, 499, 0, 0, 458, 0, 542, 0, 542, 0, 554, 615, 0, 659, 0, 670, 684, 671, 685, 672, 673, 687, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 0, 667, 0, 703, 0, 0, 0, 708, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 76, 666, 0, 0, 691, 693, 0, 715, 717, 0, 725, 727, 0, 0, 40, 20, 37, 38, 39, 41, 42, 0, 163, 21, 22, 26, 0, 25, 35, 0, 164, 154, 396, 0, 0, 0, 383, 490, 491, 404, 435, 0, 0, 0, 0, 434, 0, 0, 0, 0, 599, 602, 600, 603, 0, 0, 0, 0, 443, 0, 448, 0, 458, 0, 477, 478, 479, 480, 0, 0, 502, 501, 495, 0, 630, 517, 0, 0, 0, 516, 0, 626, 627, 0, 467, 470, 190, 459, 0, 460, 0, 532, 633, 0, 0, 0, 190, 543, 539, 636, 0, 0, 0, 548, 639, 0, 0, 0, 566, 562, 190, 190, 0, 190, 0, 553, 620, 0, 0, 653, 0, 654, 661, 662, 668, 0, 705, 0, 0, 0, 0, 0, 0, 0, 710, 0, 0, 0, 34, 27, 0, 33, 23, 0, 0, 0, 385, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 462, 0, 0, 0, 0, 0, 0, 0, 544, 0, 0, 0, 0, 0, 0, 0, 558, 557, 0, 0, 555, 0, 0, 0, 0, 0, 0, 692, 0, 0, 0, 716, 0, 0, 726, 0, 0, 0, 707, 0, 29, 31, 28, 36, 168, 171, 165, 166, 155, 158, 0, 160, 0, 153, 400, 0, 386, 0, 388, 397, 394, 386, 0, 0, 406, 410, 0, 238, 428, 208, 0, 0, 0, 492, 0, 0, 573, 0, 0, 0, 0, 0, 0, 0, 444, 437, 190, 0, 0, 453, 0, 0, 0, 508, 190, 495, 0, 494, 503, 190, 0, 0, 0, 0, 0, 0, 190, 190, 461, 468, 0, 190, 471, 0, 0, 204, 205, 206, 207, 0, 0, 0, 190, 0, 0, 0, 0, 0, 0, 50, 563, 48, 564, 0, 190, 567, 0, 0, 0, 655, 663, 0, 706, 0, 0, 576, 719, 0, 0, 749, 80, 0, 0, 32, 0, 0, 0, 0, 390, 0, 389, 0, 0, 0, 384, 0, 0, 0, 0, 0, 0, 423, 0, 0, 0, 0, 0, 0, 0, 425, 0, 0, 0, 0, 0, 450, 24, 445, 0, 0, 0, 496, 497, 0, 0, 0, 0, 0, 514, 0, 0, 191, 464, 0, 466, 463, 472, 469, 536, 0, 0, 0, 545, 546, 0, 0, 551, 0, 0, 44, 58, 0, 45, 49, 0, 561, 556, 565, 0, 0, 0, 0, 664, 660, 704, 0, 0, 0, 0, 0, 0, 0, 0, 709, 156, 159, 169, 0, 172, 0, 402, 386, 401, 0, 398, 394, 393, 0, 0, 415, 416, 411, 0, 403, 407, 0, 239, 240, 241, 242, 243, 244, 245, 246, 247, 0, 427, 0, 607, 0, 607, 0, 574, 0, 0, 0, 0, 0, 199, 198, 190, 190, 0, 436, 197, 196, 190, 0, 0, 0, 482, 0, 482, 509, 0, 500, 0, 0, 0, 0, 0, 190, 190, 473, 0, 248, 249, 250, 251, 0, 0, 0, 0, 190, 0, 190, 0, 190, 48, 0, 59, 0, 0, 621, 622, 623, 624, 0, 174, 100, 133, 136, 144, 148, 98, 657, 82, 88, 89, 93, 0, 85, 0, 92, 85, 0, 85, 0, 85, 0, 85, 0, 85, 84, 0, 655, 640, 665, 695, 591, 714, 723, 0, 719, 719, 0, 80, 0, 718, 577, 413, 738, 0, 81, 739, 0, 0, 167, 170, 387, 399, 395, 424, 0, 408, 405, 0, 0, 0, 0, 0, 0, 598, 0, 601, 426, 604, 605, 439, 438, 0, 449, 0, 474, 0, 0, 0, 0, 0, 27, 0, 515, 0, 625, 0, 0, 465, 0, 0, 0, 0, 631, 0, 634, 0, 637, 0, 46, 0, 43, 68, 0, 0, 53, 71, 55, 66, 67, 613, 0, 0, 0, 0, 91, 0, 0, 117, 0, 0, 118, 0, 0, 119, 0, 0, 120, 0, 83, 0, 656, 0, 0, 0, 720, 721, 0, 722, 0, 0, 0, 0, 0, 741, 743, 157, 417, 413, 409, 252, 253, 254, 190, 607, 190, 190, 606, 607, 611, 569, 202, 0, 0, 482, 190, 493, 190, 190, 481, 482, 488, 510, 505, 0, 190, 190, 628, 190, 190, 190, 190, 632, 635, 638, 52, 48, 71, 60, 0, 0, 70, 617, 96, 85, 94, 0, 90, 85, 87, 101, 0, 85, 85, 85, 134, 0, 85, 85, 137, 0, 85, 145, 0, 149, 150, 0, 79, 0, 712, 701, 695, 695, 80, 0, 80, 694, 0, 0, 0, 414, 575, 731, 732, 729, 730, 0, 745, 0, 0, 0, 0, 609, 608, 0, 0, 0, 0, 0, 0, 486, 0, 483, 485, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 69, 0, 54, 57, 72, 0, 0, 95, 0, 86, 99, 0, 121, 0, 122, 123, 132, 0, 124, 125, 0, 126, 0, 0, 173, 696, 697, 0, 698, 0, 700, 27, 0, 713, 0, 0, 0, 0, 742, 412, 0, 610, 612, 190, 569, 569, 568, 203, 190, 190, 487, 190, 489, 188, 186, 185, 187, 190, 511, 0, 190, 504, 0, 629, 0, 0, 0, 0, 64, 56, 0, 619, 618, 0, 616, 0, 102, 103, 104, 105, 85, 85, 85, 85, 138, 0, 146, 142, 151, 152, 0, 80, 0, 579, 592, 413, 0, 748, 0, 745, 745, 744, 0, 572, 570, 571, 0, 0, 484, 506, 0, 507, 0, 0, 0, 0, 0, 0, 63, 0, 97, 0, 0, 0, 0, 127, 128, 129, 130, 0, 0, 0, 147, 699, 711, 0, 0, 0, 0, 0, 747, 746, 258, 224, 0, 192, 189, 0, 519, 229, 0, 211, 80, 235, 0, 65, 0, 61, 106, 107, 108, 109, 110, 111, 85, 139, 0, 143, 141, 589, 584, 585, 586, 587, 588, 190, 190, 582, 0, 578, 590, 0, 0, 0, 210, 0, 0, 0, 518, 0, 228, 0, 0, 209, 233, 0, 234, 0, 62, 0, 0, 0, 131, 0, 581, 580, 0, 0, 27, 183, 180, 179, 182, 200, 181, 0, 201, 221, 85, 223, 380, 175, 177, 0, 176, 0, 219, 227, 224, 215, 259, 0, 190, 528, 523, 80, 524, 0, 232, 230, 0, 214, 211, 80, 237, 235, 0, 112, 113, 114, 115, 140, 0, 194, 733, 190, 0, 222, 0, 0, 0, 226, 0, 225, 0, 0, 0, 520, 0, 522, 0, 0, 0, 213, 212, 236, 0, 0, 135, 0, 0, 0, 0, 0, 218, 413, 0, 194, 220, 216, 261, 321, 322, 323, 324, 325, 326, 327, 263, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 265, 267, 352, 353, 354, 355, 356, 0, 260, 285, 184, 524, 526, 524, 529, 413, 231, 314, 293, 295, 297, 299, 0, 292, 309, 116, 190, 583, 736, 80, 0, 728, 750, 0, 0, 0, 0, 0, 0, 0, 255, 0, 521, 525, 0, 0, 0, 0, 0, 0, 0, 257, 0, 195, 735, 0, 217, 190, 193, 358, 362, 190, 190, 262, 190, 0, 190, 0, 264, 360, 190, 190, 266, 190, 0, 190, 268, 373, 375, 190, 367, 286, 367, 288, 190, 0, 190, 0, 527, 190, 367, 315, 367, 317, 256, 190, 294, 190, 190, 296, 190, 298, 190, 190, 300, 190, 310, 367, 313, 0, 0, 269, 276, 0, 273, 0, 0, 275, 0, 277, 284, 0, 281, 0, 0, 283, 287, 0, 291, 0, 289, 0, 363, 0, 364, 0, 316, 320, 0, 318, 301, 0, 303, 304, 305, 0, 307, 308, 311, 312, 733, 178, 190, 190, 0, 190, 0, 190, 190, 0, 190, 190, 190, 367, 190, 0, 0, 367, 190, 190, 734, 272, 0, 270, 0, 274, 361, 280, 0, 278, 359, 282, 0, 368, 369, 290, 0, 365, 372, 374, 319, 302, 306, 190, 0, 190, 190, 377, 190, 0, 271, 357, 279, 0, 370, 366, 0, 190, 378, 379, 0, 371, 376 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { -1291, -1291, -1291, -224, -241, -164, -1291, 219, -196, 249, -1291, -1291, -1291, -1291, -1291, -1291, -81, -333, -633, -113, -790, -629, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -560, -300, -1291, -1291, -1291, -857, -1291, -1291, -286, -45, 1494, 498, 2252, -1291, -755, -593, -620, -1069, -1291, -208, -1291, -1291, -207, -1291, -1291, -1291, -203, -359, -1291, -1291, -787, -1291, -1291, -1291, -1291, -1291, -783, -1291, -1291, -1291, -1291, -743, -1291, -1291, -1291, 677, -1291, -1291, -1291, -1291, -1291, 102, -1291, -1291, -503, -1291, -1291, -750, -1291, -1291, -434, -1291, -1291, -1291, -1291, -575, 1610, -479, -1290, -623, -1291, -1291, -1291, -767, -935, -62, 109, -331, -1291, -558, -1291, -1291, -1291, -740, -536, -1291, -1291, -1291, -1291, -544, -322, -487, -1291, -1291, 113, -877, -411, -486, -988, -866, -1291, -1192, -649, -1291, -1291, -1291, -1291, -658, -1291, -1291, -1291, -1291, -963, -646, -1291, -608, -1291, -843, -1291, -1141, -1260, -1018, -1291, -1046, -1291, -797, -1291, -1291, -656, -1291, 731, -101, -354, 524, -416, 51, -229, -320, 107, -1291, -1291, -1291, 387, -1291, -126, -1291, -121, -1291, -1291, -1291, -1291, -1291, -1291, -855, -1291, -1291, -1291, -1291, 638, 643, 658, 664, -278, 770, -1291, -1291, -82, 80, -1291, -1291, -1291, -1291, -1291, -111, -1291, -1291, -1291, -1291, 16, -1291, 835, 479, 589, -1291, -1291, 408, -1291, -1291, 675, -1291, -1291, -1291, -624, -1291, -1291, -1291, 605, 610, 195, -170, 6, 328, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -366, -806, -962, -1291, -1291, 684, 688, -1291, 232, -1291, -730, -636, -1291, -1291, -1291, -191, -1291, 693, -1291, -163, -1291, 667, 699, -1291, -168, -1291, 700, -1291, -176, -1291, -1291, 418, -1291, -1291, -1291, -1291, -1291, 746, -305, -1291, -1291, -377, -1291, -1291, -789, -1291, -1291, -1291, -1291, -785, -1291, -1291, 706, -1291, -1291, 637, -1291, 661, -1291, -1291, 236, -603, 237, 240, 244, 738, -1291, -1291, -1291, -1291, -1291, -1291, 739, -1291, -1291, -1291, -1291, 740, -1291, -1291, 743, -1291, -1291, 744, -1291, -1291, 748, -175, -351, 105, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, -1291, 878, -1291, 536, -172, -1291, -112, -213, -1291, -1291, -78, -1291, -7, -1291, -1291, -1291, -837, -1291, -1291, -1291, 539, 18, 881, -1291, -1291, 538, -1093, -578, -1291, -1019, 894, -1291, -1291, -1291, -73, -297, -1291, -1291 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int16 yydefgoto[] = { -1, 37, 38, 39, 237, 645, 239, 903, 240, 480, 241, 242, 433, 434, 243, 357, 244, 245, 921, 614, 523, 615, 524, 722, 917, 616, 836, 997, 617, 837, 920, 1064, 1065, 1151, 838, 839, 840, 922, 109, 217, 392, 466, 949, 634, 776, 846, 739, 740, 741, 742, 743, 744, 745, 932, 1070, 746, 747, 748, 937, 749, 750, 941, 1080, 1161, 1247, 751, 1126, 752, 944, 1082, 753, 754, 947, 1085, 499, 360, 41, 136, 247, 441, 442, 443, 640, 444, 445, 642, 756, 757, 1215, 1216, 1217, 1218, 1053, 1054, 897, 393, 684, 1219, 1275, 690, 685, 1220, 893, 1043, 507, 461, 508, 462, 1188, 1258, 1221, 1280, 1222, 1179, 509, 1185, 510, 511, 1191, 512, 712, 713, 714, 884, 1138, 1144, 1148, 1139, 1226, 1325, 1381, 1390, 1326, 1399, 1340, 1414, 1419, 1341, 1424, 1364, 1409, 1327, 1382, 1383, 1391, 1392, 1384, 1385, 1401, 1446, 1402, 1403, 1404, 1405, 1516, 1517, 1528, 1224, 42, 256, 362, 553, 44, 363, 257, 138, 249, 556, 250, 453, 648, 449, 450, 646, 644, 258, 259, 458, 459, 658, 561, 654, 872, 655, 879, 46, 47, 48, 49, 50, 51, 464, 140, 52, 53, 260, 251, 576, 55, 143, 275, 478, 465, 147, 277, 481, 56, 261, 58, 149, 204, 303, 304, 503, 305, 306, 506, 59, 60, 279, 280, 809, 281, 282, 283, 262, 263, 467, 899, 963, 385, 62, 152, 288, 289, 492, 488, 987, 765, 697, 904, 1055, 63, 64, 65, 294, 496, 1183, 1264, 1231, 1232, 1333, 66, 67, 68, 69, 70, 71, 72, 207, 73, 74, 75, 76, 77, 78, 79, 212, 80, 327, 328, 526, 329, 330, 529, 964, 977, 471, 676, 968, 540, 773, 766, 1131, 1171, 1172, 1173, 767, 768, 1090, 81, 82, 83, 264, 84, 265, 85, 86, 266, 792, 267, 268, 269, 87, 88, 162, 333, 1001, 334, 730, 89, 296, 297, 298, 299, 90, 310, 311, 91, 317, 318, 92, 322, 323, 93, 94, 336, 624, 95, 164, 340, 341, 534, 96, 182, 97, 183, 184, 965, 220, 221, 864, 99, 186, 343, 344, 536, 345, 191, 351, 352, 954, 955, 769, 770, 100, 223, 224, 630, 966, 102, 226, 227, 967, 1277, 103, 775, 337, 105, 543, 875, 876, 1031, 544, 1095 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_int16 yytable[] = { 110, 274, 689, 356, 146, 353, 61, 111, 112, 346, 113, 295, 348, 116, 869, 117, 57, 118, 489, 187, 188, 119, 435, 120, 970, 121, 956, 122, 238, 123, 738, 124, 551, 125, 321, 126, 764, 194, 894, 335, 316, 448, 309, 127, 1044, 638, 435, 463, 539, 358, 128, 45, 129, 463, 130, 190, 131, 724, 132, 958, 133, 933, 959, 998, 1088, 934, 532, 495, 938, 945, 794, 811, 1133, 1408, 952, 270, 668, 1, 953, 5, 54, 293, 5, 1202, 144, 1, 5, 15, 144, 687, 189, 835, 830, 926, 688, 930, 144, 474, 930, 5, 557, 930, 198, 701, 930, 935, 460, 930, 939, 1212, 144, 942, 5, 5, 271, 106, 3, 325, 1378, 301, 1145, 5, 1145, 1149, 6, 538, 2, 849, 342, 852, 19, 855, 33, 858, 8, 860, 4, 455, 255, 286, 1448, 9, 17, 1396, 347, 114, 229, 349, 1455, 19, 1457, 1515, 10, 14, 134, 308, 354, -686, 493, 16, 26, 34, 287, 1386, 29, 1467, 1485, 145, 26, 10, 18, 145, 29, 20, 1417, 22, 1395, 1396, 342, 145, 34, 189, 477, 248, 447, 1086, 1212, 1039, 1212, 15, 255, 1040, 25, 145, 27, 439, 28, 494, 30, 1023, 21, 1025, 1149, 486, 35, 364, 545, 486, 1113, 31, 32, 5, 366, 320, 368, 5, 1072, 370, 371, 21, 1073, 1503, 1411, 1075, 315, 1508, 31, 32, 1083, 1426, 1143, 36, 1146, 762, 33, 1132, -688, 463, 650, 382, 383, 1250, 335, 463, 386, 1094, 1227, 387, 681, 463, 36, 761, 682, 292, 391, 1152, 930, 1348, 930, 395, 1074, 397, 930, 1076, 599, 1077, 400, 495, 699, 1078, 951, 980, 758, 405, 918, 436, 877, 985, 408, 278, 1427, 651, 972, 411, 579, 414, 975, 996, 994, 416, 782, 332, 625, 678, 1193, 1208, 418, 1208, 5, 421, 10, 1223, 1, 1378, 1335, 1002, 5, 430, 1342, 1004, 1237, -689, 300, 1007, 1009, 1010, 505, 253, 1013, 1014, 1235, 255, 1016, 107, 108, 17, 1241, 254, 1177, 1153, 522, 1128, 5, 1154, 1156, 290, 2, 533, 1142, 1344, 5, 516, 5, 681, 519, 347, 4, 682, 349, -690, 731, 546, 731, 5, 194, 255, 350, 31, 32, 17, 687, 1395, 1396, 1395, 1396, 688, 324, 325, 355, 930, 5, 7, 2, 1155, 1157, 1158, 1159, 1211, 736, 1211, -30, 737, 4, 456, 231, 26, 12, 483, 469, 29, 473, 834, 1189, 248, 5, 469, 5, 447, 549, 1480, 550, 1480, 704, 1482, 731, 554, 731, 1066, 733, 734, 1480, 1242, 253, 457, 562, 537, 787, 1375, 563, 891, 7, 541, 254, 460, 24, 1351, 569, 1479, 1214, 1479, 5, 735, 736, 542, 1248, 720, 538, 1092, 1479, 1347, 718, 716, 437, 438, 586, 670, 672, 612, 335, 613, 590, 1243, 1244, 1245, 1514, 1120, 1121, 1122, 1123, 5, 5, 692, 694, 1377, 5, 1378, 5, 602, 734, 731, 300, 301, 703, 437, 734, 1262, 731, 5, 1362, 435, 438, 657, 1378, 1269, 671, 673, 1281, 1282, 248, 551, 735, 736, -51, 905, 613, 631, 735, 736, 774, 635, 693, 695, 612, 735, 736, 1214, 674, 1214, 677, 215, 216, 679, 680, 1393, 1393, 1283, 1400, 1284, 706, 691, 1410, 832, 1416, 778, 1421, 1421, 1230, 1425, 1234, 1343, 702, 1238, 732, 721, 733, 734, 723, 5, 5, 1197, 948, 715, 1387, 1378, 760, 717, 613, 731, 719, 687, 732, 733, 734, 1137, 688, 115, 1204, 735, 736, 725, 1205, 1206, 1207, 134, 230, 231, 468, 232, 233, 234, 235, 236, 338, 339, 735, 736, 737, 484, 485, 134, 230, 235, 236, 232, 233, 234, 498, 834, 1471, 1229, 736, 1233, 1372, 1252, 1236, 514, 1476, 1239, 479, 1360, 518, 1361, 1204, 521, 432, 1210, 1205, 1206, 1207, 831, 786, 531, 5, 881, 882, 883, 1486, 1378, 1204, 1209, 1487, 999, 1205, 1206, 1207, 1329, 907, 1331, 5, 1395, 1396, 1377, 995, 1378, 192, 924, 193, 927, 195, 196, 803, 197, 928, 723, 1071, 805, 1162, 199, 200, 637, 201, 202, 203, 205, 206, 208, 206, 210, 211, 1353, 213, 969, 214, 230, 1093, 1209, 232, 233, 234, 1067, 1330, 652, 653, 1334, 669, 1395, 1396, 40, 1068, 1268, 887, 1209, 218, 1346, 219, 222, 225, 1256, 228, 940, 248, 447, 943, 435, 735, 736, 1270, 898, 844, 1147, 847, 1395, 1396, 850, 1240, 853, 880, 856, 1394, 859, 1422, 1164, 861, 1526, 1527, 1165, 1166, 1167, 1168, 1415, 865, 5, -59, 868, 1525, 870, 291, -59, -59, -59, 873, 43, 834, 1204, 1099, 1100, 1204, 1205, 1206, 1207, 1205, 1206, 1207, 5, 960, 359, 5, 361, 1378, 365, 916, 5, 454, 723, 919, 784, 367, 380, 369, 1420, 1420, 731, 372, 373, 732, 733, 734, 165, 374, 375, 376, 377, 166, 378, 1169, 379, 203, 381, 522, 834, 1020, 1021, 1042, 1042, 866, 867, 384, 167, 735, 736, 737, 388, 389, 168, 390, 313, 1209, 1135, 1136, 1209, 1087, 394, 501, 396, 169, 284, 885, 398, 399, 889, 285, 402, 1046, 170, 403, 404, 585, 171, 834, 406, 407, 700, 172, 895, 1112, 209, 901, 412, 173, 174, 527, 415, 1048, 272, 417, 175, 1049, 1050, 1051, 723, 919, 419, 5, 422, 423, 425, 426, 428, 923, 429, 1204, 1079, 1137, 1081, 1205, 1206, 1207, 273, 726, 727, 5, 5, 728, 862, 1387, 1378, 729, 176, 177, 178, 435, 731, 179, 180, 732, 733, 734, 181, 98, 420, 302, 101, 141, 312, 486, 424, 427, 150, 326, 153, 1488, 155, 5, 157, 104, 159, 1033, 0, 735, 736, 737, 0, 731, 0, 0, 732, 733, 734, 0, 0, 1024, 0, 0, 1209, 762, 1063, 0, 1512, 950, 763, 1380, 1380, 1389, 1389, 0, 1398, 0, 0, 906, 735, 736, 737, 1380, 909, 910, 911, 912, 522, 230, 547, 548, 232, 233, 234, 235, 236, 361, 552, 833, 0, 0, 555, 552, 558, 559, 154, 560, 156, 0, 158, 1160, 160, 0, 565, 0, 566, 567, 0, 568, 0, 570, 571, 0, 573, 574, 0, 575, 577, 0, 0, 486, 581, 582, 583, 723, 1042, 384, 0, 5, 0, 0, 588, 589, 0, 0, 592, 593, 1470, 731, 596, 1473, 732, 733, 734, 0, 1475, 460, 5, 1478, 1105, 762, 603, 604, 1481, 0, 607, 608, 731, 610, 611, 732, 733, 734, 1114, 0, 735, 736, 737, 621, 622, 623, 1150, 0, 0, 1246, 628, 0, 629, 0, 632, 0, 633, 0, 0, 735, 736, 737, 0, 1134, 3, 230, 231, 0, 232, 233, 234, 1511, 6, 0, 0, 0, 487, 1513, 0, 0, 0, 0, 8, 0, 0, 723, 500, 0, 504, 9, 0, 0, 11, 1042, 515, 230, 231, 0, 232, 233, 234, 235, 236, 1176, 0, 833, 1204, 16, 0, 1180, 1205, 1206, 1207, 1208, 0, 1186, 5, 0, 18, 1192, 0, 20, 0, 22, 0, 0, 731, 0, 0, 732, 733, 734, 0, 470, 472, 0, 1228, 475, 476, 0, 25, 0, 27, 1201, 28, 0, 30, 0, 0, 0, 779, 497, 35, 735, 736, 737, 0, 552, 0, 0, 513, 555, 0, 558, 0, 517, 0, 0, 520, 1209, 0, 1042, 0, 0, 0, 0, 530, 0, 0, 0, 1251, 789, 0, 790, 791, 793, 791, 0, 1253, 796, 1254, 0, 0, 230, 0, 1211, 232, 233, 234, 235, 236, 832, 1265, 833, 0, 807, 808, 810, 808, 0, 812, 0, 813, 0, 815, 0, 817, 0, 0, 0, 820, 0, 0, 0, 0, 821, 822, 823, 659, 660, 661, 662, 663, 664, 665, 666, 667, 0, 5, 0, 0, 0, 0, 842, 643, 0, 0, 647, 731, 0, 0, 732, 733, 734, 0, 843, 0, 845, 0, 0, 848, 0, 851, 0, 854, 763, 857, 683, 857, 0, 0, 623, 0, 0, 863, 735, 736, 737, 0, 629, 629, 0, 633, 0, 1472, 0, 871, 0, 705, 1204, 1137, 874, 1477, 1205, 1206, 1207, 1208, 0, 0, 5, 0, 0, 1483, 0, 1484, 0, 0, 0, 0, 731, 0, 0, 732, 733, 734, 755, 0, 0, 1373, 5, 0, 755, 0, 0, 0, 755, 0, 0, 0, 731, 0, 0, 732, 733, 734, 0, 735, 736, 737, 1228, 1204, 1137, 0, 0, 1205, 1206, 1207, 1208, 0, 0, 5, 0, 1209, 1433, 0, 1436, 735, 736, 737, 0, 731, 0, 1441, 732, 733, 734, 0, 1210, 0, 0, 1474, 0, 0, 1451, 0, 1453, 0, 0, 1211, 0, 0, 0, 0, 0, 0, 0, 5, 735, 736, 737, 0, 0, 871, 0, 0, 0, 731, 0, 791, 732, 733, 734, 791, 1209, 976, 0, 978, 979, 808, 0, 0, 982, 0, 763, 808, 774, 0, 986, 813, 0, 0, 0, 0, 735, 736, 737, 134, 230, 231, 1211, 232, 233, 234, 235, 236, 0, 1000, 0, 857, 0, 1003, 0, 857, 0, 0, 0, 1006, 1008, 857, 0, 0, 1012, 857, 0, 1015, 857, 0, 1017, 0, 0, 1018, 0, 0, 0, 0, 863, 863, 1022, 0, 633, 1204, 1026, 1027, 0, 1205, 1206, 1207, 1208, 0, 0, 5, 1030, 1032, 886, 0, 0, 0, 1524, 0, 0, 731, 0, 0, 732, 733, 734, 0, 0, 0, 0, 896, 659, 660, 661, 662, 663, 664, 665, 666, 667, 707, 708, 709, 710, 711, 0, 0, 735, 736, 737, 230, 231, 0, 232, 233, 234, 235, 236, 0, 0, 0, 0, 0, 1209, 0, 925, 0, 929, 0, 0, 929, 0, 1091, 929, 0, 0, 929, 0, 1097, 929, 0, 0, 976, 976, 0, 755, 0, 0, 0, 1211, 0, 0, 0, 961, 0, 0, 0, 0, 0, 0, 1107, 0, 1108, 1109, 1110, 1111, 0, 0, 0, 0, 0, 0, 0, 0, 1116, 1117, 1118, 0, 1119, 857, 857, 857, 486, 1124, 0, 1125, 0, 0, 0, 633, 5, 1130, 0, 871, 0, 0, 0, 1030, 1030, 0, 731, 0, 0, 732, 733, 734, 0, 0, 0, 0, 0, 0, 762, 0, 0, 0, 0, 763, 0, 0, 0, 0, 0, 0, 0, 0, 0, 735, 736, 737, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1178, 0, 0, 0, 1181, 1182, 1184, 0, 1187, 633, 1190, 0, 0, 0, 0, 1194, 1195, 1038, 1196, 0, 0, 857, 0, 1198, 0, 0, 0, 1052, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1203, 0, 755, 0, 0, 0, 0, 0, 929, 0, 929, 0, 0, 252, 929, 0, 0, 0, 0, 0, 0, 0, 0, 307, 755, 0, 314, 0, 319, 0, 0, 331, 755, 0, 0, 0, 857, 0, 0, 0, 0, 0, 0, 0, 1255, 0, 1178, 1257, 0, 1259, 0, 0, 0, 1261, 1263, 0, 0, 1266, 1267, 0, 1187, 633, 0, 1190, 1271, 1272, 0, 0, 0, 3, 0, 1274, 1276, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 731, 8, 0, 732, 733, 734, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 871, 1352, 1274, 14, 0, 1354, 0, 0, 0, 16, 735, 736, 737, 1355, 0, 0, 0, 0, 929, 0, 18, 0, 0, 20, 0, 22, 0, 0, 0, 1170, 0, 0, 0, 0, 0, 0, 0, 0, 1356, 1357, 0, 0, 25, 0, 27, 0, 28, 1359, 30, 1263, 0, 1263, 0, 871, 35, 1363, 1365, 1366, 1367, 1368, 0, 0, 1370, 0, 0, 0, 0, 633, 0, 431, 0, 0, 0, 0, 0, 0, 446, 1213, 0, 451, 0, 755, 0, 755, 0, 0, 755, 0, 0, 755, 0, 0, 0, 0, 0, 0, 482, 0, 0, 0, 0, 490, 1431, 0, 1434, 0, 0, 0, 0, 0, 0, 1439, 0, 1442, 0, 0, 0, 0, 1445, 0, 1447, 0, 1449, 0, 0, 0, 0, 0, 1445, 0, 1456, 0, 0, 0, 0, 1459, 0, 0, 0, 0, 1463, 0, 0, 0, 401, 1445, 0, 0, 0, 0, 0, 0, 0, 0, 1213, 0, 1213, 0, 409, 410, 755, 413, 755, 0, 0, 755, 0, 0, 0, 0, 0, 0, 0, 0, 0, 755, 0, 0, 0, 0, 564, 0, 0, 0, 0, 0, 0, 0, 1276, 0, 572, 1490, 1492, 0, 0, 0, 1496, 580, 0, 1500, 0, 1445, 1504, 0, 0, 1445, 0, 0, 0, 0, 0, 0, 591, 0, 0, 0, 0, 0, 0, 597, 0, 0, 600, 601, 0, 0, 0, 0, 0, 0, 0, 0, 606, 0, 1521, 609, 0, 0, 0, 0, 0, 0, 618, 0, 0, 620, 0, 0, 0, 0, 626, 0, 627, 1379, 1379, 1388, 1388, 0, 1397, 0, 0, 636, 1407, 0, 1413, 1379, 1418, 1418, 0, 1423, 0, 0, 649, 0, 0, 0, 0, 656, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 686, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 698, 0, 0, 0, 0, 0, 0, 0, 578, 0, 0, 0, 0, 0, 0, 0, 584, 0, 0, 0, 0, 587, 0, 0, 0, 0, 0, 0, 594, 595, 0, 0, 0, 598, 0, 0, 0, 759, 0, 0, 0, 0, 0, 771, 605, 0, 0, 777, 0, 0, 0, 0, 0, 780, 0, 781, 619, 0, 0, 783, 0, 0, 0, 0, 785, 0, 0, 0, 0, 0, 0, 788, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 795, 1, 2, 797, 798, 799, 800, 3, 0, 0, 0, 0, 4, 0, 5, 6, 0, 806, 0, 0, 0, 0, 7, 0, 0, 8, 814, 0, 816, 0, 0, 0, 9, 10, 0, 11, 0, 12, 0, 0, 0, 824, 13, 826, 14, 828, 0, 15, 0, 0, 16, 841, 0, 0, 0, 0, 0, 0, 17, 0, 0, 18, 0, 19, 20, 21, 22, 0, 0, 0, 0, 0, 0, 0, 23, 24, 0, 0, 0, 0, 0, 0, 0, 25, 26, 27, 0, 28, 29, 30, 31, 32, 33, 0, 34, 35, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 888, 0, 890, 0, 0, 0, 0, 801, 802, 0, 0, 0, 0, 804, 0, 0, 0, 900, 0, 902, 0, 0, 0, 0, 0, 0, 908, 0, 818, 819, 0, 0, 0, 913, 0, 914, 0, 915, 0, 0, 825, 0, 827, 0, 829, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 957, 0, 0, 135, 137, 139, 139, 142, 962, 0, 148, 139, 151, 139, 148, 139, 148, 139, 148, 139, 148, 161, 163, 0, 185, 185, 185, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, 1320, 1321, 1322, 1323, 1324, 0, 1005, 0, 0, 0, 0, 1011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1019, 246, 0, 0, 0, 0, 0, 276, 0, 0, 0, 1028, 0, 0, 0, 0, 0, 0, 1029, 0, 0, 1034, 1035, 1036, 0, 0, 1037, 0, 1041, 0, 0, 1045, 0, 0, 0, 0, 1047, 0, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 0, 0, 0, 0, 0, 0, 971, 1069, 973, 974, 0, 0, 0, 0, 0, 0, 0, 0, 981, 0, 983, 984, 0, 0, 0, 0, 0, 0, 988, 989, 0, 990, 991, 992, 993, 0, 0, 1096, 0, 0, 0, 0, 0, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 0, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 0, 1115, 1320, 1321, 1322, 1323, 1324, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1127, 0, 1129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1140, 1141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 440, 0, 0, 0, 0, 452, 0, 137, 0, 1163, 0, 0, 0, 0, 1174, 1175, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 491, 137, 0, 0, 0, 0, 0, 0, 0, 502, 1098, 0, 0, 0, 0, 1101, 1102, 0, 1103, 0, 0, 0, 0, 0, 1104, 525, 0, 1106, 528, 0, 0, 0, 0, 0, 0, 535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1249, 1336, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1337, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1338, 1339, 1320, 1321, 1322, 1323, 1324, 0, 0, 0, 0, 1273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1328, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1345, 0, 1349, 1350, 0, 0, 0, 0, 0, 0, 0, 1199, 1200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 639, 641, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1358, 0, 675, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1369, 0, 0, 1260, 696, 0, 0, 0, 0, 0, 0, 1374, 0, 1376, 0, 0, 0, 0, 0, 0, 0, 0, 1406, 0, 1412, 0, 1278, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 772, 0, 0, 0, 185, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1468, 1469, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1371, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1494, 0, 0, 1498, 0, 0, 0, 0, 0, 1506, 1507, 0, 0, 0, 0, 0, 0, 1428, 0, 0, 0, 1429, 1430, 0, 1432, 0, 1435, 0, 0, 0, 1437, 1438, 0, 1440, 0, 1443, 0, 0, 1519, 1444, 0, 0, 0, 0, 1450, 0, 1452, 0, 0, 1454, 0, 0, 0, 0, 1530, 1458, 0, 1460, 1461, 0, 1462, 0, 1464, 1465, 0, 1466, 0, 0, 0, 0, 878, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 892, 0, 892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1489, 1491, 0, 1493, 0, 1495, 1497, 0, 1499, 1501, 1502, 0, 1505, 0, 0, 0, 1509, 1510, 0, 931, 0, 0, 936, 0, 0, 0, 0, 0, 0, 0, 0, 946, 0, 0, 0, 0, 0, 696, 0, 0, 696, 1518, 0, 1520, 1522, 0, 1523, 0, 0, 0, 0, 0, 0, 0, 0, 1529, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1084, 0, 0, 0, 0, 0, 0, 0, 0, 1089, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 696, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1225, 0, 0, 0, 0, 0, 1225, 0, 0, 0, 0, 0, 1225, 0, 0, 0, 0, 0, 0, 0, 0, 892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1279, 0, 892, 892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1332 }; static const yytype_int16 yycheck[] = { 45, 197, 577, 244, 115, 229, 0, 52, 53, 222, 55, 202, 225, 58, 769, 60, 0, 62, 384, 131, 132, 66, 355, 68, 879, 70, 863, 72, 192, 74, 623, 76, 448, 78, 210, 80, 629, 138, 805, 214, 208, 361, 205, 88, 979, 548, 379, 369, 425, 245, 95, 0, 97, 375, 99, 133, 101, 617, 103, 865, 105, 848, 868, 920, 1026, 848, 417, 389, 851, 859, 673, 695, 1091, 1363, 863, 196, 563, 4, 863, 17, 0, 202, 17, 1176, 6, 4, 17, 50, 6, 7, 41, 724, 721, 843, 12, 845, 6, 375, 848, 17, 454, 851, 147, 590, 854, 848, 16, 857, 851, 1178, 6, 854, 17, 17, 196, 0, 10, 52, 22, 24, 1108, 17, 1110, 1111, 18, 26, 5, 747, 79, 749, 66, 751, 95, 753, 28, 755, 15, 366, 57, 3, 1400, 35, 61, 81, 222, 170, 191, 225, 1408, 66, 1410, 82, 36, 47, 154, 72, 237, 170, 387, 53, 87, 97, 26, 1355, 91, 1425, 1456, 89, 87, 36, 64, 89, 91, 67, 1366, 69, 80, 81, 79, 89, 97, 41, 378, 62, 63, 1022, 1255, 976, 1257, 50, 57, 976, 86, 89, 88, 359, 90, 388, 92, 954, 68, 956, 1190, 9, 98, 250, 430, 9, 1065, 93, 94, 17, 257, 74, 259, 17, 1003, 262, 263, 68, 1003, 1481, 1363, 1006, 73, 1485, 93, 94, 1018, 1370, 1107, 99, 1109, 39, 95, 1090, 170, 559, 558, 284, 285, 1203, 417, 565, 289, 1030, 1181, 292, 7, 571, 99, 628, 11, 202, 299, 1112, 1006, 1276, 1008, 304, 1003, 306, 1012, 1006, 505, 1008, 311, 589, 588, 1012, 863, 895, 623, 318, 834, 356, 779, 901, 323, 199, 1373, 559, 885, 328, 480, 330, 889, 920, 917, 334, 644, 65, 533, 571, 1151, 14, 341, 14, 17, 344, 36, 1178, 4, 22, 1267, 925, 17, 352, 1271, 929, 1187, 170, 23, 933, 934, 935, 397, 19, 938, 939, 1186, 57, 942, 166, 167, 61, 1192, 29, 1134, 1116, 411, 1086, 17, 1116, 1117, 71, 5, 418, 1105, 1274, 17, 403, 17, 7, 406, 423, 15, 11, 426, 170, 27, 432, 27, 17, 455, 57, 42, 93, 94, 61, 7, 80, 81, 80, 81, 12, 51, 52, 157, 1119, 17, 25, 5, 1116, 1117, 1118, 1119, 96, 55, 96, 157, 56, 15, 367, 156, 87, 40, 381, 49, 91, 374, 724, 1147, 62, 17, 49, 17, 63, 443, 1445, 445, 1447, 593, 1449, 27, 450, 27, 1000, 31, 32, 1456, 1194, 19, 58, 459, 422, 657, 1352, 463, 796, 25, 428, 29, 16, 78, 1280, 471, 1445, 1178, 1447, 17, 54, 55, 48, 1201, 611, 26, 1030, 1456, 1276, 608, 604, 33, 34, 489, 566, 567, 162, 623, 164, 495, 1194, 1195, 1196, 1500, 1075, 1076, 1077, 1078, 17, 17, 582, 583, 20, 17, 22, 17, 512, 32, 27, 23, 24, 593, 33, 32, 1230, 27, 17, 1333, 812, 34, 562, 22, 1238, 566, 567, 1253, 1254, 62, 905, 54, 55, 162, 813, 164, 540, 54, 55, 46, 544, 582, 583, 162, 54, 55, 1255, 568, 1257, 570, 168, 169, 573, 574, 1356, 1357, 1255, 1359, 1257, 599, 581, 1363, 163, 1365, 635, 1367, 1368, 1182, 1370, 1184, 1272, 592, 1187, 30, 614, 31, 32, 617, 17, 17, 1159, 154, 603, 21, 22, 625, 607, 164, 27, 610, 7, 30, 31, 32, 8, 12, 57, 7, 54, 55, 621, 11, 12, 13, 154, 155, 156, 371, 158, 159, 160, 161, 162, 75, 76, 54, 55, 56, 382, 383, 154, 155, 161, 162, 158, 159, 160, 391, 920, 1431, 1182, 55, 1184, 1347, 1213, 1187, 400, 1439, 1190, 379, 1329, 405, 1331, 7, 408, 355, 85, 11, 12, 13, 722, 655, 416, 17, 158, 159, 160, 1459, 22, 7, 70, 1463, 921, 11, 12, 13, 1261, 817, 1263, 17, 80, 81, 20, 918, 22, 136, 843, 138, 844, 140, 141, 685, 143, 845, 724, 1003, 690, 1125, 149, 150, 547, 152, 153, 154, 155, 156, 157, 158, 159, 160, 1282, 162, 874, 164, 155, 1030, 70, 158, 159, 160, 1000, 1263, 59, 60, 1266, 565, 80, 81, 0, 1000, 1237, 791, 70, 184, 1276, 186, 187, 188, 1223, 190, 853, 62, 63, 856, 1026, 54, 55, 1240, 808, 743, 1110, 745, 80, 81, 748, 1190, 750, 787, 752, 1357, 754, 1368, 7, 757, 83, 84, 11, 12, 13, 14, 1365, 765, 17, 163, 768, 1521, 770, 202, 168, 169, 170, 775, 0, 1065, 7, 1039, 1040, 7, 11, 12, 13, 11, 12, 13, 17, 870, 247, 17, 249, 22, 251, 831, 17, 365, 834, 835, 648, 258, 278, 260, 1367, 1368, 27, 264, 265, 30, 31, 32, 129, 270, 271, 272, 273, 129, 275, 70, 277, 278, 279, 859, 1112, 952, 953, 978, 979, 766, 767, 288, 129, 54, 55, 56, 293, 294, 129, 296, 206, 70, 1094, 1095, 70, 1024, 303, 394, 305, 129, 200, 790, 309, 310, 793, 200, 313, 982, 129, 316, 317, 488, 129, 1151, 321, 322, 589, 129, 807, 1065, 158, 810, 329, 129, 129, 412, 333, 7, 196, 336, 129, 11, 12, 13, 920, 921, 343, 17, 345, 346, 347, 348, 349, 842, 351, 7, 1015, 8, 1017, 11, 12, 13, 196, 622, 622, 17, 17, 622, 758, 21, 22, 622, 129, 129, 129, 1203, 27, 129, 129, 30, 31, 32, 129, 0, 343, 203, 0, 112, 206, 9, 346, 348, 117, 211, 119, 1468, 121, 17, 123, 0, 125, 969, -1, 54, 55, 56, -1, 27, -1, -1, 30, 31, 32, -1, -1, 955, -1, -1, 70, 39, 996, -1, 1492, 43, 44, 1354, 1355, 1356, 1357, -1, 1359, -1, -1, 815, 54, 55, 56, 1366, 820, 821, 822, 823, 1018, 155, 441, 442, 158, 159, 160, 161, 162, 448, 449, 165, -1, -1, 453, 454, 455, 456, 120, 458, 122, -1, 124, 1124, 126, -1, 465, -1, 467, 468, -1, 470, -1, 472, 473, -1, 475, 476, -1, 478, 479, -1, -1, 9, 483, 484, 485, 1065, 1181, 488, -1, 17, -1, -1, 493, 494, -1, -1, 497, 498, 1431, 27, 501, 1434, 30, 31, 32, -1, 1439, 16, 17, 1442, 1054, 39, 513, 514, 1447, -1, 517, 518, 27, 520, 521, 30, 31, 32, 1068, -1, 54, 55, 56, 530, 531, 532, 1112, -1, -1, 1198, 537, -1, 539, -1, 541, -1, 543, -1, -1, 54, 55, 56, -1, 1093, 10, 155, 156, -1, 158, 159, 160, 1490, 18, -1, -1, -1, 384, 1496, -1, -1, -1, -1, 28, -1, -1, 1151, 394, -1, 396, 35, -1, -1, 38, 1274, 402, 155, 156, -1, 158, 159, 160, 161, 162, 1133, -1, 165, 7, 53, -1, 1139, 11, 12, 13, 14, -1, 1145, 17, -1, 64, 1149, -1, 67, -1, 69, -1, -1, 27, -1, -1, 30, 31, 32, -1, 372, 373, -1, 37, 376, 377, -1, 86, -1, 88, 1173, 90, -1, 92, -1, -1, -1, 637, 390, 98, 54, 55, 56, -1, 644, -1, -1, 399, 648, -1, 650, -1, 404, -1, -1, 407, 70, -1, 1352, -1, -1, -1, -1, 415, -1, -1, -1, 1210, 668, -1, 670, 671, 672, 673, -1, 1218, 676, 1220, -1, -1, 155, -1, 96, 158, 159, 160, 161, 162, 163, 1232, 165, -1, 692, 693, 694, 695, -1, 697, -1, 699, -1, 701, -1, 703, -1, -1, -1, 707, -1, -1, -1, -1, 712, 713, 714, 100, 101, 102, 103, 104, 105, 106, 107, 108, -1, 17, -1, -1, -1, -1, 730, 552, -1, -1, 555, 27, -1, -1, 30, 31, 32, -1, 742, -1, 744, -1, -1, 747, -1, 749, -1, 751, 44, 753, 575, 755, -1, -1, 758, -1, -1, 761, 54, 55, 56, -1, 766, 767, -1, 769, -1, 1433, -1, 773, -1, 596, 7, 8, 778, 1441, 11, 12, 13, 14, -1, -1, 17, -1, -1, 1451, -1, 1453, -1, -1, -1, -1, 27, -1, -1, 30, 31, 32, 623, -1, -1, 1348, 17, -1, 629, -1, -1, -1, 633, -1, -1, -1, 27, -1, -1, 30, 31, 32, -1, 54, 55, 56, 37, 7, 8, -1, -1, 11, 12, 13, 14, -1, -1, 17, -1, 70, 1383, -1, 1385, 54, 55, 56, -1, 27, -1, 1392, 30, 31, 32, -1, 85, -1, -1, 1436, -1, -1, 1403, -1, 1405, -1, -1, 96, -1, -1, -1, -1, -1, -1, -1, 17, 54, 55, 56, -1, -1, 879, -1, -1, -1, 27, -1, 885, 30, 31, 32, 889, 70, 891, -1, 893, 894, 895, -1, -1, 898, -1, 44, 901, 46, -1, 904, 905, -1, -1, -1, -1, 54, 55, 56, 154, 155, 156, 96, 158, 159, 160, 161, 162, -1, 923, -1, 925, -1, 927, -1, 929, -1, -1, -1, 933, 934, 935, -1, -1, 938, 939, -1, 941, 942, -1, 944, -1, -1, 947, -1, -1, -1, -1, 952, 953, 954, -1, 956, 7, 958, 959, -1, 11, 12, 13, 14, -1, -1, 17, 968, 969, 791, -1, -1, -1, 1517, -1, -1, 27, -1, -1, 30, 31, 32, -1, -1, -1, -1, 808, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, -1, -1, 54, 55, 56, 155, 156, -1, 158, 159, 160, 161, 162, -1, -1, -1, -1, -1, 70, -1, 843, -1, 845, -1, -1, 848, -1, 1029, 851, -1, -1, 854, -1, 1035, 857, -1, -1, 1039, 1040, -1, 863, -1, -1, -1, 96, -1, -1, -1, 871, -1, -1, -1, -1, -1, -1, 1057, -1, 1059, 1060, 1061, 1062, -1, -1, -1, -1, -1, -1, -1, -1, 1071, 1072, 1073, -1, 1075, 1076, 1077, 1078, 9, 1080, -1, 1082, -1, -1, -1, 1086, 17, 1088, -1, 1090, -1, -1, -1, 1094, 1095, -1, 27, -1, -1, 30, 31, 32, -1, -1, -1, -1, -1, -1, 39, -1, -1, -1, -1, 44, -1, -1, -1, -1, -1, -1, -1, -1, -1, 54, 55, 56, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1138, -1, -1, -1, 1142, 1143, 1144, -1, 1146, 1147, 1148, -1, -1, -1, -1, 1153, 1154, 976, 1156, -1, -1, 1159, -1, 1161, -1, -1, -1, 986, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1177, -1, 1000, -1, -1, -1, -1, -1, 1006, -1, 1008, -1, -1, 194, 1012, -1, -1, -1, -1, -1, -1, -1, -1, 204, 1022, -1, 207, -1, 209, -1, -1, 212, 1030, -1, -1, -1, 1213, -1, -1, -1, -1, -1, -1, -1, 1221, -1, 1223, 1224, -1, 1226, -1, -1, -1, 1230, 1231, -1, -1, 1234, 1235, -1, 1237, 1238, -1, 1240, 1241, 1242, -1, -1, -1, 10, -1, 1248, 1249, -1, -1, -1, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, 27, 28, -1, 30, 31, 32, -1, -1, 35, -1, -1, -1, -1, -1, -1, -1, -1, 1280, 1281, 1282, 47, -1, 1285, -1, -1, -1, 53, 54, 55, 56, 1293, -1, -1, -1, -1, 1119, -1, 64, -1, -1, 67, -1, 69, -1, -1, -1, 1130, -1, -1, -1, -1, -1, -1, -1, -1, 1318, 1319, -1, -1, 86, -1, 88, -1, 90, 1327, 92, 1329, -1, 1331, -1, 1333, 98, 1335, 1336, 1337, 1338, 1339, -1, -1, 1342, -1, -1, -1, -1, 1347, -1, 353, -1, -1, -1, -1, -1, -1, 360, 1178, -1, 363, -1, 1182, -1, 1184, -1, -1, 1187, -1, -1, 1190, -1, -1, -1, -1, -1, -1, 380, -1, -1, -1, -1, 385, 1382, -1, 1384, -1, -1, -1, -1, -1, -1, 1391, -1, 1393, -1, -1, -1, -1, 1398, -1, 1400, -1, 1402, -1, -1, -1, -1, -1, 1408, -1, 1410, -1, -1, -1, -1, 1415, -1, -1, -1, -1, 1420, -1, -1, -1, 312, 1425, -1, -1, -1, -1, -1, -1, -1, -1, 1255, -1, 1257, -1, 326, 327, 1261, 329, 1263, -1, -1, 1266, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1276, -1, -1, -1, -1, 464, -1, -1, -1, -1, -1, -1, -1, 1468, -1, 474, 1471, 1472, -1, -1, -1, 1476, 481, -1, 1479, -1, 1481, 1482, -1, -1, 1485, -1, -1, -1, -1, -1, -1, 496, -1, -1, -1, -1, -1, -1, 503, -1, -1, 506, 507, -1, -1, -1, -1, -1, -1, -1, -1, 516, -1, 1514, 519, -1, -1, -1, -1, -1, -1, 526, -1, -1, 529, -1, -1, -1, -1, 534, -1, 536, 1354, 1355, 1356, 1357, -1, 1359, -1, -1, 545, 1363, -1, 1365, 1366, 1367, 1368, -1, 1370, -1, -1, 556, -1, -1, -1, -1, 561, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 576, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 587, -1, -1, -1, -1, -1, -1, -1, 479, -1, -1, -1, -1, -1, -1, -1, 487, -1, -1, -1, -1, 492, -1, -1, -1, -1, -1, -1, 499, 500, -1, -1, -1, 504, -1, -1, -1, 624, -1, -1, -1, -1, -1, 630, 515, -1, -1, 634, -1, -1, -1, -1, -1, 640, -1, 642, 527, -1, -1, 646, -1, -1, -1, -1, 651, -1, -1, -1, -1, -1, -1, 658, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 674, 4, 5, 677, 678, 679, 680, 10, -1, -1, -1, -1, 15, -1, 17, 18, -1, 691, -1, -1, -1, -1, 25, -1, -1, 28, 700, -1, 702, -1, -1, -1, 35, 36, -1, 38, -1, 40, -1, -1, -1, 715, 45, 717, 47, 719, -1, 50, -1, -1, 53, 725, -1, -1, -1, -1, -1, -1, 61, -1, -1, 64, -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, 77, 78, -1, -1, -1, -1, -1, -1, -1, 86, 87, 88, -1, 90, 91, 92, 93, 94, 95, -1, 97, 98, 99, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 792, -1, 794, -1, -1, -1, -1, 683, 684, -1, -1, -1, -1, 689, -1, -1, -1, 809, -1, 811, -1, -1, -1, -1, -1, -1, 818, -1, 704, 705, -1, -1, -1, 825, -1, 827, -1, 829, -1, -1, 716, -1, 718, -1, 720, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 864, -1, -1, 109, 110, 111, 112, 113, 872, -1, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, -1, 130, 131, 132, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, -1, 932, -1, -1, -1, -1, 937, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 949, 192, -1, -1, -1, -1, -1, 198, -1, -1, -1, 960, -1, -1, -1, -1, -1, -1, 967, -1, -1, 970, 971, 972, -1, -1, 975, -1, 977, -1, -1, 980, -1, -1, -1, -1, 985, -1, 987, 988, 989, 990, 991, 992, 993, -1, -1, -1, -1, -1, -1, 884, 1001, 886, 887, -1, -1, -1, -1, -1, -1, -1, -1, 896, -1, 898, 899, -1, -1, -1, -1, -1, -1, 906, 907, -1, 909, 910, 911, 912, -1, -1, 1031, -1, -1, -1, -1, -1, 115, 116, 117, 118, 119, 120, 121, -1, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, -1, 1070, 149, 150, 151, 152, 153, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1085, -1, 1087, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1101, 1102, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 359, -1, -1, -1, -1, 364, -1, 366, -1, 1126, -1, -1, -1, -1, 1131, 1132, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 386, 387, -1, -1, -1, -1, -1, -1, -1, 395, 1038, -1, -1, -1, -1, 1043, 1044, -1, 1046, -1, -1, -1, -1, -1, 1052, 411, -1, 1055, 414, -1, -1, -1, -1, -1, -1, 421, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1202, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, -1, -1, -1, -1, 1247, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1260, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1275, -1, 1277, 1278, -1, -1, -1, -1, -1, -1, -1, 1170, 1171, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 549, 550, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1325, -1, 569, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1340, -1, -1, 1227, 586, -1, -1, -1, -1, -1, -1, 1351, -1, 1353, -1, -1, -1, -1, -1, -1, -1, -1, 1362, -1, 1364, -1, 1250, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 631, -1, -1, -1, 635, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1427, 1428, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1344, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1474, -1, -1, 1477, -1, -1, -1, -1, -1, 1483, 1484, -1, -1, -1, -1, -1, -1, 1375, -1, -1, -1, 1379, 1380, -1, 1382, -1, 1384, -1, -1, -1, 1388, 1389, -1, 1391, -1, 1393, -1, -1, 1512, 1397, -1, -1, -1, -1, 1402, -1, 1404, -1, -1, 1407, -1, -1, -1, -1, 1528, 1413, -1, 1415, 1416, -1, 1418, -1, 1420, 1421, -1, 1423, -1, -1, -1, -1, 786, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 803, -1, 805, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1470, 1471, -1, 1473, -1, 1475, 1476, -1, 1478, 1479, 1480, -1, 1482, -1, -1, -1, 1486, 1487, -1, 847, -1, -1, 850, -1, -1, -1, -1, -1, -1, -1, -1, 859, -1, -1, -1, -1, -1, 865, -1, -1, 868, 1511, -1, 1513, 1514, -1, 1516, -1, -1, -1, -1, -1, -1, -1, -1, 1525, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1018, -1, -1, -1, -1, -1, -1, -1, -1, 1027, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1105, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1134, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1180, -1, -1, -1, -1, -1, 1186, -1, -1, -1, -1, -1, 1192, -1, -1, -1, -1, -1, -1, -1, -1, 1201, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1251, -1, 1253, 1254, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1265 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_int16 yystos[] = { 0, 4, 5, 10, 15, 17, 18, 25, 28, 35, 36, 38, 40, 45, 47, 50, 53, 61, 64, 66, 67, 68, 69, 77, 78, 86, 87, 88, 90, 91, 92, 93, 94, 95, 97, 98, 99, 172, 173, 174, 245, 247, 329, 330, 333, 335, 356, 357, 358, 359, 360, 361, 364, 365, 367, 369, 377, 378, 379, 388, 389, 400, 402, 413, 414, 415, 423, 424, 425, 426, 427, 428, 429, 431, 432, 433, 434, 435, 436, 437, 439, 461, 462, 463, 465, 467, 468, 474, 475, 481, 486, 489, 492, 495, 496, 499, 504, 506, 509, 513, 526, 530, 531, 536, 538, 539, 0, 166, 167, 209, 209, 209, 209, 209, 170, 211, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 154, 212, 248, 212, 336, 212, 363, 363, 212, 370, 6, 89, 373, 374, 212, 380, 363, 212, 403, 363, 380, 363, 380, 363, 380, 363, 380, 212, 476, 212, 500, 358, 359, 360, 361, 388, 414, 415, 426, 431, 435, 461, 474, 481, 486, 489, 492, 495, 505, 507, 508, 212, 514, 514, 514, 41, 518, 519, 211, 211, 331, 211, 211, 211, 209, 211, 211, 211, 211, 211, 381, 211, 211, 430, 211, 430, 211, 211, 438, 211, 211, 168, 169, 210, 211, 211, 510, 511, 211, 527, 528, 211, 532, 533, 211, 209, 155, 156, 158, 159, 160, 161, 162, 175, 176, 177, 179, 181, 182, 185, 187, 188, 212, 249, 62, 337, 339, 367, 210, 19, 29, 57, 330, 335, 346, 347, 366, 378, 396, 397, 464, 466, 469, 471, 472, 473, 346, 366, 464, 466, 179, 371, 212, 375, 367, 390, 391, 393, 394, 395, 396, 397, 3, 26, 404, 405, 71, 333, 335, 346, 416, 424, 482, 483, 484, 485, 23, 24, 245, 382, 383, 385, 386, 210, 72, 428, 487, 488, 245, 382, 210, 73, 433, 490, 491, 210, 74, 437, 493, 494, 51, 52, 245, 440, 441, 443, 444, 210, 65, 477, 479, 496, 497, 538, 75, 76, 501, 502, 79, 515, 516, 518, 515, 518, 515, 518, 42, 520, 521, 174, 187, 157, 175, 186, 179, 211, 246, 211, 331, 334, 209, 211, 209, 211, 209, 211, 209, 209, 211, 211, 211, 211, 211, 211, 211, 211, 381, 211, 209, 209, 211, 401, 209, 209, 211, 211, 211, 209, 211, 266, 211, 209, 211, 209, 211, 211, 209, 266, 211, 211, 211, 209, 211, 211, 209, 266, 266, 209, 211, 266, 209, 211, 209, 211, 209, 211, 511, 209, 211, 211, 528, 211, 211, 533, 211, 211, 209, 210, 180, 183, 184, 188, 187, 33, 34, 176, 212, 250, 251, 252, 254, 255, 210, 63, 337, 342, 343, 210, 212, 340, 342, 336, 378, 58, 348, 349, 16, 276, 278, 290, 362, 373, 212, 398, 398, 49, 446, 448, 446, 378, 362, 446, 446, 179, 372, 178, 180, 376, 210, 400, 398, 398, 9, 245, 407, 409, 210, 212, 406, 336, 424, 290, 417, 446, 398, 245, 245, 385, 212, 384, 245, 187, 387, 275, 277, 285, 287, 288, 290, 446, 398, 245, 275, 446, 398, 275, 446, 398, 187, 191, 193, 212, 442, 440, 212, 445, 446, 398, 497, 187, 503, 212, 517, 520, 26, 450, 451, 520, 48, 540, 544, 174, 187, 211, 211, 209, 209, 334, 211, 332, 209, 211, 338, 332, 211, 211, 211, 351, 209, 209, 210, 211, 211, 211, 211, 209, 211, 211, 210, 211, 211, 211, 368, 211, 266, 179, 210, 211, 211, 211, 266, 401, 209, 266, 211, 211, 209, 210, 211, 211, 266, 266, 211, 210, 266, 175, 210, 210, 209, 211, 211, 266, 210, 211, 211, 210, 211, 211, 162, 164, 190, 192, 196, 199, 210, 266, 210, 211, 211, 211, 498, 175, 210, 210, 211, 211, 529, 209, 211, 211, 214, 209, 210, 251, 254, 212, 253, 212, 256, 245, 345, 176, 344, 245, 341, 210, 337, 362, 59, 60, 352, 354, 210, 187, 350, 100, 101, 102, 103, 104, 105, 106, 107, 108, 291, 276, 346, 366, 346, 366, 275, 212, 449, 275, 362, 275, 275, 7, 11, 245, 267, 271, 210, 7, 12, 265, 270, 275, 346, 366, 346, 366, 212, 410, 210, 337, 417, 291, 275, 346, 424, 245, 187, 109, 110, 111, 112, 113, 291, 292, 293, 275, 428, 275, 433, 275, 437, 187, 194, 187, 200, 275, 469, 471, 472, 473, 480, 27, 30, 31, 32, 54, 55, 56, 215, 217, 218, 219, 220, 221, 222, 223, 226, 227, 228, 230, 231, 236, 238, 241, 242, 245, 257, 258, 497, 210, 187, 450, 39, 44, 215, 409, 453, 458, 459, 524, 525, 210, 212, 452, 46, 537, 215, 210, 514, 211, 210, 210, 332, 210, 338, 210, 209, 175, 210, 211, 211, 211, 470, 211, 470, 210, 211, 210, 210, 210, 210, 266, 266, 209, 266, 209, 210, 211, 211, 392, 211, 392, 211, 211, 210, 211, 210, 211, 266, 266, 211, 211, 211, 211, 210, 266, 210, 266, 210, 266, 192, 190, 163, 165, 188, 189, 197, 200, 205, 206, 207, 210, 211, 211, 209, 211, 216, 209, 211, 216, 209, 211, 216, 209, 211, 216, 209, 211, 216, 209, 216, 209, 498, 211, 512, 209, 529, 529, 209, 214, 209, 211, 353, 209, 211, 541, 542, 254, 212, 355, 187, 158, 159, 160, 294, 378, 245, 373, 210, 378, 210, 450, 212, 273, 273, 378, 245, 265, 373, 399, 210, 378, 210, 178, 411, 337, 294, 424, 210, 294, 294, 294, 294, 210, 210, 210, 187, 195, 200, 187, 201, 189, 208, 400, 219, 245, 257, 222, 226, 245, 257, 212, 224, 230, 236, 241, 212, 229, 236, 241, 176, 232, 241, 176, 239, 191, 212, 243, 154, 213, 43, 215, 453, 458, 522, 523, 524, 210, 410, 410, 344, 245, 210, 400, 446, 509, 530, 534, 450, 515, 353, 266, 470, 266, 266, 470, 211, 447, 211, 211, 392, 266, 211, 266, 266, 392, 211, 408, 266, 266, 266, 266, 266, 266, 192, 208, 189, 198, 205, 201, 211, 478, 216, 211, 216, 210, 211, 216, 211, 216, 216, 210, 211, 216, 216, 211, 216, 211, 211, 210, 512, 512, 211, 214, 209, 214, 211, 211, 210, 210, 211, 543, 211, 542, 210, 210, 210, 210, 245, 453, 458, 210, 179, 274, 274, 210, 399, 210, 7, 11, 12, 13, 245, 263, 264, 412, 210, 210, 210, 210, 210, 210, 210, 187, 202, 203, 215, 277, 290, 210, 225, 227, 230, 236, 241, 236, 241, 241, 241, 176, 233, 176, 240, 191, 212, 244, 524, 174, 411, 212, 460, 211, 215, 409, 458, 545, 210, 211, 266, 447, 447, 266, 266, 266, 266, 209, 266, 211, 211, 211, 211, 211, 175, 205, 209, 210, 211, 211, 211, 211, 216, 216, 216, 216, 211, 211, 237, 210, 214, 210, 211, 454, 353, 537, 209, 543, 543, 8, 295, 298, 210, 210, 273, 295, 296, 298, 295, 296, 297, 298, 187, 204, 205, 230, 236, 241, 236, 241, 241, 241, 176, 234, 267, 210, 7, 11, 12, 13, 14, 70, 245, 455, 456, 457, 210, 210, 209, 410, 211, 284, 209, 211, 211, 418, 211, 286, 209, 211, 279, 214, 211, 289, 209, 205, 211, 211, 211, 216, 211, 266, 266, 209, 534, 211, 7, 11, 12, 13, 14, 70, 85, 96, 217, 245, 257, 259, 260, 261, 262, 268, 272, 281, 283, 295, 328, 212, 299, 274, 37, 215, 328, 420, 421, 215, 328, 299, 215, 295, 328, 215, 297, 299, 236, 241, 241, 241, 176, 235, 273, 210, 411, 209, 216, 209, 209, 211, 284, 211, 280, 211, 266, 211, 214, 211, 419, 209, 211, 211, 279, 214, 289, 211, 211, 210, 211, 269, 211, 535, 266, 212, 282, 273, 273, 283, 283, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 300, 303, 312, 210, 420, 215, 420, 212, 422, 215, 312, 114, 122, 147, 148, 305, 308, 312, 241, 274, 210, 215, 524, 537, 210, 210, 353, 211, 269, 211, 211, 211, 211, 210, 211, 419, 419, 353, 211, 310, 211, 211, 211, 211, 210, 211, 266, 214, 209, 210, 274, 210, 20, 22, 245, 260, 301, 313, 314, 317, 318, 301, 21, 245, 260, 302, 315, 316, 317, 302, 80, 81, 245, 260, 304, 317, 319, 321, 322, 323, 324, 210, 245, 268, 311, 317, 319, 210, 245, 306, 313, 317, 301, 245, 307, 315, 317, 307, 245, 309, 317, 319, 534, 266, 266, 266, 211, 266, 209, 211, 266, 209, 266, 266, 211, 266, 209, 211, 266, 266, 211, 320, 211, 320, 211, 266, 209, 266, 209, 266, 320, 211, 320, 266, 211, 266, 266, 266, 211, 266, 266, 266, 320, 210, 210, 260, 317, 176, 260, 187, 260, 317, 176, 260, 321, 323, 260, 323, 176, 176, 268, 317, 317, 535, 266, 211, 266, 211, 266, 210, 266, 211, 266, 210, 266, 211, 266, 266, 320, 211, 266, 210, 210, 320, 266, 266, 260, 265, 260, 323, 82, 325, 326, 266, 210, 266, 211, 266, 266, 209, 325, 83, 84, 327, 266, 210 }; /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_int16 yyr1[] = { 0, 171, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 173, 173, 173, 173, 173, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 180, 181, 182, 183, 184, 185, 185, 186, 186, 187, 187, 187, 187, 188, 188, 189, 190, 191, 191, 191, 192, 192, 193, 194, 195, 196, 197, 197, 198, 198, 199, 200, 201, 202, 202, 202, 203, 204, 205, 205, 206, 207, 207, 208, 208, 209, 209, 210, 210, 211, 212, 213, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 218, 218, 218, 219, 219, 219, 219, 220, 221, 222, 223, 224, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 241, 242, 243, 243, 244, 244, 245, 246, 246, 246, 246, 246, 246, 246, 247, 248, 249, 249, 250, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 259, 260, 261, 262, 262, 262, 262, 262, 263, 264, 264, 264, 264, 265, 266, 266, 267, 268, 269, 269, 270, 270, 271, 271, 272, 272, 273, 274, 275, 275, 275, 275, 276, 277, 278, 279, 279, 279, 279, 280, 280, 281, 282, 283, 283, 283, 283, 283, 284, 284, 284, 284, 285, 286, 286, 286, 286, 287, 288, 289, 289, 289, 290, 291, 291, 291, 291, 291, 291, 291, 291, 291, 292, 292, 293, 293, 294, 294, 294, 295, 296, 297, 298, 299, 300, 300, 300, 300, 300, 300, 300, 300, 300, 301, 301, 301, 301, 301, 301, 301, 301, 302, 302, 302, 302, 302, 302, 302, 302, 303, 303, 304, 304, 304, 304, 304, 305, 305, 305, 305, 305, 305, 305, 305, 305, 306, 306, 306, 306, 307, 307, 307, 307, 308, 308, 309, 309, 309, 310, 310, 311, 311, 311, 311, 311, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 312, 313, 314, 315, 316, 317, 318, 319, 319, 319, 319, 320, 320, 320, 320, 320, 321, 322, 323, 324, 325, 326, 327, 327, 328, 329, 329, 330, 331, 331, 332, 332, 333, 334, 334, 335, 336, 337, 338, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 350, 350, 351, 351, 352, 353, 353, 354, 354, 355, 356, 356, 356, 357, 357, 358, 359, 360, 361, 362, 362, 363, 364, 364, 365, 365, 366, 366, 367, 368, 368, 368, 369, 369, 370, 371, 372, 373, 374, 374, 375, 376, 376, 377, 377, 378, 379, 379, 379, 380, 381, 381, 381, 381, 381, 381, 381, 381, 382, 383, 384, 385, 386, 387, 387, 387, 388, 389, 389, 390, 390, 390, 390, 391, 392, 392, 392, 392, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 401, 401, 402, 403, 404, 405, 405, 406, 407, 408, 408, 408, 409, 410, 411, 412, 413, 413, 414, 415, 416, 416, 417, 418, 418, 418, 418, 418, 419, 419, 419, 420, 421, 422, 423, 423, 424, 425, 425, 425, 426, 427, 427, 428, 429, 429, 430, 430, 430, 430, 431, 432, 433, 434, 434, 435, 436, 437, 438, 438, 438, 438, 438, 439, 439, 440, 441, 442, 442, 443, 444, 445, 446, 447, 447, 447, 447, 448, 449, 450, 451, 452, 453, 454, 454, 454, 455, 456, 457, 457, 457, 457, 457, 457, 458, 459, 460, 461, 461, 461, 462, 462, 463, 464, 464, 465, 466, 466, 467, 468, 469, 470, 470, 470, 471, 472, 473, 474, 475, 476, 477, 478, 478, 478, 479, 480, 480, 480, 480, 481, 482, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, 496, 497, 497, 498, 498, 498, 499, 500, 501, 502, 502, 503, 503, 503, 504, 505, 505, 506, 507, 507, 507, 507, 507, 507, 507, 507, 507, 507, 507, 507, 507, 507, 508, 508, 508, 508, 508, 508, 508, 509, 510, 510, 511, 512, 512, 512, 512, 512, 512, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 527, 528, 529, 529, 529, 529, 529, 530, 531, 532, 532, 533, 534, 534, 534, 534, 535, 535, 535, 535, 536, 537, 538, 539, 540, 541, 541, 542, 543, 543, 543, 543, 544, 545 }; /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ static const yytype_int8 yyr2[] = { 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 0, 1, 3, 1, 1, 2, 2, 2, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 6, 0, 1, 1, 1, 1, 3, 3, 1, 2, 1, 1, 1, 1, 3, 4, 2, 1, 1, 1, 1, 1, 3, 2, 0, 2, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 2, 1, 0, 3, 2, 1, 1, 3, 2, 1, 1, 3, 4, 3, 6, 1, 4, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 7, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 8, 4, 1, 1, 10, 1, 1, 1, 1, 1, 7, 0, 2, 1, 1, 1, 6, 1, 1, 1, 1, 1, 7, 0, 2, 4, 6, 2, 4, 2, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 1, 4, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 7, 0, 3, 7, 5, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 10, 0, 3, 3, 2, 0, 2, 5, 1, 1, 3, 1, 2, 1, 0, 3, 3, 2, 10, 0, 2, 4, 2, 10, 10, 0, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 7, 6, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 2, 4, 6, 4, 2, 4, 2, 2, 2, 4, 6, 4, 2, 4, 2, 2, 1, 3, 2, 1, 2, 4, 2, 1, 1, 3, 1, 3, 1, 3, 1, 3, 2, 4, 2, 2, 2, 4, 2, 2, 1, 3, 2, 2, 1, 0, 2, 2, 1, 2, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 4, 1, 4, 1, 2, 2, 4, 6, 0, 3, 3, 5, 7, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1, 1, 5, 5, 3, 0, 3, 7, 3, 3, 1, 1, 5, 0, 3, 1, 1, 1, 4, 1, 1, 1, 5, 1, 4, 1, 1, 2, 3, 0, 2, 5, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 8, 10, 8, 10, 3, 1, 1, 1, 1, 1, 1, 1, 1, 9, 0, 3, 3, 1, 1, 1, 1, 1, 6, 1, 1, 1, 4, 2, 1, 3, 7, 1, 1, 1, 1, 0, 2, 2, 4, 3, 5, 5, 7, 4, 1, 1, 4, 1, 1, 2, 3, 10, 1, 1, 1, 1, 1, 1, 7, 0, 3, 5, 3, 3, 9, 7, 9, 1, 1, 1, 1, 7, 0, 3, 3, 1, 1, 5, 1, 1, 1, 7, 0, 3, 3, 1, 1, 1, 1, 1, 1, 8, 10, 1, 1, 10, 0, 3, 5, 3, 2, 0, 3, 2, 5, 1, 1, 1, 1, 5, 1, 1, 1, 8, 1, 1, 5, 1, 1, 0, 2, 3, 5, 8, 1, 5, 1, 1, 8, 1, 5, 0, 3, 5, 3, 3, 1, 1, 4, 1, 1, 1, 4, 1, 1, 7, 0, 3, 3, 3, 1, 1, 5, 1, 1, 7, 0, 3, 3, 1, 5, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 10, 1, 1, 10, 10, 7, 0, 3, 3, 9, 7, 9, 10, 1, 1, 9, 0, 2, 2, 1, 1, 1, 1, 1, 10, 1, 1, 7, 9, 1, 10, 7, 1, 10, 7, 1, 10, 7, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 2, 1, 1, 4, 1, 1, 1, 2, 3, 4, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 3, 1, 8, 0, 3, 3, 3, 5, 3, 2, 1, 1, 4, 1, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 1, 6, 0, 3, 3, 3, 2, 1, 4, 3, 1, 16, 1, 1, 1, 1, 0, 6, 3, 2, 1, 1, 9, 1, 4, 3, 1, 4, 0, 3, 3, 2, 1, 7 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYEMPTY (-2) #define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY) \ { \ yychar = (Token); \ yylval = (Value); \ YYPOPSTACK (yylen); \ yystate = *yyssp; \ goto yybackup; \ } \ else \ { \ yyerror (context, YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (0) /* Error token number */ #define YYTERROR 1 #define YYERRCODE 256 /* Enable debugging if requested. */ #if YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (0) /* This macro is provided for backward compatibility. */ #ifndef YY_LOCATION_PRINT # define YY_LOCATION_PRINT(File, Loc) ((void) 0) #endif # define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Type, Value, context); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ static void yy_symbol_value_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, pj_wkt2_parse_context *context) { FILE *yyoutput = yyo; YYUSE (yyoutput); YYUSE (context); if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) YYPRINT (yyo, yytoknum[yytype], *yyvaluep); # endif YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ static void yy_symbol_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, pj_wkt2_parse_context *context) { YYFPRINTF (yyo, "%s %s (", yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); yy_symbol_value_print (yyo, yytype, yyvaluep, context); YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, int yyrule, pj_wkt2_parse_context *context) { int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, yystos[+yyssp[yyi + 1 - yynrhs]], &yyvsp[(yyi + 1) - (yynrhs)] , context); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyssp, yyvsp, Rule, context); \ } while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ # define YYDPRINTF(Args) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif #if YYERROR_VERBOSE # ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) # else /* Return the length of YYSTR. */ static YYPTRDIFF_T yystrlen (const char *yystr) { YYPTRDIFF_T yylen; for (yylen = 0; yystr && yystr[yylen]; yylen++) continue; return yylen; } # endif # endif # ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ static char * yystpcpy (char *yydest, const char *yysrc) { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif # endif # ifndef yytnamerr /* Copy to YYRES the contents of YYSTR after stripping away unnecessary quotes and backslashes, so that it's suitable for yyerror. The heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ static YYPTRDIFF_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { YYPTRDIFF_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { case '\'': case ',': goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; else goto append; append: default: if (yyres) yyres[yyn] = *yyp; yyn++; break; case '"': if (yyres) yyres[yyn] = '\0'; return yyn; } do_not_strip_quotes: ; } if (yyres) return (YYPTRDIFF_T)(yystpcpy (yyres, yystr) - yyres); else return yystrlen (yystr); } # endif /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message about the unexpected token YYTOKEN for the state stack whose top is YYSSP. Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return 2 if the required number of bytes is too large to store. */ static int yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, yy_state_t *yyssp, int yytoken) { enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; /* Internationalized format string. */ const char *yyformat = YY_NULLPTR; /* Arguments of yyformat: reported tokens (one for the "unexpected", one per "expected"). */ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; /* Actual size of YYARG. */ int yycount = 0; /* Cumulated lengths of YYARG. */ YYPTRDIFF_T yysize = 0; /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected tokens because there are none. - The only way there can be no lookahead present (in yychar) is if this state is a consistent state with a default action. Thus, detecting the absence of a lookahead is sufficient to determine that there is no unexpected or expected token to report. In that case, just report a simple "syntax error". - Don't assume there isn't a lookahead just because this state is a consistent state with a default action. There might have been a previous inconsistent state, consistent state with a non-default action, or user semantic action that manipulated yychar. - Of course, the expected token list depends on states to have correct lookahead information, and it depends on the parser not to perform extra reductions after fetching a lookahead from the scanner and before detecting a syntax error. Thus, state merging (from LALR or IELR) and default reductions corrupt the expected token list. However, the list is correct for canonical LR with one exception: it will still contain any token that will not be accepted due to an error action in a later state. */ if (yytoken != YYEMPTY) { int yyn = yypact[+*yyssp]; YYPTRDIFF_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); yysize = yysize0; yyarg[yycount++] = yytname[yytoken]; if (!yypact_value_is_default (yyn)) { /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for this state because they are default actions. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yyx; for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR && !yytable_value_is_error (yytable[yyx + yyn])) { if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) { yycount = 1; yysize = yysize0; break; } yyarg[yycount++] = yytname[yyx]; { YYPTRDIFF_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return 2; } } } } switch (yycount) { # define YYCASE_(N, S) \ case N: \ yyformat = S; \ break default: /* Avoid compiler warnings. */ YYCASE_(0, YY_("syntax error")); YYCASE_(1, YY_("syntax error, unexpected %s")); YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); # undef YYCASE_ } { /* Don't count the "%s"s in the final size, but reserve room for the terminator. */ YYPTRDIFF_T yysize1 = yysize + (yystrlen (yyformat) - 2 * yycount) + 1; if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return 2; } if (*yymsg_alloc < yysize) { *yymsg_alloc = 2 * yysize; if (! (yysize <= *yymsg_alloc && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; return 1; } /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ { char *yyp = *yymsg; int yyi = 0; while ((*yyp = *yyformat) != '\0') if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) { yyp += yytnamerr (yyp, yyarg[yyi++]); yyformat += 2; } else { ++yyp; ++yyformat; } } return 0; } #endif /* YYERROR_VERBOSE */ /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, pj_wkt2_parse_context *context) { YYUSE (yyvaluep); YYUSE (context); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*----------. | yyparse. | `----------*/ int yyparse (pj_wkt2_parse_context *context) { /* The lookahead symbol. */ int yychar; /* The semantic value of the lookahead symbol. */ /* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ YY_INITIAL_VALUE (static YYSTYPE yyval_default;) YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Number of syntax errors so far. */ /* int yynerrs; */ yy_state_fast_t yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* The stacks and their tools: 'yyss': related to states. 'yyvs': related to semantic values. Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ yy_state_t yyssa[YYINITDEPTH]; yy_state_t *yyss; yy_state_t *yyssp; /* The semantic value stack. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs; YYSTYPE *yyvsp; YYPTRDIFF_T yystacksize; int yyn; int yyresult; /* Lookahead token as an internal (translated) token number. */ int yytoken = 0; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; #if YYERROR_VERBOSE /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; #endif #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; yyssp = yyss = yyssa; yyvsp = yyvs = yyvsa; yystacksize = YYINITDEPTH; YYDPRINTF ((stderr, "Starting parse\n")); yystate = 0; yyerrstatus = 0; /* yynerrs = 0; */ yychar = YYEMPTY; /* Cause a token to be read. */ goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; /*--------------------------------------------------------------------. | yysetstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: YYDPRINTF ((stderr, "Entering state %d\n", yystate)); YY_ASSERT (0 <= yystate && yystate < YYNSTATES); YY_IGNORE_USELESS_CAST_BEGIN *yyssp = YY_CAST (yy_state_t, yystate); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE goto yyexhaustedlab; #else { /* Get the current used size of the three stacks, in elements. */ YYPTRDIFF_T yysize = (YYPTRDIFF_T)(yyssp - yyss + 1); # if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * YYSIZEOF (*yyssp), &yyvs1, yysize * YYSIZEOF (*yyvsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; } # else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yy_state_t *yyss1 = yyss; union yyalloc *yyptr = YY_CAST (union yyalloc *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; YY_IGNORE_USELESS_CAST_BEGIN YYDPRINTF ((stderr, "Stack size increased to %ld\n", YY_CAST (long, yystacksize))); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token: ")); yychar = yylex (&yylval, context); } if (yychar <= YYEOF) { yychar = yytoken = YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yytable_value_is_error (yyn)) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Discard the shifted token. */ yychar = YYEMPTY; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; YY_REDUCE_PRINT (yyn); switch (yyn) { default: break; } /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, but that translation would be missed if the semantic action invokes YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an incorrect destructor might then be invoked immediately. In the case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ { const int yylhs = yyr1[yyn] - YYNTOKENS; const int yyi = yypgoto[yylhs] + *yyssp; yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp ? yytable[yyi] : yydefgoto[yylhs]); } goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { /* ++yynerrs; */ #if ! YYERROR_VERBOSE yyerror (context, YY_("syntax error")); #else # define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ yyssp, yytoken) { char const *yymsgp = YY_("syntax error"); int yysyntax_error_status; yysyntax_error_status = YYSYNTAX_ERROR; if (yysyntax_error_status == 0) yymsgp = yymsg; else if (yysyntax_error_status == 1) { if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = YY_CAST (char *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); if (!yymsg) { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; yysyntax_error_status = 2; } else { yysyntax_error_status = YYSYNTAX_ERROR; yymsgp = yymsg; } } yyerror (context, yymsgp); if (yysyntax_error_status == 2) goto yyexhaustedlab; } # undef YYSYNTAX_ERROR #endif } if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval, context); yychar = YYEMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ #if 0 yyerrorlab: /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (0) YYERROR; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ #endif yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { yyn += YYTERROR; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yydestruct ("Error: popping", yystos[yystate], yyvsp, context); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; #if !defined yyoverflow || YYERROR_VERBOSE /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ yyexhaustedlab: yyerror (context, YY_("memory exhausted")); yyresult = 2; /* Fall through. */ #endif /*-----------------------------------------------------. | yyreturn -- parsing is finished, return the result. | `-----------------------------------------------------*/ yyreturn: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = YYTRANSLATE (yychar); yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval, context); } /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", yystos[+*yyssp], yyvsp, context); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif #if YYERROR_VERBOSE if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif return yyresult; } proj-9.8.1/src/proj_internal.h000664 001750 001750 00000116320 15166171715 016264 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Internal plumbing for the PROJ.4 library. * * Author: Thomas Knudsen, * ****************************************************************************** * Copyright (c) 2016, 2017, Thomas Knudsen / SDFE * * 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 COORD SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef PROJ_INTERNAL_H #define PROJ_INTERNAL_H #ifndef __cplusplus #error "proj_internal.h can only be included from a C++ file" #endif #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_NO_DEPRECATE #endif #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #endif #endif /* enable predefined math constants M_* for MS Visual Studio */ #if defined(_MSC_VER) || defined(_WIN32) #ifndef _USE_MATH_DEFINES #define _USE_MATH_DEFINES #endif #endif // Use "PROJ_FALLTHROUGH;" to annotate deliberate fall-through in switches, // use it analogously to "break;". The trailing semi-colon is required. #if !defined(PROJ_FALLTHROUGH) && defined(__has_cpp_attribute) #if __cplusplus >= 201703L && __has_cpp_attribute(fallthrough) #define PROJ_FALLTHROUGH [[fallthrough]] #elif __cplusplus >= 201103L && __has_cpp_attribute(gnu::fallthrough) #define PROJ_FALLTHROUGH [[gnu::fallthrough]] #elif __cplusplus >= 201103L && __has_cpp_attribute(clang::fallthrough) #define PROJ_FALLTHROUGH [[clang::fallthrough]] #endif #endif #ifndef PROJ_FALLTHROUGH #define PROJ_FALLTHROUGH ((void)0) #endif /* standard inclusions */ #include #include #include #include #include #include #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include #include #include #include "proj.h" #ifdef PROJ_RENAME_SYMBOLS #include "proj_symbol_rename.h" #endif #define STATIC_ASSERT(COND) ((void)sizeof(char[(COND) ? 1 : -1])) #ifndef PJ_TODEG #define PJ_TODEG(rad) ((rad)*180.0 / M_PI) #endif #ifndef PJ_TORAD #define PJ_TORAD(deg) ((deg)*M_PI / 180.0) #endif /* Maximum latitudinal overshoot accepted */ #define PJ_EPS_LAT 1e-12 #define C_NAMESPACE extern "C" #define C_NAMESPACE_VAR extern "C" #ifndef NULL #define NULL 0 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef MAX #define MIN(a, b) ((a < b) ? a : b) #define MAX(a, b) ((a > b) ? a : b) #endif #ifndef ABS #define ABS(x) ((x < 0) ? (-1 * (x)) : x) #endif /* maximum path/filename */ #ifndef MAX_PATH_FILENAME #define MAX_PATH_FILENAME 1024 #endif /* If we still haven't got M_PI*, we rely on our own defines. * For example, this is necessary when compiling with gcc and * the -ansi flag. */ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_1_PI #define M_1_PI 0.318309886183790671538 #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif #ifndef M_PI_4 #define M_PI_4 0.78539816339744830962 #endif #ifndef M_2_PI #define M_2_PI 0.63661977236758134308 #endif /* M_SQRT2 might be missing */ #ifndef M_SQRT2 #define M_SQRT2 1.41421356237309504880 #endif /* some more useful math constants and aliases */ #define M_FORTPI M_PI_4 /* pi/4 */ #define M_HALFPI M_PI_2 /* pi/2 */ #define M_PI_HALFPI 4.71238898038468985769 /* 1.5*pi */ #ifndef M_TWOPI #define M_TWOPI 6.28318530717958647693 /* 2*pi */ #endif #define M_TWO_D_PI M_2_PI /* 2/pi */ #define M_TWOPI_HALFPI 7.85398163397448309616 /* 2.5*pi */ /* maximum tag id length for +init and default files */ #ifndef ID_TAG_MAX #define ID_TAG_MAX 50 #endif /* Use WIN32 as a standard windows 32 bit declaration */ #if defined(_WIN32) && !defined(WIN32) #define WIN32 #endif #if defined(_WINDOWS) && !defined(WIN32) #define WIN32 #endif /* directory delimiter for DOS support */ #ifdef WIN32 #define DIR_CHAR '\\' #else #define DIR_CHAR '/' #endif enum pj_io_units { PJ_IO_UNITS_WHATEVER = 0, /* Doesn't matter (or depends on pipeline neighbours) */ PJ_IO_UNITS_CLASSIC = 1, /* Scaled meters (right), projected system */ PJ_IO_UNITS_PROJECTED = 2, /* Meters, projected system */ PJ_IO_UNITS_CARTESIAN = 3, /* Meters, 3D cartesian system */ PJ_IO_UNITS_RADIANS = 4, /* Radians */ PJ_IO_UNITS_DEGREES = 5, /* Degrees */ }; enum pj_io_units pj_left(PJ *P); enum pj_io_units pj_right(PJ *P); PJ_COORD PROJ_DLL proj_coord_error(void); void proj_context_errno_set(PJ_CONTEXT *ctx, int err); void PROJ_DLL proj_context_set(PJ *P, PJ_CONTEXT *ctx); void proj_context_inherit(PJ *parent, PJ *child); struct projCppContext; /* not sure why we need to export it, but mingw needs it */ void PROJ_DLL proj_context_delete_cpp_context(struct projCppContext *cppContext); bool pj_fwd4d(PJ_COORD &coo, PJ *P); bool pj_inv4d(PJ_COORD &coo, PJ *P); PJ_COORD PROJ_DLL pj_approx_2D_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coo); PJ_COORD PROJ_DLL pj_approx_3D_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coo); /* Provision for gettext translatable strings */ #define _(str) (str) void PROJ_DLL proj_log_error(const PJ *P, const char *fmt, ...); void proj_log_debug(PJ *P, const char *fmt, ...); void proj_log_trace(PJ *P, const char *fmt, ...); void proj_context_log_debug(PJ_CONTEXT *ctx, const char *fmt, ...); int pj_ellipsoid(PJ *); void pj_inherit_ellipsoid_def(const PJ *src, PJ *dst); int pj_calc_ellipsoid_params(PJ *P, double a, double es); /* Geographical to geocentric latitude - another of the "simple, but useful" */ PJ_COORD pj_geocentric_latitude(const PJ *P, PJ_DIRECTION direction, PJ_COORD coord); char PROJ_DLL *pj_chomp(char *c); char PROJ_DLL *pj_shrink(char *c); size_t pj_trim_argc(char *args); char **pj_trim_argv(size_t argc, char *args); char *pj_make_args(size_t argc, char **argv); typedef struct { double r, i; } COMPLEX; /* Forward declarations and typedefs for stuff needed inside the PJ object */ struct PJconsts; union PJ_COORD; struct geod_geodesic; struct ARG_list; struct PJ_REGION_S; typedef struct PJ_REGION_S PJ_Region; typedef struct ARG_list paralist; /* parameter list */ #ifndef PROJ_H typedef struct PJconsts PJ; /* the PJ object herself */ typedef union PJ_COORD PJ_COORD; #endif struct PJ_REGION_S { double ll_long; /* lower left corner coordinates (radians) */ double ll_lat; double ur_long; /* upper right corner coordinates (radians) */ double ur_lat; }; struct PJ_AREA { bool bbox_set = false; double west_lon_degree = 0; double south_lat_degree = 0; double east_lon_degree = 0; double north_lat_degree = 0; std::string name{}; }; /***************************************************************************** Some function types that are especially useful when working with PJs ****************************************************************************** PJ_CONSTRUCTOR: A function taking a pointer-to-PJ as arg, and returning a pointer-to-PJ. Historically called twice: First with a 0 argument, to allocate memory, second with the first return value as argument, for actual setup. PJ_DESTRUCTOR: A function taking a pointer-to-PJ and an integer as args, then first handling the deallocation of the PJ, afterwards handing the integer over to the error reporting subsystem, and finally returning a null pointer in support of the "return free (P)" (aka "get the hell out of here") idiom. PJ_OPERATOR: A function taking a reference to a PJ_COORD and a pointer-to-PJ as args, applying the PJ to the PJ_COORD, and modifying in-place the passed PJ_COORD. *****************************************************************************/ typedef PJ *(*PJ_CONSTRUCTOR)(PJ *); typedef PJ *(*PJ_DESTRUCTOR)(PJ *, int); typedef void (*PJ_OPERATOR)(PJ_COORD &, PJ *); /****************************************************************************/ /* datum_type values */ #define PJD_UNKNOWN 0 #define PJD_3PARAM 1 #define PJD_7PARAM 2 #define PJD_GRIDSHIFT 3 #define PJD_WGS84 4 /* WGS84 (or anything considered equivalent) */ struct PJCoordOperation { public: int idxInOriginalList; // [min|max][x|y]Src define the bounding box of the area of validity of // the transformation, expressed in the source CRS. Except if the source // CRS is a geocentric one, in which case the bounding box is defined in // a geographic lon,lat CRS (pjSrcGeocentricToLonLat will have to be used // on input coordinates in the forward direction) double minxSrc = 0.0; double minySrc = 0.0; double maxxSrc = 0.0; double maxySrc = 0.0; // [min|max][x|y]Dst define the bounding box of the area of validity of // the transformation, expressed in the target CRS. Except if the target // CRS is a geocentric one, in which case the bounding box is defined in // a geographic lon,lat CRS (pjDstGeocentricToLonLat will have to be used // on input coordinates in the inverse direction) double minxDst = 0.0; double minyDst = 0.0; double maxxDst = 0.0; double maxyDst = 0.0; PJ *pj = nullptr; std::string name{}; double accuracy = -1.0; double pseudoArea = 0.0; std::string areaName{}; bool isOffshore = false; bool isUnknownAreaName = false; bool isPriorityOp = false; bool srcIsLonLatDegree = false; bool srcIsLatLonDegree = false; bool dstIsLonLatDegree = false; bool dstIsLatLonDegree = false; // pjSrcGeocentricToLonLat is defined if the source CRS of pj is geocentric // and in that case it transforms from those geocentric coordinates to // geographic ones in lon, lat order PJ *pjSrcGeocentricToLonLat = nullptr; // pjDstGeocentricToLonLat is defined if the target CRS of pj is geocentric // and in that case it transforms from those geocentric coordinates to // geographic ones in lon, lat order PJ *pjDstGeocentricToLonLat = nullptr; PJCoordOperation(int idxInOriginalListIn, double minxSrcIn, double minySrcIn, double maxxSrcIn, double maxySrcIn, double minxDstIn, double minyDstIn, double maxxDstIn, double maxyDstIn, PJ *pjIn, const std::string &nameIn, double accuracyIn, double pseudoAreaIn, const char *areaName, const PJ *pjSrcGeocentricToLonLatIn, const PJ *pjDstGeocentricToLonLatIn); PJCoordOperation(const PJCoordOperation &) = delete; PJCoordOperation(PJ_CONTEXT *ctx, const PJCoordOperation &other) : idxInOriginalList(other.idxInOriginalList), minxSrc(other.minxSrc), minySrc(other.minySrc), maxxSrc(other.maxxSrc), maxySrc(other.maxySrc), minxDst(other.minxDst), minyDst(other.minyDst), maxxDst(other.maxxDst), maxyDst(other.maxyDst), pj(proj_clone(ctx, other.pj)), name(std::move(other.name)), accuracy(other.accuracy), pseudoArea(other.pseudoArea), areaName(other.areaName), isOffshore(other.isOffshore), isUnknownAreaName(other.isUnknownAreaName), isPriorityOp(other.isPriorityOp), srcIsLonLatDegree(other.srcIsLonLatDegree), srcIsLatLonDegree(other.srcIsLatLonDegree), dstIsLonLatDegree(other.dstIsLonLatDegree), dstIsLatLonDegree(other.dstIsLatLonDegree), pjSrcGeocentricToLonLat( other.pjSrcGeocentricToLonLat ? proj_clone(ctx, other.pjSrcGeocentricToLonLat) : nullptr), pjDstGeocentricToLonLat( other.pjDstGeocentricToLonLat ? proj_clone(ctx, other.pjDstGeocentricToLonLat) : nullptr) {} PJCoordOperation(PJCoordOperation &&other) : idxInOriginalList(other.idxInOriginalList), minxSrc(other.minxSrc), minySrc(other.minySrc), maxxSrc(other.maxxSrc), maxySrc(other.maxySrc), minxDst(other.minxDst), minyDst(other.minyDst), maxxDst(other.maxxDst), maxyDst(other.maxyDst), name(std::move(other.name)), accuracy(other.accuracy), pseudoArea(other.pseudoArea), areaName(std::move(other.areaName)), isOffshore(other.isOffshore), isUnknownAreaName(other.isUnknownAreaName), isPriorityOp(other.isPriorityOp), srcIsLonLatDegree(other.srcIsLonLatDegree), srcIsLatLonDegree(other.srcIsLatLonDegree), dstIsLonLatDegree(other.dstIsLonLatDegree), dstIsLatLonDegree(other.dstIsLatLonDegree) { pj = other.pj; other.pj = nullptr; pjSrcGeocentricToLonLat = other.pjSrcGeocentricToLonLat; other.pjSrcGeocentricToLonLat = nullptr; pjDstGeocentricToLonLat = other.pjDstGeocentricToLonLat; other.pjDstGeocentricToLonLat = nullptr; } PJCoordOperation &operator=(const PJCoordOperation &) = delete; bool operator==(const PJCoordOperation &other) const { return idxInOriginalList == other.idxInOriginalList && minxSrc == other.minxSrc && minySrc == other.minySrc && maxxSrc == other.maxxSrc && maxySrc == other.maxySrc && minxDst == other.minxDst && minyDst == other.minyDst && maxxDst == other.maxxDst && maxyDst == other.maxyDst && name == other.name && proj_is_equivalent_to(pj, other.pj, PJ_COMP_STRICT) && accuracy == other.accuracy && areaName == other.areaName; } bool operator!=(const PJCoordOperation &other) const { return !(operator==(other)); } ~PJCoordOperation(); bool isInstantiable() const; private: static constexpr int INSTANTIABLE_STATUS_UNKNOWN = -1; // must be different from 0(=false) and 1(=true) mutable int isInstantiableCached = INSTANTIABLE_STATUS_UNKNOWN; }; enum class TMercAlgo { AUTO, // Poder/Engsager if far from central meridian, otherwise // Evenden/Snyder EVENDEN_SNYDER, PODER_ENGSAGER, }; enum class AuxLat { GEOGRAPHIC, // 0 PARAMETRIC, GEOCENTRIC, RECTIFYING, CONFORMAL, AUTHALIC, NUMBER, // The number of auxiliary latitudes = 6 // The order of the expansion in n (ACCIDENTALLY equal to AUXNUMBER) ORDER = 6, }; /* base projection data structure */ struct PJconsts { /************************************************************************************* G E N E R A L P A R A M E T E R S T R U C T ************************************************************************************** TODO: Need some description here - especially about the thread context... This is the struct behind the PJ typedef **************************************************************************************/ PJ_CONTEXT *ctx = nullptr; const char *short_name = nullptr; /* From pj_list.h */ const char *descr = nullptr; /* From pj_list.h or individual PJ_*.c file */ paralist *params = nullptr; /* Parameter list */ char *def_full = nullptr; /* Full textual definition (usually 0 - set by proj_pj_info) */ PJconsts *parent = nullptr; /* Parent PJ of pipeline steps - nullptr if not a pipeline step */ /* For debugging / logging purposes */ char *def_size = nullptr; /* Shape and size parameters extracted from params */ char *def_shape = nullptr; char *def_spherification = nullptr; char *def_ellps = nullptr; struct geod_geodesic *geod = nullptr; /* For geodesic computations */ void *opaque = nullptr; /* Projection specific parameters, Defined in PJ_*.c */ int inverted = 0; /* Tell high level API functions to swap inv/fwd */ /************************************************************************************* F U N C T I O N P O I N T E R S ************************************************************************************** For projection xxx, these are pointers to functions in the corresponding PJ_xxx.c file. pj_init() delegates the setup of these to pj_projection_specific_setup_xxx(), a name which is currently hidden behind the magic curtain of the PROJECTION macro. **************************************************************************************/ PJ_XY (*fwd)(PJ_LP, PJ *) = nullptr; PJ_LP (*inv)(PJ_XY, PJ *) = nullptr; PJ_XYZ (*fwd3d)(PJ_LPZ, PJ *) = nullptr; PJ_LPZ (*inv3d)(PJ_XYZ, PJ *) = nullptr; PJ_OPERATOR fwd4d = nullptr; PJ_OPERATOR inv4d = nullptr; PJ_DESTRUCTOR destructor = nullptr; void (*reassign_context)(PJ *, PJ_CONTEXT *) = nullptr; /************************************************************************************* E L L I P S O I D P A R A M E T E R S ************************************************************************************** Despite YAGNI, we add a large number of ellipsoidal shape parameters, which are not yet set up in pj_init. They are, however, inexpensive to compute, compared to the overall time taken for setting up the complex PJ object (cf. e.g. https://en.wikipedia.org/wiki/Angular_eccentricity). But during single point projections it will often be a useful thing to have these readily available without having to recompute at every pj_fwd / pj_inv call. With this wide selection, we should be ready for quite a number of geodetic algorithms, without having to incur further ABI breakage. **************************************************************************************/ /* The linear parameters */ double a = 0.0; /* semimajor axis (radius if eccentricity==0) */ double b = 0.0; /* semiminor axis */ double ra = 0.0; /* 1/a */ double rb = 0.0; /* 1/b */ /* The eccentricities */ double alpha = 0.0; /* angular eccentricity */ double e = 0.0; /* first eccentricity */ double es = 0.0; /* first eccentricity squared */ double e2 = 0.0; /* second eccentricity */ double e2s = 0.0; /* second eccentricity squared */ double e3 = 0.0; /* third eccentricity */ double e3s = 0.0; /* third eccentricity squared */ double one_es = 0.0; /* 1 - e^2 */ double rone_es = 0.0; /* 1/one_es */ /* The flattenings */ double f = 0.0; /* first flattening */ double f2 = 0.0; /* second flattening */ double n = 0.0; /* third flattening */ double rf = 0.0; /* 1/f */ double rf2 = 0.0; /* 1/f2 */ double rn = 0.0; /* 1/n */ /* This one's for GRS80 */ double J = 0.0; /* "Dynamic form factor" */ double es_orig = 0.0; /* es and a before any +proj related adjustment */ double a_orig = 0.0; /************************************************************************************* C O O R D I N A T E H A N D L I N G **************************************************************************************/ int over = 0; /* Over-range flag */ int geoc = 0; /* Geocentric latitude flag */ int is_latlong = 0; /* proj=latlong ... not really a projection at all */ int is_geocent = 0; /* proj=geocent ... not really a projection at all */ int need_ellps = 0; /* 0 for operations that are purely cartesian */ int skip_fwd_prepare = 0; int skip_fwd_finalize = 0; int skip_inv_prepare = 0; int skip_inv_finalize = 0; enum pj_io_units left = PJ_IO_UNITS_WHATEVER; /* Flags for input/output coordinate types */ enum pj_io_units right = PJ_IO_UNITS_WHATEVER; /* These PJs are used for implementing cs2cs style coordinate handling in * the 4D API */ PJ *axisswap = nullptr; PJ *cart = nullptr; PJ *cart_wgs84 = nullptr; PJ *helmert = nullptr; PJ *hgridshift = nullptr; PJ *vgridshift = nullptr; /************************************************************************************* C A R T O G R A P H I C O F F S E T S **************************************************************************************/ double lam0 = 0.0; /* central meridian */ double phi0 = 0.0; /* central parallel */ double x0 = 0.0; /* false easting */ double y0 = 0.0; /* false northing */ double z0 = 0.0; /* height origin */ double t0 = 0.0; /* time origin */ /************************************************************************************* S C A L I N G **************************************************************************************/ double k0 = 0.0; /* General scaling factor - e.g. the 0.9996 of UTM */ double to_meter = 0.0, fr_meter = 0.0; /* Plane coordinate scaling. Internal unit [m] */ double vto_meter = 0.0, vfr_meter = 0.0; /* Vertical scaling. Internal unit [m] */ /************************************************************************************* D A T U M S A N D H E I G H T S Y S T E M S ************************************************************************************** It may be possible, and meaningful, to move the list parts of this up to the PJ_CONTEXT level. **************************************************************************************/ int datum_type = PJD_UNKNOWN; /* PJD_UNKNOWN/3PARAM/7PARAM/GRIDSHIFT/WGS84 */ double datum_params[7] = {0, 0, 0, 0, 0, 0, 0}; /* Parameters for 3PARAM and 7PARAM */ int has_geoid_vgrids = 0; /* used by legacy transform.cpp */ void *hgrids_legacy = nullptr; /* used by legacy transform.cpp. Is a pointer to a ListOfHGrids* */ void *vgrids_legacy = nullptr; /* used by legacy transform.cpp. Is a pointer to a ListOfVGrids* */ double from_greenwich = 0.0; /* prime meridian offset (in radians) */ double long_wrap_center = 0.0; /* 0.0 for -180 to 180, actually in radians*/ int is_long_wrap_set = 0; char axis[4] = {0, 0, 0, 0}; /* Axis order, pj_transform/pj_adjust_axis */ /************************************************************************************* ISO-19111 interface **************************************************************************************/ NS_PROJ::util::BaseObjectPtr iso_obj{}; bool iso_obj_is_coordinate_operation = false; double coordinateEpoch = 0; bool hasCoordinateEpoch = false; // cached results mutable std::string lastWKT{}; mutable std::string lastPROJString{}; mutable std::string lastJSONString{}; mutable bool gridsNeededAsked = false; mutable std::vector gridsNeeded{}; // cache pj_get_type() result to help for repeated calls to proj_factors() mutable PJ_TYPE type = PJ_TYPE_UNKNOWN; /************************************************************************************* proj_create_crs_to_crs() alternative coordinate operations **************************************************************************************/ std::vector alternativeCoordinateOperations{}; int iCurCoordOp = -1; bool errorIfBestTransformationNotAvailable = false; bool warnIfBestTransformationNotAvailable = true; /* to remove in PROJ 10? */ bool skipNonInstantiable = true; // Used internally by proj_factors() PJ *cached_op_for_proj_factors = nullptr; /************************************************************************************* E N D O F G E N E R A L P A R A M E T E R S T R U C T **************************************************************************************/ PJconsts(); PJconsts(const PJconsts &) = delete; PJconsts &operator=(const PJconsts &) = delete; void copyStateFrom(const PJconsts &); }; /* Parameter list (a copy of the +proj=... etc. parameters) */ struct ARG_list { paralist *next; char used; #if (defined(__GNUC__) && __GNUC__ >= 8) || \ (defined(__clang__) && __clang_major__ >= 9) char param[]; /* variable-length member */ /* Safer to use [] for gcc 8. See https://github.com/OSGeo/proj.4/pull/1087 */ /* and https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86914 */ #else char param[1]; /* variable-length member */ #endif }; typedef union { double f; int i; char *s; } PROJVALUE; struct PJ_DATUMS { const char *id; /* datum keyword */ const char *defn; /* ie. "to_wgs84=..." */ const char *ellipse_id; /* ie from ellipse table */ const char *comments; /* EPSG code, etc */ }; struct DERIVS { double x_l, x_p; /* derivatives of x for lambda-phi */ double y_l, y_p; /* derivatives of y for lambda-phi */ }; struct FACTORS { struct DERIVS der; double h, k; /* meridional, parallel scales */ double omega, thetap; /* angular distortion, theta prime */ double conv; /* convergence */ double s; /* areal scale factor */ double a, b; /* max-min scale error */ int code; /* always 0 */ }; // Legacy struct projFileAPI_t; struct projCppContext; struct projNetworkCallbacksAndData { bool enabled = false; proj_network_open_cbk_type open = nullptr; proj_network_close_cbk_type close = nullptr; proj_network_get_header_value_cbk_type get_header_value = nullptr; proj_network_read_range_type read_range = nullptr; void *user_data = nullptr; }; struct projGridChunkCache { bool enabled = true; std::string filename{}; long long max_size = 300 * 1024 * 1024; int ttl = 86400; // 1 day }; struct projFileApiCallbackAndData { PROJ_FILE_HANDLE *(*open_cbk)(PJ_CONTEXT *ctx, const char *filename, PROJ_OPEN_ACCESS access, void *user_data) = nullptr; size_t (*read_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, void *buffer, size_t size, void *user_data) = nullptr; size_t (*write_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, const void *buffer, size_t size, void *user_data) = nullptr; int (*seek_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, long long offset, int whence, void *user_data) = nullptr; unsigned long long (*tell_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, void *user_data) = nullptr; void (*close_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, void *user_data) = nullptr; int (*exists_cbk)(PJ_CONTEXT *ctx, const char *filename, void *user_data) = nullptr; int (*mkdir_cbk)(PJ_CONTEXT *ctx, const char *filename, void *user_data) = nullptr; int (*unlink_cbk)(PJ_CONTEXT *ctx, const char *filename, void *user_data) = nullptr; int (*rename_cbk)(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath, void *user_data) = nullptr; void *user_data = nullptr; }; /* proj thread context */ struct PROJ_GCC_DLL pj_ctx { std::string lastFullErrorMessage{}; // used by proj_context_errno_string int last_errno = 0; int debug_level = PJ_LOG_ERROR; bool errorIfBestTransformationNotAvailableDefault = false; bool warnIfBestTransformationNotAvailableDefault = true; void (*logger)(void *, int, const char *) = nullptr; void *logger_app_data = nullptr; struct projCppContext *cpp_context = nullptr; /* internal context for C++ code */ int use_proj4_init_rules = -1; /* -1 = unknown, 0 = no, 1 = yes */ bool forceOver = false; int epsg_file_exists = -1; /* -1 = unknown, 0 = no, 1 = yes */ std::string env_var_proj_data{}; // content of PROJ_DATA (or legacy PROJ_LIB) // environment variable. Use // Filemanager::getProjDataEnvVar() to access std::vector search_paths{}; const char **c_compat_paths = nullptr; // same, but for projinfo usage const char *(*file_finder)(PJ_CONTEXT *, const char *, void *user_data) = nullptr; void *file_finder_user_data = nullptr; // Cache result of pj_find_file() std::map lookupedFiles{}; bool defer_grid_opening = false; // set transiently by pj_obj_create() projFileApiCallbackAndData fileApi{}; std::string custom_sqlite3_vfs_name{}; std::string user_writable_directory{}; // BEGIN ini file settings bool iniFileLoaded = false; std::string endpoint{}; projNetworkCallbacksAndData networking{}; std::string ca_bundle_path{}; bool native_ca = false; projGridChunkCache gridChunkCache{}; TMercAlgo defaultTmercAlgo = TMercAlgo::PODER_ENGSAGER; // can be overridden by content of proj.ini // END ini file settings int projStringParserCreateFromPROJStringRecursionCounter = 0; // to avoid potential infinite recursion in // PROJStringParser::createFromPROJString() int pipelineInitRecursiongCounter = 0; // to avoid potential infinite recursion in pipeline.cpp pj_ctx() = default; pj_ctx(const pj_ctx &); ~pj_ctx(); pj_ctx &operator=(const pj_ctx &) = delete; projCppContext PROJ_FOR_TEST *get_cpp_context(); void set_search_paths(const std::vector &search_paths_in); void set_ca_bundle_path(const std::string &ca_bundle_path_in); static pj_ctx createDefault(); }; #ifndef DO_NOT_DEFINE_PROJ_HEAD #define PROJ_HEAD(name, desc) static const char des_##name[] = desc #define OPERATION(name, NEED_ELLPS) \ \ pj_projection_specific_setup_##name(PJ *P); \ C_NAMESPACE PJ *pj_##name(PJ *P); \ \ C_NAMESPACE_VAR const char *const pj_s_##name = des_##name; \ \ C_NAMESPACE PJ *pj_##name(PJ *P) { \ if (P) \ return pj_projection_specific_setup_##name(P); \ P = pj_new(); \ if (nullptr == P) \ return nullptr; \ P->short_name = #name; \ P->descr = des_##name; \ P->need_ellps = NEED_ELLPS; \ P->left = PJ_IO_UNITS_RADIANS; \ P->right = PJ_IO_UNITS_CLASSIC; \ return P; \ } \ \ PJ *pj_projection_specific_setup_##name(PJ *P) /* In ISO19000 lingo, an operation is either a conversion or a transformation */ #define PJ_CONVERSION(name, need_ellps) OPERATION(name, need_ellps) #define PJ_TRANSFORMATION(name, need_ellps) OPERATION(name, need_ellps) /* In PROJ.4 a projection is a conversion taking angular input and giving scaled * linear output */ #define PJ_PROJECTION(name) PJ_CONVERSION(name, 1) #endif /* DO_NOT_DEFINE_PROJ_HEAD */ /* procedure prototypes */ double PROJ_DLL dmstor(const char *, char **); double dmstor_ctx(PJ_CONTEXT *ctx, const char *, char **); void PROJ_DLL set_rtodms(int, int); char PROJ_DLL *rtodms(char *, size_t, double, int, int); double PROJ_DLL adjlon(double); double aacos(PJ_CONTEXT *, double); double aasin(PJ_CONTEXT *, double); double asqrt(double); double aatan2(double, double); PROJVALUE PROJ_DLL pj_param(PJ_CONTEXT *ctx, paralist *, const char *); paralist PROJ_DLL *pj_param_exists(paralist *list, const char *parameter); paralist PROJ_DLL *pj_mkparam(const char *); paralist *pj_mkparam_ws(const char *str, const char **next_str); int PROJ_DLL pj_ell_set(PJ_CONTEXT *ctx, paralist *, double *, double *); int pj_datum_set(PJ_CONTEXT *, paralist *, PJ *); int pj_angular_units_set(paralist *, PJ *); paralist *pj_clone_paralist(const paralist *); paralist *pj_search_initcache(const char *filekey); void pj_insert_initcache(const char *filekey, const paralist *list); paralist *pj_expand_init(PJ_CONTEXT *ctx, paralist *init); void *free_params(PJ_CONTEXT *ctx, paralist *start, int errlev); double *pj_enfn(double); double pj_mlfn(double, double, double, const double *); double pj_inv_mlfn(double, const double *); double pj_tsfn(double, double, double); double pj_msfn(double, double, double); double PROJ_DLL pj_phi2(PJ_CONTEXT *, const double, const double); double pj_sinhpsi2tanphi(PJ_CONTEXT *, const double, const double); // From latitudes.cpp double pj_conformal_lat(double phi, const PJ *P); double pj_conformal_lat_inverse(double chi, const PJ *P); double *pj_authalic_lat_compute_coeffs(double n); double pj_authalic_lat_q(double sinphi, const PJ *P); double pj_authalic_lat(double phi, double sinphi, double cosphi, const double *APA, const PJ *P, double qp); double pj_authalic_lat_inverse(double beta, const double *APA, const PJ *P, double qp); void pj_auxlat_coeffs(double n, AuxLat auxin, AuxLat auxout, double F[]); double pj_polyval(double x, const double p[], int N); double pj_clenshaw(double szeta, double czeta, const double F[], int K); double pj_auxlat_convert(double phi, double sphi, double cphi, const double F[], int K = int(AuxLat::ORDER)); double pj_auxlat_convert(double phi, const double F[], int K = int(AuxLat::ORDER)); void pj_auxlat_convert(double sphi, double cphi, double &saux, double &caux, const double F[], int K = int(AuxLat::ORDER)); double pj_rectifying_radius(double n); COMPLEX pj_zpoly1(COMPLEX, const COMPLEX *, int); COMPLEX pj_zpolyd1(COMPLEX, const COMPLEX *, int, COMPLEX *); int pj_deriv(PJ_LP, double, const PJ *, struct DERIVS *); void *proj_mdist_ini(double); double proj_mdist(double, double, double, const void *); double proj_inv_mdist(PJ_CONTEXT *ctx, double, const void *); void *pj_gauss_ini(double, double, double *, double *); PJ_LP pj_gauss(PJ_CONTEXT *, PJ_LP, const void *); PJ_LP pj_inv_gauss(PJ_CONTEXT *, PJ_LP, const void *); const struct PJ_DATUMS PROJ_DLL *pj_get_datums_ref(void); PJ *pj_new(void); PJ *pj_default_destructor(PJ *P, int errlev); double PROJ_DLL pj_atof(const char *nptr); double pj_strtod(const char *nptr, char **endptr); void pj_freeup_plain(PJ *P); PJ *pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int allow_init_epsg); std::string PROJ_DLL pj_add_type_crs_if_needed(const std::string &str); std::string pj_double_quote_string_param_if_needed(const std::string &str); PJ *pj_create_internal(PJ_CONTEXT *ctx, const char *definition); PJ *pj_create_argv_internal(PJ_CONTEXT *ctx, int argc, char **argv); // For use by projinfo void pj_load_ini(PJ_CONTEXT *ctx); // Exported for testing purposes only std::string PROJ_DLL pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx); // For use by projsync std::string PROJ_DLL pj_get_relative_share_proj(PJ_CONTEXT *ctx); std::vector pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, PJ_OBJ_LIST *op_list); int pj_get_suggested_operation(PJ_CONTEXT *ctx, const std::vector &opList, const int iExcluded[2], bool skipNonInstantiable, PJ_DIRECTION direction, PJ_COORD coord); const PJ_UNITS *pj_list_linear_units(); const PJ_UNITS *pj_list_angular_units(); void pj_clear_hgridshift_knowngrids_cache(); void pj_clear_vgridshift_knowngrids_cache(); void pj_clear_gridshift_knowngrids_cache(); void pj_clear_sqlite_cache(); PJ_LP pj_generic_inverse_2d(PJ_XY xy, PJ *P, PJ_LP lpInitial, double deltaXYTolerance); PJ *pj_obj_create(PJ_CONTEXT *ctx, const NS_PROJ::util::BaseObjectNNPtr &objIn); PJ_DIRECTION pj_opposite_direction(PJ_DIRECTION dir); void pj_warn_about_missing_grid(PJ *P); /*****************************************************************************/ /* */ /* proj_api.h */ /* */ /* The rest of this header file includes what used to be "proj_api.h" */ /* */ /*****************************************************************************/ /* pj_init() and similar functions can be used with a non-C locale */ /* Can be detected too at runtime if the symbol pj_atof exists */ #define PJ_LOCALE_SAFE 1 #define RAD_TO_DEG 57.295779513082321 #define DEG_TO_RAD .017453292519943296 extern char const PROJ_DLL pj_release[]; /* global release id string */ /* procedure prototypes */ PJ_CONTEXT PROJ_DLL *pj_get_default_ctx(void); PJ_CONTEXT *pj_get_ctx(PJ *); PJ_XY PROJ_DLL pj_fwd(PJ_LP, PJ *); PJ_LP PROJ_DLL pj_inv(PJ_XY, PJ *); PJ_XYZ pj_fwd3d(PJ_LPZ, PJ *); PJ_LPZ pj_inv3d(PJ_XYZ, PJ *); void pj_clear_initcache(void); void PROJ_DLL pj_pr_list(PJ *); /* used by proj.cpp */ char *pj_get_def(const PJ *, int); int pj_has_inverse(PJ *); char *pj_strdup(const char *str); const char PROJ_DLL *pj_get_release(void); void pj_acquire_lock(void); void pj_release_lock(void); bool pj_log_active(PJ_CONTEXT *ctx, int level); void pj_log(PJ_CONTEXT *ctx, int level, const char *fmt, ...); void pj_stderr_logger(void *, int, const char *); // PROJ_DLL for tests int PROJ_DLL pj_find_file(PJ_CONTEXT *ctx, const char *short_filename, char *out_full_filename, size_t out_full_filename_size); // To remove when PROJ_LIB definitely goes away void PROJ_DLL pj_stderr_proj_lib_deprecation_warning(); #endif /* ndef PROJ_INTERNAL_H */ proj-9.8.1/src/fwd.cpp000664 001750 001750 00000021633 15166171715 014533 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Forward operation invocation * Author: Thomas Knudsen, thokn@sdfe.dk, 2018-01-02 * Based on material from Gerald Evenden (original pj_fwd) * and Piyush Agram (original pj_fwd3d) * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * Copyright (c) 2018, Thomas Knudsen / SDFE * * 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 #include #include "proj_internal.h" #include #define INPUT_UNITS P->left #define OUTPUT_UNITS P->right static void fwd_prepare(PJ *P, PJ_COORD &coo) { if (HUGE_VAL == coo.v[0] || HUGE_VAL == coo.v[1] || HUGE_VAL == coo.v[2]) { coo = proj_coord_error(); return; } /* The helmert datum shift will choke unless it gets a sensible 4D * coordinate */ if (HUGE_VAL == coo.v[2] && P->helmert) coo.v[2] = 0.0; if (HUGE_VAL == coo.v[3] && P->helmert) coo.v[3] = 0.0; /* Check validity of angular input coordinates */ if (INPUT_UNITS == PJ_IO_UNITS_RADIANS) { double t; /* check for latitude or longitude over-range */ t = (coo.lp.phi < 0 ? -coo.lp.phi : coo.lp.phi) - M_HALFPI; if (t > PJ_EPS_LAT) { proj_log_error(P, _("Invalid latitude")); proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); coo = proj_coord_error(); return; } if (coo.lp.lam > 10 || coo.lp.lam < -10) { proj_log_error(P, _("Invalid longitude")); proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_INVALID_COORD); coo = proj_coord_error(); return; } /* Clamp latitude to -90..90 degree range */ if (coo.lp.phi > M_HALFPI) coo.lp.phi = M_HALFPI; if (coo.lp.phi < -M_HALFPI) coo.lp.phi = -M_HALFPI; /* If input latitude is geocentrical, convert to geographical */ if (P->geoc) coo = pj_geocentric_latitude(P, PJ_INV, coo); /* Ensure longitude is in the -pi:pi range */ if (0 == P->over) coo.lp.lam = adjlon(coo.lp.lam); if (P->hgridshift) coo = proj_trans(P->hgridshift, PJ_INV, coo); else if (P->helmert || (P->cart_wgs84 != nullptr && P->cart != nullptr)) { coo = proj_trans(P->cart_wgs84, PJ_FWD, coo); /* Go cartesian in WGS84 frame */ if (P->helmert) coo = proj_trans(P->helmert, PJ_INV, coo); /* Step into local frame */ coo = proj_trans(P->cart, PJ_INV, coo); /* Go back to angular using local ellps */ } if (coo.lp.lam == HUGE_VAL) return; if (P->vgridshift) coo = proj_trans(P->vgridshift, PJ_FWD, coo); /* Go orthometric from geometric */ /* Distance from central meridian, taking system zero meridian into * account */ coo.lp.lam = (coo.lp.lam - P->from_greenwich) - P->lam0; /* Ensure longitude is in the -pi:pi range */ if (0 == P->over) coo.lp.lam = adjlon(coo.lp.lam); return; } /* We do not support gridshifts on cartesian input */ if (INPUT_UNITS == PJ_IO_UNITS_CARTESIAN && P->helmert) coo = proj_trans(P->helmert, PJ_INV, coo); return; } static void fwd_finalize(PJ *P, PJ_COORD &coo) { switch (OUTPUT_UNITS) { /* Handle false eastings/northings and non-metric linear units */ case PJ_IO_UNITS_CARTESIAN: if (P->is_geocent) { coo = proj_trans(P->cart, PJ_FWD, coo); } coo.xyz.x *= P->fr_meter; coo.xyz.y *= P->fr_meter; coo.xyz.z *= P->fr_meter; break; /* Classic proj.4 functions return plane coordinates in units of the * semimajor axis */ case PJ_IO_UNITS_CLASSIC: coo.xy.x *= P->a; coo.xy.y *= P->a; PROJ_FALLTHROUGH; /* to continue processing in common with PJ_IO_UNITS_PROJECTED */ case PJ_IO_UNITS_PROJECTED: coo.xyz.x = P->fr_meter * (coo.xyz.x + P->x0); coo.xyz.y = P->fr_meter * (coo.xyz.y + P->y0); coo.xyz.z = P->vfr_meter * (coo.xyz.z + P->z0); break; case PJ_IO_UNITS_WHATEVER: break; case PJ_IO_UNITS_DEGREES: break; case PJ_IO_UNITS_RADIANS: coo.lpz.z = P->vfr_meter * (coo.lpz.z + P->z0); if (P->is_long_wrap_set) { if (coo.lpz.lam != HUGE_VAL) { coo.lpz.lam = P->long_wrap_center + adjlon(coo.lpz.lam - P->long_wrap_center); } } break; } if (P->axisswap) coo = proj_trans(P->axisswap, PJ_FWD, coo); } static inline PJ_COORD error_or_coord(PJ *P, PJ_COORD coord, int last_errno) { if (P->ctx->last_errno) return proj_coord_error(); P->ctx->last_errno = last_errno; return coord; } PJ_XY pj_fwd(PJ_LP lp, PJ *P) { PJ_COORD coo = {{0, 0, 0, 0}}; coo.lp = lp; const int last_errno = P->ctx->last_errno; P->ctx->last_errno = 0; if (!P->skip_fwd_prepare) fwd_prepare(P, coo); if (HUGE_VAL == coo.v[0] || HUGE_VAL == coo.v[1]) return proj_coord_error().xy; /* Do the transformation, using the lowest dimensional transformer available */ if (P->fwd) { const auto xy = P->fwd(coo.lp, P); coo.xy = xy; } else if (P->fwd3d) { const auto xyz = P->fwd3d(coo.lpz, P); coo.xyz = xyz; } else if (P->fwd4d) P->fwd4d(coo, P); else { proj_errno_set(P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error().xy; } if (HUGE_VAL == coo.v[0]) return proj_coord_error().xy; if (!P->skip_fwd_finalize) fwd_finalize(P, coo); return error_or_coord(P, coo, last_errno).xy; } PJ_XYZ pj_fwd3d(PJ_LPZ lpz, PJ *P) { PJ_COORD coo = {{0, 0, 0, 0}}; coo.lpz = lpz; const int last_errno = P->ctx->last_errno; P->ctx->last_errno = 0; if (!P->skip_fwd_prepare) fwd_prepare(P, coo); if (HUGE_VAL == coo.v[0]) return proj_coord_error().xyz; /* Do the transformation, using the lowest dimensional transformer feasible */ if (P->fwd3d) { const auto xyz = P->fwd3d(coo.lpz, P); coo.xyz = xyz; } else if (P->fwd4d) P->fwd4d(coo, P); else if (P->fwd) { const auto xy = P->fwd(coo.lp, P); coo.xy = xy; } else { proj_errno_set(P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error().xyz; } if (HUGE_VAL == coo.v[0]) return proj_coord_error().xyz; if (!P->skip_fwd_finalize) fwd_finalize(P, coo); return error_or_coord(P, coo, last_errno).xyz; } bool pj_fwd4d(PJ_COORD &coo, PJ *P) { const int last_errno = P->ctx->last_errno; P->ctx->last_errno = 0; if (!P->skip_fwd_prepare) fwd_prepare(P, coo); if (HUGE_VAL == coo.v[0]) { coo = proj_coord_error(); return false; } /* Call the highest dimensional converter available */ if (P->fwd4d) P->fwd4d(coo, P); else if (P->fwd3d) { const auto xyz = P->fwd3d(coo.lpz, P); coo.xyz = xyz; } else if (P->fwd) { const auto xy = P->fwd(coo.lp, P); coo.xy = xy; } else { proj_errno_set(P, PROJ_ERR_OTHER_NO_INVERSE_OP); coo = proj_coord_error(); return false; } if (HUGE_VAL == coo.v[0]) { coo = proj_coord_error(); return false; } if (!P->skip_fwd_finalize) fwd_finalize(P, coo); if (P->ctx->last_errno) { coo = proj_coord_error(); return false; } P->ctx->last_errno = last_errno; return true; } proj-9.8.1/src/datums.cpp000664 001750 001750 00000007564 15166171715 015257 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Built in datum list. * Author: Frank Warmerdam, warmerda@home.com * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include "proj.h" #include "proj_internal.h" /* * The ellipse code must match one from pj_ellps.c. The datum id should * be kept to 12 characters or less if possible. Use the official OGC * datum name for the comments if available. */ static const struct PJ_DATUMS pj_datums[] = { /* id definition ellipse comments */ /* -- ---------- ------- -------- */ {"WGS84", "towgs84=0,0,0", "WGS84", ""}, {"GGRS87", "towgs84=-199.87,74.79,246.62", "GRS80", "Greek_Geodetic_Reference_System_1987"}, {"NAD83", "towgs84=0,0,0", "GRS80", "North_American_Datum_1983"}, {"NAD27", "nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", "clrk66", "North_American_Datum_1927"}, {"potsdam", /*"towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7",*/ "nadgrids=@BETA2007.gsb", "bessel", "Potsdam Rauenberg 1950 DHDN"}, {"carthage", "towgs84=-263.0,6.0,431.0", "clrk80ign", "Carthage 1934 Tunisia"}, {"hermannskogel", "towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232", "bessel", "Hermannskogel"}, {"ire65", "towgs84=482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", "mod_airy", "Ireland 1965"}, {"nzgd49", "towgs84=59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", "intl", "New Zealand Geodetic Datum 1949"}, {"OSGB36", "towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", "airy", "Airy 1830"}, {nullptr, nullptr, nullptr, nullptr}}; const struct PJ_DATUMS *pj_get_datums_ref() { return pj_datums; } /* * This list is no longer updated, and some values may conflict with * other sources. */ static const struct PJ_PRIME_MERIDIANS pj_prime_meridians[] = { /* id definition */ /* -- ---------- */ {"greenwich", "0dE"}, {"lisbon", "9d07'54.862\"W"}, {"paris", "2d20'14.025\"E"}, {"bogota", "74d04'51.3\"W"}, {"madrid", "3d41'16.58\"W"}, /* EPSG:8905 is 3d41'14.55"W */ {"rome", "12d27'8.4\"E"}, {"bern", "7d26'22.5\"E"}, {"jakarta", "106d48'27.79\"E"}, {"ferro", "17d40'W"}, {"brussels", "4d22'4.71\"E"}, {"stockholm", "18d3'29.8\"E"}, {"athens", "23d42'58.815\"E"}, {"oslo", "10d43'22.5\"E"}, {"copenhagen", "12d34'40.35\"E"}, /* EPSG:1026 is 12d34'39.9"E */ {nullptr, nullptr}}; const PJ_PRIME_MERIDIANS *proj_list_prime_meridians(void) { return pj_prime_meridians; } proj-9.8.1/src/sqlite3_utils.hpp000664 001750 001750 00000010335 15166171715 016561 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: SQLite3 related utilities * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2019, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef SQLITE3_HPP_INCLUDED #define SQLITE3_HPP_INCLUDED #include #include #include "proj.h" #include "proj/util.hpp" NS_PROJ_START //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- struct pj_sqlite3_vfs { sqlite3_vfs base{}; std::string namePtr{}; virtual ~pj_sqlite3_vfs(); }; // --------------------------------------------------------------------------- class SQLite3VFS { pj_sqlite3_vfs *vfs_ = nullptr; explicit SQLite3VFS(pj_sqlite3_vfs *vfs); SQLite3VFS(const SQLite3VFS &) = delete; SQLite3VFS &operator=(const SQLite3VFS &) = delete; public: ~SQLite3VFS(); static std::unique_ptr create(bool fakeSync, bool fakeLock, bool skipStatJournalAndWAL); #ifdef EMBED_RESOURCE_FILES static std::unique_ptr createMem(const void *membuffer, size_t bufferSize); #endif const char *name() const; sqlite3_vfs *raw() { return &(vfs_->base); } }; // --------------------------------------------------------------------------- class SQLiteStatement { sqlite3_stmt *hStmt = nullptr; int iBindIdx = 1; int iResIdx = 0; SQLiteStatement(const SQLiteStatement &) = delete; SQLiteStatement &operator=(const SQLiteStatement &) = delete; public: explicit SQLiteStatement(sqlite3_stmt *hStmtIn); ~SQLiteStatement() { sqlite3_finalize(hStmt); } int execute() { return sqlite3_step(hStmt); } void bindNull() { sqlite3_bind_null(hStmt, iBindIdx); iBindIdx++; } void bindText(const char *txt) { sqlite3_bind_text(hStmt, iBindIdx, txt, -1, nullptr); iBindIdx++; } void bindInt64(sqlite3_int64 v) { sqlite3_bind_int64(hStmt, iBindIdx, v); iBindIdx++; } void bindBlob(const void *blob, size_t blob_size) { sqlite3_bind_blob(hStmt, iBindIdx, blob, static_cast(blob_size), nullptr); iBindIdx++; } const char *getText() { auto ret = sqlite3_column_text(hStmt, iResIdx); iResIdx++; return reinterpret_cast(ret); } sqlite3_int64 getInt64() { auto ret = sqlite3_column_int64(hStmt, iResIdx); iResIdx++; return ret; } const void *getBlob(int &size) { size = sqlite3_column_bytes(hStmt, iResIdx); auto ret = sqlite3_column_blob(hStmt, iResIdx); iResIdx++; return ret; } void reset() { sqlite3_reset(hStmt); iBindIdx = 1; iResIdx = 0; } void resetResIndex() { iResIdx = 0; } }; //! @endcond Doxygen_Suppress NS_PROJ_END #endif // SQLITE3_HPP_INCLUDED proj-9.8.1/src/dist.cpp000664 001750 001750 00000007053 15166171715 014716 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Distance computation * * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 * ****************************************************************************** * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "geodesic.h" #include "proj.h" #include "proj_internal.h" #include #include "proj/internal/io_internal.hpp" /* Geodesic distance (in meter) + fwd and rev azimuth between two points on the * ellipsoid */ PJ_COORD proj_geod(const PJ *P, PJ_COORD a, PJ_COORD b) { PJ_COORD c; if (!P->geod) { return proj_coord_error(); } /* Note: the geodesic code takes arguments in degrees */ geod_inverse(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), c.v, c.v + 1, c.v + 2); // cppcheck-suppress uninitvar return c; } PJ_COORD proj_geod_direct(const PJ *P, PJ_COORD a, double azimuth, double distance) { if (!P->geod) { return proj_coord_error(); } double lat = 0, lon = 0, azi = 0; geod_direct(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), PJ_TODEG(azimuth), distance, &lat, &lon, &azi); return proj_coord(PJ_TORAD(lon), PJ_TORAD(lat), PJ_TORAD(azi), 0); } /* Geodesic distance (in meter) between two points with angular 2D coordinates */ double proj_lp_dist(const PJ *P, PJ_COORD a, PJ_COORD b) { double s12, azi1, azi2; /* Note: the geodesic code takes arguments in degrees */ if (!P->geod) { return HUGE_VAL; } geod_inverse(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), &s12, &azi1, &azi2); return s12; } /* The geodesic distance AND the vertical offset */ double proj_lpz_dist(const PJ *P, PJ_COORD a, PJ_COORD b) { if (HUGE_VAL == a.lpz.lam || HUGE_VAL == b.lpz.lam) return HUGE_VAL; return hypot(proj_lp_dist(P, a, b), a.lpz.z - b.lpz.z); } /* Euclidean distance between two points with linear 2D coordinates */ double proj_xy_dist(PJ_COORD a, PJ_COORD b) { return hypot(a.xy.x - b.xy.x, a.xy.y - b.xy.y); } /* Euclidean distance between two points with linear 3D coordinates */ double proj_xyz_dist(PJ_COORD a, PJ_COORD b) { return hypot(proj_xy_dist(a, b), a.xyz.z - b.xyz.z); } proj-9.8.1/src/coordinates.cpp000664 001750 001750 00000011240 15166171715 016256 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functions related to PJ_COORD 4D geodetic spatiotemporal * data type. * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 * ****************************************************************************** * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" #include "proj/internal/internal.hpp" using namespace NS_PROJ::internal; /* Initialize PJ_COORD struct */ PJ_COORD proj_coord(double x, double y, double z, double t) { PJ_COORD res; res.v[0] = x; res.v[1] = y; res.v[2] = z; res.v[3] = t; return res; } PJ_DIRECTION pj_opposite_direction(PJ_DIRECTION dir) { return static_cast(-dir); } /*****************************************************************************/ int proj_angular_input(PJ *P, enum PJ_DIRECTION dir) { /****************************************************************************** Returns 1 if the operator P expects angular input coordinates when operating in direction dir, 0 otherwise. dir: {PJ_FWD, PJ_INV} ******************************************************************************/ if (PJ_FWD == dir) return pj_left(P) == PJ_IO_UNITS_RADIANS; return pj_right(P) == PJ_IO_UNITS_RADIANS; } /*****************************************************************************/ int proj_angular_output(PJ *P, enum PJ_DIRECTION dir) { /****************************************************************************** Returns 1 if the operator P provides angular output coordinates when operating in direction dir, 0 otherwise. dir: {PJ_FWD, PJ_INV} ******************************************************************************/ return proj_angular_input(P, pj_opposite_direction(dir)); } /*****************************************************************************/ int proj_degree_input(PJ *P, enum PJ_DIRECTION dir) { /****************************************************************************** Returns 1 if the operator P expects degree input coordinates when operating in direction dir, 0 otherwise. dir: {PJ_FWD, PJ_INV} ******************************************************************************/ if (PJ_FWD == dir) return pj_left(P) == PJ_IO_UNITS_DEGREES; return pj_right(P) == PJ_IO_UNITS_DEGREES; } /*****************************************************************************/ int proj_degree_output(PJ *P, enum PJ_DIRECTION dir) { /****************************************************************************** Returns 1 if the operator P provides degree output coordinates when operating in direction dir, 0 otherwise. dir: {PJ_FWD, PJ_INV} ******************************************************************************/ return proj_degree_input(P, pj_opposite_direction(dir)); } double proj_torad(double angle_in_degrees) { return PJ_TORAD(angle_in_degrees); } double proj_todeg(double angle_in_radians) { return PJ_TODEG(angle_in_radians); } double proj_dmstor(const char *is, char **rs) { return dmstor(is, rs); } char *proj_rtodms(char *s, double r, int pos, int neg) { // 40 is the size used for the buffer in proj.cpp size_t arbitrary_size = 40; return rtodms(s, arbitrary_size, r, pos, neg); } char *proj_rtodms2(char *s, size_t sizeof_s, double r, int pos, int neg) { return rtodms(s, sizeof_s, r, pos, neg); } proj-9.8.1/src/wkt_parser.cpp000664 001750 001750 00000004746 15166171715 016142 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: WKT parser common routines * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018 Even Rouault, * * 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 "wkt_parser.hpp" #include #include // --------------------------------------------------------------------------- void pj_wkt_error(pj_wkt_parse_context *context, const char *msg) { context->errorMsg = "Parsing error : "; context->errorMsg += msg; context->errorMsg += ". Error occurred around:\n"; std::string ctxtMsg; const int n = static_cast(context->pszLastSuccess - context->pszInput); int start_i = std::max(0, n - 40); for (int i = start_i; i < n + 40 && context->pszInput[i]; i++) { if (context->pszInput[i] == '\r' || context->pszInput[i] == '\n') { if (i > n) { break; } else { ctxtMsg.clear(); start_i = i + 1; } } else { ctxtMsg += context->pszInput[i]; } } context->errorMsg += ctxtMsg; context->errorMsg += '\n'; for (int i = start_i; i < n; i++) context->errorMsg += ' '; context->errorMsg += '^'; } proj-9.8.1/src/msfn.cpp000664 001750 001750 00000000323 15166171715 014707 0ustar00eveneven000000 000000 /* determine constant small m */ #include "proj.h" #include "proj_internal.h" #include double pj_msfn(double sinphi, double cosphi, double es) { return (cosphi / sqrt(1. - es * sinphi * sinphi)); } proj-9.8.1/src/filemanager.cpp000664 001750 001750 00000232031 15166171715 016221 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: File manager * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2019, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif // proj_config.h must be included before testing HAVE_LIBDL #include "proj_config.h" #if defined(__CYGWIN__) && defined(HAVE_LIBDL) && !defined(_GNU_SOURCE) // Required for dladdr() on Cygwin #define _GNU_SOURCE #endif #include #include #include #include #include #include #include "filemanager.hpp" #include "proj.h" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/io.hpp" #include "proj_internal.h" #include #ifdef _WIN32 #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) #define UWP 1 #else #define UWP 0 #endif #include #include #else #ifdef HAVE_LIBDL #include #endif #include #include #endif #ifdef EMBED_RESOURCE_FILES #include "embedded_resources.h" #endif //! @cond Doxygen_Suppress using namespace NS_PROJ::internal; NS_PROJ_START // --------------------------------------------------------------------------- File::File(const std::string &filename) : name_(filename) {} // --------------------------------------------------------------------------- File::~File() = default; // --------------------------------------------------------------------------- std::string File::read_line(size_t maxLen, bool &maxLenReached, bool &eofReached) { constexpr size_t MAX_MAXLEN = 1024 * 1024; maxLen = std::min(maxLen, MAX_MAXLEN); while (true) { // Consume existing lines in buffer size_t pos = readLineBuffer_.find_first_of("\r\n"); if (pos != std::string::npos) { if (pos > maxLen) { std::string ret(readLineBuffer_.substr(0, maxLen)); readLineBuffer_ = readLineBuffer_.substr(maxLen); maxLenReached = true; eofReached = false; return ret; } std::string ret(readLineBuffer_.substr(0, pos)); if (readLineBuffer_[pos] == '\r' && readLineBuffer_[pos + 1] == '\n') { pos += 1; } readLineBuffer_ = readLineBuffer_.substr(pos + 1); maxLenReached = false; eofReached = false; return ret; } const size_t prevSize = readLineBuffer_.size(); if (maxLen <= prevSize) { std::string ret(readLineBuffer_.substr(0, maxLen)); readLineBuffer_ = readLineBuffer_.substr(maxLen); maxLenReached = true; eofReached = false; return ret; } if (eofReadLine_) { std::string ret = readLineBuffer_; readLineBuffer_.clear(); maxLenReached = false; eofReached = ret.empty(); return ret; } readLineBuffer_.resize(maxLen); const size_t nRead = read(&readLineBuffer_[prevSize], maxLen - prevSize); if (nRead < maxLen - prevSize) eofReadLine_ = true; readLineBuffer_.resize(prevSize + nRead); } } // --------------------------------------------------------------------------- #ifdef _WIN32 /* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from * FLTK. It was originally downloaded from: * http://svn.easysw.com/public/fltk/fltk/trunk/src/utf.c * And already used by GDAL */ /************************************************************************/ /* ==================================================================== */ /* UTF.C code from FLTK with some modifications. */ /* ==================================================================== */ /************************************************************************/ /* Set to 1 to turn bad UTF8 bytes into ISO-8859-1. If this is to zero they are instead turned into the Unicode REPLACEMENT CHARACTER, of value 0xfffd. If this is on utf8decode will correctly map most (perhaps all) human-readable text that is in ISO-8859-1. This may allow you to completely ignore character sets in your code because virtually everything is either ISO-8859-1 or UTF-8. */ #define ERRORS_TO_ISO8859_1 1 /* Set to 1 to turn bad UTF8 bytes in the 0x80-0x9f range into the Unicode index for Microsoft's CP1252 character set. You should also set ERRORS_TO_ISO8859_1. With this a huge amount of more available text (such as all web pages) are correctly converted to Unicode. */ #define ERRORS_TO_CP1252 1 /* A number of Unicode code points are in fact illegal and should not be produced by a UTF-8 converter. Turn this on will replace the bytes in those encodings with errors. If you do this then converting arbitrary 16-bit data to UTF-8 and then back is not an identity, which will probably break a lot of software. */ #define STRICT_RFC3629 0 #if ERRORS_TO_CP1252 // Codes 0x80..0x9f from the Microsoft CP1252 character set, translated // to Unicode: constexpr unsigned short cp1252[32] = { 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178}; #endif /************************************************************************/ /* utf8decode() */ /************************************************************************/ /* Decode a single UTF-8 encoded character starting at \e p. The resulting Unicode value (in the range 0-0x10ffff) is returned, and \e len is set the number of bytes in the UTF-8 encoding (adding \e len to \e p will point at the next character). If \a p points at an illegal UTF-8 encoding, including one that would go past \e end, or where a code is uses more bytes than necessary, then *reinterpret_cast(p) is translated as though it is in the Microsoft CP1252 character set and \e len is set to 1. Treating errors this way allows this to decode almost any ISO-8859-1 or CP1252 text that has been mistakenly placed where UTF-8 is expected, and has proven very useful. If you want errors to be converted to error characters (as the standards recommend), adding a test to see if the length is unexpectedly 1 will work: \code if( *p & 0x80 ) { // What should be a multibyte encoding. code = utf8decode(p, end, &len); if( len<2 ) code = 0xFFFD; // Turn errors into REPLACEMENT CHARACTER. } else { // Handle the 1-byte utf8 encoding: code = *p; len = 1; } \endcode Direct testing for the 1-byte case (as shown above) will also speed up the scanning of strings where the majority of characters are ASCII. */ static unsigned utf8decode(const char *p, const char *end, int *len) { unsigned char c = *reinterpret_cast(p); if (c < 0x80) { *len = 1; return c; #if ERRORS_TO_CP1252 } else if (c < 0xa0) { *len = 1; return cp1252[c - 0x80]; #endif } else if (c < 0xc2) { goto FAIL; } if (p + 1 >= end || (p[1] & 0xc0) != 0x80) goto FAIL; if (c < 0xe0) { *len = 2; return ((p[0] & 0x1f) << 6) + ((p[1] & 0x3f)); } else if (c == 0xe0) { if ((reinterpret_cast(p))[1] < 0xa0) goto FAIL; goto UTF8_3; #if STRICT_RFC3629 } else if (c == 0xed) { // RFC 3629 says surrogate chars are illegal. if ((reinterpret_cast(p))[1] >= 0xa0) goto FAIL; goto UTF8_3; } else if (c == 0xef) { // 0xfffe and 0xffff are also illegal characters. if ((reinterpret_cast(p))[1] == 0xbf && (reinterpret_cast(p))[2] >= 0xbe) goto FAIL; goto UTF8_3; #endif } else if (c < 0xf0) { UTF8_3: if (p + 2 >= end || (p[2] & 0xc0) != 0x80) goto FAIL; *len = 3; return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + ((p[2] & 0x3f)); } else if (c == 0xf0) { if ((reinterpret_cast(p))[1] < 0x90) goto FAIL; goto UTF8_4; } else if (c < 0xf4) { UTF8_4: if (p + 3 >= end || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80) goto FAIL; *len = 4; #if STRICT_RFC3629 // RFC 3629 says all codes ending in fffe or ffff are illegal: if ((p[1] & 0xf) == 0xf && (reinterpret_cast(p))[2] == 0xbf && (reinterpret_cast(p))[3] >= 0xbe) goto FAIL; #endif return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + ((p[2] & 0x3f) << 6) + ((p[3] & 0x3f)); } else if (c == 0xf4) { if ((reinterpret_cast(p))[1] > 0x8f) goto FAIL; // After 0x10ffff. goto UTF8_4; } else { FAIL: *len = 1; #if ERRORS_TO_ISO8859_1 return c; #else return 0xfffd; // Unicode REPLACEMENT CHARACTER #endif } } /************************************************************************/ /* utf8towc() */ /************************************************************************/ /* Convert a UTF-8 sequence into an array of wchar_t. These are used by some system calls, especially on Windows. \a src points at the UTF-8, and \a srclen is the number of bytes to convert. \a dst points at an array to write, and \a dstlen is the number of locations in this array. At most \a dstlen-1 words will be written there, plus a 0 terminating word. Thus this function will never overwrite the buffer and will always return a zero-terminated string. If \a dstlen is zero then \a dst can be null and no data is written, but the length is returned. The return value is the number of words that \e would be written to \a dst if it were long enough, not counting the terminating zero. If the return value is greater or equal to \a dstlen it indicates truncation, you can then allocate a new array of size return+1 and call this again. Errors in the UTF-8 are converted as though each byte in the erroneous string is in the Microsoft CP1252 encoding. This allows ISO-8859-1 text mistakenly identified as UTF-8 to be printed correctly. Notice that sizeof(wchar_t) is 2 on Windows and is 4 on Linux and most other systems. Where wchar_t is 16 bits, Unicode characters in the range 0x10000 to 0x10ffff are converted to "surrogate pairs" which take two words each (this is called UTF-16 encoding). If wchar_t is 32 bits this rather nasty problem is avoided. */ static unsigned utf8towc(const char *src, unsigned srclen, wchar_t *dst, unsigned dstlen) { const char *p = src; const char *e = src + srclen; unsigned count = 0; if (dstlen) while (true) { if (p >= e) { dst[count] = 0; return count; } if (!(*p & 0x80)) { // ASCII dst[count] = *p++; } else { int len = 0; unsigned ucs = utf8decode(p, e, &len); p += len; #ifdef _WIN32 if (ucs < 0x10000) { dst[count] = static_cast(ucs); } else { // Make a surrogate pair: if (count + 2 >= dstlen) { dst[count] = 0; count += 2; break; } dst[count] = static_cast( (((ucs - 0x10000u) >> 10) & 0x3ff) | 0xd800); dst[++count] = static_cast((ucs & 0x3ff) | 0xdc00); } #else dst[count] = static_cast(ucs); #endif } if (++count == dstlen) { dst[count - 1] = 0; break; } } // We filled dst, measure the rest: while (p < e) { if (!(*p & 0x80)) { p++; } else { int len = 0; #ifdef _WIN32 const unsigned ucs = utf8decode(p, e, &len); p += len; if (ucs >= 0x10000) ++count; #else utf8decode(p, e, &len); p += len; #endif } ++count; } return count; } // --------------------------------------------------------------------------- struct NonValidUTF8Exception : public std::exception {}; // May throw exceptions static std::wstring UTF8ToWString(const std::string &str) { std::wstring wstr; wstr.resize(str.size()); wstr.resize(utf8towc(str.data(), static_cast(str.size()), &wstr[0], static_cast(wstr.size()) + 1)); for (const auto ch : wstr) { if (ch == 0xfffd) { throw NonValidUTF8Exception(); } } return wstr; } // --------------------------------------------------------------------------- /************************************************************************/ /* utf8fromwc() */ /************************************************************************/ /* Turn "wide characters" as returned by some system calls (especially on Windows) into UTF-8. Up to \a dstlen bytes are written to \a dst, including a null terminator. The return value is the number of bytes that would be written, not counting the null terminator. If greater or equal to \a dstlen then if you malloc a new array of size n+1 you will have the space needed for the entire string. If \a dstlen is zero then nothing is written and this call just measures the storage space needed. \a srclen is the number of words in \a src to convert. On Windows this is not necessarily the number of characters, due to there possibly being "surrogate pairs" in the UTF-16 encoding used. On Unix wchar_t is 32 bits and each location is a character. On Unix if a src word is greater than 0x10ffff then this is an illegal character according to RFC 3629. These are converted as though they are 0xFFFD (REPLACEMENT CHARACTER). Characters in the range 0xd800 to 0xdfff, or ending with 0xfffe or 0xffff are also illegal according to RFC 3629. However I encode these as though they are legal, so that utf8towc will return the original data. On Windows "surrogate pairs" are converted to a single character and UTF-8 encoded (as 4 bytes). Mismatched halves of surrogate pairs are converted as though they are individual characters. */ static unsigned int utf8fromwc(char *dst, unsigned dstlen, const wchar_t *src, unsigned srclen) { unsigned int i = 0; unsigned int count = 0; if (dstlen) while (true) { if (i >= srclen) { dst[count] = 0; return count; } unsigned int ucs = src[i++]; if (ucs < 0x80U) { dst[count++] = static_cast(ucs); if (count >= dstlen) { dst[count - 1] = 0; break; } } else if (ucs < 0x800U) { // 2 bytes. if (count + 2 >= dstlen) { dst[count] = 0; count += 2; break; } dst[count++] = 0xc0 | static_cast(ucs >> 6); dst[count++] = 0x80 | static_cast(ucs & 0x3F); #ifdef _WIN32 } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen && src[i] >= 0xdc00 && src[i] <= 0xdfff) { // Surrogate pair. unsigned int ucs2 = src[i++]; ucs = 0x10000U + ((ucs & 0x3ff) << 10) + (ucs2 & 0x3ff); // All surrogate pairs turn into 4-byte utf8. #else } else if (ucs >= 0x10000) { if (ucs > 0x10ffff) { ucs = 0xfffd; goto J1; } #endif if (count + 4 >= dstlen) { dst[count] = 0; count += 4; break; } dst[count++] = 0xf0 | static_cast(ucs >> 18); dst[count++] = 0x80 | static_cast((ucs >> 12) & 0x3F); dst[count++] = 0x80 | static_cast((ucs >> 6) & 0x3F); dst[count++] = 0x80 | static_cast(ucs & 0x3F); } else { #ifndef _WIN32 J1: #endif // All others are 3 bytes: if (count + 3 >= dstlen) { dst[count] = 0; count += 3; break; } dst[count++] = 0xe0 | static_cast(ucs >> 12); dst[count++] = 0x80 | static_cast((ucs >> 6) & 0x3F); dst[count++] = 0x80 | static_cast(ucs & 0x3F); } } // We filled dst, measure the rest: while (i < srclen) { unsigned int ucs = src[i++]; if (ucs < 0x80U) { count++; } else if (ucs < 0x800U) { // 2 bytes. count += 2; #ifdef _WIN32 } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen - 1 && src[i + 1] >= 0xdc00 && src[i + 1] <= 0xdfff) { // Surrogate pair. ++i; #else } else if (ucs >= 0x10000 && ucs <= 0x10ffff) { #endif count += 4; } else { count += 3; } } return count; } // --------------------------------------------------------------------------- static std::string WStringToUTF8(const std::wstring &wstr) { std::string str; str.resize(wstr.size()); str.resize(utf8fromwc(&str[0], static_cast(str.size() + 1), wstr.data(), static_cast(wstr.size()))); return str; } // --------------------------------------------------------------------------- static std::string Win32Recode(const char *src, unsigned src_code_page, unsigned dst_code_page) { // Convert from source code page to Unicode. // Compute the length in wide characters. int wlen = MultiByteToWideChar(src_code_page, MB_ERR_INVALID_CHARS, src, -1, nullptr, 0); if (wlen == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) { return std::string(); } // Do the actual conversion. std::wstring wbuf; wbuf.resize(wlen); MultiByteToWideChar(src_code_page, 0, src, -1, &wbuf[0], wlen); // Convert from Unicode to destination code page. // Compute the length in chars. int len = WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, nullptr, 0, nullptr, nullptr); // Do the actual conversion. std::string out; out.resize(len); WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, &out[0], len, nullptr, nullptr); out.resize(strlen(out.c_str())); return out; } #endif // _defined(_WIN32) #if !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES) #ifdef _WIN32 // --------------------------------------------------------------------------- class FileWin32 : public File { PJ_CONTEXT *m_ctx; HANDLE m_handle; FileWin32(const FileWin32 &) = delete; FileWin32 &operator=(const FileWin32 &) = delete; protected: FileWin32(const std::string &name, PJ_CONTEXT *ctx, HANDLE handle) : File(name), m_ctx(ctx), m_handle(handle) {} public: ~FileWin32() override; size_t read(void *buffer, size_t sizeBytes) override; size_t write(const void *buffer, size_t sizeBytes) override; bool seek(unsigned long long offset, int whence = SEEK_SET) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } // We may lie, but the real use case is only for network files bool hasChanged() const override { return false; } static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, FileAccess access); }; // --------------------------------------------------------------------------- FileWin32::~FileWin32() { CloseHandle(m_handle); } // --------------------------------------------------------------------------- size_t FileWin32::read(void *buffer, size_t sizeBytes) { DWORD dwSizeRead = 0; size_t nResult = 0; if (!ReadFile(m_handle, buffer, static_cast(sizeBytes), &dwSizeRead, nullptr)) nResult = 0; else nResult = dwSizeRead; return nResult; } // --------------------------------------------------------------------------- size_t FileWin32::write(const void *buffer, size_t sizeBytes) { DWORD dwSizeWritten = 0; size_t nResult = 0; if (!WriteFile(m_handle, buffer, static_cast(sizeBytes), &dwSizeWritten, nullptr)) nResult = 0; else nResult = dwSizeWritten; return nResult; } // --------------------------------------------------------------------------- bool FileWin32::seek(unsigned long long offset, int whence) { LONG dwMoveMethod, dwMoveHigh; uint32_t nMoveLow; LARGE_INTEGER li; switch (whence) { case SEEK_CUR: dwMoveMethod = FILE_CURRENT; break; case SEEK_END: dwMoveMethod = FILE_END; break; case SEEK_SET: default: dwMoveMethod = FILE_BEGIN; break; } li.QuadPart = offset; nMoveLow = li.LowPart; dwMoveHigh = li.HighPart; SetLastError(0); SetFilePointer(m_handle, nMoveLow, &dwMoveHigh, dwMoveMethod); return GetLastError() == NO_ERROR; } // --------------------------------------------------------------------------- unsigned long long FileWin32::tell() { LARGE_INTEGER li; li.HighPart = 0; li.LowPart = SetFilePointer(m_handle, 0, &(li.HighPart), FILE_CURRENT); return static_cast(li.QuadPart); } // --------------------------------------------------------------------------- std::unique_ptr FileWin32::open(PJ_CONTEXT *ctx, const char *filename, FileAccess access) { DWORD dwDesiredAccess = access == FileAccess::READ_ONLY ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE; DWORD dwCreationDisposition = access == FileAccess::CREATE ? CREATE_ALWAYS : OPEN_EXISTING; DWORD dwFlagsAndAttributes = (dwDesiredAccess == GENERIC_READ) ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; try { #if UWP CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; ZeroMemory(&extendedParameters, sizeof(extendedParameters)); extendedParameters.dwSize = sizeof(extendedParameters); extendedParameters.dwFileAttributes = dwFlagsAndAttributes; HANDLE hFile = CreateFile2( UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, dwCreationDisposition, &extendedParameters); #else // UWP HANDLE hFile = CreateFileW( UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, dwCreationDisposition, dwFlagsAndAttributes, nullptr); #endif // UWP return std::unique_ptr(hFile != INVALID_HANDLE_VALUE ? new FileWin32(filename, ctx, hFile) : nullptr); } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return nullptr; } } #else // if !defined(_WIN32) // --------------------------------------------------------------------------- class FileStdio : public File { PJ_CONTEXT *m_ctx; FILE *m_fp; FileStdio(const FileStdio &) = delete; FileStdio &operator=(const FileStdio &) = delete; protected: FileStdio(const std::string &filename, PJ_CONTEXT *ctx, FILE *fp) : File(filename), m_ctx(ctx), m_fp(fp) {} public: ~FileStdio() override; size_t read(void *buffer, size_t sizeBytes) override; size_t write(const void *buffer, size_t sizeBytes) override; bool seek(unsigned long long offset, int whence = SEEK_SET) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } // We may lie, but the real use case is only for network files bool hasChanged() const override { return false; } static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, FileAccess access); }; // --------------------------------------------------------------------------- FileStdio::~FileStdio() { fclose(m_fp); } // --------------------------------------------------------------------------- size_t FileStdio::read(void *buffer, size_t sizeBytes) { return fread(buffer, 1, sizeBytes, m_fp); } // --------------------------------------------------------------------------- size_t FileStdio::write(const void *buffer, size_t sizeBytes) { return fwrite(buffer, 1, sizeBytes, m_fp); } // --------------------------------------------------------------------------- bool FileStdio::seek(unsigned long long offset, int whence) { // TODO one day: use 64-bit offset compatible API if (offset != static_cast(static_cast(offset))) { pj_log(m_ctx, PJ_LOG_ERROR, "Attempt at seeking to a 64 bit offset. Not supported yet"); return false; } return fseek(m_fp, static_cast(offset), whence) == 0; } // --------------------------------------------------------------------------- unsigned long long FileStdio::tell() { // TODO one day: use 64-bit offset compatible API return ftell(m_fp); } // --------------------------------------------------------------------------- std::unique_ptr FileStdio::open(PJ_CONTEXT *ctx, const char *filename, FileAccess access) { auto fp = fopen(filename, access == FileAccess::READ_ONLY ? "rb" : access == FileAccess::READ_UPDATE ? "r+b" : "w+b"); return std::unique_ptr(fp ? new FileStdio(filename, ctx, fp) : nullptr); } #endif // _WIN32 #endif // !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES) // --------------------------------------------------------------------------- class FileApiAdapter : public File { PJ_CONTEXT *m_ctx; PROJ_FILE_HANDLE *m_fp; FileApiAdapter(const FileApiAdapter &) = delete; FileApiAdapter &operator=(const FileApiAdapter &) = delete; protected: FileApiAdapter(const std::string &filename, PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *fp) : File(filename), m_ctx(ctx), m_fp(fp) {} public: ~FileApiAdapter() override; size_t read(void *buffer, size_t sizeBytes) override; size_t write(const void *, size_t) override; bool seek(unsigned long long offset, int whence = SEEK_SET) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } // We may lie, but the real use case is only for network files bool hasChanged() const override { return false; } static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, FileAccess access); }; // --------------------------------------------------------------------------- FileApiAdapter::~FileApiAdapter() { m_ctx->fileApi.close_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data); } // --------------------------------------------------------------------------- size_t FileApiAdapter::read(void *buffer, size_t sizeBytes) { return m_ctx->fileApi.read_cbk(m_ctx, m_fp, buffer, sizeBytes, m_ctx->fileApi.user_data); } // --------------------------------------------------------------------------- size_t FileApiAdapter::write(const void *buffer, size_t sizeBytes) { return m_ctx->fileApi.write_cbk(m_ctx, m_fp, buffer, sizeBytes, m_ctx->fileApi.user_data); } // --------------------------------------------------------------------------- bool FileApiAdapter::seek(unsigned long long offset, int whence) { return m_ctx->fileApi.seek_cbk(m_ctx, m_fp, static_cast(offset), whence, m_ctx->fileApi.user_data) != 0; } // --------------------------------------------------------------------------- unsigned long long FileApiAdapter::tell() { return m_ctx->fileApi.tell_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data); } // --------------------------------------------------------------------------- std::unique_ptr FileApiAdapter::open(PJ_CONTEXT *ctx, const char *filename, FileAccess eAccess) { PROJ_OPEN_ACCESS eCAccess = PROJ_OPEN_ACCESS_READ_ONLY; switch (eAccess) { case FileAccess::READ_ONLY: // Initialized above break; case FileAccess::READ_UPDATE: eCAccess = PROJ_OPEN_ACCESS_READ_UPDATE; break; case FileAccess::CREATE: eCAccess = PROJ_OPEN_ACCESS_CREATE; break; } auto fp = ctx->fileApi.open_cbk(ctx, filename, eCAccess, ctx->fileApi.user_data); return std::unique_ptr(fp ? new FileApiAdapter(filename, ctx, fp) : nullptr); } // --------------------------------------------------------------------------- #if EMBED_RESOURCE_FILES class FileMemory : public File { PJ_CONTEXT *m_ctx; const unsigned char *const m_data; const size_t m_size; size_t m_pos = 0; FileMemory(const FileMemory &) = delete; FileMemory &operator=(const FileMemory &) = delete; protected: FileMemory(const std::string &filename, PJ_CONTEXT *ctx, const unsigned char *data, size_t size) : File(filename), m_ctx(ctx), m_data(data), m_size(size) {} public: size_t read(void *buffer, size_t sizeBytes) override; size_t write(const void *, size_t) override; bool seek(unsigned long long offset, int whence = SEEK_SET) override; unsigned long long tell() override { return m_pos; } void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; } bool hasChanged() const override { return false; } static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, FileAccess access, const unsigned char *data, size_t size) { if (access != FileAccess::READ_ONLY) return nullptr; return std::unique_ptr(new FileMemory(filename, ctx, data, size)); } }; size_t FileMemory::read(void *buffer, size_t sizeBytes) { if (m_pos >= m_size) return 0; if (sizeBytes >= m_size - m_pos) { const size_t bytesToCopy = m_size - m_pos; memcpy(buffer, m_data + m_pos, bytesToCopy); m_pos = m_size; return bytesToCopy; } memcpy(buffer, m_data + m_pos, sizeBytes); m_pos += sizeBytes; return sizeBytes; } size_t FileMemory::write(const void *, size_t) { // shouldn't happen given we have bailed out in open() in non read-only // modes return 0; } bool FileMemory::seek(unsigned long long offset, int whence) { if (whence == SEEK_SET) { m_pos = static_cast(offset); return m_pos == offset; } else if (whence == SEEK_CUR) { const unsigned long long newPos = m_pos + offset; m_pos = static_cast(newPos); return m_pos == newPos; } else { if (offset != 0) return false; m_pos = m_size; return true; } } #endif // --------------------------------------------------------------------------- std::unique_ptr FileManager::open(PJ_CONTEXT *ctx, const char *filename, FileAccess access) { if (starts_with(filename, "http://") || starts_with(filename, "https://")) { if (!proj_context_is_network_enabled(ctx)) { pj_log( ctx, PJ_LOG_ERROR, "Attempt at accessing remote resource not authorized. Either " "set PROJ_NETWORK=ON or " "proj_context_set_enable_network(ctx, TRUE)"); return nullptr; } return pj_network_file_open(ctx, filename); } if (ctx->fileApi.open_cbk != nullptr) { return FileApiAdapter::open(ctx, filename, access); } std::unique_ptr ret; #if !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES) #ifdef _WIN32 ret = FileWin32::open(ctx, filename, access); #else ret = FileStdio::open(ctx, filename, access); #endif #endif #if EMBED_RESOURCE_FILES #if USE_ONLY_EMBEDDED_RESOURCE_FILES if (!ret) #endif { unsigned int size = 0; const unsigned char *in_memory_data = pj_get_embedded_resource(filename, &size); if (in_memory_data) { ret = FileMemory::open(ctx, filename, access, in_memory_data, size); } } #endif return ret; } // --------------------------------------------------------------------------- bool FileManager::exists(PJ_CONTEXT *ctx, const char *filename) { if (ctx->fileApi.exists_cbk) { return ctx->fileApi.exists_cbk(ctx, filename, ctx->fileApi.user_data) != 0; } #ifdef _WIN32 struct __stat64 buf; try { return _wstat64(UTF8ToWString(filename).c_str(), &buf) == 0; } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return false; } #else (void)ctx; struct stat sStat; return stat(filename, &sStat) == 0; #endif } // --------------------------------------------------------------------------- bool FileManager::mkdir(PJ_CONTEXT *ctx, const char *filename) { if (ctx->fileApi.mkdir_cbk) { return ctx->fileApi.mkdir_cbk(ctx, filename, ctx->fileApi.user_data) != 0; } #ifdef _WIN32 try { return _wmkdir(UTF8ToWString(filename).c_str()) == 0; } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return false; } #else (void)ctx; return ::mkdir(filename, 0755) == 0; #endif } // --------------------------------------------------------------------------- bool FileManager::unlink(PJ_CONTEXT *ctx, const char *filename) { if (ctx->fileApi.unlink_cbk) { return ctx->fileApi.unlink_cbk(ctx, filename, ctx->fileApi.user_data) != 0; } #ifdef _WIN32 try { return _wunlink(UTF8ToWString(filename).c_str()) == 0; } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return false; } #else (void)ctx; return ::unlink(filename) == 0; #endif } // --------------------------------------------------------------------------- bool FileManager::rename(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath) { if (ctx->fileApi.rename_cbk) { return ctx->fileApi.rename_cbk(ctx, oldPath, newPath, ctx->fileApi.user_data) != 0; } #ifdef _WIN32 try { return _wrename(UTF8ToWString(oldPath).c_str(), UTF8ToWString(newPath).c_str()) == 0; } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return false; } #else (void)ctx; return ::rename(oldPath, newPath) == 0; #endif } // --------------------------------------------------------------------------- std::string FileManager::getProjDataEnvVar(PJ_CONTEXT *ctx) { if (!ctx->env_var_proj_data.empty()) { return ctx->env_var_proj_data; } (void)ctx; std::string str; const char *envvar = getenv("PROJ_DATA"); if (!envvar) { envvar = getenv("PROJ_LIB"); // Legacy name. We should probably keep it // for a long time for nostalgic people :-) if (envvar) { pj_log(ctx, PJ_LOG_DEBUG, "PROJ_LIB environment variable is deprecated, and will be " "removed in a future release. You are encouraged to set " "PROJ_DATA instead"); } } if (!envvar) return str; str = envvar; #ifdef _WIN32 // Assume this is UTF-8. If not try to convert from ANSI page bool looksLikeUTF8 = false; try { UTF8ToWString(envvar); looksLikeUTF8 = true; } catch (const std::exception &) { } if (!looksLikeUTF8 || !exists(ctx, envvar)) { str = Win32Recode(envvar, CP_ACP, CP_UTF8); if (str.empty() || !exists(ctx, str.c_str())) str = envvar; } #endif ctx->env_var_proj_data = str; return str; } NS_PROJ_END // --------------------------------------------------------------------------- static void CreateDirectoryRecursively(PJ_CONTEXT *ctx, const std::string &path) { if (NS_PROJ::FileManager::exists(ctx, path.c_str())) return; auto pos = path.find_last_of("/\\"); if (pos == 0 || pos == std::string::npos) return; CreateDirectoryRecursively(ctx, path.substr(0, pos)); NS_PROJ::FileManager::mkdir(ctx, path.c_str()); } //! @endcond // --------------------------------------------------------------------------- /** Set a file API * * All callbacks should be provided (non NULL pointers). If read-only usage * is intended, then the callbacks might have a dummy implementation. * * \note Those callbacks will not be used for SQLite3 database access. If * custom I/O is desired for that, then proj_context_set_sqlite3_vfs_name() * should be used. * * @param ctx PROJ context, or NULL * @param fileapi Pointer to file API structure (content will be copied). * @param user_data Arbitrary pointer provided by the user, and passed to the * above callbacks. May be NULL. * @return TRUE in case of success. * @since 7.0 */ int proj_context_set_fileapi(PJ_CONTEXT *ctx, const PROJ_FILE_API *fileapi, void *user_data) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (!fileapi) { return false; } if (fileapi->version != 1) { return false; } if (!fileapi->open_cbk || !fileapi->close_cbk || !fileapi->read_cbk || !fileapi->write_cbk || !fileapi->seek_cbk || !fileapi->tell_cbk || !fileapi->exists_cbk || !fileapi->mkdir_cbk || !fileapi->unlink_cbk || !fileapi->rename_cbk) { return false; } ctx->fileApi.open_cbk = fileapi->open_cbk; ctx->fileApi.close_cbk = fileapi->close_cbk; ctx->fileApi.read_cbk = fileapi->read_cbk; ctx->fileApi.write_cbk = fileapi->write_cbk; ctx->fileApi.seek_cbk = fileapi->seek_cbk; ctx->fileApi.tell_cbk = fileapi->tell_cbk; ctx->fileApi.exists_cbk = fileapi->exists_cbk; ctx->fileApi.mkdir_cbk = fileapi->mkdir_cbk; ctx->fileApi.unlink_cbk = fileapi->unlink_cbk; ctx->fileApi.rename_cbk = fileapi->rename_cbk; ctx->fileApi.user_data = user_data; return true; } // --------------------------------------------------------------------------- /** Set the name of a custom SQLite3 VFS. * * This should be a valid SQLite3 VFS name, such as the one passed to the * sqlite3_vfs_register(). See https://www.sqlite.org/vfs.html * * It will be used to read proj.db or create&access the cache.db file in the * PROJ user writable directory. * * @param ctx PROJ context, or NULL * @param name SQLite3 VFS name. If NULL is passed, default implementation by * SQLite will be used. * @since 7.0 */ void proj_context_set_sqlite3_vfs_name(PJ_CONTEXT *ctx, const char *name) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } ctx->custom_sqlite3_vfs_name = name ? name : std::string(); } // --------------------------------------------------------------------------- /** Get the PROJ user writable directory for downloadable resource files, such * as datum shift grids. * * @param ctx PROJ context, or NULL * @param create If set to TRUE, create the directory if it does not exist * already. * @return The path to the PROJ user writable directory. * @since 7.1 * @see proj_context_set_user_writable_directory() */ const char *proj_context_get_user_writable_directory(PJ_CONTEXT *ctx, int create) { if (!ctx) ctx = pj_get_default_ctx(); if (ctx->user_writable_directory.empty()) { // For testing purposes only const char *env_var_PROJ_USER_WRITABLE_DIRECTORY = getenv("PROJ_USER_WRITABLE_DIRECTORY"); if (env_var_PROJ_USER_WRITABLE_DIRECTORY && env_var_PROJ_USER_WRITABLE_DIRECTORY[0] != '\0') { ctx->user_writable_directory = env_var_PROJ_USER_WRITABLE_DIRECTORY; } } if (ctx->user_writable_directory.empty()) { std::string path; #ifdef _WIN32 #ifdef __MINGW32__ std::wstring wPath; wPath.resize(MAX_PATH); if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, &wPath[0]) == S_OK) { wPath.resize(wcslen(wPath.data())); path = NS_PROJ::WStringToUTF8(wPath); #else #if UWP if (false) { #else // UWP wchar_t *wPath; if (SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &wPath) == S_OK) { std::wstring ws(wPath); std::string str = NS_PROJ::WStringToUTF8(ws); path = str; CoTaskMemFree(wPath); #endif // UWP #endif } else { const char *local_app_data = getenv("LOCALAPPDATA"); if (!local_app_data) { local_app_data = getenv("TEMP"); if (!local_app_data) { local_app_data = "c:/users"; } } path = local_app_data; } #else const char *xdg_data_home = getenv("XDG_DATA_HOME"); if (xdg_data_home != nullptr) { path = xdg_data_home; } else { const char *home = getenv("HOME"); if (home && access(home, W_OK) == 0) { #if defined(__MACH__) && defined(__APPLE__) path = std::string(home) + "/Library/Application Support"; #else path = std::string(home) + "/.local/share"; #endif } else { path = "/tmp"; } } #endif path += "/proj"; ctx->user_writable_directory = std::move(path); } if (create != FALSE) { CreateDirectoryRecursively(ctx, ctx->user_writable_directory); } return ctx->user_writable_directory.c_str(); } // --------------------------------------------------------------------------- /** Set the PROJ user writable directory for downloadable resource files, such * as datum shift grids. * * If not explicitly set, the following locations are used: *
    *
  • on Windows, ${LOCALAPPDATA}/proj
  • *
  • on macOS, ${HOME}/Library/Application Support/proj
  • *
  • on other platforms (Linux), ${XDG_DATA_HOME}/proj if XDG_DATA_HOME is * defined. Else ${HOME}/.local/share/proj
  • *
* * @param ctx PROJ context, or NULL * @param path Path to the PROJ user writable directory. If set to NULL, the * default location will be used. * @param create If set to TRUE, create the directory if it does not exist * already. * @since 9.5 * @see proj_context_get_user_writable_directory() */ void proj_context_set_user_writable_directory(PJ_CONTEXT *ctx, const char *path, int create) { if (!ctx) ctx = pj_get_default_ctx(); ctx->user_writable_directory = path ? path : ""; if (!path || create) { proj_context_get_user_writable_directory(ctx, create); } } // --------------------------------------------------------------------------- /** Get the URL endpoint to query for remote grids. * * @param ctx PROJ context, or NULL * @return Endpoint URL. The returned pointer would be invalidated * by a later call to proj_context_set_url_endpoint() * @since 7.1 */ const char *proj_context_get_url_endpoint(PJ_CONTEXT *ctx) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (!ctx->endpoint.empty()) { return ctx->endpoint.c_str(); } pj_load_ini(ctx); return ctx->endpoint.c_str(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- #ifdef WIN32 static const char dir_chars[] = "/\\"; #else static const char dir_chars[] = "/"; #endif static bool is_tilde_slash(const char *name) { return *name == '~' && strchr(dir_chars, name[1]); } static bool is_rel_or_absolute_filename(const char *name) { return strchr(dir_chars, *name) || (*name == '.' && strchr(dir_chars, name[1])) || (!strncmp(name, "..", 2) && strchr(dir_chars, name[2])) || (name[0] != '\0' && name[1] == ':' && strchr(dir_chars, name[2])); } // --------------------------------------------------------------------------- static std::string pj_get_relative_share_proj_internal_no_check() { #if defined(_WIN32) || defined(HAVE_LIBDL) #ifdef _WIN32 HMODULE hm = NULL; #if !UWP if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)&pj_get_relative_share_proj, &hm) == 0) { return std::string(); } #endif // UWP DWORD path_size = 1024; std::wstring wout; for (;;) { wout.clear(); wout.resize(path_size); DWORD result = GetModuleFileNameW(hm, &wout[0], path_size - 1); DWORD last_error = GetLastError(); if (result == 0) { return std::string(); } else if (result == path_size - 1) { if (ERROR_INSUFFICIENT_BUFFER != last_error) { return std::string(); } path_size = path_size * 2; } else { break; } } wout.resize(wcslen(wout.c_str())); std::string out = NS_PROJ::WStringToUTF8(wout); constexpr char dir_sep = '\\'; #else Dl_info info; if (!dladdr((void *)pj_get_relative_share_proj, &info)) { return std::string(); } std::string out(info.dli_fname); constexpr char dir_sep = '/'; // "optimization" for cmake builds where RUNPATH is set to ${prefix}/lib out = replaceAll(out, "/bin/../", "/"); #ifdef __linux // If we get a filename without any path, this is most likely a static // binary. Resolve the executable name if (out.find(dir_sep) == std::string::npos) { constexpr size_t BUFFER_SIZE = 1024; std::vector path(BUFFER_SIZE + 1); ssize_t nResultLen = readlink("/proc/self/exe", &path[0], BUFFER_SIZE); if (nResultLen >= 0 && static_cast(nResultLen) < BUFFER_SIZE) { out.assign(path.data(), static_cast(nResultLen)); } } #endif if (starts_with(out, "./")) out = out.substr(2); #endif auto pos = out.find_last_of(dir_sep); if (pos == std::string::npos) { // The initial path was something like libproj.so" out = "../share/proj"; return out; } out.resize(pos); pos = out.find_last_of(dir_sep); if (pos == std::string::npos) { // The initial path was something like bin/libproj.so" out = "share/proj"; return out; } out.resize(pos); // The initial path was something like foo/bin/libproj.so" out += "/share/proj"; return out; #else return std::string(); #endif } static std::string pj_get_relative_share_proj_internal_check_exists(PJ_CONTEXT *ctx) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } std::string path(pj_get_relative_share_proj_internal_no_check()); if (!path.empty() && NS_PROJ::FileManager::exists(ctx, path.c_str())) { return path; } return std::string(); } std::string pj_get_relative_share_proj(PJ_CONTEXT *ctx) { static std::string path( pj_get_relative_share_proj_internal_check_exists(ctx)); return path; } // --------------------------------------------------------------------------- static bool get_path_from_relative_share_proj(PJ_CONTEXT *ctx, const char *name, std::string &out) { out = pj_get_relative_share_proj(ctx); if (out.empty()) { return false; } out += '/'; out += name; return NS_PROJ::FileManager::exists(ctx, out.c_str()); } /************************************************************************/ /* pj_open_lib_internal() */ /************************************************************************/ #ifdef WIN32 static const char dirSeparator = ';'; #else static const char dirSeparator = ':'; #endif static const char *proj_data_name = #ifdef PROJ_DATA PROJ_DATA; #else nullptr; #endif #ifdef PROJ_DATA_ENV_VAR_TRIED_LAST static bool gbPROJ_DATA_ENV_VAR_TRIED_LAST = true; #else static bool gbPROJ_DATA_ENV_VAR_TRIED_LAST = false; #endif static bool dontReadUserWritableDirectory() { // Env var mostly for testing purposes and being independent from // an existing installation const char *envVar = getenv("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY"); return envVar != nullptr && envVar[0] != '\0'; } static void *pj_open_lib_internal( PJ_CONTEXT *ctx, const char *name, const char *mode, void *(*open_file)(PJ_CONTEXT *, const char *, const char *), char *out_full_filename, size_t out_full_filename_size) { try { std::string fname; void *fid = nullptr; const char *tmpname = nullptr; std::string projLib; if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (out_full_filename != nullptr && out_full_filename_size > 0) out_full_filename[0] = '\0'; auto open_lib_from_paths = [&ctx, open_file, &name, &fname, &mode](const std::string &projLibPaths) { void *lib_fid = nullptr; auto paths = NS_PROJ::internal::split(projLibPaths, dirSeparator); for (const auto &path : paths) { fname = NS_PROJ::internal::stripQuotes(path); fname += DIR_CHAR; fname += name; lib_fid = open_file(ctx, fname.c_str(), mode); if (lib_fid) break; } return lib_fid; }; /* check if ~/name */ if (is_tilde_slash(name)) if (const char *home = getenv("HOME")) { fname = home; fname += DIR_CHAR; fname += name; } else return nullptr; /* or fixed path: /name, ./name or ../name */ else if (is_rel_or_absolute_filename(name)) { fname = name; #ifdef _WIN32 try { NS_PROJ::UTF8ToWString(name); } catch (const std::exception &) { fname = NS_PROJ::Win32Recode(name, CP_ACP, CP_UTF8); } #endif } else if (starts_with(name, "http://") || starts_with(name, "https://")) fname = name; /* or try to use application provided file finder */ else if (ctx->file_finder != nullptr && (tmpname = ctx->file_finder( ctx, name, ctx->file_finder_user_data)) != nullptr) { fname = tmpname; } /* The user has search paths set */ else if (!ctx->search_paths.empty()) { for (const auto &path : ctx->search_paths) { try { fname = path; fname += DIR_CHAR; fname += name; fid = open_file(ctx, fname.c_str(), mode); } catch (const std::exception &) { } if (fid) break; } } else if (!dontReadUserWritableDirectory() && (fid = open_file( ctx, (std::string(proj_context_get_user_writable_directory( ctx, false)) + DIR_CHAR + name) .c_str(), mode)) != nullptr) { fname = proj_context_get_user_writable_directory(ctx, false); fname += DIR_CHAR; fname += name; } /* if the environment PROJ_DATA defined, and *not* tried as last possibility */ else if (!gbPROJ_DATA_ENV_VAR_TRIED_LAST && !(projLib = NS_PROJ::FileManager::getProjDataEnvVar(ctx)) .empty()) { fid = open_lib_from_paths(projLib); } else if (get_path_from_relative_share_proj(ctx, name, fname)) { /* check if it lives in a ../share/proj dir of the proj dll */ } else if (proj_data_name != nullptr && (fid = open_file( ctx, (std::string(proj_data_name) + DIR_CHAR + name).c_str(), mode)) != nullptr) { /* or hardcoded path */ fname = proj_data_name; fname += DIR_CHAR; fname += name; } /* if the environment PROJ_DATA defined, and tried as last possibility */ else if (gbPROJ_DATA_ENV_VAR_TRIED_LAST && !(projLib = NS_PROJ::FileManager::getProjDataEnvVar(ctx)) .empty()) { fid = open_lib_from_paths(projLib); } else { /* just try it bare bones */ fname = name; } if (fid != nullptr || (fid = open_file(ctx, fname.c_str(), mode)) != nullptr) { if (out_full_filename != nullptr && out_full_filename_size > 0) { // cppcheck-suppress nullPointer strncpy(out_full_filename, fname.c_str(), out_full_filename_size); out_full_filename[out_full_filename_size - 1] = '\0'; } errno = 0; } #if EMBED_RESOURCE_FILES if (!fid && fname != name && name[0] != '.' && name[0] != '/' && name[0] != '~' && !starts_with(name, "http://") && !starts_with(name, "https://")) { fid = open_file(ctx, name, mode); if (fid) { if (out_full_filename != nullptr && out_full_filename_size > 0) { // cppcheck-suppress nullPointer strncpy(out_full_filename, name, out_full_filename_size); out_full_filename[out_full_filename_size - 1] = '\0'; } fname = name; errno = 0; } } #endif if (ctx->last_errno == 0 && errno != 0) proj_context_errno_set(ctx, errno); pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): call fopen(%s) - %s", name, fname.c_str(), fid == nullptr ? "failed" : "succeeded"); return (fid); } catch (const std::exception &) { pj_log(ctx, PJ_LOG_DEBUG, "pj_open_lib(%s): out of memory", name); return nullptr; } } /************************************************************************/ /* pj_get_default_searchpaths() */ /************************************************************************/ std::vector pj_get_default_searchpaths(PJ_CONTEXT *ctx) { std::vector ret; // Env var mostly for testing purposes and being independent from // an existing installation const char *ignoreUserWritableDirectory = getenv("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY"); if (ignoreUserWritableDirectory == nullptr || ignoreUserWritableDirectory[0] == '\0') { ret.push_back(proj_context_get_user_writable_directory(ctx, false)); } std::string envPROJ_DATA = NS_PROJ::FileManager::getProjDataEnvVar(ctx); std::string relativeSharedProj = pj_get_relative_share_proj(ctx); if (gbPROJ_DATA_ENV_VAR_TRIED_LAST) { /* Situation where PROJ_DATA environment variable is tried in last */ #ifdef PROJ_DATA ret.push_back(PROJ_DATA); #endif if (!relativeSharedProj.empty()) { ret.push_back(std::move(relativeSharedProj)); } if (!envPROJ_DATA.empty()) { ret.push_back(std::move(envPROJ_DATA)); } } else { /* Situation where PROJ_DATA environment variable is used if defined */ if (!envPROJ_DATA.empty()) { ret.push_back(std::move(envPROJ_DATA)); } else { if (!relativeSharedProj.empty()) { ret.push_back(std::move(relativeSharedProj)); } #ifdef PROJ_DATA ret.push_back(PROJ_DATA); #endif } } return ret; } /************************************************************************/ /* pj_open_file_with_manager() */ /************************************************************************/ static void *pj_open_file_with_manager(PJ_CONTEXT *ctx, const char *name, const char * /* mode */) { return NS_PROJ::FileManager::open(ctx, name, NS_PROJ::FileAccess::READ_ONLY) .release(); } // --------------------------------------------------------------------------- static NS_PROJ::io::DatabaseContextPtr getDBcontext(PJ_CONTEXT *ctx) { try { return ctx->get_cpp_context()->getDatabaseContext().as_nullable(); } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return nullptr; } } /************************************************************************/ /* FileManager::open_resource_file() */ /************************************************************************/ std::unique_ptr NS_PROJ::FileManager::open_resource_file(PJ_CONTEXT *ctx, const char *name, char *out_full_filename, size_t out_full_filename_size) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } auto file = std::unique_ptr(reinterpret_cast( pj_open_lib_internal(ctx, name, "rb", pj_open_file_with_manager, out_full_filename, out_full_filename_size))); // Retry with the new proj grid name if the file name doesn't end with .tif std::string tmpString; // keep it in this upper scope ! if (file == nullptr && !is_tilde_slash(name) && !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") && !starts_with(name, "https://") && strcmp(name, "proj.db") != 0 && strstr(name, ".tif") == nullptr) { auto dbContext = getDBcontext(ctx); if (dbContext) { try { auto filename = dbContext->getProjGridName(name); if (!filename.empty()) { file.reset(reinterpret_cast( pj_open_lib_internal(ctx, filename.c_str(), "rb", pj_open_file_with_manager, out_full_filename, out_full_filename_size))); if (file) { proj_context_errno_set(ctx, 0); } else { // For final network access attempt, use the new // name. tmpString = std::move(filename); name = tmpString.c_str(); } } } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return nullptr; } } } // Retry with the old proj grid name if the file name ends with .tif else if (file == nullptr && !is_tilde_slash(name) && !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") && !starts_with(name, "https://") && strstr(name, ".tif") != nullptr) { auto dbContext = getDBcontext(ctx); if (dbContext) { try { const auto filename = dbContext->getOldProjGridName(name); if (!filename.empty()) { file.reset(reinterpret_cast( pj_open_lib_internal(ctx, filename.c_str(), "rb", pj_open_file_with_manager, out_full_filename, out_full_filename_size))); if (file) { proj_context_errno_set(ctx, 0); } } } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return nullptr; } } } if (file == nullptr && !is_tilde_slash(name) && !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") && !starts_with(name, "https://") && proj_context_is_network_enabled(ctx)) { std::string remote_file; auto dbContext = getDBcontext(ctx); if (dbContext) { try { std::string fullFilename, packageName, url; bool directDownload = false; bool openLicense = false; bool gridAvailable = false; proj_context_set_enable_network(ctx, false); // prevent recursion const bool found = dbContext->lookForGridInfo( name, /* considerKnownGridsAsAvailable = */ true, fullFilename, packageName, url, directDownload, openLicense, gridAvailable); proj_context_set_enable_network(ctx, true); if (found && !url.empty() && directDownload) { remote_file = url; if (starts_with(url, "https://cdn.proj.org/")) { std::string endpoint = proj_context_get_url_endpoint(ctx); if (!endpoint.empty()) { remote_file = std::move(endpoint); if (remote_file.back() != '/') { remote_file += '/'; } remote_file += name; } } } } catch (const std::exception &e) { proj_context_set_enable_network(ctx, true); pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return nullptr; } } if (remote_file.empty()) { remote_file = proj_context_get_url_endpoint(ctx); if (!remote_file.empty()) { if (remote_file.back() != '/') { remote_file += '/'; } remote_file += name; } } if (!remote_file.empty()) { file = open(ctx, remote_file.c_str(), NS_PROJ::FileAccess::READ_ONLY); if (file) { if (out_full_filename) { strncpy(out_full_filename, remote_file.c_str(), out_full_filename_size); out_full_filename[out_full_filename_size - 1] = '\0'; } pj_log(ctx, PJ_LOG_DEBUG, "Using %s", remote_file.c_str()); proj_context_errno_set(ctx, 0); } } } return file; } /************************************************************************/ /* pj_find_file() */ /************************************************************************/ /** Returns the full filename corresponding to a proj resource file specified * as a short filename. * * @param ctx context. * @param short_filename short filename (e.g. us_nga_egm96_15.tif). * Must not be NULL. * @param out_full_filename output buffer, of size out_full_filename_size, that * will receive the full filename on success. * Will be zero-terminated. * @param out_full_filename_size size of out_full_filename. * @return 1 if the file was found, 0 otherwise. */ int pj_find_file(PJ_CONTEXT *ctx, const char *short_filename, char *out_full_filename, size_t out_full_filename_size) { const auto iter = ctx->lookupedFiles.find(short_filename); if (iter != ctx->lookupedFiles.end()) { if (iter->second.empty()) { out_full_filename[0] = 0; return 0; } snprintf(out_full_filename, out_full_filename_size, "%s", iter->second.c_str()); return 1; } const bool old_network_enabled = proj_context_is_network_enabled(ctx) != FALSE; if (old_network_enabled) proj_context_set_enable_network(ctx, false); auto file = NS_PROJ::FileManager::open_resource_file( ctx, short_filename, out_full_filename, out_full_filename_size); if (old_network_enabled) proj_context_set_enable_network(ctx, true); if (file) { ctx->lookupedFiles[short_filename] = out_full_filename; } else { ctx->lookupedFiles[short_filename] = std::string(); } return file != nullptr; } /************************************************************************/ /* trim() */ /************************************************************************/ static std::string trim(const std::string &s) { const auto first = s.find_first_not_of(' '); const auto last = s.find_last_not_of(' '); if (first == std::string::npos || last == std::string::npos) { return std::string(); } return s.substr(first, last - first + 1); } /************************************************************************/ /* pj_load_ini() */ /************************************************************************/ void pj_load_ini(PJ_CONTEXT *ctx) { if (ctx->iniFileLoaded) return; // Start reading environment variables that have priority over the // .ini file const char *proj_network = getenv("PROJ_NETWORK"); if (proj_network && proj_network[0] != '\0') { ctx->networking.enabled = ci_equal(proj_network, "ON") || ci_equal(proj_network, "YES") || ci_equal(proj_network, "TRUE"); } else { proj_network = nullptr; } const char *endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT"); if (endpoint_from_env && endpoint_from_env[0] != '\0') { ctx->endpoint = endpoint_from_env; } // Custom path to SSL certificates. const char *ca_bundle_path = getenv("PROJ_CURL_CA_BUNDLE"); if (ca_bundle_path == nullptr) { // Name of environment variable used by the curl binary ca_bundle_path = getenv("CURL_CA_BUNDLE"); } if (ca_bundle_path == nullptr) { // Name of environment variable used by the curl binary (tested // after CURL_CA_BUNDLE ca_bundle_path = getenv("SSL_CERT_FILE"); } if (ca_bundle_path != nullptr) { ctx->ca_bundle_path = ca_bundle_path; } // Load default value for errorIfBestTransformationNotAvailableDefault // from environment first const char *proj_only_best_default = getenv("PROJ_ONLY_BEST_DEFAULT"); if (proj_only_best_default && proj_only_best_default[0] != '\0') { ctx->warnIfBestTransformationNotAvailableDefault = false; ctx->errorIfBestTransformationNotAvailableDefault = ci_equal(proj_only_best_default, "ON") || ci_equal(proj_only_best_default, "YES") || ci_equal(proj_only_best_default, "TRUE"); } const char *native_ca = getenv("PROJ_NATIVE_CA"); if (native_ca && native_ca[0] != '\0') { ctx->native_ca = ci_equal(native_ca, "ON") || ci_equal(native_ca, "YES") || ci_equal(native_ca, "TRUE"); } else { native_ca = nullptr; } ctx->iniFileLoaded = true; std::string content; auto file = std::unique_ptr( reinterpret_cast(pj_open_lib_internal( ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0))); if (file) { file->seek(0, SEEK_END); const auto filesize = file->tell(); if (filesize == 0 || filesize > 100 * 1024U) return; file->seek(0, SEEK_SET); content.resize(static_cast(filesize)); const auto nread = file->read(&content[0], content.size()); if (nread != content.size()) return; } content += '\n'; size_t pos = 0; while (pos != std::string::npos) { const auto eol = content.find_first_of("\r\n", pos); if (eol == std::string::npos) { break; } const auto equal = content.find('=', pos); if (equal < eol) { const auto key = trim(content.substr(pos, equal - pos)); auto value = trim(content.substr(equal + 1, eol - (equal + 1))); if (ctx->endpoint.empty() && key == "cdn_endpoint") { ctx->endpoint = std::move(value); } else if (proj_network == nullptr && key == "network") { ctx->networking.enabled = ci_equal(value, "ON") || ci_equal(value, "YES") || ci_equal(value, "TRUE"); } else if (key == "cache_enabled") { ctx->gridChunkCache.enabled = ci_equal(value, "ON") || ci_equal(value, "YES") || ci_equal(value, "TRUE"); } else if (key == "cache_size_MB") { const int val = atoi(value.c_str()); ctx->gridChunkCache.max_size = val > 0 ? static_cast(val) * 1024 * 1024 : -1; } else if (key == "cache_ttl_sec") { ctx->gridChunkCache.ttl = atoi(value.c_str()); } else if (key == "tmerc_default_algo") { if (value == "auto") { ctx->defaultTmercAlgo = TMercAlgo::AUTO; } else if (value == "evenden_snyder") { ctx->defaultTmercAlgo = TMercAlgo::EVENDEN_SNYDER; } else if (value == "poder_engsager") { ctx->defaultTmercAlgo = TMercAlgo::PODER_ENGSAGER; } else { pj_log( ctx, PJ_LOG_ERROR, "pj_load_ini(): Invalid value for tmerc_default_algo"); } } else if (ca_bundle_path == nullptr && key == "ca_bundle_path") { ctx->ca_bundle_path = std::move(value); } else if (proj_only_best_default == nullptr && key == "only_best_default") { ctx->warnIfBestTransformationNotAvailableDefault = false; ctx->errorIfBestTransformationNotAvailableDefault = ci_equal(value, "ON") || ci_equal(value, "YES") || ci_equal(value, "TRUE"); } else if (native_ca == nullptr && key == "native_ca") { ctx->native_ca = ci_equal(value, "ON") || ci_equal(value, "YES") || ci_equal(value, "TRUE"); } } pos = content.find_first_not_of("\r\n", eol); } } //! @endcond /************************************************************************/ /* proj_context_set_file_finder() */ /************************************************************************/ /** \brief Assign a file finder callback to a context. * * This callback will be used whenever PROJ must open one of its resource files * (proj.db database, grids, etc...) * * The callback will be called with the context currently in use at the moment * where it is used (not necessarily the one provided during this call), and * with the provided user_data (which may be NULL). * The user_data must remain valid during the whole lifetime of the context. * * A finder set on the default context will be inherited by contexts created * later. * * @param ctx PROJ context, or NULL for the default context. * @param finder Finder callback. May be NULL * @param user_data User data provided to the finder callback. May be NULL. * * @since PROJ 6.0 */ void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder, void *user_data) { if (!ctx) ctx = pj_get_default_ctx(); if (!ctx) return; ctx->file_finder = finder; ctx->file_finder_user_data = user_data; } /************************************************************************/ /* proj_context_set_search_paths() */ /************************************************************************/ /** \brief Sets search paths. * * Those search paths will be used whenever PROJ must open one of its resource * files * (proj.db database, grids, etc...) * * If set on the default context, they will be inherited by contexts created * later. * * Starting with PROJ 7.0, the path(s) should be encoded in UTF-8. * * @param ctx PROJ context, or NULL for the default context. * @param count_paths Number of paths. 0 if paths == NULL. * @param paths Paths. May be NULL. * * @since PROJ 6.0 */ void proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths, const char *const *paths) { if (!ctx) ctx = pj_get_default_ctx(); if (!ctx) return; try { std::vector vector_of_paths; for (int i = 0; i < count_paths; i++) { vector_of_paths.emplace_back(paths[i]); } ctx->set_search_paths(vector_of_paths); } catch (const std::exception &) { } } /************************************************************************/ /* proj_context_set_ca_bundle_path() */ /************************************************************************/ /** \brief Sets CA Bundle path. * * Those CA Bundle path will be used by PROJ when curl and PROJ_NETWORK * are enabled. * * If set on the default context, they will be inherited by contexts created * later. * * The path should be encoded in UTF-8. * * @param ctx PROJ context, or NULL for the default context. * @param path Path. May be NULL. * * @since PROJ 7.2 */ void proj_context_set_ca_bundle_path(PJ_CONTEXT *ctx, const char *path) { if (!ctx) ctx = pj_get_default_ctx(); if (!ctx) return; pj_load_ini(ctx); try { ctx->set_ca_bundle_path(path != nullptr ? path : ""); } catch (const std::exception &) { } } // --------------------------------------------------------------------------- void pj_stderr_proj_lib_deprecation_warning() { if (getenv("PROJ_LIB") != nullptr && getenv("PROJ_DATA") == nullptr) { fprintf(stderr, "DeprecationWarning: PROJ_LIB environment variable is " "deprecated, and will be removed in a future release. " "You are encouraged to set PROJ_DATA instead.\n"); } } proj-9.8.1/src/proj_constants.h000664 001750 001750 00000124021 15166171715 016461 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Constants * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PROJ_CONSTANTS_INCLUDED #define PROJ_CONSTANTS_INCLUDED /* Projection methods */ #define EPSG_NAME_METHOD_TRANSVERSE_MERCATOR "Transverse Mercator" #define EPSG_CODE_METHOD_TRANSVERSE_MERCATOR 9807 #define EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_3D "Transverse Mercator (3D)" #define EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_3D 1111 #define EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED \ "Transverse Mercator (South Orientated)" #define EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED 9808 #define PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT "Two Point Equidistant" #define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP \ "Lambert Conic Conformal (1SP)" #define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP 9801 #define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B \ "Lambert Conic Conformal (1SP variant B)" #define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B 1102 #define EPSG_NAME_METHOD_NZMG "New Zealand Map Grid" #define EPSG_CODE_METHOD_NZMG 9811 /* Deprecated because of wrong name. Use EPSG_xxx_METHOD_TUNISIA_MINING_GRID * instead */ #define EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID "Tunisia Mapping Grid" #define EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID 9816 #define EPSG_NAME_METHOD_TUNISIA_MINING_GRID "Tunisia Mining Grid" #define EPSG_CODE_METHOD_TUNISIA_MINING_GRID 9816 #define EPSG_NAME_METHOD_ALBERS_EQUAL_AREA "Albers Equal Area" #define EPSG_CODE_METHOD_ALBERS_EQUAL_AREA 9822 #define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP \ "Lambert Conic Conformal (2SP)" #define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP 9802 #define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM \ "Lambert Conic Conformal (2SP Belgium)" #define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM 9803 #define EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN \ "Lambert Conic Conformal (2SP Michigan)" #define EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN 1051 #define EPSG_NAME_METHOD_AZIMUTHAL_EQUIDISTANT "Azimuthal Equidistant" #define EPSG_CODE_METHOD_AZIMUTHAL_EQUIDISTANT 1125 #define EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT \ "Modified Azimuthal Equidistant" #define EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT 9832 #define EPSG_NAME_METHOD_GUAM_PROJECTION "Guam Projection" #define EPSG_CODE_METHOD_GUAM_PROJECTION 9831 #define EPSG_NAME_METHOD_BONNE "Bonne" #define EPSG_CODE_METHOD_BONNE 9827 #define PROJ_WKT2_NAME_METHOD_COMPACT_MILLER "Compact Miller" #define EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL \ "Lambert Cylindrical Equal Area (Spherical)" #define EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL 9834 #define EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA \ "Lambert Cylindrical Equal Area" #define EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA 9835 #define EPSG_NAME_METHOD_CASSINI_SOLDNER "Cassini-Soldner" #define EPSG_CODE_METHOD_CASSINI_SOLDNER 9806 #define EPSG_NAME_METHOD_HYPERBOLIC_CASSINI_SOLDNER "Hyperbolic Cassini-Soldner" #define EPSG_CODE_METHOD_HYPERBOLIC_CASSINI_SOLDNER 9833 #define PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC "Equidistant Conic" #define EPSG_NAME_METHOD_EQUIDISTANT_CONIC "Equidistant Conic" #define EPSG_CODE_METHOD_EQUIDISTANT_CONIC 1119 #define PROJ_WKT2_NAME_METHOD_ECKERT_I "Eckert I" #define PROJ_WKT2_NAME_METHOD_ECKERT_II "Eckert II" #define PROJ_WKT2_NAME_METHOD_ECKERT_III "Eckert III" #define PROJ_WKT2_NAME_METHOD_ECKERT_IV "Eckert IV" #define PROJ_WKT2_NAME_METHOD_ECKERT_V "Eckert V" #define PROJ_WKT2_NAME_METHOD_ECKERT_VI "Eckert VI" #define EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL "Equidistant Cylindrical" #define EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL 1028 #define EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL \ "Equidistant Cylindrical (Spherical)" #define EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL 1029 #define PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC "Flat Polar Quartic" #define PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC "Gall Stereographic" #define PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE "Goode Homolosine" #define PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE \ "Interrupted Goode Homolosine" #define PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN \ "Interrupted Goode Homolosine Ocean" #define PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X \ "Geostationary Satellite (Sweep X)" #define PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y \ "Geostationary Satellite (Sweep Y)" #define PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR \ "Gauss Schreiber Transverse Mercator" #define PROJ_WKT2_NAME_METHOD_GNOMONIC "Gnomonic" #define EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A \ "Hotine Oblique Mercator (variant A)" #define EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A 9812 #define EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B \ "Hotine Oblique Mercator (variant B)" #define EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B 9815 #define PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN \ "Hotine Oblique Mercator Two Point Natural Origin" #define PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC \ "International Map of the World Polyconic" #define EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED "Krovak (North Orientated)" #define EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED 1041 #define EPSG_NAME_METHOD_KROVAK "Krovak" #define EPSG_CODE_METHOD_KROVAK 9819 #define EPSG_NAME_METHOD_KROVAK_MODIFIED "Krovak Modified" #define EPSG_CODE_METHOD_KROVAK_MODIFIED 1042 #define EPSG_NAME_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED \ "Krovak Modified (North Orientated)" #define EPSG_CODE_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED 1043 #define EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA \ "Lambert Azimuthal Equal Area" #define EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA 9820 #define EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL \ "Lambert Azimuthal Equal Area (Spherical)" #define EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL 1027 #define PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL "Miller Cylindrical" #define EPSG_CODE_METHOD_MERCATOR_VARIANT_A 9804 #define EPSG_NAME_METHOD_MERCATOR_VARIANT_A "Mercator (variant A)" #define EPSG_CODE_METHOD_MERCATOR_VARIANT_B 9805 #define EPSG_NAME_METHOD_MERCATOR_VARIANT_B "Mercator (variant B)" #define EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR \ "Popular Visualisation Pseudo Mercator" #define EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR 1024 #define EPSG_NAME_METHOD_MERCATOR_SPHERICAL "Mercator (Spherical)" #define EPSG_CODE_METHOD_MERCATOR_SPHERICAL 1026 #define PROJ_WKT2_NAME_METHOD_MOLLWEIDE "Mollweide" #define PROJ_WKT2_NAME_METHOD_NATURAL_EARTH "Natural Earth" #define PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II "Natural Earth II" #define EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC "Oblique Stereographic" #define EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC 9809 #define EPSG_NAME_METHOD_ORTHOGRAPHIC "Orthographic" #define EPSG_CODE_METHOD_ORTHOGRAPHIC 9840 #define EPSG_NAME_METHOD_LOCAL_ORTHOGRAPHIC "Local Orthographic" #define EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC 1130 #define PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL "Orthographic (Spherical)" #define PROJ_WKT2_NAME_METHOD_PATTERSON "Patterson" #define EPSG_NAME_METHOD_AMERICAN_POLYCONIC "American Polyconic" #define EPSG_CODE_METHOD_AMERICAN_POLYCONIC 9818 #define EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A \ "Polar Stereographic (variant A)" #define EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A 9810 #define EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B \ "Polar Stereographic (variant B)" #define EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B 9829 #define PROJ_WKT2_NAME_METHOD_ROBINSON "Robinson" #define PROJ_WKT2_NAME_METHOD_SINUSOIDAL "Sinusoidal" #define PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC "Stereographic" #define PROJ_WKT2_NAME_METHOD_TIMES "Times" #define PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN "Van Der Grinten" #define PROJ_WKT2_NAME_METHOD_WAGNER_I "Wagner I" #define PROJ_WKT2_NAME_METHOD_WAGNER_II "Wagner II" #define PROJ_WKT2_NAME_METHOD_WAGNER_III "Wagner III" #define PROJ_WKT2_NAME_METHOD_WAGNER_IV "Wagner IV" #define PROJ_WKT2_NAME_METHOD_WAGNER_V "Wagner V" #define PROJ_WKT2_NAME_METHOD_WAGNER_VI "Wagner VI" #define PROJ_WKT2_NAME_METHOD_WAGNER_VII "Wagner VII" #define PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE \ "Quadrilateralized Spherical Cube" #define PROJ_WKT2_NAME_METHOD_S2 "S2" #define PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT \ "Spherical Cross-Track Height" #define EPSG_NAME_METHOD_EQUAL_EARTH "Equal Earth" #define EPSG_CODE_METHOD_EQUAL_EARTH 1078 #define EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR "Laborde Oblique Mercator" #define EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR 9813 #define EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE "Vertical Perspective" #define EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE 9838 #define PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION \ "Pole rotation (GRIB convention)" #define PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION \ "Pole rotation (netCDF CF convention)" #define EPSG_CODE_METHOD_COLOMBIA_URBAN 1052 #define EPSG_NAME_METHOD_COLOMBIA_URBAN "Colombia Urban" #define PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE \ "Peirce Quincuncial (Square)" #define PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND \ "Peirce Quincuncial (Diamond)" /* ------------------------------------------------------------------------ */ /* Projection parameters */ #define EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS "Co-latitude of cone axis" #define EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS 1036 #define EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN \ "Latitude of natural origin" #define EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN 8801 #define EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN \ "Longitude of natural origin" #define EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN 8802 #define EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN \ "Scale factor at natural origin" #define EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN 8805 #define EPSG_NAME_PARAMETER_FALSE_EASTING "False easting" #define EPSG_CODE_PARAMETER_FALSE_EASTING 8806 #define EPSG_NAME_PARAMETER_FALSE_NORTHING "False northing" #define EPSG_CODE_PARAMETER_FALSE_NORTHING 8807 #define EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE \ "Latitude of projection centre" #define EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE 8811 #define EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE \ "Longitude of projection centre" #define EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE 8812 // Before EPSG 11.015 #define EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE "Azimuth of initial line" #define EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE 8813 // Since EPSG 11.015 #define EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE \ "Azimuth at projection centre" #define EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE 8813 #define EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID \ "Angle from Rectified to Skew Grid" #define EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID 8814 // Before EPSG 11.015 #define EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE \ "Scale factor on initial line" #define EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE 8815 // Since EPSG 11.015 #define EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE \ "Scale factor at projection centre" #define EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE 8815 #define EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE \ "Easting at projection centre" #define EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE 8816 #define EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE \ "Northing at projection centre" #define EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE 8817 #define EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL \ "Latitude of pseudo standard parallel" #define EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL 8818 #define EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL \ "Scale factor on pseudo standard parallel" #define EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL 8819 #define EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN "Latitude of false origin" #define EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN 8821 #define EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN "Longitude of false origin" #define EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN 8822 #define EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL \ "Latitude of 1st standard parallel" #define EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL 8823 #define EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL \ "Latitude of 2nd standard parallel" #define EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL 8824 #define EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN "Easting at false origin" #define EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN 8826 #define EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN "Northing at false origin" #define EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN 8827 #define EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL \ "Latitude of standard parallel" #define EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL 8832 #define EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN "Longitude of origin" #define EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN 8833 #define EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR "Ellipsoid scaling factor" #define EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR 1038 #define EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN \ "Latitude of topocentric origin" #define EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN 8834 #define EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN \ "Longitude of topocentric origin" #define EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN 8835 #define EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN \ "Ellipsoidal height of topocentric origin" #define EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN 8836 #define EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT "Viewpoint height" #define EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT 8840 #define EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT \ "Projection plane origin height" #define EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT 1039 /* ------------------------------------------------------------------------ */ /* Other conversions and transformations */ #define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC \ "Coordinate Frame rotation (geocentric domain)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC 1032 #define EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC \ "Coordinate Frame rotation full matrix (geocen)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC 1132 #define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D \ "Coordinate Frame rotation (geog2D domain)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D 9607 #define EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D \ "Coordinate Frame rotation full matrix (geog2D)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D 1133 #define EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D \ "Coordinate Frame rotation full matrix (geog3D)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D 1140 #define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D \ "Coordinate Frame rotation (geog3D domain)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D 1038 #define EPSG_NAME_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND \ "Coordinate Frame rotation (geog3D to compound)" #define EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND 1149 #define EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC \ "Position Vector transformation (geocentric domain)" #define EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC 1033 #define EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D \ "Position Vector transformation (geog2D domain)" #define EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D 9606 #define EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D \ "Position Vector transformation (geog3D domain)" #define EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D 1037 #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC \ "Geocentric translations (geocentric domain)" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC 1031 #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D \ "Geocentric translations (geog2D domain)" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D 9603 #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D \ "Geocentric translations (geog3D domain)" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D 1035 #define EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC \ "Time-dependent Position Vector tfm (geocentric)" #define EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC 1053 #define EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D \ "Time-dependent Position Vector tfm (geog2D)" #define EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D 1054 #define EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D \ "Time-dependent Position Vector tfm (geog3D)" #define EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D 1055 #define EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC \ "Time-dependent Coordinate Frame rotation geocen)" #define EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC 1056 #define EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D \ "Time-dependent Coordinate Frame rotation (geog2D)" #define EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D 1057 #define EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D \ "Time-dependent Coordinate Frame rotation (geog3D)" #define EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D 1058 #define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC \ "Molodensky-Badekas (CF geocentric domain)" #define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC 1034 #define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC \ "Molodensky-Badekas (PV geocentric domain)" #define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC 1061 #define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D \ "Molodensky-Badekas (CF geog3D domain)" #define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D 1039 #define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D \ "Molodensky-Badekas (PV geog3D domain)" #define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D 1062 #define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D \ "Molodensky-Badekas (CF geog2D domain)" #define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D 9636 #define EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D \ "Molodensky-Badekas (PV geog2D domain)" #define EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D 1063 #define EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION 8605 #define EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION 8606 #define EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION 8607 #define EPSG_CODE_PARAMETER_X_AXIS_ROTATION 8608 #define EPSG_CODE_PARAMETER_Y_AXIS_ROTATION 8609 #define EPSG_CODE_PARAMETER_Z_AXIS_ROTATION 8610 #define EPSG_CODE_PARAMETER_SCALE_DIFFERENCE 8611 #define EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION 1040 #define EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION 1041 #define EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION 1042 #define EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION 1043 #define EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION 1044 #define EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION 1045 #define EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE 1046 #define EPSG_CODE_PARAMETER_REFERENCE_EPOCH 1047 #define EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH 1049 #define EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION "X-axis translation" #define EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION "Y-axis translation" #define EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION "Z-axis translation" #define EPSG_NAME_PARAMETER_X_AXIS_ROTATION "X-axis rotation" #define EPSG_NAME_PARAMETER_Y_AXIS_ROTATION "Y-axis rotation" #define EPSG_NAME_PARAMETER_Z_AXIS_ROTATION "Z-axis rotation" #define EPSG_NAME_PARAMETER_SCALE_DIFFERENCE "Scale difference" #define EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION \ "Rate of change of X-axis translation" #define EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION \ "Rate of change of Y-axis translation" #define EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION \ "Rate of change of Z-axis translation" #define EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION \ "Rate of change of X-axis rotation" #define EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION \ "Rate of change of Y-axis rotation" #define EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION \ "Rate of change of Z-axis rotation" #define EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE \ "Rate of change of Scale difference" #define EPSG_NAME_PARAMETER_REFERENCE_EPOCH "Parameter reference epoch" #define EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT 8617 #define EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT 8618 #define EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT 8667 #define EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT \ "Ordinate 1 of evaluation point" #define EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT \ "Ordinate 2 of evaluation point" #define EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT \ "Ordinate 3 of evaluation point" #define EPSG_NAME_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH \ "Transformation reference epoch" #define EPSG_NAME_METHOD_MOLODENSKY "Molodensky" #define EPSG_CODE_METHOD_MOLODENSKY 9604 #define EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY "Abridged Molodensky" #define EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY 9605 #define EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE 8654 #define EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE 8655 #define EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE \ "Semi-major axis length difference" #define EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE "Flattening difference" #define PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION \ "Latitude of the southern pole (GRIB convention)" #define PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION \ "Longitude of the southern pole (GRIB convention)" #define PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION \ "Axis rotation (GRIB convention)" #define PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION \ "Grid north pole latitude (netCDF CF convention)" #define PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION \ "Grid north pole longitude (netCDF CF convention)" #define PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION \ "North pole grid longitude (netCDF CF convention)" /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_NTV1 9614 #define EPSG_NAME_METHOD_NTV1 "NTv1" #define EPSG_CODE_METHOD_NTV2 9615 #define EPSG_NAME_METHOD_NTV2 "NTv2" #define EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE 8656 #define EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE \ "Latitude and longitude difference file" #define EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME \ "Geoid (height correction) model file" #define EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME 8666 /* Before EPSG 12.019 */ #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN \ "Geocentric translation by Grid Interpolation (IGN)" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN 1087 /* Since EPSG 12.019 */ #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN \ "Geocentric translations (geog2D domain) by grid (IGN)" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN 1087 #define EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE 8727 #define EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE \ "Geocentric translation file" #define EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS \ "EPSG code for Interpolation CRS" #define EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS 1048 /* ------------------------------------------------------------------------ */ #define EPSG_NAME_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL \ "Point motion by grid (Canada NTv2_Vel)" #define EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL 1070 // Before EPSG 12.019 #define EPSG_NAME_METHOD_POINT_MOTION_BY_GRID_CANADA_NEU_DOMAIN_NTV2_VEL \ "Point motion by grid (NEU domain) (NTv2_Vel)" #define EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NEU_DOMAIN_NTV2_VEL 1141 // Since EPSG 12.019 #define EPSG_NAME_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL \ "Point motion (geog3D domain) using NEU velocity grid (NTv2_Vel)" #define EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL \ 1141 #define EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE 1050 #define EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE \ "Point motion velocity grid file" #define EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT \ 1139 #define EPSG_NAME_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT \ "Point motion (geocen domain) using NEU velocity grid (Gravsoft)" #define EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE 1072 #define EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE \ "Point motion velocity north grid file" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG \ 1142 #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG \ "Geocen translations by grid (gtg) & Geocen translations NEU " \ "velocities (gtg)" #define EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG \ 1143 #define EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG \ "Position Vector (geocen) & Geocen translations NEU velocities (gtg)" #define EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG \ 1144 #define EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG \ "Geocentric translations using NEU velocity grid (gtg)" #define EPSG_CODE_PARAMETER_SOURCE_EPOCH 1068 #define EPSG_NAME_PARAMETER_SOURCE_EPOCH "Source epoch" #define EPSG_CODE_PARAMETER_TARGET_EPOCH 1069 #define EPSG_NAME_PARAMETER_TARGET_EPOCH "Target epoch" /* ------------------------------------------------------------------------ */ #define EPSG_NAME_METHOD_NEW_ZEALAND_DEFORMATION_MODEL \ "New Zealand Deformation Model" #define EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL 1079 /* ------------------------------------------------------------------------ */ /* Has been renamed to * EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL */ #define EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NRCAN \ "Geographic3D Offset by velocity grid (NRCan byn)" #define EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NRCAN 1114 #define EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL \ "Geographic3D Offset by velocity grid (NTv2_Vel)" #define EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL 1114 /* ------------------------------------------------------------------------ */ /* Before EPSG 12.019 */ #define EPSG_NAME_METHOD_VERTICAL_OFFSET_BY_VELOCITY_GRID_NRCAN \ "Vertical Offset by velocity grid (NRCan NTv2_Vel)" #define EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_VELOCITY_GRID_NRCAN 1113 /* Since EPSG 12.019 */ #define EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL \ "Vertical Offset using NEU velocity grid (NTv2_Vel)" #define EPSG_CODE_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL 1113 /* ------------------------------------------------------------------------ */ #define EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT \ "Geographic3D to GravityRelatedHeight" #define EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT 1136 #define EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT \ "Geog3D to Geog2D+GravityRelatedHeight" #define EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT 1131 /* ------------------------------------------------------------------------ */ #define PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D \ "GravityRelatedHeight to Geographic3D" #define PROJ_WKT2_NAME_METHOD_CTABLE2 "CTABLE2" #define PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF "HORIZONTAL_SHIFT_GTIFF" #define PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF "GENERAL_SHIFT_GTIFF" #define PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE \ "Latitude, longitude and ellipsoidal height difference file" /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_VERTCON 9658 #define EPSG_NAME_METHOD_VERTCON_OLDNAME "VERTCON" #define EPSG_NAME_METHOD_VERTCON \ "Vertical Offset by Grid Interpolation (VERTCON)" #define EPSG_CODE_METHOD_VERTICALGRID_NZLVD 1071 #define EPSG_NAME_METHOD_VERTICALGRID_NZLVD \ "Vertical Offset by Grid Interpolation (NZLVD)" #define EPSG_CODE_METHOD_VERTICALGRID_BEV_AT 1080 #define EPSG_NAME_METHOD_VERTICALGRID_BEV_AT \ "Vertical Offset by Grid Interpolation (BEV AT)" #define EPSG_CODE_METHOD_VERTICALGRID_GTX 1084 #define EPSG_NAME_METHOD_VERTICALGRID_GTX \ "Vertical Offset by Grid Interpolation (gtx)" #define EPSG_CODE_METHOD_VERTICALGRID_ASC 1085 #define EPSG_NAME_METHOD_VERTICALGRID_ASC \ "Vertical Offset by Grid Interpolation (asc)" #define EPSG_CODE_METHOD_VERTICALGRID_GTG 1129 #define EPSG_NAME_METHOD_VERTICALGRID_GTG \ "Vertical Offset by Grid Interpolation (gtg)" #define EPSG_CODE_METHOD_VERTICALGRID_PL_TXT 1101 #define EPSG_NAME_METHOD_VERTICALGRID_PL_TXT \ "Vertical Offset by Grid Interpolation (PL txt)" /* has been deprecated by * EPSG_CODE_METHOD_VERTICALCHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN */ #define EPSG_CODE_METHOD_VERTICALGRID_NRCAN_BYN 1112 #define EPSG_NAME_METHOD_VERTICALGRID_NRCAN_BYN \ "Vertical Offset by Grid Interpolation (NRCan byn)" #define EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE "Vertical offset file" #define EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE 8732 #define EPSG_CODE_METHOD_VERTICALCHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN 1126 #define EPSG_NAME_METHOD_VERTICALCHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN \ "Vertical change by geoid grid difference (NRCan)" #define EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE \ "Geoid model difference file" #define EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE 1063 /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_NADCON 9613 #define EPSG_NAME_METHOD_NADCON "NADCON" #define EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE "Latitude difference file" #define EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE 8657 #define EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE \ "Longitude difference file" #define EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE 8658 #define EPSG_CODE_METHOD_NADCON5_2D 1074 #define EPSG_NAME_METHOD_NADCON5_2D "NADCON5 (2D)" #define EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_DIFFERENCE_FILE \ "Ellipsoidal height difference file" #define EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_DIFFERENCE_FILE 1058 #define EPSG_CODE_METHOD_NADCON5_3D 1075 #define EPSG_NAME_METHOD_NADCON5_3D "NADCON5 (3D)" /* ------------------------------------------------------------------------ */ /* TIN-based transformations */ #define EPSG_NAME_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON \ "Vertical Offset by TIN Interpolation (JSON)" #define EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON 1137 #define EPSG_NAME_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON \ "Cartesian Grid Offsets by TIN Interpolation (JSON)" #define EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON 1138 #define EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON \ "Geographic2D Offsets by TIN Interpolation (JSON)" #define EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON 1145 #define EPSG_NAME_PARAMETER_TIN_OFFSET_FILE "TIN offset file" #define EPSG_CODE_PARAMETER_TIN_OFFSET_FILE 1064 /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT 1069 #define EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT "Change of Vertical Unit" #define EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR 1104 #define EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR \ "Change of Vertical Unit" #define EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR "Unit conversion scalar" #define EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR 1051 /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_LONGITUDE_ROTATION 9601 #define EPSG_NAME_METHOD_LONGITUDE_ROTATION "Longitude rotation" #define EPSG_CODE_METHOD_VERTICAL_OFFSET 9616 #define EPSG_NAME_METHOD_VERTICAL_OFFSET "Vertical Offset" #define EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE 1046 #define EPSG_NAME_METHOD_VERTICAL_OFFSET_AND_SLOPE "Vertical Offset and Slope" #define EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS 9619 #define EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS "Geographic2D offsets" #define EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS 9618 #define EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS \ "Geographic2D with Height Offsets" #define EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS 9660 #define EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS "Geographic3D offsets" #define EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC 9602 #define EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC \ "Geographic/geocentric conversions" #define EPSG_NAME_PARAMETER_LATITUDE_OFFSET "Latitude offset" #define EPSG_CODE_PARAMETER_LATITUDE_OFFSET 8601 #define EPSG_NAME_PARAMETER_LONGITUDE_OFFSET "Longitude offset" #define EPSG_CODE_PARAMETER_LONGITUDE_OFFSET 8602 #define EPSG_NAME_PARAMETER_VERTICAL_OFFSET "Vertical Offset" #define EPSG_CODE_PARAMETER_VERTICAL_OFFSET 8603 #define EPSG_NAME_PARAMETER_GEOID_HEIGHT "Geoid height" #define EPSG_CODE_PARAMETER_GEOID_HEIGHT 8604 /* Geoid undulation is the name before EPSG v11.023 */ #define EPSG_NAME_PARAMETER_GEOID_UNDULATION "Geoid undulation" #define EPSG_CODE_PARAMETER_GEOID_UNDULATION 8604 #define EPSG_NAME_PARAMETER_INCLINATION_IN_LATITUDE "Inclination in latitude" #define EPSG_CODE_PARAMETER_INCLINATION_IN_LATITUDE 8730 #define EPSG_NAME_PARAMETER_INCLINATION_IN_LONGITUDE "Inclination in longitude" #define EPSG_CODE_PARAMETER_INCLINATION_IN_LONGITUDE 8731 #define EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS \ "EPSG code for Horizontal CRS" #define EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS 1037 /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION 9624 #define EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION \ "Affine parametric transformation" #define EPSG_NAME_PARAMETER_A0 "A0" #define EPSG_CODE_PARAMETER_A0 8623 #define EPSG_NAME_PARAMETER_A1 "A1" #define EPSG_CODE_PARAMETER_A1 8624 #define EPSG_NAME_PARAMETER_A2 "A2" #define EPSG_CODE_PARAMETER_A2 8625 #define EPSG_NAME_PARAMETER_B0 "B0" #define EPSG_CODE_PARAMETER_B0 8639 #define EPSG_NAME_PARAMETER_B1 "B1" #define EPSG_CODE_PARAMETER_B1 8640 #define EPSG_NAME_PARAMETER_B2 "B2" #define EPSG_CODE_PARAMETER_B2 8641 /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION 9621 #define EPSG_NAME_METHOD_SIMILARITY_TRANSFORMATION "Similarity transformation" #define EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS \ "Ordinate 1 of evaluation point in target CRS" #define EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS 8621 #define EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS \ "Ordinate 2 of evaluation point in target CRS" #define EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS 8622 #define EPSG_NAME_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES \ "Scale factor for source CRS axes" #define EPSG_CODE_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES 1061 #define EPSG_NAME_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES \ "Rotation angle of source CRS axes" #define EPSG_CODE_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES 8614 /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D 9843 #define EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D "Axis Order Reversal (2D)" #define EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D 9844 #define EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D \ "Axis Order Reversal (Geographic3D horizontal)" /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL 1068 #define EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL "Height Depth Reversal" /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS 9656 #define EPSG_NAME_METHOD_CARTESIAN_GRID_OFFSETS "Cartesian Grid Offsets" #define EPSG_CODE_PARAMETER_EASTING_OFFSET 8728 #define EPSG_NAME_PARAMETER_EASTING_OFFSET "Easting offset" #define EPSG_CODE_PARAMETER_NORTHING_OFFSET 8729 #define EPSG_NAME_PARAMETER_NORTHING_OFFSET "Northing offset" /* ------------------------------------------------------------------------ */ #define EPSG_NAME_METHOD_GEOCENTRIC_TOPOCENTRIC \ "Geocentric/topocentric conversions" #define EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC 9836 #define EPSG_NAME_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN \ "Geocentric X of topocentric origin" #define EPSG_CODE_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN 8837 #define EPSG_NAME_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN \ "Geocentric Y of topocentric origin" #define EPSG_CODE_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN 8838 #define EPSG_NAME_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN \ "Geocentric Z of topocentric origin" #define EPSG_CODE_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN 8839 /* ------------------------------------------------------------------------ */ #define EPSG_NAME_METHOD_GEOGRAPHIC_TOPOCENTRIC \ "Geographic/topocentric conversions" #define EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC 9837 /* ------------------------------------------------------------------------ */ #define PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE \ "Geographic latitude / Geocentric latitude" #endif /* PROJ_CONSTANTS_INCLUDED */ proj-9.8.1/src/units.cpp000664 001750 001750 00000004205 15166171715 015111 0ustar00eveneven000000 000000 /* definition of standard cartesian units */ #include #include "proj.h" #include "proj_internal.h" /* Field 2 that contains the multiplier to convert named units to meters ** may be expressed by either a simple floating point constant or a ** numerator/denomenator values (e.g. 1/1000) */ static const struct PJ_UNITS pj_units[] = { {"km", "1000", "Kilometer", 1000.0}, {"m", "1", "Meter", 1.0}, {"dm", "1/10", "Decimeter", 0.1}, {"cm", "1/100", "Centimeter", 0.01}, {"mm", "1/1000", "Millimeter", 0.001}, {"kmi", "1852", "International Nautical Mile", 1852.0}, {"in", "0.0254", "International Inch", 0.0254}, {"ft", "0.3048", "International Foot", 0.3048}, {"yd", "0.9144", "International Yard", 0.9144}, {"mi", "1609.344", "International Statute Mile", 1609.344}, {"fath", "1.8288", "International Fathom", 1.8288}, {"ch", "20.1168", "International Chain", 20.1168}, {"link", "0.201168", "International Link", 0.201168}, {"us-in", "1/39.37", "U.S. Surveyor's Inch", 100 / 3937.0}, {"us-ft", "0.304800609601219", "U.S. Surveyor's Foot", 1200 / 3937.0}, {"us-yd", "0.914401828803658", "U.S. Surveyor's Yard", 3600 / 3937.0}, {"us-ch", "20.11684023368047", "U.S. Surveyor's Chain", 79200 / 3937.0}, {"us-mi", "1609.347218694437", "U.S. Surveyor's Statute Mile", 6336000 / 3937.0}, {"ind-yd", "0.91439523", "Indian Yard", 0.91439523}, {"ind-ft", "0.30479841", "Indian Foot", 0.30479841}, {"ind-ch", "20.11669506", "Indian Chain", 20.11669506}, {nullptr, nullptr, nullptr, 0.0}}; // For internal use const PJ_UNITS *pj_list_linear_units() { return pj_units; } const PJ_UNITS *proj_list_units() { return pj_units; } /* M_PI / 200 */ #define GRAD_TO_RAD 0.015707963267948967 const struct PJ_UNITS pj_angular_units[] = { {"rad", "1.0", "Radian", 1.0}, {"deg", "0.017453292519943296", "Degree", DEG_TO_RAD}, {"grad", "0.015707963267948967", "Grad", GRAD_TO_RAD}, {nullptr, nullptr, nullptr, 0.0}}; // For internal use const PJ_UNITS *pj_list_angular_units() { return pj_angular_units; } const PJ_UNITS *proj_list_angular_units() { return pj_angular_units; } proj-9.8.1/src/tracing.cpp000664 001750 001750 00000015423 15166171715 015402 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Tracing/profiling * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifdef ENABLE_TRACING #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include #include "proj/internal/internal.hpp" #include "proj/internal/tracing.hpp" //! @cond Doxygen_Suppress #if defined(_WIN32) && !defined(__CYGWIN__) #include #else #include /* for gettimeofday() */ #define CPLTimeVal timeval #define CPLGettimeofday(t, u) gettimeofday(t, u) #endif NS_PROJ_START using namespace internal; namespace tracing { #if defined(_WIN32) && !defined(__CYGWIN__) struct CPLTimeVal { time_t tv_sec; /* seconds */ long tv_usec; /* and microseconds */ }; // --------------------------------------------------------------------------- static void CPLGettimeofday(struct CPLTimeVal *tp, void * /* timezonep*/) { struct _timeb theTime; _ftime(&theTime); tp->tv_sec = static_cast(theTime.time); tp->tv_usec = theTime.millitm * 1000; } #endif struct Singleton { FILE *f = nullptr; int callLevel = 0; int minDelayMicroSec = 10 * 1000; // 10 millisec long long startTimeStamp = 0; std::string componentsWhiteList{}; std::string componentsBlackList{}; Singleton(); ~Singleton(); Singleton(const Singleton &) = delete; Singleton &operator=(const Singleton &) = delete; void logTraceRaw(const std::string &str); }; // --------------------------------------------------------------------------- Singleton::Singleton() { const char *traceFile = getenv("PROJ_TRACE_FILE"); if (traceFile) f = fopen(traceFile, "wb"); if (!f) f = stderr; const char *minDelay = getenv("PROJ_TRACE_MIN_DELAY"); if (minDelay) { minDelayMicroSec = atoi(minDelay); } const char *whiteList = getenv("PROJ_TRACE_WHITE_LIST"); if (whiteList) { componentsWhiteList = whiteList; } const char *blackList = getenv("PROJ_TRACE_BLACK_LIST"); if (blackList) { componentsBlackList = blackList; } CPLTimeVal ts; CPLGettimeofday(&ts, nullptr); startTimeStamp = static_cast(ts.tv_sec) * 1000000 + ts.tv_usec; logTraceRaw(""); ++callLevel; } // --------------------------------------------------------------------------- Singleton::~Singleton() { --callLevel; logTraceRaw(""); fflush(f); if (f != stderr) fclose(f); } // --------------------------------------------------------------------------- static Singleton &getSingleton() { static Singleton singleton; return singleton; } // --------------------------------------------------------------------------- void Singleton::logTraceRaw(const std::string &str) { CPLTimeVal ts; CPLGettimeofday(&ts, nullptr); const auto ts_usec = static_cast(ts.tv_sec) * 1000000 + ts.tv_usec; fprintf(f, " ", static_cast((ts_usec - startTimeStamp) / 1000000), static_cast((ts_usec - startTimeStamp) % 1000000)); for (int i = 0; i < callLevel; i++) fprintf(f, " "); fprintf(f, "%s\n", str.c_str()); fflush(f); } // --------------------------------------------------------------------------- void logTrace(const std::string &str, const std::string &component) { auto &singleton = getSingleton(); if (!singleton.componentsWhiteList.empty() && (component.empty() || singleton.componentsWhiteList.find(component) == std::string::npos)) { return; } if (!singleton.componentsBlackList.empty() && !component.empty() && singleton.componentsBlackList.find(component) != std::string::npos) { return; } std::string rawStr("msg_ = msg; CPLGettimeofday(&d->startTimeStamp_, nullptr); singleton.logTraceRaw(""); ++singleton.callLevel; singleton.logTraceRaw("" + d->msg_ + ""); } // --------------------------------------------------------------------------- EnterBlock::~EnterBlock() { auto &singleton = getSingleton(); CPLTimeVal endTimeStamp; CPLGettimeofday(&endTimeStamp, nullptr); int delayMicroSec = static_cast( (endTimeStamp.tv_usec - d->startTimeStamp_.tv_usec) + 1000000 * (endTimeStamp.tv_sec - d->startTimeStamp_.tv_sec)); std::string lengthStr; if (delayMicroSec >= singleton.minDelayMicroSec) { lengthStr = " length='" + toString(delayMicroSec / 1000) + "." + toString((delayMicroSec % 1000) / 100) + " msec'"; } singleton.logTraceRaw("" + d->msg_ + ""); --singleton.callLevel; singleton.logTraceRaw(""); } } // namespace tracing NS_PROJ_END //! @endcond #endif // ENABLE_TRACING proj-9.8.1/src/area.cpp000664 001750 001750 00000004375 15166171715 014667 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: PJ_AREA related code * * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 * ****************************************************************************** * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" /** Create an area of use */ PJ_AREA *proj_area_create(void) { return new PJ_AREA(); } /** Assign a bounding box to an area of use. */ void proj_area_set_bbox(PJ_AREA *area, double west_lon_degree, double south_lat_degree, double east_lon_degree, double north_lat_degree) { area->bbox_set = TRUE; area->west_lon_degree = west_lon_degree; area->south_lat_degree = south_lat_degree; area->east_lon_degree = east_lon_degree; area->north_lat_degree = north_lat_degree; } /** Assign the name of an area of use. */ void proj_area_set_name(PJ_AREA *area, const char *name) { area->name = name; } /** Free an area of use */ void proj_area_destroy(PJ_AREA *area) { delete area; } proj-9.8.1/src/log.cpp000664 001750 001750 00000016241 15166171715 014533 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of pj_log() function. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2010, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include #include #include "proj.h" #include "proj_internal.h" /************************************************************************/ /* pj_stderr_logger() */ /************************************************************************/ void pj_stderr_logger(void *app_data, int level, const char *msg) { (void)app_data; (void)level; fprintf(stderr, "%s\n", msg); } /************************************************************************/ /* pj_log_active() */ /************************************************************************/ bool pj_log_active(PJ_CONTEXT *ctx, int level) { int debug_level = ctx->debug_level; int shutup_unless_errno_set = debug_level < 0; /* For negative debug levels, we first start logging when errno is set */ if (ctx->last_errno == 0 && shutup_unless_errno_set) return false; if (debug_level < 0) debug_level = -debug_level; if (level > debug_level) return false; return true; } /************************************************************************/ /* pj_vlog() */ /************************************************************************/ static void pj_vlog(PJ_CONTEXT *ctx, int level, const PJ *P, const char *fmt, va_list args) { char *msg_buf; if (!pj_log_active(ctx, level)) return; constexpr size_t BUF_SIZE = 100000; msg_buf = (char *)malloc(BUF_SIZE); if (msg_buf == nullptr) return; if (P == nullptr || P->short_name == nullptr) vsnprintf(msg_buf, BUF_SIZE, fmt, args); else { std::string fmt_with_P_short_name(P->short_name); fmt_with_P_short_name += ": "; fmt_with_P_short_name += fmt; vsnprintf(msg_buf, BUF_SIZE, fmt_with_P_short_name.c_str(), args); } msg_buf[BUF_SIZE - 1] = '\0'; ctx->logger(ctx->logger_app_data, level, msg_buf); free(msg_buf); } /************************************************************************/ /* pj_log() */ /************************************************************************/ void pj_log(PJ_CONTEXT *ctx, int level, const char *fmt, ...) { va_list args; if (level > ctx->debug_level) return; va_start(args, fmt); pj_vlog(ctx, level, nullptr, fmt, args); va_end(args); } /***************************************************************************************/ PJ_LOG_LEVEL proj_log_level(PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { /**************************************************************************************** Set logging level 0-3. Higher number means more debug info. 0 turns it off ****************************************************************************************/ PJ_LOG_LEVEL previous; if (nullptr == ctx) ctx = pj_get_default_ctx(); if (nullptr == ctx) return PJ_LOG_TELL; previous = static_cast(abs(ctx->debug_level)); if (PJ_LOG_TELL == log_level) return previous; ctx->debug_level = log_level; return previous; } /*****************************************************************************/ void proj_log_error(const PJ *P, const char *fmt, ...) { /****************************************************************************** For reporting the most severe events. ******************************************************************************/ va_list args; va_start(args, fmt); pj_vlog(pj_get_ctx((PJ *)P), PJ_LOG_ERROR, P, fmt, args); va_end(args); } /*****************************************************************************/ void proj_log_debug(PJ *P, const char *fmt, ...) { /****************************************************************************** For reporting debugging information. ******************************************************************************/ va_list args; va_start(args, fmt); pj_vlog(pj_get_ctx(P), PJ_LOG_DEBUG, P, fmt, args); va_end(args); } /*****************************************************************************/ void proj_context_log_debug(PJ_CONTEXT *ctx, const char *fmt, ...) { /****************************************************************************** For reporting debugging information. ******************************************************************************/ va_list args; va_start(args, fmt); pj_vlog(ctx, PJ_LOG_DEBUG, nullptr, fmt, args); va_end(args); } /*****************************************************************************/ void proj_log_trace(PJ *P, const char *fmt, ...) { /****************************************************************************** For reporting embarrassingly detailed debugging information. ******************************************************************************/ va_list args; va_start(args, fmt); pj_vlog(pj_get_ctx(P), PJ_LOG_TRACE, P, fmt, args); va_end(args); } /*****************************************************************************/ void proj_log_func(PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { /****************************************************************************** Put a new logging function into P's context. The opaque object app_data is passed as first arg at each call to the logger ******************************************************************************/ if (nullptr == ctx) ctx = pj_get_default_ctx(); ctx->logger_app_data = app_data; if (nullptr != logf) ctx->logger = logf; } proj-9.8.1/src/proj_experimental.h000664 001750 001750 00000003546 15166171715 017152 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Experimental C API * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PROJ_EXPERIMENTAL_H #define PROJ_EXPERIMENTAL_H #ifdef __cplusplus extern "C" { #endif #include "proj.h" /** * \file proj_experimental.h * * Experimental C API (none currently) * * \warning * This API has been considered now to be experimental, and may change or * be removed in the future. */ #ifdef __cplusplus } #endif #endif /* ndef PROJ_EXPERIMENTAL_H */ proj-9.8.1/src/wkt1_parser.h000664 001750 001750 00000004054 15166171715 015660 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: WKT1 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2013, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PJ_WKT1_PARSER_H_INCLUDED #define PJ_WKT1_PARSER_H_INCLUDED #ifndef DOXYGEN_SKIP #ifdef __cplusplus extern "C" { #endif typedef struct pj_wkt1_parse_context pj_wkt1_parse_context; #include "wkt1_generated_parser.h" void pj_wkt1_error(pj_wkt1_parse_context *context, const char *msg); int pj_wkt1_lex(YYSTYPE *pNode, pj_wkt1_parse_context *context); int pj_wkt1_parse(pj_wkt1_parse_context *context); #ifdef __cplusplus } std::string pj_wkt1_parse(const std::string &wkt); #endif #endif /* #ifndef DOXYGEN_SKIP */ #endif /* PJ_WKT1_PARSER_H_INCLUDED */ proj-9.8.1/src/grids.hpp000664 001750 001750 00000025326 15166171715 015073 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Grid management * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2019, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef GRIDS_HPP_INCLUDED #define GRIDS_HPP_INCLUDED #include #include #include "proj.h" #include "proj/util.hpp" NS_PROJ_START struct ExtentAndRes { bool isGeographic; // whether extent and resolutions are in a geographic or // projected CRS double west; // in radian for geographic, in CRS units otherwise double south; // in radian for geographic, in CRS units otherwise double east; // in radian for geographic, in CRS units otherwise double north; // in radian for geographic, in CRS units otherwise double resX; // in radian for geographic, in CRS units otherwise double resY; // in radian for geographic, in CRS units otherwise double invResX; // = 1 / resX; double invResY; // = 1 / resY; void computeInvRes(); bool fullWorldLongitude() const; bool contains(const ExtentAndRes &other) const; bool intersects(const ExtentAndRes &other) const; }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL Grid { protected: friend class GTiffDataset; std::string m_name; int m_width; int m_height; ExtentAndRes m_extent; Grid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn); public: PROJ_FOR_TEST virtual ~Grid(); PROJ_FOR_TEST int width() const { return m_width; } PROJ_FOR_TEST int height() const { return m_height; } PROJ_FOR_TEST const ExtentAndRes &extentAndRes() const { return m_extent; } PROJ_FOR_TEST const std::string &name() const { return m_name; } virtual const std::string &metadataItem(const std::string &key, int sample = -1) const = 0; PROJ_FOR_TEST virtual bool isNullGrid() const { return false; } PROJ_FOR_TEST virtual bool hasChanged() const = 0; }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL VerticalShiftGrid : public Grid { protected: std::vector> m_children{}; public: PROJ_FOR_TEST VerticalShiftGrid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn); PROJ_FOR_TEST ~VerticalShiftGrid() override; PROJ_FOR_TEST const VerticalShiftGrid *gridAt(double longitude, double lat) const; PROJ_FOR_TEST virtual bool isNodata(float /*val*/, double /* multiplier */) const = 0; // x = 0 is western-most column, y = 0 is southern-most line PROJ_FOR_TEST virtual bool valueAt(int x, int y, float &out) const = 0; PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0; }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL VerticalShiftGridSet { protected: std::string m_name{}; std::string m_format{}; std::vector> m_grids{}; VerticalShiftGridSet(); public: PROJ_FOR_TEST virtual ~VerticalShiftGridSet(); PROJ_FOR_TEST static std::unique_ptr open(PJ_CONTEXT *ctx, const std::string &filename); PROJ_FOR_TEST const std::string &name() const { return m_name; } PROJ_FOR_TEST const std::string &format() const { return m_format; } PROJ_FOR_TEST const std::vector> & grids() const { return m_grids; } PROJ_FOR_TEST const VerticalShiftGrid *gridAt(double longitude, double lat) const; PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx); PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx); }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL HorizontalShiftGrid : public Grid { protected: std::vector> m_children{}; public: PROJ_FOR_TEST HorizontalShiftGrid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn); PROJ_FOR_TEST ~HorizontalShiftGrid() override; PROJ_FOR_TEST const HorizontalShiftGrid *gridAt(double longitude, double lat) const; // x = 0 is western-most column, y = 0 is southern-most line PROJ_FOR_TEST virtual bool valueAt(int x, int y, bool compensateNTConvention, float &longShift, float &latShift) const = 0; PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0; }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL HorizontalShiftGridSet { protected: std::string m_name{}; std::string m_format{}; std::vector> m_grids{}; HorizontalShiftGridSet(); public: PROJ_FOR_TEST virtual ~HorizontalShiftGridSet(); PROJ_FOR_TEST static std::unique_ptr open(PJ_CONTEXT *ctx, const std::string &filename); PROJ_FOR_TEST const std::string &name() const { return m_name; } PROJ_FOR_TEST const std::string &format() const { return m_format; } PROJ_FOR_TEST const std::vector> & grids() const { return m_grids; } PROJ_FOR_TEST const HorizontalShiftGrid *gridAt(double longitude, double lat) const; PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx); PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx); }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL GenericShiftGrid : public Grid { protected: std::vector> m_children{}; public: PROJ_FOR_TEST GenericShiftGrid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn); PROJ_FOR_TEST ~GenericShiftGrid() override; PROJ_FOR_TEST const GenericShiftGrid *gridAt(double x, double y) const; virtual const std::string &type() const = 0; PROJ_FOR_TEST virtual std::string unit(int sample) const = 0; PROJ_FOR_TEST virtual std::string description(int sample) const = 0; PROJ_FOR_TEST virtual int samplesPerPixel() const = 0; // x = 0 is western-most column, y = 0 is southern-most line PROJ_FOR_TEST virtual bool valueAt(int x, int y, int sample, float &out) const = 0; PROJ_FOR_TEST virtual bool valuesAt(int x_start, int y_start, int x_count, int y_count, int sample_count, const int *sample_idx, float *out, bool &nodataFound) const; PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0; }; // --------------------------------------------------------------------------- class PROJ_GCC_DLL GenericShiftGridSet { protected: std::string m_name{}; std::string m_format{}; std::vector> m_grids{}; GenericShiftGridSet(); public: PROJ_FOR_TEST virtual ~GenericShiftGridSet(); PROJ_FOR_TEST static std::unique_ptr open(PJ_CONTEXT *ctx, const std::string &filename); PROJ_FOR_TEST const std::string &name() const { return m_name; } PROJ_FOR_TEST const std::string &format() const { return m_format; } PROJ_FOR_TEST const std::vector> & grids() const { return m_grids; } PROJ_FOR_TEST const GenericShiftGrid *gridAt(double x, double y) const; PROJ_FOR_TEST const GenericShiftGrid *gridAt(const std::string &type, double x, double y) const; PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx); PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx); }; // --------------------------------------------------------------------------- typedef std::vector> ListOfHGrids; typedef std::vector> ListOfVGrids; typedef std::vector> ListOfGenericGrids; ListOfVGrids pj_vgrid_init(PJ *P, const char *grids); ListOfHGrids pj_hgrid_init(PJ *P, const char *grids); ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *grids); PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp); double pj_vgrid_value(PJ *P, const ListOfVGrids &, PJ_LP lp, double vmultiplier); PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp, PJ_DIRECTION direction); const GenericShiftGrid *pj_find_generic_grid(const ListOfGenericGrids &grids, const PJ_LP &input, GenericShiftGridSet *&gridSetOut); bool pj_bilinear_interpolation_three_samples( PJ_CONTEXT *ctx, const GenericShiftGrid *grid, const PJ_LP &lp, int idx1, int idx2, int idx3, double &v1, double &v2, double &v3, bool &must_retry); NS_PROJ_END #endif // GRIDS_HPP_INCLUDED proj-9.8.1/src/proj.h000664 001750 001750 00000275552 15166171715 014405 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Revised, experimental API for PROJ.4, intended as the foundation * for added geodetic functionality. * * The original proj API (defined previously in projects.h) has grown * organically over the years, but it has also grown somewhat messy. * * The same has happened with the newer high level API (defined in * proj_api.h): To support various historical objectives, proj_api.h * contains a rather complex combination of conditional defines and * typedefs. Probably for good (historical) reasons, which are not * always evident from today's perspective. * * This is an evolving attempt at creating a re-rationalized API * with primary design goals focused on sanitizing the namespaces. * Hence, all symbols exposed are being moved to the proj_ namespace, * while all data types are being moved to the PJ_ namespace. * * Please note that this API is *orthogonal* to the previous APIs: * Apart from some inclusion guards, projects.h and proj_api.h are not * touched - if you do not include proj.h, the projects and proj_api * APIs should work as they always have. * * A few implementation details: * * Apart from the namespacing efforts, I'm trying to eliminate three * proj_api elements, which I have found especially confusing. * * FIRST and foremost, I try to avoid typedef'ing away pointer * semantics. I agree that it can be occasionally useful, but I * prefer having the pointer nature of function arguments being * explicitly visible. * * Hence, projCtx has been replaced by PJ_CONTEXT *. * and projPJ has been replaced by PJ * * * SECOND, I try to eliminate cases of information hiding implemented * by redefining data types to void pointers. * * I prefer using a combination of forward declarations and typedefs. * Hence: * typedef void *projCtx; * Has been replaced by: * struct projCtx_t; * typedef struct projCtx_t PJ_CONTEXT; * This makes it possible for the calling program to know that the * PJ_CONTEXT data type exists, and handle pointers to that data type * without having any idea about its internals. * * (obviously, in this example, struct projCtx_t should also be * renamed struct pj_ctx some day, but for backwards compatibility * it remains as-is for now). * * THIRD, I try to eliminate implicit type punning. Hence this API * introduces the PJ_COORD union data type, for generic 4D coordinate * handling. * * PJ_COORD makes it possible to make explicit the previously used * "implicit type punning", where a XY is turned into a LP by * re#defining both as UV, behind the back of the user. * * The PJ_COORD union is used for storing 1D, 2D, 3D and 4D *coordinates. * * The bare essentials API presented here follows the PROJ.4 * convention of sailing the coordinate to be reprojected, up on * the stack ("call by value"), and symmetrically returning the * result on the stack. Although the PJ_COORD object is twice as large * as the traditional XY and LP objects, timing results have shown the * overhead to be very reasonable. * * Contexts and thread safety * -------------------------- * * After a year of experiments (and previous experience from the * trlib transformation library) it has become clear that the * context subsystem is unavoidable in a multi-threaded world. * Hence, instead of hiding it away, we move it into the limelight, * highly recommending (but not formally requiring) the bracketing * of any code block calling PROJ.4 functions with calls to * proj_context_create(...)/proj_context_destroy() * * Legacy single threaded code need not do anything, but *may* * implement a bit of future compatibility by using the backward * compatible call proj_context_create(0), which will not create * a new context, but simply provide a pointer to the default one. * * See proj_4D_api_test.c for examples of how to use the API. * * Author: Thomas Knudsen, * Benefitting from a large number of comments and suggestions * by (primarily) Kristian Evers and Even Rouault. * ****************************************************************************** * Copyright (c) 2016, 2017, Thomas Knudsen / SDFE * Copyright (c) 2018, Even Rouault * * 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 COORD SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef PROJ_H #define PROJ_H #include /* For size_t */ #ifdef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H #error "The proj_api.h header has been removed from PROJ with version 8.0.0" #endif #ifdef PROJ_RENAME_SYMBOLS #include "proj_symbol_rename.h" #endif #ifdef __cplusplus extern "C" { #endif /** * \file proj.h * * C API new generation */ /*! @cond Doxygen_Suppress */ #ifndef PROJ_DLL #if defined(_MSC_VER) #ifdef PROJ_MSVC_DLL_EXPORT #define PROJ_DLL __declspec(dllexport) #else #define PROJ_DLL __declspec(dllimport) #endif #elif defined(__GNUC__) #define PROJ_DLL __attribute__((visibility("default"))) #else #define PROJ_DLL #endif #endif #ifdef PROJ_SUPPRESS_DEPRECATION_MESSAGE #define PROJ_DEPRECATED(decl, msg) decl #elif defined(__has_extension) #if __has_extension(attribute_deprecated_with_message) #define PROJ_DEPRECATED(decl, msg) decl __attribute__((deprecated(msg))) #elif defined(__GNUC__) #define PROJ_DEPRECATED(decl, msg) decl __attribute__((deprecated)) #else #define PROJ_DEPRECATED(decl, msg) decl #endif #elif defined(__GNUC__) #define PROJ_DEPRECATED(decl, msg) decl __attribute__((deprecated)) #elif defined(_MSVC_VER) #define PROJ_DEPRECATED(decl, msg) __declspec(deprecated(msg)) decl #else #define PROJ_DEPRECATED(decl, msg) decl #endif /* The version numbers should be updated with every release! **/ #define PROJ_VERSION_MAJOR 9 #define PROJ_VERSION_MINOR 8 #define PROJ_VERSION_PATCH 1 /* Note: the following 3 defines have been introduced in PROJ 8.0.1 */ /* Macro to compute a PROJ version number from its components */ #define PROJ_COMPUTE_VERSION(maj, min, patch) \ ((maj)*10000 + (min)*100 + (patch)) /* Current PROJ version from the above version numbers */ #define PROJ_VERSION_NUMBER \ PROJ_COMPUTE_VERSION(PROJ_VERSION_MAJOR, PROJ_VERSION_MINOR, \ PROJ_VERSION_PATCH) /* Macro that returns true if the current PROJ version is at least the version * specified by (maj,min,patch) */ #define PROJ_AT_LEAST_VERSION(maj, min, patch) \ (PROJ_VERSION_NUMBER >= PROJ_COMPUTE_VERSION(maj, min, patch)) extern char const PROJ_DLL pj_release[]; /* global release id string */ /* first forward declare everything needed */ /* Data type for generic geodetic 3D data plus epoch information */ union PJ_COORD; typedef union PJ_COORD PJ_COORD; struct PJ_AREA; typedef struct PJ_AREA PJ_AREA; struct P5_FACTORS { /* Common designation */ double meridional_scale; /* h */ double parallel_scale; /* k */ double areal_scale; /* s */ double angular_distortion; /* omega */ double meridian_parallel_angle; /* theta-prime */ double meridian_convergence; /* alpha */ double tissot_semimajor; /* a */ double tissot_semiminor; /* b */ double dx_dlam, dx_dphi; double dy_dlam, dy_dphi; }; typedef struct P5_FACTORS PJ_FACTORS; /* Data type for projection/transformation information */ struct PJconsts; typedef struct PJconsts PJ; /* the PJ object herself */ /* Data type for library level information */ struct PJ_INFO; typedef struct PJ_INFO PJ_INFO; struct PJ_PROJ_INFO; typedef struct PJ_PROJ_INFO PJ_PROJ_INFO; struct PJ_GRID_INFO; typedef struct PJ_GRID_INFO PJ_GRID_INFO; struct PJ_INIT_INFO; typedef struct PJ_INIT_INFO PJ_INIT_INFO; /* Data types for list of operations, ellipsoids, datums and units used in * PROJ.4 */ struct PJ_LIST { const char *id; /* projection keyword */ PJ *(*proj)(PJ *); /* projection entry point */ const char *const *descr; /* description text */ }; typedef struct PJ_LIST PJ_OPERATIONS; struct PJ_ELLPS { const char *id; /* ellipse keyword name */ const char *major; /* a= value */ const char *ell; /* elliptical parameter */ const char *name; /* comments */ }; typedef struct PJ_ELLPS PJ_ELLPS; struct PJ_UNITS { const char *id; /* units keyword */ const char *to_meter; /* multiply by value to get meters */ const char *name; /* comments */ double factor; /* to_meter factor in actual numbers */ }; typedef struct PJ_UNITS PJ_UNITS; struct PJ_PRIME_MERIDIANS { const char *id; /* prime meridian keyword */ const char *defn; /* offset from greenwich in DMS format. */ }; typedef struct PJ_PRIME_MERIDIANS PJ_PRIME_MERIDIANS; /* Geodetic, mostly spatiotemporal coordinate types */ typedef struct { double x, y, z, t; } PJ_XYZT; typedef struct { double u, v, w, t; } PJ_UVWT; typedef struct { double lam, phi, z, t; } PJ_LPZT; typedef struct { double o, p, k; } PJ_OPK; /* Rotations: omega, phi, kappa */ typedef struct { double e, n, u; } PJ_ENU; /* East, North, Up */ typedef struct { double s, a1, a2; } PJ_GEOD; /* Geodesic length, fwd azi, rev azi */ /* Classic proj.4 pair/triplet types - moved into the PJ_ name space */ typedef struct { double u, v; } PJ_UV; typedef struct { double x, y; } PJ_XY; typedef struct { double lam, phi; } PJ_LP; typedef struct { double x, y, z; } PJ_XYZ; typedef struct { double u, v, w; } PJ_UVW; typedef struct { double lam, phi, z; } PJ_LPZ; /* Avoid preprocessor renaming and implicit type-punning: Use a union to make it * explicit */ union PJ_COORD { double v[4]; /* First and foremost, it really is "just 4 numbers in a vector" */ PJ_XYZT xyzt; PJ_UVWT uvwt; PJ_LPZT lpzt; PJ_GEOD geod; PJ_OPK opk; PJ_ENU enu; PJ_XYZ xyz; PJ_UVW uvw; PJ_LPZ lpz; PJ_XY xy; PJ_UV uv; PJ_LP lp; }; struct PJ_INFO { int major; /* Major release number */ int minor; /* Minor release number */ int patch; /* Patch level */ const char *release; /* Release info. Version + date */ const char *version; /* Full version number */ const char *searchpath; /* Paths where init and grid files are */ /* looked for. Paths are separated by */ /* semi-colons on Windows, and colons */ /* on non-Windows platforms. */ const char *const *paths; size_t path_count; }; struct PJ_PROJ_INFO { const char *id; /* Name of the projection in question */ const char *description; /* Description of the projection */ const char *definition; /* Projection definition */ int has_inverse; /* 1 if an inverse mapping exists, 0 otherwise */ double accuracy; /* Expected accuracy of the transformation. -1 if unknown. */ }; struct PJ_GRID_INFO { char gridname[32]; /* name of grid */ char filename[260]; /* full path to grid */ char format[8]; /* file format of grid */ PJ_LP lowerleft; /* Coordinates of lower left corner */ PJ_LP upperright; /* Coordinates of upper right corner */ int n_lon, n_lat; /* Grid size */ double cs_lon, cs_lat; /* Cell size of grid */ }; struct PJ_INIT_INFO { char name[32]; /* name of init file */ char filename[260]; /* full path to the init file. */ char version[32]; /* version of the init file */ char origin[32]; /* origin of the file, e.g. EPSG */ char lastupdate[16]; /* Date of last update in YYYY-MM-DD format */ }; typedef enum PJ_LOG_LEVEL { PJ_LOG_NONE = 0, PJ_LOG_ERROR = 1, PJ_LOG_DEBUG = 2, PJ_LOG_TRACE = 3, PJ_LOG_TELL = 4, PJ_LOG_DEBUG_MAJOR = 2, /* for proj_api.h compatibility */ PJ_LOG_DEBUG_MINOR = 3 /* for proj_api.h compatibility */ } PJ_LOG_LEVEL; typedef void (*PJ_LOG_FUNCTION)(void *, int, const char *); /* The context type - properly namespaced synonym for pj_ctx */ struct pj_ctx; typedef struct pj_ctx PJ_CONTEXT; /* A P I */ /** * The objects returned by the functions defined in this section have minimal * interaction with the functions of the * \ref iso19111_functions section, and vice versa. See its introduction * paragraph for more details. */ /* Functionality for handling thread contexts */ #ifdef __cplusplus #define PJ_DEFAULT_CTX nullptr #else #define PJ_DEFAULT_CTX 0 #endif PJ_CONTEXT PROJ_DLL *proj_context_create(void); PJ_CONTEXT PROJ_DLL *proj_context_destroy(PJ_CONTEXT *ctx); PJ_CONTEXT PROJ_DLL *proj_context_clone(PJ_CONTEXT *ctx); /** Callback to resolve a filename to a full path */ typedef const char *(*proj_file_finder)(PJ_CONTEXT *ctx, const char *, void *user_data); /*! @endcond */ void PROJ_DLL proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder, void *user_data); void PROJ_DLL proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths, const char *const *paths); void PROJ_DLL proj_context_set_ca_bundle_path(PJ_CONTEXT *ctx, const char *path); /*! @cond Doxygen_Suppress */ void PROJ_DLL proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable); int PROJ_DLL proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path); /*! @endcond */ /** Opaque structure for PROJ for a file handle. Implementations might cast it * to their structure/class of choice. */ typedef struct PROJ_FILE_HANDLE PROJ_FILE_HANDLE; /** Open access / mode */ typedef enum PROJ_OPEN_ACCESS { /** Read-only access. Equivalent to "rb" */ PROJ_OPEN_ACCESS_READ_ONLY, /** Read-update access. File should be created if not existing. Equivalent to "r+b" */ PROJ_OPEN_ACCESS_READ_UPDATE, /** Create access. File should be truncated to 0-byte if already existing. Equivalent to "w+b" */ PROJ_OPEN_ACCESS_CREATE } PROJ_OPEN_ACCESS; /** File API callbacks */ typedef struct PROJ_FILE_API { /** Version of this structure. Should be set to 1 currently. */ int version; /** Open file. Return NULL if error */ PROJ_FILE_HANDLE *(*open_cbk)(PJ_CONTEXT *ctx, const char *filename, PROJ_OPEN_ACCESS access, void *user_data); /** Read sizeBytes into buffer from current position and return number of * bytes read */ size_t (*read_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, void *buffer, size_t sizeBytes, void *user_data); /** Write sizeBytes into buffer from current position and return number of * bytes written */ size_t (*write_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, const void *buffer, size_t sizeBytes, void *user_data); /** Seek to offset using whence=SEEK_SET/SEEK_CUR/SEEK_END. Return TRUE in * case of success */ int (*seek_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, long long offset, int whence, void *user_data); /** Return current file position */ unsigned long long (*tell_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, void *user_data); /** Close file */ void (*close_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE *, void *user_data); /** Return TRUE if a file exists */ int (*exists_cbk)(PJ_CONTEXT *ctx, const char *filename, void *user_data); /** Return TRUE if directory exists or could be created */ int (*mkdir_cbk)(PJ_CONTEXT *ctx, const char *filename, void *user_data); /** Return TRUE if file could be removed */ int (*unlink_cbk)(PJ_CONTEXT *ctx, const char *filename, void *user_data); /** Return TRUE if file could be renamed */ int (*rename_cbk)(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath, void *user_data); } PROJ_FILE_API; int PROJ_DLL proj_context_set_fileapi(PJ_CONTEXT *ctx, const PROJ_FILE_API *fileapi, void *user_data); void PROJ_DLL proj_context_set_sqlite3_vfs_name(PJ_CONTEXT *ctx, const char *name); /** Opaque structure for PROJ for a network handle. Implementations might cast * it to their structure/class of choice. */ typedef struct PROJ_NETWORK_HANDLE PROJ_NETWORK_HANDLE; /** Network access: open callback * * Should try to read the size_to_read first bytes at the specified offset of * the file given by URL url, * and write them to buffer. *out_size_read should be updated with the actual * amount of bytes read (== size_to_read if the file is larger than * size_to_read). During this read, the implementation should make sure to store * the HTTP headers from the server response to be able to respond to * proj_network_get_header_value_cbk_type callback. * * error_string_max_size should be the maximum size that can be written into * the out_error_string buffer (including terminating nul character). * * @return a non-NULL opaque handle in case of success. */ typedef PROJ_NETWORK_HANDLE *(*proj_network_open_cbk_type)( PJ_CONTEXT *ctx, const char *url, unsigned long long offset, size_t size_to_read, void *buffer, size_t *out_size_read, size_t error_string_max_size, char *out_error_string, void *user_data); /** Network access: close callback */ typedef void (*proj_network_close_cbk_type)(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, void *user_data); /** Network access: get HTTP headers */ typedef const char *(*proj_network_get_header_value_cbk_type)( PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, const char *header_name, void *user_data); /** Network access: read range * * Read size_to_read bytes from handle, starting at offset, into * buffer. * During this read, the implementation should make sure to store the HTTP * headers from the server response to be able to respond to * proj_network_get_header_value_cbk_type callback. * * error_string_max_size should be the maximum size that can be written into * the out_error_string buffer (including terminating nul character). * * @return the number of bytes actually read (0 in case of error) */ typedef size_t (*proj_network_read_range_type)( PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, unsigned long long offset, size_t size_to_read, void *buffer, size_t error_string_max_size, char *out_error_string, void *user_data); int PROJ_DLL proj_context_set_network_callbacks( PJ_CONTEXT *ctx, proj_network_open_cbk_type open_cbk, proj_network_close_cbk_type close_cbk, proj_network_get_header_value_cbk_type get_header_value_cbk, proj_network_read_range_type read_range_cbk, void *user_data); int PROJ_DLL proj_context_set_enable_network(PJ_CONTEXT *ctx, int enabled); int PROJ_DLL proj_context_is_network_enabled(PJ_CONTEXT *ctx); void PROJ_DLL proj_context_set_url_endpoint(PJ_CONTEXT *ctx, const char *url); const char PROJ_DLL *proj_context_get_url_endpoint(PJ_CONTEXT *ctx); const char PROJ_DLL *proj_context_get_user_writable_directory(PJ_CONTEXT *ctx, int create); void PROJ_DLL proj_context_set_user_writable_directory(PJ_CONTEXT *ctx, const char *path, int create); void PROJ_DLL proj_grid_cache_set_enable(PJ_CONTEXT *ctx, int enabled); void PROJ_DLL proj_grid_cache_set_filename(PJ_CONTEXT *ctx, const char *fullname); void PROJ_DLL proj_grid_cache_set_max_size(PJ_CONTEXT *ctx, int max_size_MB); void PROJ_DLL proj_grid_cache_set_ttl(PJ_CONTEXT *ctx, int ttl_seconds); void PROJ_DLL proj_grid_cache_clear(PJ_CONTEXT *ctx); int PROJ_DLL proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename, int ignore_ttl_setting); int PROJ_DLL proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, int ignore_ttl_setting, int (*progress_cbk)(PJ_CONTEXT *, double pct, void *user_data), void *user_data); /*! @cond Doxygen_Suppress */ /* Manage the transformation definition object PJ */ PJ PROJ_DLL *proj_create(PJ_CONTEXT *ctx, const char *definition); PJ PROJ_DLL *proj_create_argv(PJ_CONTEXT *ctx, int argc, char **argv); PJ PROJ_DLL *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *source_crs, const char *target_crs, PJ_AREA *area); PJ PROJ_DLL *proj_create_crs_to_crs_from_pj(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, PJ_AREA *area, const char *const *options); /*! @endcond */ PJ PROJ_DLL *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj); /*! @cond Doxygen_Suppress */ void PROJ_DLL proj_assign_context(PJ *pj, PJ_CONTEXT *ctx); PJ PROJ_DLL *proj_destroy(PJ *P); PJ_AREA PROJ_DLL *proj_area_create(void); void PROJ_DLL proj_area_set_bbox(PJ_AREA *area, double west_lon_degree, double south_lat_degree, double east_lon_degree, double north_lat_degree); void PROJ_DLL proj_area_set_name(PJ_AREA *area, const char *name); void PROJ_DLL proj_area_destroy(PJ_AREA *area); /* Apply transformation to observation - in forward or inverse direction */ enum PJ_DIRECTION { PJ_FWD = 1, /* Forward */ PJ_IDENT = 0, /* Do nothing */ PJ_INV = -1 /* Inverse */ }; typedef enum PJ_DIRECTION PJ_DIRECTION; int PROJ_DLL proj_angular_input(PJ *P, enum PJ_DIRECTION dir); int PROJ_DLL proj_angular_output(PJ *P, enum PJ_DIRECTION dir); int PROJ_DLL proj_degree_input(PJ *P, enum PJ_DIRECTION dir); int PROJ_DLL proj_degree_output(PJ *P, enum PJ_DIRECTION dir); PJ_COORD PROJ_DLL proj_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coord); PJ PROJ_DLL *proj_trans_get_last_used_operation(PJ *P); int PROJ_DLL proj_trans_array(PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord); size_t PROJ_DLL proj_trans_generic(PJ *P, PJ_DIRECTION direction, double *x, size_t sx, size_t nx, double *y, size_t sy, size_t ny, double *z, size_t sz, size_t nz, double *t, size_t st, size_t nt); /*! @endcond */ int PROJ_DLL proj_trans_bounds(PJ_CONTEXT *context, PJ *P, PJ_DIRECTION direction, double xmin, double ymin, double xmax, double ymax, double *out_xmin, double *out_ymin, double *out_xmax, double *out_ymax, int densify_pts); int PROJ_DLL proj_trans_bounds_3D(PJ_CONTEXT *context, PJ *P, PJ_DIRECTION direction, const double xmin, const double ymin, const double zmin, const double xmax, const double ymax, const double zmax, double *out_xmin, double *out_ymin, double *out_zmin, double *out_xmax, double *out_ymax, double *out_zmax, const int densify_pts); /*! @cond Doxygen_Suppress */ /* Initializers */ PJ_COORD PROJ_DLL proj_coord(double x, double y, double z, double t); /* Measure internal consistency - in forward or inverse direction */ double PROJ_DLL proj_roundtrip(PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord); /* Geodesic distance between two points with angular 2D coordinates */ double PROJ_DLL proj_lp_dist(const PJ *P, PJ_COORD a, PJ_COORD b); /* The geodesic distance AND the vertical offset */ double PROJ_DLL proj_lpz_dist(const PJ *P, PJ_COORD a, PJ_COORD b); /* Euclidean distance between two points with linear 2D coordinates */ double PROJ_DLL proj_xy_dist(PJ_COORD a, PJ_COORD b); /* Euclidean distance between two points with linear 3D coordinates */ double PROJ_DLL proj_xyz_dist(PJ_COORD a, PJ_COORD b); /* Geodesic distance (in meter) + fwd and rev azimuth between two points on the * ellipsoid */ PJ_COORD PROJ_DLL proj_geod(const PJ *P, PJ_COORD a, PJ_COORD b); /** * @brief Solves the direct geodesic problem for given projection ellipsoid * * @param P * Transformation or CRS object * * @param a * Coordinate of first point. The coordinates needs to be given as * longitude and latitude in radians. Note that the axis order of the `P` object * is not taken into account in this function, so even though a CRS object comes * with axis ordering latitude/longitude coordinates used in this function * should be reordered as longitude latitude. * * @param azimuth * Initial azimuth from first point to second point in radians, measured * clockwise from true north * * @param distance * Geodesic distance from the starting point to the destination, in meters * * @return * `PJ_COORD` where the first value is the longitude in radians, second * value is latitude in radians and third value is forward azimuth at second * point in radians. The fourth coordinate value is unused. * * @see proj_geod() for solving the inverse geodesic problem. */ PJ_COORD PROJ_DLL proj_geod_direct(const PJ *P, PJ_COORD a, double azimuth, double distance); /* PROJ error codes */ /** Error codes typically related to coordinate operation initialization * Note: some of them can also be emitted during coordinate transformation, * like PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID in case the resource * loading is deferred until it is really needed. */ #define PROJ_ERR_INVALID_OP \ 1024 /* other/unspecified error related to coordinate operation \ initialization */ #define PROJ_ERR_INVALID_OP_WRONG_SYNTAX \ (PROJ_ERR_INVALID_OP + \ 1) /* invalid pipeline structure, missing +proj argument, etc */ #define PROJ_ERR_INVALID_OP_MISSING_ARG \ (PROJ_ERR_INVALID_OP + 2) /* missing required operation parameter */ #define PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE \ (PROJ_ERR_INVALID_OP + \ 3) /* one of the operation parameter has an illegal value */ #define PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS \ (PROJ_ERR_INVALID_OP + 4) /* mutually exclusive arguments */ #define PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID \ (PROJ_ERR_INVALID_OP + 5) /* file not found (particular case of \ PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE) */ /** Error codes related to transformation on a specific coordinate */ #define PROJ_ERR_COORD_TRANSFM \ 2048 /* other error related to coordinate transformation */ #define PROJ_ERR_COORD_TRANSFM_INVALID_COORD \ (PROJ_ERR_COORD_TRANSFM + 1) /* for e.g lat > 90deg */ #define PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN \ (PROJ_ERR_COORD_TRANSFM + \ 2) /* coordinate is outside of the projection domain. e.g approximate \ mercator with |longitude - lon_0| > 90deg, or iterative convergence \ method failed */ #define PROJ_ERR_COORD_TRANSFM_NO_OPERATION \ (PROJ_ERR_COORD_TRANSFM + \ 3) /* no operation found, e.g if no match the required accuracy, or if \ ballpark transformations were asked to not be used and they would \ be only such candidate */ #define PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID \ (PROJ_ERR_COORD_TRANSFM + \ 4) /* point to transform falls outside grid or subgrid */ #define PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA \ (PROJ_ERR_COORD_TRANSFM + \ 5) /* point to transform falls in a grid cell that evaluates to nodata */ #define PROJ_ERR_COORD_TRANSFM_NO_CONVERGENCE \ (PROJ_ERR_COORD_TRANSFM + 6) /* iterative convergence method fail */ #define PROJ_ERR_COORD_TRANSFM_MISSING_TIME \ (PROJ_ERR_COORD_TRANSFM + 7) /* operation requires time, but not provided \ */ /** Other type of errors */ #define PROJ_ERR_OTHER 4096 #define PROJ_ERR_OTHER_API_MISUSE \ (PROJ_ERR_OTHER + 1) /* error related to a misuse of PROJ API */ #define PROJ_ERR_OTHER_NO_INVERSE_OP \ (PROJ_ERR_OTHER + 2) /* no inverse method available */ #define PROJ_ERR_OTHER_NETWORK_ERROR \ (PROJ_ERR_OTHER + 3) /* failure when accessing a network resource */ /* Set or read error level */ int PROJ_DLL proj_context_errno(PJ_CONTEXT *ctx); int PROJ_DLL proj_errno(const PJ *P); int PROJ_DLL proj_errno_set(const PJ *P, int err); int PROJ_DLL proj_errno_reset(const PJ *P); int PROJ_DLL proj_errno_restore(const PJ *P, int err); const char PROJ_DLL * proj_errno_string(int err); /* deprecated. use proj_context_errno_string() */ const char PROJ_DLL *proj_context_errno_string(PJ_CONTEXT *ctx, int err); PJ_LOG_LEVEL PROJ_DLL proj_log_level(PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level); void PROJ_DLL proj_log_func(PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf); /* Scaling and angular distortion factors */ PJ_FACTORS PROJ_DLL proj_factors(PJ *P, PJ_COORD lp); /* Info functions - get information about various PROJ.4 entities */ PJ_INFO PROJ_DLL proj_info(void); PJ_PROJ_INFO PROJ_DLL proj_pj_info(PJ *P); PJ_GRID_INFO PROJ_DLL proj_grid_info(const char *gridname); PJ_INIT_INFO PROJ_DLL proj_init_info(const char *initname); /* List functions: */ /* Get lists of operations, ellipsoids, units and prime meridians. */ const PJ_OPERATIONS PROJ_DLL *proj_list_operations(void); const PJ_ELLPS PROJ_DLL *proj_list_ellps(void); PROJ_DEPRECATED(const PJ_UNITS PROJ_DLL *proj_list_units(void), "Deprecated by proj_get_units_from_database"); PROJ_DEPRECATED(const PJ_UNITS PROJ_DLL *proj_list_angular_units(void), "Deprecated by proj_get_units_from_database"); const PJ_PRIME_MERIDIANS PROJ_DLL *proj_list_prime_meridians(void); /* These are trivial, and while occasionally useful in real code, primarily here * to */ /* simplify demo code, and in acknowledgement of the proj-internal discrepancy * between */ /* angular units expected by classical proj, and by Charles Karney's geodesics * subsystem */ double PROJ_DLL proj_torad(double angle_in_degrees); double PROJ_DLL proj_todeg(double angle_in_radians); double PROJ_DLL proj_dmstor(const char *is, char **rs); PROJ_DEPRECATED(char PROJ_DLL *proj_rtodms(char *s, double r, int pos, int neg), "Deprecated by proj_rtodms2"); char PROJ_DLL *proj_rtodms2(char *s, size_t sizeof_s, double r, int pos, int neg); void PROJ_DLL proj_cleanup(void); /*! @endcond */ /* ------------------------------------------------------------------------- */ /* Binding in C of C++ API */ /* ------------------------------------------------------------------------- */ /** @defgroup iso19111_types Data types for ISO19111 C API * Data types for ISO19111 C API * @{ */ /** \brief Type representing a NULL terminated list of NULL-terminate strings. */ typedef char **PROJ_STRING_LIST; /** \brief Guessed WKT "dialect". */ typedef enum { /** \ref WKT2_2019 */ PJ_GUESSED_WKT2_2019, /** Deprecated alias for PJ_GUESSED_WKT2_2019 */ PJ_GUESSED_WKT2_2018 = PJ_GUESSED_WKT2_2019, /** \ref WKT2_2015 */ PJ_GUESSED_WKT2_2015, /** \ref WKT1 */ PJ_GUESSED_WKT1_GDAL, /** ESRI variant of WKT1 */ PJ_GUESSED_WKT1_ESRI, /** Not WKT / unrecognized */ PJ_GUESSED_NOT_WKT } PJ_GUESSED_WKT_DIALECT; /** \brief Object category. */ typedef enum { PJ_CATEGORY_ELLIPSOID, PJ_CATEGORY_PRIME_MERIDIAN, PJ_CATEGORY_DATUM, PJ_CATEGORY_CRS, PJ_CATEGORY_COORDINATE_OPERATION, PJ_CATEGORY_DATUM_ENSEMBLE } PJ_CATEGORY; /** \brief Object type. */ typedef enum { PJ_TYPE_UNKNOWN, PJ_TYPE_ELLIPSOID, PJ_TYPE_PRIME_MERIDIAN, PJ_TYPE_GEODETIC_REFERENCE_FRAME, PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME, PJ_TYPE_VERTICAL_REFERENCE_FRAME, PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME, PJ_TYPE_DATUM_ENSEMBLE, /** Abstract type, not returned by proj_get_type() */ PJ_TYPE_CRS, PJ_TYPE_GEODETIC_CRS, PJ_TYPE_GEOCENTRIC_CRS, /** proj_get_type() will never return that type, but * PJ_TYPE_GEOGRAPHIC_2D_CRS or PJ_TYPE_GEOGRAPHIC_3D_CRS. */ PJ_TYPE_GEOGRAPHIC_CRS, PJ_TYPE_GEOGRAPHIC_2D_CRS, PJ_TYPE_GEOGRAPHIC_3D_CRS, PJ_TYPE_VERTICAL_CRS, PJ_TYPE_PROJECTED_CRS, PJ_TYPE_COMPOUND_CRS, PJ_TYPE_TEMPORAL_CRS, PJ_TYPE_ENGINEERING_CRS, PJ_TYPE_BOUND_CRS, PJ_TYPE_OTHER_CRS, PJ_TYPE_CONVERSION, PJ_TYPE_TRANSFORMATION, PJ_TYPE_CONCATENATED_OPERATION, PJ_TYPE_OTHER_COORDINATE_OPERATION, PJ_TYPE_TEMPORAL_DATUM, PJ_TYPE_ENGINEERING_DATUM, PJ_TYPE_PARAMETRIC_DATUM, PJ_TYPE_DERIVED_PROJECTED_CRS, PJ_TYPE_COORDINATE_METADATA, } PJ_TYPE; /** Comparison criterion. */ typedef enum { /** All properties are identical. */ PJ_COMP_STRICT, /** The objects are equivalent for the purpose of coordinate * operations. They can differ by the name of their objects, * identifiers, other metadata. * Parameters may be expressed in different units, provided that the * value is (with some tolerance) the same once expressed in a * common unit. */ PJ_COMP_EQUIVALENT, /** Same as EQUIVALENT, relaxed with an exception that the axis order * of the base CRS of a DerivedCRS/ProjectedCRS or the axis order of * a GeographicCRS is ignored. Only to be used * with DerivedCRS/ProjectedCRS/GeographicCRS */ PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, } PJ_COMPARISON_CRITERION; /** \brief WKT version. */ typedef enum { /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2 */ PJ_WKT2_2015, /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2_SIMPLIFIED */ PJ_WKT2_2015_SIMPLIFIED, /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2_2019 */ PJ_WKT2_2019, /** Deprecated alias for PJ_WKT2_2019 */ PJ_WKT2_2018 = PJ_WKT2_2019, /** cf osgeo::proj::io::WKTFormatter::Convention::WKT2_2019_SIMPLIFIED */ PJ_WKT2_2019_SIMPLIFIED, /** Deprecated alias for PJ_WKT2_2019 */ PJ_WKT2_2018_SIMPLIFIED = PJ_WKT2_2019_SIMPLIFIED, /** cf osgeo::proj::io::WKTFormatter::Convention::WKT1_GDAL */ PJ_WKT1_GDAL, /** cf osgeo::proj::io::WKTFormatter::Convention::WKT1_ESRI */ PJ_WKT1_ESRI } PJ_WKT_TYPE; /** Specify how source and target CRS extent should be used to restrict * candidate operations (only taken into account if no explicit area of * interest is specified. */ typedef enum { /** Ignore CRS extent */ PJ_CRS_EXTENT_NONE, /** Test coordinate operation extent against both CRS extent. */ PJ_CRS_EXTENT_BOTH, /** Test coordinate operation extent against the intersection of both CRS extent. */ PJ_CRS_EXTENT_INTERSECTION, /** Test coordinate operation against the smallest of both CRS extent. */ PJ_CRS_EXTENT_SMALLEST } PROJ_CRS_EXTENT_USE; /** Describe how grid availability is used. */ typedef enum { /** Grid availability is only used for sorting results. Operations * where some grids are missing will be sorted last. */ PROJ_GRID_AVAILABILITY_USED_FOR_SORTING, /** Completely discard an operation if a required grid is missing. */ PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID, /** Ignore grid availability at all. Results will be presented as if * all grids were available. */ PROJ_GRID_AVAILABILITY_IGNORED, /** Results will be presented as if grids known to PROJ (that is * registered in the grid_alternatives table of its database) were * available. Used typically when networking is enabled. */ PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE, } PROJ_GRID_AVAILABILITY_USE; /** \brief PROJ string version. */ typedef enum { /** cf osgeo::proj::io::PROJStringFormatter::Convention::PROJ_5 */ PJ_PROJ_5, /** cf osgeo::proj::io::PROJStringFormatter::Convention::PROJ_4 */ PJ_PROJ_4 } PJ_PROJ_STRING_TYPE; /** Spatial criterion to restrict candidate operations. */ typedef enum { /** The area of validity of transforms should strictly contain the * are of interest. */ PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT, /** The area of validity of transforms should at least intersect the * area of interest. */ PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION } PROJ_SPATIAL_CRITERION; /** Describe if and how intermediate CRS should be used */ typedef enum { /** Always search for intermediate CRS. */ PROJ_INTERMEDIATE_CRS_USE_ALWAYS, /** Only attempt looking for intermediate CRS if there is no direct * transformation available. */ PROJ_INTERMEDIATE_CRS_USE_IF_NO_DIRECT_TRANSFORMATION, /* Do not attempt looking for intermediate CRS. */ PROJ_INTERMEDIATE_CRS_USE_NEVER, } PROJ_INTERMEDIATE_CRS_USE; /** Type of coordinate system. */ typedef enum { PJ_CS_TYPE_UNKNOWN, PJ_CS_TYPE_CARTESIAN, PJ_CS_TYPE_ELLIPSOIDAL, PJ_CS_TYPE_VERTICAL, PJ_CS_TYPE_SPHERICAL, PJ_CS_TYPE_ORDINAL, PJ_CS_TYPE_PARAMETRIC, PJ_CS_TYPE_DATETIMETEMPORAL, PJ_CS_TYPE_TEMPORALCOUNT, PJ_CS_TYPE_TEMPORALMEASURE } PJ_COORDINATE_SYSTEM_TYPE; /** \brief Structure given overall description of a CRS. * * This structure may grow over time, and should not be directly allocated by * client code. */ typedef struct { /** Authority name. */ char *auth_name; /** Object code. */ char *code; /** Object name. */ char *name; /** Object type. */ PJ_TYPE type; /** Whether the object is deprecated */ int deprecated; /** Whereas the west_lon_degree, south_lat_degree, east_lon_degree and * north_lat_degree fields are valid. */ int bbox_valid; /** Western-most longitude of the area of use, in degrees. */ double west_lon_degree; /** Southern-most latitude of the area of use, in degrees. */ double south_lat_degree; /** Eastern-most longitude of the area of use, in degrees. */ double east_lon_degree; /** Northern-most latitude of the area of use, in degrees. */ double north_lat_degree; /** Name of the area of use. */ char *area_name; /** Name of the projection method for a projected CRS. Might be NULL even *for projected CRS in some cases. */ char *projection_method_name; /** Name of the celestial body of the CRS (e.g. "Earth"). * @since 8.1 */ char *celestial_body_name; } PROJ_CRS_INFO; /** \brief Structure describing optional parameters for proj_get_crs_list(); * * This structure may grow over time, and should not be directly allocated by * client code. */ typedef struct { /** Array of allowed object types. Should be NULL if all types are allowed*/ const PJ_TYPE *types; /** Size of types. Should be 0 if all types are allowed*/ size_t typesCount; /** If TRUE and bbox_valid == TRUE, then only CRS whose area of use * entirely contains the specified bounding box will be returned. * If FALSE and bbox_valid == TRUE, then only CRS whose area of use * intersects the specified bounding box will be returned. */ int crs_area_of_use_contains_bbox; /** To set to TRUE so that west_lon_degree, south_lat_degree, * east_lon_degree and north_lat_degree fields are taken into account. */ int bbox_valid; /** Western-most longitude of the area of use, in degrees. */ double west_lon_degree; /** Southern-most latitude of the area of use, in degrees. */ double south_lat_degree; /** Eastern-most longitude of the area of use, in degrees. */ double east_lon_degree; /** Northern-most latitude of the area of use, in degrees. */ double north_lat_degree; /** Whether deprecated objects are allowed. Default to FALSE. */ int allow_deprecated; /** Celestial body of the CRS (e.g. "Earth"). The default value, NULL, * means no restriction * @since 8.1 */ const char *celestial_body_name; } PROJ_CRS_LIST_PARAMETERS; /** \brief Structure given description of a unit. * * This structure may grow over time, and should not be directly allocated by * client code. * @since 7.1 */ typedef struct { /** Authority name. */ char *auth_name; /** Object code. */ char *code; /** Object name. For example "metre", "US survey foot", etc. */ char *name; /** Category of the unit: one of "linear", "linear_per_time", "angular", * "angular_per_time", "scale", "scale_per_time" or "time" */ char *category; /** Conversion factor to apply to transform from that unit to the * corresponding SI unit (metre for "linear", radian for "angular", etc.). * It might be 0 in some cases to indicate no known conversion factor. */ double conv_factor; /** PROJ short name, like "m", "ft", "us-ft", etc... Might be NULL */ char *proj_short_name; /** Whether the object is deprecated */ int deprecated; } PROJ_UNIT_INFO; /** \brief Structure given description of a celestial body. * * This structure may grow over time, and should not be directly allocated by * client code. * @since 8.1 */ typedef struct { /** Authority name. */ char *auth_name; /** Object name. For example "Earth" */ char *name; } PROJ_CELESTIAL_BODY_INFO; /**@}*/ /** * \defgroup iso19111_functions Binding in C of basic methods from the C++ API * Functions for ISO19111 C API * * The PJ* objects returned by proj_create_from_wkt(), * proj_create_from_database() and other functions in that section * will have generally minimal interaction with the functions declared in the * upper section of this header file (calling those functions on those objects * will either return an error or default/nonsensical values). The exception is * for ISO19111 objects of type CoordinateOperation that can be exported as a * valid PROJ pipeline. In this case, the PJ objects will work for example with * proj_trans_generic(). * Conversely, objects returned by proj_create() and proj_create_argv(), which * are not of type CRS, will return an error when used with functions of this * section. * @{ */ /*! @cond Doxygen_Suppress */ typedef struct PJ_OBJ_LIST PJ_OBJ_LIST; /*! @endcond */ void PROJ_DLL proj_string_list_destroy(PROJ_STRING_LIST list); void PROJ_DLL proj_context_set_autoclose_database(PJ_CONTEXT *ctx, int autoclose); int PROJ_DLL proj_context_set_database_path(PJ_CONTEXT *ctx, const char *dbPath, const char *const *auxDbPaths, const char *const *options); const char PROJ_DLL *proj_context_get_database_path(PJ_CONTEXT *ctx); const char PROJ_DLL *proj_context_get_database_metadata(PJ_CONTEXT *ctx, const char *key); PROJ_STRING_LIST PROJ_DLL proj_context_get_database_structure( PJ_CONTEXT *ctx, const char *const *options); PJ_GUESSED_WKT_DIALECT PROJ_DLL proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx, const char *wkt); PJ PROJ_DLL *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, const char *const *options, PROJ_STRING_LIST *out_warnings, PROJ_STRING_LIST *out_grammar_errors); PJ PROJ_DLL *proj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *code, PJ_CATEGORY category, int usePROJAlternativeGridNames, const char *const *options); int PROJ_DLL proj_uom_get_info_from_database( PJ_CONTEXT *ctx, const char *auth_name, const char *code, const char **out_name, double *out_conv_factor, const char **out_category); int PROJ_DLL proj_grid_get_info_from_database( PJ_CONTEXT *ctx, const char *grid_name, const char **out_full_name, const char **out_package_name, const char **out_url, int *out_direct_download, int *out_open_license, int *out_available); PJ PROJ_DLL *proj_clone(PJ_CONTEXT *ctx, const PJ *obj); PJ_OBJ_LIST PROJ_DLL * proj_create_from_name(PJ_CONTEXT *ctx, const char *auth_name, const char *searchedName, const PJ_TYPE *types, size_t typesCount, int approximateMatch, size_t limitResultCount, const char *const *options); PJ_TYPE PROJ_DLL proj_get_type(const PJ *obj); int PROJ_DLL proj_is_deprecated(const PJ *obj); PJ_OBJ_LIST PROJ_DLL *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj); int PROJ_DLL proj_is_equivalent_to(const PJ *obj, const PJ *other, PJ_COMPARISON_CRITERION criterion); int PROJ_DLL proj_is_equivalent_to_with_ctx(PJ_CONTEXT *ctx, const PJ *obj, const PJ *other, PJ_COMPARISON_CRITERION criterion); int PROJ_DLL proj_is_crs(const PJ *obj); const char PROJ_DLL *proj_get_name(const PJ *obj); const char PROJ_DLL *proj_get_id_auth_name(const PJ *obj, int index); const char PROJ_DLL *proj_get_id_code(const PJ *obj, int index); const char PROJ_DLL *proj_get_remarks(const PJ *obj); int PROJ_DLL proj_get_domain_count(const PJ *obj); const char PROJ_DLL *proj_get_scope(const PJ *obj); const char PROJ_DLL *proj_get_scope_ex(const PJ *obj, int domainIdx); int PROJ_DLL proj_get_area_of_use(PJ_CONTEXT *ctx, const PJ *obj, double *out_west_lon_degree, double *out_south_lat_degree, double *out_east_lon_degree, double *out_north_lat_degree, const char **out_area_name); int PROJ_DLL proj_get_area_of_use_ex(PJ_CONTEXT *ctx, const PJ *obj, int domainIdx, double *out_west_lon_degree, double *out_south_lat_degree, double *out_east_lon_degree, double *out_north_lat_degree, const char **out_area_name); const char PROJ_DLL *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type, const char *const *options); const char PROJ_DLL *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj, PJ_PROJ_STRING_TYPE type, const char *const *options); const char PROJ_DLL *proj_as_projjson(PJ_CONTEXT *ctx, const PJ *obj, const char *const *options); PJ PROJ_DLL *proj_get_source_crs(PJ_CONTEXT *ctx, const PJ *obj); PJ PROJ_DLL *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj); PJ_OBJ_LIST PROJ_DLL *proj_identify(PJ_CONTEXT *ctx, const PJ *obj, const char *auth_name, const char *const *options, int **out_confidence); PROJ_STRING_LIST PROJ_DLL proj_get_geoid_models_from_database( PJ_CONTEXT *ctx, const char *auth_name, const char *code, const char *const *options); void PROJ_DLL proj_int_list_destroy(int *list); /* ------------------------------------------------------------------------- */ PROJ_STRING_LIST PROJ_DLL proj_get_authorities_from_database(PJ_CONTEXT *ctx); PROJ_STRING_LIST PROJ_DLL proj_get_codes_from_database(PJ_CONTEXT *ctx, const char *auth_name, PJ_TYPE type, int allow_deprecated); PROJ_CELESTIAL_BODY_INFO PROJ_DLL **proj_get_celestial_body_list_from_database( PJ_CONTEXT *ctx, const char *auth_name, int *out_result_count); void PROJ_DLL proj_celestial_body_list_destroy(PROJ_CELESTIAL_BODY_INFO **list); PROJ_CRS_LIST_PARAMETERS PROJ_DLL *proj_get_crs_list_parameters_create(void); void PROJ_DLL proj_get_crs_list_parameters_destroy(PROJ_CRS_LIST_PARAMETERS *params); PROJ_CRS_INFO PROJ_DLL ** proj_get_crs_info_list_from_database(PJ_CONTEXT *ctx, const char *auth_name, const PROJ_CRS_LIST_PARAMETERS *params, int *out_result_count); void PROJ_DLL proj_crs_info_list_destroy(PROJ_CRS_INFO **list); PROJ_UNIT_INFO PROJ_DLL **proj_get_units_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *category, int allow_deprecated, int *out_result_count); void PROJ_DLL proj_unit_list_destroy(PROJ_UNIT_INFO **list); /* ------------------------------------------------------------------------- */ /*! @cond Doxygen_Suppress */ typedef struct PJ_INSERT_SESSION PJ_INSERT_SESSION; /*! @endcond */ PJ_INSERT_SESSION PROJ_DLL *proj_insert_object_session_create(PJ_CONTEXT *ctx); void PROJ_DLL proj_insert_object_session_destroy(PJ_CONTEXT *ctx, PJ_INSERT_SESSION *session); PROJ_STRING_LIST PROJ_DLL proj_get_insert_statements( PJ_CONTEXT *ctx, PJ_INSERT_SESSION *session, const PJ *object, const char *authority, const char *code, int numeric_codes, const char *const *allowed_authorities, const char *const *options); char PROJ_DLL *proj_suggests_code_for(PJ_CONTEXT *ctx, const PJ *object, const char *authority, int numeric_code, const char *const *options); void PROJ_DLL proj_string_destroy(char *str); /* ------------------------------------------------------------------------- */ /*! @cond Doxygen_Suppress */ typedef struct PJ_OPERATION_FACTORY_CONTEXT PJ_OPERATION_FACTORY_CONTEXT; /*! @endcond */ PJ_OPERATION_FACTORY_CONTEXT PROJ_DLL * proj_create_operation_factory_context(PJ_CONTEXT *ctx, const char *authority); void PROJ_DLL proj_operation_factory_context_destroy(PJ_OPERATION_FACTORY_CONTEXT *ctx); void PROJ_DLL proj_operation_factory_context_set_desired_accuracy( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, double accuracy); void PROJ_DLL proj_operation_factory_context_set_area_of_interest( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, double west_lon_degree, double south_lat_degree, double east_lon_degree, double north_lat_degree); void PROJ_DLL proj_operation_factory_context_set_area_of_interest_name( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, const char *area_name); void PROJ_DLL proj_operation_factory_context_set_crs_extent_use( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_CRS_EXTENT_USE use); void PROJ_DLL proj_operation_factory_context_set_spatial_criterion( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_SPATIAL_CRITERION criterion); void PROJ_DLL proj_operation_factory_context_set_grid_availability_use( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_GRID_AVAILABILITY_USE use); void PROJ_DLL proj_operation_factory_context_set_use_proj_alternative_grid_names( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int usePROJNames); void PROJ_DLL proj_operation_factory_context_set_allow_use_intermediate_crs( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_INTERMEDIATE_CRS_USE use); void PROJ_DLL proj_operation_factory_context_set_allowed_intermediate_crs( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, const char *const *list_of_auth_name_codes); void PROJ_DLL proj_operation_factory_context_set_discard_superseded( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int discard); void PROJ_DLL proj_operation_factory_context_set_allow_ballpark_transformations( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int allow); /* ------------------------------------------------------------------------- */ PJ_OBJ_LIST PROJ_DLL * proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, const PJ_OPERATION_FACTORY_CONTEXT *operationContext); int PROJ_DLL proj_list_get_count(const PJ_OBJ_LIST *result); PJ PROJ_DLL *proj_list_get(PJ_CONTEXT *ctx, const PJ_OBJ_LIST *result, int index); void PROJ_DLL proj_list_destroy(PJ_OBJ_LIST *result); int PROJ_DLL proj_get_suggested_operation(PJ_CONTEXT *ctx, PJ_OBJ_LIST *operations, PJ_DIRECTION direction, PJ_COORD coord); /* ------------------------------------------------------------------------- */ int PROJ_DLL proj_crs_is_derived(PJ_CONTEXT *ctx, const PJ *crs); PJ PROJ_DLL *proj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ *crs); PJ PROJ_DLL *proj_crs_get_horizontal_datum(PJ_CONTEXT *ctx, const PJ *crs); PJ PROJ_DLL *proj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ *crs, int index); PJ PROJ_DLL *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs); PJ PROJ_DLL *proj_crs_get_datum_ensemble(PJ_CONTEXT *ctx, const PJ *crs); PJ PROJ_DLL *proj_crs_get_datum_forced(PJ_CONTEXT *ctx, const PJ *crs); int PROJ_DLL proj_crs_has_point_motion_operation(PJ_CONTEXT *ctx, const PJ *crs); int PROJ_DLL proj_datum_ensemble_get_member_count(PJ_CONTEXT *ctx, const PJ *datum_ensemble); double PROJ_DLL proj_datum_ensemble_get_accuracy(PJ_CONTEXT *ctx, const PJ *datum_ensemble); PJ PROJ_DLL *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble, int member_index); double PROJ_DLL proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx, const PJ *datum); PJ PROJ_DLL *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs); PJ_COORDINATE_SYSTEM_TYPE PROJ_DLL proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs); int PROJ_DLL proj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ *cs); int PROJ_DLL proj_cs_get_axis_info( PJ_CONTEXT *ctx, const PJ *cs, int index, const char **out_name, const char **out_abbrev, const char **out_direction, double *out_unit_conv_factor, const char **out_unit_name, const char **out_unit_auth_name, const char **out_unit_code); PJ PROJ_DLL *proj_get_ellipsoid(PJ_CONTEXT *ctx, const PJ *obj); int PROJ_DLL proj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ *ellipsoid, double *out_semi_major_metre, double *out_semi_minor_metre, int *out_is_semi_minor_computed, double *out_inv_flattening); const char PROJ_DLL *proj_get_celestial_body_name(PJ_CONTEXT *ctx, const PJ *obj); PJ PROJ_DLL *proj_get_prime_meridian(PJ_CONTEXT *ctx, const PJ *obj); int PROJ_DLL proj_prime_meridian_get_parameters(PJ_CONTEXT *ctx, const PJ *prime_meridian, double *out_longitude, double *out_unit_conv_factor, const char **out_unit_name); PJ PROJ_DLL *proj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ *crs); int PROJ_DLL proj_coordoperation_get_method_info( PJ_CONTEXT *ctx, const PJ *coordoperation, const char **out_method_name, const char **out_method_auth_name, const char **out_method_code); int PROJ_DLL proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx, const PJ *coordoperation); int PROJ_DLL proj_coordoperation_has_ballpark_transformation( PJ_CONTEXT *ctx, const PJ *coordoperation); int PROJ_DLL proj_coordoperation_requires_per_coordinate_input_time( PJ_CONTEXT *ctx, const PJ *coordoperation); int PROJ_DLL proj_coordoperation_get_param_count(PJ_CONTEXT *ctx, const PJ *coordoperation); int PROJ_DLL proj_coordoperation_get_param_index(PJ_CONTEXT *ctx, const PJ *coordoperation, const char *name); int PROJ_DLL proj_coordoperation_get_param( PJ_CONTEXT *ctx, const PJ *coordoperation, int index, const char **out_name, const char **out_auth_name, const char **out_code, double *out_value, const char **out_value_string, double *out_unit_conv_factor, const char **out_unit_name, const char **out_unit_auth_name, const char **out_unit_code, const char **out_unit_category); int PROJ_DLL proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx, const PJ *coordoperation); int PROJ_DLL proj_coordoperation_get_grid_used( PJ_CONTEXT *ctx, const PJ *coordoperation, int index, const char **out_short_name, const char **out_full_name, const char **out_package_name, const char **out_url, int *out_direct_download, int *out_open_license, int *out_available); double PROJ_DLL proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx, const PJ *obj); int PROJ_DLL proj_coordoperation_get_towgs84_values( PJ_CONTEXT *ctx, const PJ *coordoperation, double *out_values, int value_count, int emit_error_if_incompatible); PJ PROJ_DLL *proj_coordoperation_create_inverse(PJ_CONTEXT *ctx, const PJ *obj); int PROJ_DLL proj_concatoperation_get_step_count(PJ_CONTEXT *ctx, const PJ *concatoperation); PJ PROJ_DLL *proj_concatoperation_get_step(PJ_CONTEXT *ctx, const PJ *concatoperation, int i_step); PJ PROJ_DLL *proj_coordinate_metadata_create(PJ_CONTEXT *ctx, const PJ *crs, double epoch); double PROJ_DLL proj_coordinate_metadata_get_epoch(PJ_CONTEXT *ctx, const PJ *obj); /**@}*/ /* ------------------------------------------------------------------------- */ /* Binding in C of advanced methods from the C++ API */ /* */ /* Manual construction of CRS objects. */ /* ------------------------------------------------------------------------- */ /** * \defgroup iso19111_advanced_types C types for advanced methods from the C++ * API * @{ */ /** Type of unit of measure. */ typedef enum { /** Angular unit of measure */ PJ_UT_ANGULAR, /** Linear unit of measure */ PJ_UT_LINEAR, /** Scale unit of measure */ PJ_UT_SCALE, /** Time unit of measure */ PJ_UT_TIME, /** Parametric unit of measure */ PJ_UT_PARAMETRIC } PJ_UNIT_TYPE; /** \brief Axis description. */ typedef struct { /** Axis name. */ char *name; /** Axis abbreviation. */ char *abbreviation; /** Axis direction. */ char *direction; /** Axis unit name. */ char *unit_name; /** Conversion factor to SI of the unit. */ double unit_conv_factor; /** Type of unit */ PJ_UNIT_TYPE unit_type; } PJ_AXIS_DESCRIPTION; /** Type of Cartesian 2D coordinate system. */ typedef enum { /** Easting-Norting */ PJ_CART2D_EASTING_NORTHING, /** Northing-Easting */ PJ_CART2D_NORTHING_EASTING, /** North Pole Easting/SOUTH-Norting/SOUTH */ PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH, /** South Pole Easting/NORTH-Norting/NORTH */ PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH, /** Westing-southing */ PJ_CART2D_WESTING_SOUTHING, } PJ_CARTESIAN_CS_2D_TYPE; /** Type of Ellipsoidal 2D coordinate system. */ typedef enum { /** Longitude-Latitude */ PJ_ELLPS2D_LONGITUDE_LATITUDE, /** Latitude-Longitude */ PJ_ELLPS2D_LATITUDE_LONGITUDE, } PJ_ELLIPSOIDAL_CS_2D_TYPE; /** Type of Ellipsoidal 3D coordinate system. */ typedef enum { /** Longitude-Latitude-Height(up) */ PJ_ELLPS3D_LONGITUDE_LATITUDE_HEIGHT, /** Latitude-Longitude-Height(up) */ PJ_ELLPS3D_LATITUDE_LONGITUDE_HEIGHT, } PJ_ELLIPSOIDAL_CS_3D_TYPE; /** \brief Description of a parameter value for a Conversion. */ typedef struct { /** Parameter name. */ const char *name; /** Parameter authority name. */ const char *auth_name; /** Parameter code. */ const char *code; /** Parameter value. */ double value; /** Name of unit in which parameter value is expressed. */ const char *unit_name; /** Conversion factor to SI of the unit. */ double unit_conv_factor; /** Type of unit */ PJ_UNIT_TYPE unit_type; } PJ_PARAM_DESCRIPTION; /**@}*/ /** * \defgroup iso19111_advanced_functions Binding in C of advanced methods from * the C++ API * @{ */ PJ PROJ_DLL *proj_create_cs(PJ_CONTEXT *ctx, PJ_COORDINATE_SYSTEM_TYPE type, int axis_count, const PJ_AXIS_DESCRIPTION *axis); PJ PROJ_DLL *proj_create_cartesian_2D_cs(PJ_CONTEXT *ctx, PJ_CARTESIAN_CS_2D_TYPE type, const char *unit_name, double unit_conv_factor); PJ PROJ_DLL *proj_create_ellipsoidal_2D_cs(PJ_CONTEXT *ctx, PJ_ELLIPSOIDAL_CS_2D_TYPE type, const char *unit_name, double unit_conv_factor); PJ PROJ_DLL * proj_create_ellipsoidal_3D_cs(PJ_CONTEXT *ctx, PJ_ELLIPSOIDAL_CS_3D_TYPE type, const char *horizontal_angular_unit_name, double horizontal_angular_unit_conv_factor, const char *vertical_linear_unit_name, double vertical_linear_unit_conv_factor); PJ_OBJ_LIST PROJ_DLL *proj_query_geodetic_crs_from_datum( PJ_CONTEXT *ctx, const char *crs_auth_name, const char *datum_auth_name, const char *datum_code, const char *crs_type); PJ PROJ_DLL *proj_create_geographic_crs( PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *ellps_name, double semi_major_metre, double inv_flattening, const char *prime_meridian_name, double prime_meridian_offset, const char *pm_angular_units, double pm_units_conv, const PJ *ellipsoidal_cs); PJ PROJ_DLL * proj_create_geographic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name, const PJ *datum_or_datum_ensemble, const PJ *ellipsoidal_cs); PJ PROJ_DLL *proj_create_geocentric_crs( PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *ellps_name, double semi_major_metre, double inv_flattening, const char *prime_meridian_name, double prime_meridian_offset, const char *angular_units, double angular_units_conv, const char *linear_units, double linear_units_conv); PJ PROJ_DLL *proj_create_geocentric_crs_from_datum( PJ_CONTEXT *ctx, const char *crs_name, const PJ *datum_or_datum_ensemble, const char *linear_units, double linear_units_conv); PJ PROJ_DLL *proj_create_derived_geographic_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *base_geographic_crs, const PJ *conversion, const PJ *ellipsoidal_cs); int PROJ_DLL proj_is_derived_crs(PJ_CONTEXT *ctx, const PJ *crs); PJ PROJ_DLL *proj_alter_name(PJ_CONTEXT *ctx, const PJ *obj, const char *name); PJ PROJ_DLL *proj_alter_id(PJ_CONTEXT *ctx, const PJ *obj, const char *auth_name, const char *code); PJ PROJ_DLL *proj_crs_alter_geodetic_crs(PJ_CONTEXT *ctx, const PJ *obj, const PJ *new_geod_crs); PJ PROJ_DLL *proj_crs_alter_cs_angular_unit(PJ_CONTEXT *ctx, const PJ *obj, const char *angular_units, double angular_units_conv, const char *unit_auth_name, const char *unit_code); PJ PROJ_DLL *proj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ *obj, const char *linear_units, double linear_units_conv, const char *unit_auth_name, const char *unit_code); PJ PROJ_DLL *proj_crs_alter_parameters_linear_unit( PJ_CONTEXT *ctx, const PJ *obj, const char *linear_units, double linear_units_conv, const char *unit_auth_name, const char *unit_code, int convert_to_new_unit); PJ PROJ_DLL *proj_crs_promote_to_3D(PJ_CONTEXT *ctx, const char *crs_3D_name, const PJ *crs_2D); PJ PROJ_DLL * proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx, const char *crs_name, const PJ *projected_2D_crs, const PJ *geog_3D_crs); PJ PROJ_DLL *proj_crs_demote_to_2D(PJ_CONTEXT *ctx, const char *crs_2D_name, const PJ *crs_3D); PJ PROJ_DLL *proj_create_engineering_crs(PJ_CONTEXT *ctx, const char *crsName); PJ PROJ_DLL *proj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *linear_units, double linear_units_conv); PJ PROJ_DLL *proj_create_vertical_crs_ex( PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *datum_auth_name, const char *datum_code, const char *linear_units, double linear_units_conv, const char *geoid_model_name, const char *geoid_model_auth_name, const char *geoid_model_code, const PJ *geoid_geog_crs, const char *const *options); PJ PROJ_DLL *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *horiz_crs, const PJ *vert_crs); PJ PROJ_DLL *proj_create_conversion(PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code, const char *method_name, const char *method_auth_name, const char *method_code, int param_count, const PJ_PARAM_DESCRIPTION *params); PJ PROJ_DLL *proj_create_conversion(PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code, const char *method_name, const char *method_auth_name, const char *method_code, int param_count, const PJ_PARAM_DESCRIPTION *params); /* clang-format off */ PJ PROJ_DLL *proj_create_linear_affine_parametric_conversion( PJ_CONTEXT *ctx, const char* name, double A0, const char *A0_unit_name, double A0_unit_conv_factor, double A1, const char *A1_unit_name, double A1_unit_conv_factor, double A2, const char *A2_unit_name, double A2_unit_conv_factor, double B0, const char *B0_unit_name, double B0_unit_conv_factor, double B1, const char *B1_unit_name, double B1_unit_conv_factor, double B2, const char *B2_unit_name, double B2_unit_conv_factor); /* clang-format on */ PJ PROJ_DLL *proj_create_transformation( PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code, const PJ *source_crs, const PJ *target_crs, const PJ *interpolation_crs, const char *method_name, const char *method_auth_name, const char *method_code, int param_count, const PJ_PARAM_DESCRIPTION *params, double accuracy); PJ PROJ_DLL * proj_convert_conversion_to_other_method(PJ_CONTEXT *ctx, const PJ *conversion, int new_method_epsg_code, const char *new_method_name); PJ PROJ_DLL *proj_create_projected_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *geodetic_crs, const PJ *conversion, const PJ *coordinate_system); PJ PROJ_DLL *proj_create_derived_projected_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *base_proj_crs, const PJ *deriving_conversion, const PJ *coordinate_system); PJ PROJ_DLL *proj_crs_add_horizontal_derived_conversion( PJ_CONTEXT *ctx, const char *crs_name, const PJ *base_crs, const PJ *deriving_conversion, const PJ *coordinate_system); PJ PROJ_DLL *proj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ *base_crs, const PJ *hub_crs, const PJ *transformation); PJ PROJ_DLL *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs, const char *const *options); PJ PROJ_DLL *proj_crs_create_bound_vertical_crs(PJ_CONTEXT *ctx, const PJ *vert_crs, const PJ *hub_geographic_3D_crs, const char *grid_name); /* BEGIN: Generated by scripts/create_c_api_projections.py*/ PJ PROJ_DLL *proj_create_conversion_utm(PJ_CONTEXT *ctx, int zone, int north); PJ PROJ_DLL *proj_create_conversion_transverse_mercator( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_gauss_schreiber_transverse_mercator( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_transverse_mercator_south_oriented( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_two_point_equidistant( PJ_CONTEXT *ctx, double latitude_first_point, double longitude_first_point, double latitude_second_point, double longitude_second_point, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_tunisia_mapping_grid( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_tunisia_mining_grid( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_albers_equal_area( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_conic_conformal_1sp( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_conic_conformal_1sp_variant_b( PJ_CONTEXT *ctx, double latitude_nat_origin, double scale, double latitude_false_origin, double longitude_false_origin, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_conic_conformal_2sp( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_conic_conformal_2sp_michigan( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, double ellipsoid_scaling_factor, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_conic_conformal_2sp_belgium( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_azimuthal_equidistant( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_guam_projection( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_bonne( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_cylindrical_equal_area_spherical( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_cylindrical_equal_area( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_cassini_soldner( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_equidistant_conic( PJ_CONTEXT *ctx, double center_lat, double center_long, double latitude_first_parallel, double latitude_second_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_eckert_i( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_eckert_ii( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_eckert_iii( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_eckert_iv( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_eckert_v( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_eckert_vi( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_equidistant_cylindrical( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_equidistant_cylindrical_spherical( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_gall(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_goode_homolosine( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_interrupted_goode_homolosine( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_geostationary_satellite_sweep_x( PJ_CONTEXT *ctx, double center_long, double height, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_geostationary_satellite_sweep_y( PJ_CONTEXT *ctx, double center_long, double height, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_gnomonic( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_hotine_oblique_mercator_variant_a( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_projection_centre, double azimuth_initial_line, double angle_from_rectified_to_skrew_grid, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_hotine_oblique_mercator_variant_b( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_projection_centre, double azimuth_initial_line, double angle_from_rectified_to_skrew_grid, double scale, double easting_projection_centre, double northing_projection_centre, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL * proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin( PJ_CONTEXT *ctx, double latitude_projection_centre, double latitude_point1, double longitude_point1, double latitude_point2, double longitude_point2, double scale, double easting_projection_centre, double northing_projection_centre, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_laborde_oblique_mercator( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_projection_centre, double azimuth_initial_line, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_international_map_world_polyconic( PJ_CONTEXT *ctx, double center_long, double latitude_first_parallel, double latitude_second_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_krovak_north_oriented( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_of_origin, double colatitude_cone_axis, double latitude_pseudo_standard_parallel, double scale_factor_pseudo_standard_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_krovak( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_of_origin, double colatitude_cone_axis, double latitude_pseudo_standard_parallel, double scale_factor_pseudo_standard_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_lambert_azimuthal_equal_area( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_miller_cylindrical( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_mercator_variant_a( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_mercator_variant_b( PJ_CONTEXT *ctx, double latitude_first_parallel, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_popular_visualisation_pseudo_mercator( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_mollweide( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_new_zealand_mapping_grid( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_oblique_stereographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_orthographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_local_orthographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double azimuth, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_american_polyconic( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_polar_stereographic_variant_a( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_polar_stereographic_variant_b( PJ_CONTEXT *ctx, double latitude_standard_parallel, double longitude_of_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_robinson( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_sinusoidal( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_stereographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_van_der_grinten( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_i( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_ii( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_iii( PJ_CONTEXT *ctx, double latitude_true_scale, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_iv( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_v( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_vi( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_wagner_vii( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_quadrilateralized_spherical_cube( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_spherical_cross_track_height( PJ_CONTEXT *ctx, double peg_point_lat, double peg_point_long, double peg_point_heading, double peg_point_height, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_equal_earth( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_vertical_perspective( PJ_CONTEXT *ctx, double topo_origin_lat, double topo_origin_long, double topo_origin_height, double view_point_height, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_pole_rotation_grib_convention( PJ_CONTEXT *ctx, double south_pole_lat_in_unrotated_crs, double south_pole_long_in_unrotated_crs, double axis_rotation, const char *ang_unit_name, double ang_unit_conv_factor); PJ PROJ_DLL *proj_create_conversion_pole_rotation_netcdf_cf_convention( PJ_CONTEXT *ctx, double grid_north_pole_latitude, double grid_north_pole_longitude, double north_pole_grid_longitude, const char *ang_unit_name, double ang_unit_conv_factor); /* END: Generated by scripts/create_c_api_projections.py*/ /**@}*/ #ifdef __cplusplus } #endif #endif /* ndef PROJ_H */ proj-9.8.1/src/iso19111/000775 001750 001750 00000000000 15166171735 014433 5ustar00eveneven000000 000000 proj-9.8.1/src/iso19111/util.cpp000664 001750 001750 00000055272 15166171715 016125 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/util.hpp" #include "proj/io.hpp" #include "proj/internal/internal.hpp" #include #include #include using namespace NS_PROJ::internal; #if 0 namespace dropbox{ namespace oxygen { template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; }} #endif NS_PROJ_START namespace util { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct BaseObject::Private { // This is a manual implementation of std::enable_shared_from_this<> that // avoids publicly deriving from it. std::weak_ptr self_{}; }; //! @endcond // --------------------------------------------------------------------------- BaseObject::BaseObject() : d(std::make_unique()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress BaseObject::~BaseObject() = default; // --------------------------------------------------------------------------- BaseObjectNNPtr::~BaseObjectNNPtr() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // cppcheck-suppress operatorEqVarError BaseObject &BaseObject::operator=(BaseObject &&) { d->self_.reset(); return *this; } //! @endcond // --------------------------------------------------------------------------- /** Keep a reference to ourselves as an internal weak pointer. So that * extractGeographicBaseObject() can later return a shared pointer on itself. */ void BaseObject::assignSelf(const BaseObjectNNPtr &self) { assert(self.get() == this); d->self_ = self.as_nullable(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress BaseObjectNNPtr BaseObject::shared_from_this() const { // This assertion checks that in all code paths where we create a // shared pointer, we took care of assigning it to self_, by calling // assignSelf(); return NN_CHECK_ASSERT(d->self_.lock()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct BoxedValue::Private { BoxedValue::Type type_{BoxedValue::Type::INTEGER}; std::string stringValue_{}; int integerValue_{}; bool booleanValue_{}; explicit Private(const std::string &stringValueIn) : type_(BoxedValue::Type::STRING), stringValue_(stringValueIn) {} explicit Private(int integerValueIn) : type_(BoxedValue::Type::INTEGER), integerValue_(integerValueIn) {} explicit Private(bool booleanValueIn) : type_(BoxedValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} }; //! @endcond // --------------------------------------------------------------------------- BoxedValue::BoxedValue() : d(std::make_unique(std::string())) {} // --------------------------------------------------------------------------- /** \brief Constructs a BoxedValue from a string. */ BoxedValue::BoxedValue(const char *stringValueIn) : d(std::make_unique( std::string(stringValueIn ? stringValueIn : ""))) {} // --------------------------------------------------------------------------- /** \brief Constructs a BoxedValue from a string. */ BoxedValue::BoxedValue(const std::string &stringValueIn) : d(std::make_unique(stringValueIn)) {} // --------------------------------------------------------------------------- /** \brief Constructs a BoxedValue from an integer. */ BoxedValue::BoxedValue(int integerValueIn) : d(std::make_unique(integerValueIn)) {} // --------------------------------------------------------------------------- /** \brief Constructs a BoxedValue from a boolean. */ BoxedValue::BoxedValue(bool booleanValueIn) : d(std::make_unique(booleanValueIn)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress BoxedValue::BoxedValue(const BoxedValue &other) : d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- BoxedValue::~BoxedValue() = default; // --------------------------------------------------------------------------- const BoxedValue::Type &BoxedValue::type() const { return d->type_; } // --------------------------------------------------------------------------- const std::string &BoxedValue::stringValue() const { return d->stringValue_; } // --------------------------------------------------------------------------- int BoxedValue::integerValue() const { return d->integerValue_; } // --------------------------------------------------------------------------- bool BoxedValue::booleanValue() const { return d->booleanValue_; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ArrayOfBaseObject::Private { std::vector values_{}; }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ArrayOfBaseObject::ArrayOfBaseObject() : d(std::make_unique()) {} // --------------------------------------------------------------------------- ArrayOfBaseObject::~ArrayOfBaseObject() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Adds an object to the array. * * @param obj the object to add. */ void ArrayOfBaseObject::add(const BaseObjectNNPtr &obj) { d->values_.emplace_back(obj); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::vector::const_iterator ArrayOfBaseObject::begin() const { return d->values_.begin(); } // --------------------------------------------------------------------------- std::vector::const_iterator ArrayOfBaseObject::end() const { return d->values_.end(); } // --------------------------------------------------------------------------- bool ArrayOfBaseObject::empty() const { return d->values_.empty(); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a ArrayOfBaseObject. * * @return a new ArrayOfBaseObject. */ ArrayOfBaseObjectNNPtr ArrayOfBaseObject::create() { return ArrayOfBaseObject::nn_make_shared(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PropertyMap::Private { std::list> list_{}; // cppcheck-suppress functionStatic void set(const std::string &key, const BoxedValueNNPtr &val) { for (auto &pair : list_) { if (pair.first == key) { pair.second = val; return; } } list_.emplace_back(key, val); } }; //! @endcond // --------------------------------------------------------------------------- PropertyMap::PropertyMap() : d(std::make_unique()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PropertyMap::PropertyMap(const PropertyMap &other) : d(std::make_unique(*(other.d))) {} //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PropertyMap::~PropertyMap() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const BaseObjectNNPtr *PropertyMap::get(const std::string &key) const { for (const auto &pair : d->list_) { if (pair.first == key) { return &(pair.second); } } return nullptr; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void PropertyMap::unset(const std::string &key) { auto &list = d->list_; for (auto iter = list.begin(); iter != list.end(); ++iter) { if (iter->first == key) { list.erase(iter); return; } } } //! @endcond // --------------------------------------------------------------------------- /** \brief Set a BaseObjectNNPtr as the value of a key. */ PropertyMap &PropertyMap::set(const std::string &key, const BaseObjectNNPtr &val) { for (auto &pair : d->list_) { if (pair.first == key) { pair.second = val; return *this; } } d->list_.emplace_back(key, val); return *this; } // --------------------------------------------------------------------------- /** \brief Set a string as the value of a key. */ PropertyMap &PropertyMap::set(const std::string &key, const std::string &val) { d->set(key, util::nn_make_shared(val)); return *this; } // --------------------------------------------------------------------------- /** \brief Set a string as the value of a key. */ PropertyMap &PropertyMap::set(const std::string &key, const char *val) { d->set(key, util::nn_make_shared(val)); return *this; } // --------------------------------------------------------------------------- /** \brief Set a integer as the value of a key. */ PropertyMap &PropertyMap::set(const std::string &key, int val) { d->set(key, util::nn_make_shared(val)); return *this; } // --------------------------------------------------------------------------- /** \brief Set a boolean as the value of a key. */ PropertyMap &PropertyMap::set(const std::string &key, bool val) { d->set(key, util::nn_make_shared(val)); return *this; } // --------------------------------------------------------------------------- /** \brief Set a vector of strings as the value of a key. */ PropertyMap &PropertyMap::set(const std::string &key, const std::vector &arrayIn) { ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); for (const auto &str : arrayIn) { array->add(util::nn_make_shared(str)); } return set(key, array); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool PropertyMap::getStringValue( const std::string &key, std::string &outVal) const // throw(InvalidValueTypeException) { for (const auto &pair : d->list_) { if (pair.first == key) { auto genVal = dynamic_cast(pair.second.get()); if (genVal && genVal->type() == BoxedValue::Type::STRING) { outVal = genVal->stringValue(); return true; } throw InvalidValueTypeException("Invalid value type for " + key); } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool PropertyMap::getStringValue( const std::string &key, optional &outVal) const // throw(InvalidValueTypeException) { for (const auto &pair : d->list_) { if (pair.first == key) { auto genVal = dynamic_cast(pair.second.get()); if (genVal && genVal->type() == BoxedValue::Type::STRING) { outVal = genVal->stringValue(); return true; } throw InvalidValueTypeException("Invalid value type for " + key); } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GenericName::Private {}; //! @endcond // --------------------------------------------------------------------------- GenericName::GenericName() : d(std::make_unique()) {} // --------------------------------------------------------------------------- GenericName::GenericName(const GenericName &other) : d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GenericName::~GenericName() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct NameSpace::Private { GenericNamePtr name{}; bool isGlobal{}; std::string separator = std::string(":"); std::string separatorHead = std::string(":"); }; //! @endcond // --------------------------------------------------------------------------- NameSpace::NameSpace(const GenericNamePtr &nameIn) : d(std::make_unique()) { d->name = nameIn; } // --------------------------------------------------------------------------- NameSpace::NameSpace(const NameSpace &other) : d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress NameSpace::~NameSpace() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Returns whether this is a global namespace. */ bool NameSpace::isGlobal() const { return d->isGlobal; } // --------------------------------------------------------------------------- NameSpaceNNPtr NameSpace::getGlobalFromThis() const { NameSpaceNNPtr ns(NameSpace::nn_make_shared(*this)); ns->d->isGlobal = true; ns->d->name = LocalName::make_shared("global"); return ns; } // --------------------------------------------------------------------------- /** \brief Returns the name of this namespace. */ const GenericNamePtr &NameSpace::name() const { return d->name; } // --------------------------------------------------------------------------- const std::string &NameSpace::separator() const { return d->separator; } // --------------------------------------------------------------------------- NameSpaceNNPtr NameSpace::createGLOBAL() { NameSpaceNNPtr ns(NameSpace::nn_make_shared( LocalName::make_shared("global"))); ns->d->isGlobal = true; return ns; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct LocalName::Private { NameSpacePtr scope{}; std::string name{}; }; //! @endcond // --------------------------------------------------------------------------- LocalName::LocalName(const std::string &name) : d(std::make_unique()) { d->name = name; } // --------------------------------------------------------------------------- LocalName::LocalName(const NameSpacePtr &ns, const std::string &name) : d(std::make_unique()) { d->scope = ns ? ns : static_cast(NameSpace::GLOBAL); d->name = name; } // --------------------------------------------------------------------------- LocalName::LocalName(const LocalName &other) : GenericName(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress LocalName::~LocalName() = default; //! @endcond // --------------------------------------------------------------------------- const NameSpacePtr LocalName::scope() const { if (d->scope) return d->scope; return NameSpace::GLOBAL; } // --------------------------------------------------------------------------- GenericNameNNPtr LocalName::toFullyQualifiedName() const { if (scope()->isGlobal()) return LocalName::nn_make_shared(*this); return LocalName::nn_make_shared( d->scope->getGlobalFromThis(), d->scope->name()->toFullyQualifiedName()->toString() + d->scope->separator() + d->name); } // --------------------------------------------------------------------------- std::string LocalName::toString() const { return d->name; } // --------------------------------------------------------------------------- /** \brief Instantiate a NameSpace. * * @param name name of the namespace. * @param properties Properties. Allowed keys are "separator" and * "separator.head". * @return a new NameFactory. */ NameSpaceNNPtr NameFactory::createNameSpace(const GenericNameNNPtr &name, const PropertyMap &properties) { NameSpaceNNPtr ns(NameSpace::nn_make_shared(name)); properties.getStringValue("separator", ns->d->separator); properties.getStringValue("separator.head", ns->d->separatorHead); return ns; } // --------------------------------------------------------------------------- /** \brief Instantiate a LocalName. * * @param scope scope. * @param name string of the local name. * @return a new LocalName. */ LocalNameNNPtr NameFactory::createLocalName(const NameSpacePtr &scope, const std::string &name) { return LocalName::nn_make_shared(scope, name); } // --------------------------------------------------------------------------- /** \brief Instantiate a GenericName. * * @param scope scope. * @param parsedNames the components of the name. * @return a new GenericName. */ GenericNameNNPtr NameFactory::createGenericName(const NameSpacePtr &scope, const std::vector &parsedNames) { std::string name; const std::string separator(scope ? scope->separator() : NameSpace::GLOBAL->separator()); bool first = true; for (const auto &str : parsedNames) { if (!first) name += separator; first = false; name += str; } return LocalName::nn_make_shared(scope, name); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CodeList::~CodeList() = default; //! @endcond // --------------------------------------------------------------------------- CodeList &CodeList::operator=(const CodeList &other) { name_ = other.name_; return *this; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Exception::Exception(const char *message) : msg_(message) {} // --------------------------------------------------------------------------- Exception::Exception(const std::string &message) : msg_(message) {} // --------------------------------------------------------------------------- Exception::Exception(const Exception &) = default; // --------------------------------------------------------------------------- Exception::~Exception() = default; //! @endcond // --------------------------------------------------------------------------- /** Return the exception text. */ const char *Exception::what() const noexcept { return msg_.c_str(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress InvalidValueTypeException::InvalidValueTypeException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- InvalidValueTypeException::InvalidValueTypeException(const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- InvalidValueTypeException::~InvalidValueTypeException() = default; // --------------------------------------------------------------------------- InvalidValueTypeException::InvalidValueTypeException( const InvalidValueTypeException &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress UnsupportedOperationException::UnsupportedOperationException( const char *message) : Exception(message) {} // --------------------------------------------------------------------------- UnsupportedOperationException::UnsupportedOperationException( const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- UnsupportedOperationException::~UnsupportedOperationException() = default; // --------------------------------------------------------------------------- UnsupportedOperationException::UnsupportedOperationException( const UnsupportedOperationException &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress IComparable::~IComparable() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Returns whether an object is equivalent to another one. * @param other other object to compare to * @param criterion comparison criterion. * @param dbContext Database context, or nullptr. * @return true if objects are equivalent. */ bool IComparable::isEquivalentTo( const IComparable *other, Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (this == other) return true; return _isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- } // namespace util NS_PROJ_END proj-9.8.1/src/iso19111/coordinates.cpp000664 001750 001750 00000023271 15166171715 017454 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2023, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/coordinates.hpp" #include "proj/common.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj_json_streaming_writer.hpp" #include #include using namespace NS_PROJ::internal; NS_PROJ_START namespace coordinates { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateMetadata::Private { crs::CRSNNPtr crs_; util::optional coordinateEpoch_{}; explicit Private(const crs::CRSNNPtr &crs) : crs_(crs) {} Private(const crs::CRSNNPtr &crs, const common::DataEpoch &coordinateEpoch) : crs_(crs), coordinateEpoch_(coordinateEpoch) {} }; //! @endcond // --------------------------------------------------------------------------- CoordinateMetadata::CoordinateMetadata(const crs::CRSNNPtr &crsIn) : d(std::make_unique(crsIn)) {} // --------------------------------------------------------------------------- CoordinateMetadata::CoordinateMetadata(const crs::CRSNNPtr &crsIn, double coordinateEpochAsDecimalYearIn) : d(std::make_unique(crsIn, common::DataEpoch(common::Measure( coordinateEpochAsDecimalYearIn, common::UnitOfMeasure::YEAR)))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateMetadata::~CoordinateMetadata() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateMetadata from a static CRS. * @param crsIn a static CRS * @return new CoordinateMetadata. * @throw util::Exception if crsIn is a dynamic CRS. */ CoordinateMetadataNNPtr CoordinateMetadata::create(const crs::CRSNNPtr &crsIn) { if (crsIn->isDynamic(/*considerWGS84AsDynamic=*/false)) { throw util::Exception( "Coordinate epoch should be provided for a dynamic CRS"); } auto coordinateMetadata( CoordinateMetadata::nn_make_shared(crsIn)); coordinateMetadata->assignSelf(coordinateMetadata); return coordinateMetadata; } // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateMetadata from a dynamic CRS and an associated * coordinate epoch. * * @param crsIn a dynamic CRS * @param coordinateEpochIn coordinate epoch expressed in decimal year. * @return new CoordinateMetadata. * @throw util::Exception if crsIn is a static CRS. */ CoordinateMetadataNNPtr CoordinateMetadata::create(const crs::CRSNNPtr &crsIn, double coordinateEpochIn) { return create(crsIn, coordinateEpochIn, nullptr); } // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateMetadata from a dynamic CRS and an associated * coordinate epoch. * * @param crsIn a dynamic CRS * @param coordinateEpochIn coordinate epoch expressed in decimal year. * @param dbContext Database context (may be null) * @return new CoordinateMetadata. * @throw util::Exception if crsIn is a static CRS. */ CoordinateMetadataNNPtr CoordinateMetadata::create(const crs::CRSNNPtr &crsIn, double coordinateEpochIn, const io::DatabaseContextPtr &dbContext) { if (!crsIn->isDynamic(/*considerWGS84AsDynamic=*/true)) { bool ok = false; if (dbContext) { auto geodCrs = crsIn->extractGeodeticCRS(); if (geodCrs) { auto factory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string()); ok = !factory ->getPointMotionOperationsFor(NN_NO_CHECK(geodCrs), false) .empty(); } } if (!ok) { throw util::Exception( "Coordinate epoch should not be provided for a static CRS"); } } auto coordinateMetadata( CoordinateMetadata::nn_make_shared( crsIn, coordinateEpochIn)); coordinateMetadata->assignSelf(coordinateMetadata); return coordinateMetadata; } // --------------------------------------------------------------------------- /** \brief Get the CRS associated with this CoordinateMetadata object. */ const crs::CRSNNPtr &CoordinateMetadata::crs() PROJ_PURE_DEFN { return d->crs_; } // --------------------------------------------------------------------------- /** \brief Get the coordinate epoch associated with this CoordinateMetadata * object. * * The coordinate epoch is mandatory for a dynamic CRS, * and forbidden for a static CRS. */ const util::optional & CoordinateMetadata::coordinateEpoch() PROJ_PURE_DEFN { return d->coordinateEpoch_; } // --------------------------------------------------------------------------- /** \brief Get the coordinate epoch associated with this CoordinateMetadata * object, as decimal year. * * The coordinate epoch is mandatory for a dynamic CRS, * and forbidden for a static CRS. */ double CoordinateMetadata::coordinateEpochAsDecimalYear() PROJ_PURE_DEFN { if (d->coordinateEpoch_.has_value()) { return getRoundedEpochInDecimalYear( d->coordinateEpoch_->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); } return std::numeric_limits::quiet_NaN(); } // --------------------------------------------------------------------------- /** \brief Return a variant of this CoordinateMetadata "promoted" to a 3D one, * if not already the case. * * @param newName Name of the new underlying CRS. If empty, nameStr() will be * used. * @param dbContext Database context to look for potentially already registered * 3D CRS. May be nullptr. * @return a new CoordinateMetadata object promoted to 3D, or the current one if * already 3D or not applicable. */ CoordinateMetadataNNPtr CoordinateMetadata::promoteTo3D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const { auto crs = d->crs_->promoteTo3D(newName, dbContext); if (d->coordinateEpoch_.has_value()) { auto coordinateMetadata( CoordinateMetadata::nn_make_shared( crs, coordinateEpochAsDecimalYear())); coordinateMetadata->assignSelf(coordinateMetadata); return coordinateMetadata; } else { auto coordinateMetadata( CoordinateMetadata::nn_make_shared(crs)); coordinateMetadata->assignSelf(coordinateMetadata); return coordinateMetadata; } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateMetadata::_exportToWKT(io::WKTFormatter *formatter) const { if (formatter->version() != io::WKTFormatter::Version::WKT2 || !formatter->use2019Keywords()) { io::FormattingException::Throw( "CoordinateMetadata can only be exported since WKT2:2019"); } formatter->startNode(io::WKTConstants::COORDINATEMETADATA, false); crs()->_exportToWKT(formatter); if (d->coordinateEpoch_.has_value()) { formatter->startNode(io::WKTConstants::EPOCH, false); formatter->add(coordinateEpochAsDecimalYear()); formatter->endNode(); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateMetadata::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("CoordinateMetadata", false)); writer->AddObjKey("crs"); crs()->_exportToJSON(formatter); if (d->coordinateEpoch_.has_value()) { writer->AddObjKey("coordinateEpoch"); writer->Add(coordinateEpochAsDecimalYear()); } } //! @endcond } // namespace coordinates NS_PROJ_END proj-9.8.1/src/iso19111/io.cpp000664 001750 001750 00002025176 15166171715 015561 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include #include #include #include #include #include #include #include #include #include #include // std::istringstream #include #include #include #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "operation/coordinateoperation_internal.hpp" #include "operation/esriparammappings.hpp" #include "operation/oputils.hpp" #include "operation/parammappings.hpp" #include "proj/internal/coordinatesystem_internal.hpp" #include "proj/internal/datum_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/include_nlohmann_json.hpp" #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include "wkt1_parser.h" #include "wkt2_parser.h" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // clang-format on using namespace NS_PROJ::common; using namespace NS_PROJ::coordinates; using namespace NS_PROJ::crs; using namespace NS_PROJ::cs; using namespace NS_PROJ::datum; using namespace NS_PROJ::internal; using namespace NS_PROJ::metadata; using namespace NS_PROJ::operation; using namespace NS_PROJ::util; using json = nlohmann::json; //! @cond Doxygen_Suppress static const std::string emptyString{}; //! @endcond #if 0 namespace dropbox{ namespace oxygen { template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn>::~nn() = default; template<> nn > >::~nn() = default; template<> nn > >::~nn() = default; template<> nn > >::~nn() = default; }} #endif NS_PROJ_START namespace io { //! @cond Doxygen_Suppress const char *JSONFormatter::PROJJSON_v0_7 = "https://proj.org/schemas/v0.7/projjson.schema.json"; #define PROJJSON_DEFAULT_VERSION JSONFormatter::PROJJSON_v0_7 //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress IWKTExportable::~IWKTExportable() = default; // --------------------------------------------------------------------------- std::string IWKTExportable::exportToWKT(WKTFormatter *formatter) const { _exportToWKT(formatter); return formatter->toString(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct WKTFormatter::Private { struct Params { WKTFormatter::Convention convention_ = WKTFormatter::Convention::WKT2; WKTFormatter::Version version_ = WKTFormatter::Version::WKT2; bool multiLine_ = true; bool strict_ = true; int indentWidth_ = 4; bool idOnTopLevelOnly_ = false; bool outputAxisOrder_ = false; bool primeMeridianOmittedIfGreenwich_ = false; bool ellipsoidUnitOmittedIfMetre_ = false; bool primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = false; bool forceUNITKeyword_ = false; bool outputCSUnitOnlyOnceIfSame_ = false; bool primeMeridianInDegree_ = false; bool use2019Keywords_ = false; bool useESRIDialect_ = false; bool allowEllipsoidalHeightAsVerticalCRS_ = false; bool allowLINUNITNode_ = false; OutputAxisRule outputAxis_ = WKTFormatter::OutputAxisRule::YES; }; Params params_{}; DatabaseContextPtr dbContext_{}; int indentLevel_ = 0; int level_ = 0; std::vector stackHasChild_{}; std::vector stackHasId_{false}; std::vector stackEmptyKeyword_{}; std::vector stackDisableUsage_{}; std::vector outputUnitStack_{true}; std::vector outputIdStack_{true}; std::vector axisLinearUnitStack_{ util::nn_make_shared(UnitOfMeasure::METRE)}; std::vector axisAngularUnitStack_{ util::nn_make_shared(UnitOfMeasure::DEGREE)}; bool abridgedTransformation_ = false; bool useDerivingConversion_ = false; std::vector toWGS84Parameters_{}; std::string hDatumExtension_{}; std::string vDatumExtension_{}; crs::GeographicCRSPtr geogCRSOfCompoundCRS_{}; std::string result_{}; // cppcheck-suppress functionStatic void addNewLine(); void addIndentation(); // cppcheck-suppress functionStatic void startNewChild(); }; //! @endcond // --------------------------------------------------------------------------- /** \brief Constructs a new formatter. * * A formatter can be used only once (its internal state is mutated) * * Its default behavior can be adjusted with the different setters. * * @param convention WKT flavor. Defaults to Convention::WKT2 * @param dbContext Database context, to allow queries in it if needed. * This is used for example for WKT1_ESRI output to do name substitutions. * * @return new formatter. */ WKTFormatterNNPtr WKTFormatter::create(Convention convention, // cppcheck-suppress passedByValue DatabaseContextPtr dbContext) { auto ret = NN_NO_CHECK(WKTFormatter::make_unique(convention)); ret->d->dbContext_ = std::move(dbContext); return ret; } // --------------------------------------------------------------------------- /** \brief Constructs a new formatter from another one. * * A formatter can be used only once (its internal state is mutated) * * Its default behavior can be adjusted with the different setters. * * @param other source formatter. * @return new formatter. */ WKTFormatterNNPtr WKTFormatter::create(const WKTFormatterNNPtr &other) { auto f = create(other->d->params_.convention_, other->d->dbContext_); f->d->params_ = other->d->params_; return f; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress WKTFormatter::~WKTFormatter() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Whether to use multi line output or not. */ WKTFormatter &WKTFormatter::setMultiLine(bool multiLine) noexcept { d->params_.multiLine_ = multiLine; return *this; } // --------------------------------------------------------------------------- /** \brief Set number of spaces for each indentation level (defaults to 4). */ WKTFormatter &WKTFormatter::setIndentationWidth(int width) noexcept { d->params_.indentWidth_ = width; return *this; } // --------------------------------------------------------------------------- /** \brief Set whether AXIS nodes should be output. */ WKTFormatter & WKTFormatter::setOutputAxis(OutputAxisRule outputAxisIn) noexcept { d->params_.outputAxis_ = outputAxisIn; return *this; } // --------------------------------------------------------------------------- /** \brief Set whether the formatter should operate on strict more or not. * * The default is strict mode, in which case a FormattingException can be * thrown. * In non-strict mode, a Geographic 3D CRS can be for example exported as * WKT1_GDAL with 3 axes, whereas this is normally not allowed. */ WKTFormatter &WKTFormatter::setStrict(bool strictIn) noexcept { d->params_.strict_ = strictIn; return *this; } // --------------------------------------------------------------------------- /** \brief Returns whether the formatter is in strict mode. */ bool WKTFormatter::isStrict() const noexcept { return d->params_.strict_; } // --------------------------------------------------------------------------- /** \brief Set whether the formatter should export, in WKT1, a Geographic or * Projected 3D CRS as a compound CRS whose vertical part represents an * ellipsoidal height. */ WKTFormatter & WKTFormatter::setAllowEllipsoidalHeightAsVerticalCRS(bool allow) noexcept { d->params_.allowEllipsoidalHeightAsVerticalCRS_ = allow; return *this; } // --------------------------------------------------------------------------- /** \brief Return whether the formatter should export, in WKT1, a Geographic or * Projected 3D CRS as a compound CRS whose vertical part represents an * ellipsoidal height. */ bool WKTFormatter::isAllowedEllipsoidalHeightAsVerticalCRS() const noexcept { return d->params_.allowEllipsoidalHeightAsVerticalCRS_; } // --------------------------------------------------------------------------- /** \brief Set whether the formatter should export, in WKT1_ESRI, a Geographic * 3D CRS with the relatively new (ArcGIS Pro >= 2.7) LINUNIT node. * Defaults to true. * @since PROJ 9.1 */ WKTFormatter &WKTFormatter::setAllowLINUNITNode(bool allow) noexcept { d->params_.allowLINUNITNode_ = allow; return *this; } // --------------------------------------------------------------------------- /** \brief Return whether the formatter should export, in WKT1_ESRI, a * Geographic 3D CRS with the relatively new (ArcGIS Pro >= 2.7) LINUNIT node. * Defaults to true. * @since PROJ 9.1 */ bool WKTFormatter::isAllowedLINUNITNode() const noexcept { return d->params_.allowLINUNITNode_; } // --------------------------------------------------------------------------- /** Returns the WKT string from the formatter. */ const std::string &WKTFormatter::toString() const { if (d->indentLevel_ > 0 || d->level_ > 0) { // For intermediary nodes, the formatter is in a inconsistent // state. throw FormattingException("toString() called on intermediate nodes"); } if (d->axisLinearUnitStack_.size() != 1) throw FormattingException( "Unbalanced pushAxisLinearUnit() / popAxisLinearUnit()"); if (d->axisAngularUnitStack_.size() != 1) throw FormattingException( "Unbalanced pushAxisAngularUnit() / popAxisAngularUnit()"); if (d->outputIdStack_.size() != 1) throw FormattingException("Unbalanced pushOutputId() / popOutputId()"); if (d->outputUnitStack_.size() != 1) throw FormattingException( "Unbalanced pushOutputUnit() / popOutputUnit()"); if (d->stackHasId_.size() != 1) throw FormattingException("Unbalanced pushHasId() / popHasId()"); if (!d->stackDisableUsage_.empty()) throw FormattingException( "Unbalanced pushDisableUsage() / popDisableUsage()"); return d->result_; } //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- WKTFormatter::WKTFormatter(Convention convention) : d(std::make_unique()) { d->params_.convention_ = convention; switch (convention) { case Convention::WKT2_2019: d->params_.use2019Keywords_ = true; PROJ_FALLTHROUGH; case Convention::WKT2: d->params_.version_ = WKTFormatter::Version::WKT2; d->params_.outputAxisOrder_ = true; break; case Convention::WKT2_2019_SIMPLIFIED: d->params_.use2019Keywords_ = true; PROJ_FALLTHROUGH; case Convention::WKT2_SIMPLIFIED: d->params_.version_ = WKTFormatter::Version::WKT2; d->params_.idOnTopLevelOnly_ = true; d->params_.outputAxisOrder_ = false; d->params_.primeMeridianOmittedIfGreenwich_ = true; d->params_.ellipsoidUnitOmittedIfMetre_ = true; d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_ = true; d->params_.forceUNITKeyword_ = true; d->params_.outputCSUnitOnlyOnceIfSame_ = true; break; case Convention::WKT1_GDAL: d->params_.version_ = WKTFormatter::Version::WKT1; d->params_.outputAxisOrder_ = false; d->params_.forceUNITKeyword_ = true; d->params_.primeMeridianInDegree_ = true; d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE; break; case Convention::WKT1_ESRI: d->params_.version_ = WKTFormatter::Version::WKT1; d->params_.outputAxisOrder_ = false; d->params_.forceUNITKeyword_ = true; d->params_.primeMeridianInDegree_ = true; d->params_.useESRIDialect_ = true; d->params_.multiLine_ = false; d->params_.outputAxis_ = WKTFormatter::OutputAxisRule::NO; d->params_.allowLINUNITNode_ = true; break; default: assert(false); break; } } // --------------------------------------------------------------------------- WKTFormatter &WKTFormatter::setOutputId(bool outputIdIn) { if (d->indentLevel_ != 0) { throw Exception( "setOutputId() shall only be called when the stack state is empty"); } d->outputIdStack_[0] = outputIdIn; return *this; } // --------------------------------------------------------------------------- void WKTFormatter::Private::addNewLine() { result_ += '\n'; } // --------------------------------------------------------------------------- void WKTFormatter::Private::addIndentation() { result_ += std::string( static_cast(indentLevel_) * params_.indentWidth_, ' '); } // --------------------------------------------------------------------------- void WKTFormatter::enter() { if (d->indentLevel_ == 0 && d->level_ == 0) { d->stackHasChild_.push_back(false); } ++d->level_; } // --------------------------------------------------------------------------- void WKTFormatter::leave() { assert(d->level_ > 0); --d->level_; if (d->indentLevel_ == 0 && d->level_ == 0) { d->stackHasChild_.pop_back(); } } // --------------------------------------------------------------------------- bool WKTFormatter::isAtTopLevel() const { return d->level_ == 0 && d->indentLevel_ == 0; } // --------------------------------------------------------------------------- void WKTFormatter::startNode(const std::string &keyword, bool hasId) { if (!d->stackHasChild_.empty()) { d->startNewChild(); } else if (!d->result_.empty()) { d->result_ += ','; if (d->params_.multiLine_ && !keyword.empty()) { d->addNewLine(); } } if (d->params_.multiLine_) { if ((d->indentLevel_ || d->level_) && !keyword.empty()) { if (!d->result_.empty()) { d->addNewLine(); } d->addIndentation(); } } if (!keyword.empty()) { d->result_ += keyword; d->result_ += '['; } d->indentLevel_++; d->stackHasChild_.push_back(false); d->stackEmptyKeyword_.push_back(keyword.empty()); // Starting from a node that has a ID, we should emit ID nodes for : // - this node // - and for METHOD&PARAMETER nodes in WKT2, unless idOnTopLevelOnly_ is // set. // For WKT2, all other intermediate nodes shouldn't have ID ("not // recommended") if (!d->params_.idOnTopLevelOnly_ && d->indentLevel_ >= 2 && d->params_.version_ == WKTFormatter::Version::WKT2 && (keyword == WKTConstants::METHOD || keyword == WKTConstants::PARAMETER)) { pushOutputId(d->outputIdStack_[0]); } else if (d->indentLevel_ >= 2 && d->params_.version_ == WKTFormatter::Version::WKT2) { pushOutputId(d->outputIdStack_[0] && !d->stackHasId_.back()); } else { pushOutputId(outputId()); } d->stackHasId_.push_back(hasId || d->stackHasId_.back()); } // --------------------------------------------------------------------------- void WKTFormatter::endNode() { assert(d->indentLevel_ > 0); d->stackHasId_.pop_back(); popOutputId(); d->indentLevel_--; bool emptyKeyword = d->stackEmptyKeyword_.back(); d->stackEmptyKeyword_.pop_back(); d->stackHasChild_.pop_back(); if (!emptyKeyword) d->result_ += ']'; } // --------------------------------------------------------------------------- WKTFormatter &WKTFormatter::simulCurNodeHasId() { d->stackHasId_.back() = true; return *this; } // --------------------------------------------------------------------------- void WKTFormatter::Private::startNewChild() { assert(!stackHasChild_.empty()); if (stackHasChild_.back()) { result_ += ','; } stackHasChild_.back() = true; } // --------------------------------------------------------------------------- void WKTFormatter::addQuotedString(const char *str) { addQuotedString(std::string(str)); } void WKTFormatter::addQuotedString(const std::string &str) { d->startNewChild(); d->result_ += '"'; d->result_ += replaceAll(str, "\"", "\"\""); d->result_ += '"'; } // --------------------------------------------------------------------------- void WKTFormatter::add(const std::string &str) { d->startNewChild(); d->result_ += str; } // --------------------------------------------------------------------------- void WKTFormatter::add(int number) { d->startNewChild(); d->result_ += internal::toString(number); } // --------------------------------------------------------------------------- #ifdef __MINGW32__ static std::string normalizeExponent(const std::string &in) { // mingw will output 1e-0xy instead of 1e-xy. Fix that auto pos = in.find("e-0"); if (pos == std::string::npos) { return in; } if (pos + 4 < in.size() && isdigit(in[pos + 3]) && isdigit(in[pos + 4])) { return in.substr(0, pos + 2) + in.substr(pos + 3); } return in; } #else static inline std::string normalizeExponent(const std::string &in) { return in; } #endif static inline std::string normalizeSerializedString(const std::string &in) { auto ret(normalizeExponent(in)); return ret; } // --------------------------------------------------------------------------- void WKTFormatter::add(double number, int precision) { d->startNewChild(); if (number == 0.0) { if (d->params_.useESRIDialect_) { d->result_ += "0.0"; } else { d->result_ += '0'; } } else { std::string val( normalizeSerializedString(internal::toString(number, precision))); d->result_ += replaceAll(val, "e", "E"); if (d->params_.useESRIDialect_ && val.find('.') == std::string::npos) { d->result_ += ".0"; } } } // --------------------------------------------------------------------------- void WKTFormatter::pushOutputUnit(bool outputUnitIn) { d->outputUnitStack_.push_back(outputUnitIn); } // --------------------------------------------------------------------------- void WKTFormatter::popOutputUnit() { d->outputUnitStack_.pop_back(); } // --------------------------------------------------------------------------- bool WKTFormatter::outputUnit() const { return d->outputUnitStack_.back(); } // --------------------------------------------------------------------------- void WKTFormatter::pushOutputId(bool outputIdIn) { d->outputIdStack_.push_back(outputIdIn); } // --------------------------------------------------------------------------- void WKTFormatter::popOutputId() { d->outputIdStack_.pop_back(); } // --------------------------------------------------------------------------- bool WKTFormatter::outputId() const { return !d->params_.useESRIDialect_ && d->outputIdStack_.back(); } // --------------------------------------------------------------------------- void WKTFormatter::pushHasId(bool hasId) { d->stackHasId_.push_back(hasId); } // --------------------------------------------------------------------------- void WKTFormatter::popHasId() { d->stackHasId_.pop_back(); } // --------------------------------------------------------------------------- void WKTFormatter::pushDisableUsage() { d->stackDisableUsage_.push_back(true); } // --------------------------------------------------------------------------- void WKTFormatter::popDisableUsage() { d->stackDisableUsage_.pop_back(); } // --------------------------------------------------------------------------- bool WKTFormatter::outputUsage() const { return outputId() && d->stackDisableUsage_.empty(); } // --------------------------------------------------------------------------- void WKTFormatter::pushAxisLinearUnit(const UnitOfMeasureNNPtr &unit) { d->axisLinearUnitStack_.push_back(unit); } // --------------------------------------------------------------------------- void WKTFormatter::popAxisLinearUnit() { d->axisLinearUnitStack_.pop_back(); } // --------------------------------------------------------------------------- const UnitOfMeasureNNPtr &WKTFormatter::axisLinearUnit() const { return d->axisLinearUnitStack_.back(); } // --------------------------------------------------------------------------- void WKTFormatter::pushAxisAngularUnit(const UnitOfMeasureNNPtr &unit) { d->axisAngularUnitStack_.push_back(unit); } // --------------------------------------------------------------------------- void WKTFormatter::popAxisAngularUnit() { d->axisAngularUnitStack_.pop_back(); } // --------------------------------------------------------------------------- const UnitOfMeasureNNPtr &WKTFormatter::axisAngularUnit() const { return d->axisAngularUnitStack_.back(); } // --------------------------------------------------------------------------- WKTFormatter::OutputAxisRule WKTFormatter::outputAxis() const { return d->params_.outputAxis_; } // --------------------------------------------------------------------------- bool WKTFormatter::outputAxisOrder() const { return d->params_.outputAxisOrder_; } // --------------------------------------------------------------------------- bool WKTFormatter::primeMeridianOmittedIfGreenwich() const { return d->params_.primeMeridianOmittedIfGreenwich_; } // --------------------------------------------------------------------------- bool WKTFormatter::ellipsoidUnitOmittedIfMetre() const { return d->params_.ellipsoidUnitOmittedIfMetre_; } // --------------------------------------------------------------------------- bool WKTFormatter::primeMeridianOrParameterUnitOmittedIfSameAsAxis() const { return d->params_.primeMeridianOrParameterUnitOmittedIfSameAsAxis_; } // --------------------------------------------------------------------------- bool WKTFormatter::outputCSUnitOnlyOnceIfSame() const { return d->params_.outputCSUnitOnlyOnceIfSame_; } // --------------------------------------------------------------------------- bool WKTFormatter::forceUNITKeyword() const { return d->params_.forceUNITKeyword_; } // --------------------------------------------------------------------------- bool WKTFormatter::primeMeridianInDegree() const { return d->params_.primeMeridianInDegree_; } // --------------------------------------------------------------------------- bool WKTFormatter::idOnTopLevelOnly() const { return d->params_.idOnTopLevelOnly_; } // --------------------------------------------------------------------------- bool WKTFormatter::topLevelHasId() const { return d->stackHasId_.size() >= 2 && d->stackHasId_[1]; } // --------------------------------------------------------------------------- WKTFormatter::Version WKTFormatter::version() const { return d->params_.version_; } // --------------------------------------------------------------------------- bool WKTFormatter::use2019Keywords() const { return d->params_.use2019Keywords_; } // --------------------------------------------------------------------------- bool WKTFormatter::useESRIDialect() const { return d->params_.useESRIDialect_; } // --------------------------------------------------------------------------- const DatabaseContextPtr &WKTFormatter::databaseContext() const { return d->dbContext_; } // --------------------------------------------------------------------------- void WKTFormatter::setAbridgedTransformation(bool outputIn) { d->abridgedTransformation_ = outputIn; } // --------------------------------------------------------------------------- bool WKTFormatter::abridgedTransformation() const { return d->abridgedTransformation_; } // --------------------------------------------------------------------------- void WKTFormatter::setUseDerivingConversion(bool useDerivingConversionIn) { d->useDerivingConversion_ = useDerivingConversionIn; } // --------------------------------------------------------------------------- bool WKTFormatter::useDerivingConversion() const { return d->useDerivingConversion_; } // --------------------------------------------------------------------------- void WKTFormatter::setTOWGS84Parameters(const std::vector ¶ms) { d->toWGS84Parameters_ = params; } // --------------------------------------------------------------------------- const std::vector &WKTFormatter::getTOWGS84Parameters() const { return d->toWGS84Parameters_; } // --------------------------------------------------------------------------- void WKTFormatter::setVDatumExtension(const std::string &filename) { d->vDatumExtension_ = filename; } // --------------------------------------------------------------------------- const std::string &WKTFormatter::getVDatumExtension() const { return d->vDatumExtension_; } // --------------------------------------------------------------------------- void WKTFormatter::setHDatumExtension(const std::string &filename) { d->hDatumExtension_ = filename; } // --------------------------------------------------------------------------- const std::string &WKTFormatter::getHDatumExtension() const { return d->hDatumExtension_; } // --------------------------------------------------------------------------- void WKTFormatter::setGeogCRSOfCompoundCRS(const crs::GeographicCRSPtr &crs) { d->geogCRSOfCompoundCRS_ = crs; } // --------------------------------------------------------------------------- const crs::GeographicCRSPtr &WKTFormatter::getGeogCRSOfCompoundCRS() const { return d->geogCRSOfCompoundCRS_; } // --------------------------------------------------------------------------- std::string WKTFormatter::morphNameToESRI(const std::string &name) { for (const auto *suffix : {"(m)", "(ftUS)", "(E-N)", "(N-E)"}) { if (ends_with(name, suffix)) { return morphNameToESRI( name.substr(0, name.size() - strlen(suffix))) + suffix; } } std::string ret; bool insertUnderscore = false; // Replace any special character by underscore, except at the beginning // and of the name where those characters are removed. for (char ch : name) { if (ch == '+' || ch == '-' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { if (insertUnderscore && !ret.empty()) { ret += '_'; } ret += ch; insertUnderscore = false; } else { insertUnderscore = true; } } return ret; } // --------------------------------------------------------------------------- void WKTFormatter::ingestWKTNode(const WKTNodeNNPtr &node) { startNode(node->value(), true); for (const auto &child : node->children()) { if (!child->children().empty()) { ingestWKTNode(child); } else { add(child->value()); } } endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static WKTNodeNNPtr null_node(NN_NO_CHECK(std::make_unique(std::string()))); static inline bool isNull(const WKTNodeNNPtr &node) { return &node == &null_node; } struct WKTNode::Private { std::string value_{}; std::vector children_{}; explicit Private(const std::string &valueIn) : value_(valueIn) {} // cppcheck-suppress functionStatic inline const std::string &value() PROJ_PURE_DEFN { return value_; } // cppcheck-suppress functionStatic inline const std::vector &children() PROJ_PURE_DEFN { return children_; } // cppcheck-suppress functionStatic inline size_t childrenSize() PROJ_PURE_DEFN { return children_.size(); } // cppcheck-suppress functionStatic const WKTNodeNNPtr &lookForChild(const std::string &childName, int occurrence) const noexcept; // cppcheck-suppress functionStatic const WKTNodeNNPtr &lookForChild(const std::string &name) const noexcept; // cppcheck-suppress functionStatic const WKTNodeNNPtr &lookForChild(const std::string &name, const std::string &name2) const noexcept; // cppcheck-suppress functionStatic const WKTNodeNNPtr &lookForChild(const std::string &name, const std::string &name2, const std::string &name3) const noexcept; // cppcheck-suppress functionStatic const WKTNodeNNPtr &lookForChild(const std::string &name, const std::string &name2, const std::string &name3, const std::string &name4) const noexcept; }; #define GP() getPrivate() // --------------------------------------------------------------------------- const WKTNodeNNPtr & WKTNode::Private::lookForChild(const std::string &childName, int occurrence) const noexcept { int occCount = 0; for (const auto &child : children_) { if (ci_equal(child->GP()->value(), childName)) { if (occurrence == occCount) { return child; } occCount++; } } return null_node; } const WKTNodeNNPtr & WKTNode::Private::lookForChild(const std::string &name) const noexcept { for (const auto &child : children_) { const auto &v = child->GP()->value(); if (ci_equal(v, name)) { return child; } } return null_node; } const WKTNodeNNPtr & WKTNode::Private::lookForChild(const std::string &name, const std::string &name2) const noexcept { for (const auto &child : children_) { const auto &v = child->GP()->value(); if (ci_equal(v, name) || ci_equal(v, name2)) { return child; } } return null_node; } const WKTNodeNNPtr & WKTNode::Private::lookForChild(const std::string &name, const std::string &name2, const std::string &name3) const noexcept { for (const auto &child : children_) { const auto &v = child->GP()->value(); if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) { return child; } } return null_node; } const WKTNodeNNPtr &WKTNode::Private::lookForChild( const std::string &name, const std::string &name2, const std::string &name3, const std::string &name4) const noexcept { for (const auto &child : children_) { const auto &v = child->GP()->value(); if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) || ci_equal(v, name4)) { return child; } } return null_node; } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a WKTNode. * * @param valueIn the name of the node. */ WKTNode::WKTNode(const std::string &valueIn) : d(std::make_unique(valueIn)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress WKTNode::~WKTNode() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Adds a child to the current node. * * @param child child to add. This should not be a parent of this node. */ void WKTNode::addChild(WKTNodeNNPtr &&child) { d->children_.push_back(std::move(child)); } // --------------------------------------------------------------------------- /** \brief Return the (occurrence-1)th sub-node of name childName. * * @param childName name of the child. * @param occurrence occurrence index (starting at 0) * @return the child, or nullptr. */ const WKTNodePtr &WKTNode::lookForChild(const std::string &childName, int occurrence) const noexcept { int occCount = 0; for (const auto &child : d->children_) { if (ci_equal(child->GP()->value(), childName)) { if (occurrence == occCount) { return child; } occCount++; } } return null_node; } // --------------------------------------------------------------------------- /** \brief Return the count of children of given name. * * @param childName name of the children to look for. * @return count */ int WKTNode::countChildrenOfName(const std::string &childName) const noexcept { int occCount = 0; for (const auto &child : d->children_) { if (ci_equal(child->GP()->value(), childName)) { occCount++; } } return occCount; } // --------------------------------------------------------------------------- /** \brief Return the value of a node. */ const std::string &WKTNode::value() const { return d->value_; } // --------------------------------------------------------------------------- /** \brief Return the children of a node. */ const std::vector &WKTNode::children() const { return d->children_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static size_t skipSpace(const std::string &str, size_t start) { size_t i = start; while (i < str.size() && ::isspace(static_cast(str[i]))) { ++i; } return i; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // As used in examples of OGC 12-063r5 static const std::string startPrintedQuote("\xE2\x80\x9C"); static const std::string endPrintedQuote("\xE2\x80\x9D"); //! @endcond WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart, int recLevel, size_t &indexEnd) { if (recLevel == 16) { throw ParsingException("too many nesting levels"); } std::string value; size_t i = skipSpace(wkt, indexStart); if (i == wkt.size()) { throw ParsingException("whitespace only string"); } std::string closingStringMarker; bool inString = false; for (; i < wkt.size() && (inString || (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' && wkt[i] != ')' && !::isspace(static_cast(wkt[i])))); ++i) { if (wkt[i] == '"') { if (!inString) { inString = true; closingStringMarker = "\""; } else if (closingStringMarker == "\"") { if (i + 1 < wkt.size() && wkt[i + 1] == '"') { i++; } else { inString = false; closingStringMarker.clear(); } } } else if (i + 3 <= wkt.size() && wkt.substr(i, 3) == startPrintedQuote) { if (!inString) { inString = true; closingStringMarker = endPrintedQuote; value += '"'; i += 2; continue; } } else if (i + 3 <= wkt.size() && closingStringMarker == endPrintedQuote && wkt.substr(i, 3) == endPrintedQuote) { inString = false; closingStringMarker.clear(); value += '"'; i += 2; continue; } value += wkt[i]; } i = skipSpace(wkt, i); if (i == wkt.size()) { if (indexStart == 0) { throw ParsingException("missing ["); } else { throw ParsingException("missing , or ]"); } } auto node = NN_NO_CHECK(std::make_unique(value)); if (indexStart > 0) { if (wkt[i] == ',') { indexEnd = i + 1; return node; } if (wkt[i] == ']' || wkt[i] == ')') { indexEnd = i; return node; } } if (wkt[i] != '[' && wkt[i] != '(') { throw ParsingException("missing ["); } ++i; // skip [ i = skipSpace(wkt, i); while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') { size_t indexEndChild; node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild)); assert(indexEndChild > i); i = indexEndChild; i = skipSpace(wkt, i); if (i < wkt.size() && wkt[i] == ',') { ++i; i = skipSpace(wkt, i); } } if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) { throw ParsingException("missing ]"); } indexEnd = i + 1; return node; } // --------------------------------------------------------------------------- /** \brief Instantiate a WKTNode hierarchy from a WKT string. * * @param wkt the WKT string to parse. * @param indexStart the start index in the wkt string. * @throw ParsingException if the string cannot be parsed. */ WKTNodeNNPtr WKTNode::createFrom(const std::string &wkt, size_t indexStart) { size_t indexEnd; return createFrom(wkt, indexStart, 0, indexEnd); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static std::string escapeIfQuotedString(const std::string &str) { if (str.size() > 2 && str[0] == '"' && str.back() == '"') { std::string res("\""); res += replaceAll(str.substr(1, str.size() - 2), "\"", "\"\""); res += '"'; return res; } else { return str; } } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a WKT representation of the tree structure. */ std::string WKTNode::toString() const { std::string str(escapeIfQuotedString(d->value_)); if (!d->children_.empty()) { str += "["; bool first = true; for (auto &child : d->children_) { if (!first) { str += ','; } first = false; str += child->toString(); } str += "]"; } return str; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct WKTParser::Private { struct ci_less_struct { bool operator()(const std::string &lhs, const std::string &rhs) const noexcept { return ci_less(lhs, rhs); } }; bool strict_ = true; bool unsetIdentifiersIfIncompatibleDef_ = true; std::list warningList_{}; std::list grammarErrorList_{}; std::vector toWGS84Parameters_{}; std::string datumPROJ4Grids_{}; bool esriStyle_ = false; bool maybeEsriStyle_ = false; DatabaseContextPtr dbContext_{}; crs::GeographicCRSPtr geogCRSOfCompoundCRS_{}; static constexpr unsigned int MAX_PROPERTY_SIZE = 1024; std::vector> properties_{}; Private() = default; ~Private() = default; Private(const Private &) = delete; Private &operator=(const Private &) = delete; void emitRecoverableWarning(const std::string &warningMsg); void emitGrammarError(const std::string &errorMsg); void emitRecoverableMissingUNIT(const std::string &parentNodeName, const UnitOfMeasure &fallbackUnit); BaseObjectNNPtr build(const WKTNodeNNPtr &node); IdentifierPtr buildId(const WKTNodeNNPtr &parentNode, const WKTNodeNNPtr &node, bool tolerant, bool removeInverseOf); PropertyMap &buildProperties(const WKTNodeNNPtr &node, bool removeInverseOf = false, bool hasName = true); ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node); static std::string stripQuotes(const WKTNodeNNPtr &node); static double asDouble(const WKTNodeNNPtr &node); UnitOfMeasure buildUnit(const WKTNodeNNPtr &node, UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); UnitOfMeasure buildUnitInSubNode( const WKTNodeNNPtr &node, common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN); EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node); PrimeMeridianNNPtr buildPrimeMeridian(const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit); static optional getAnchor(const WKTNodeNNPtr &node); static optional getAnchorEpoch(const WKTNodeNNPtr &node); static void parseDynamic(const WKTNodeNNPtr &dynamicNode, double &frameReferenceEpoch, util::optional &modelName); GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian, const WKTNodeNNPtr &dynamicNode); DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node, const PrimeMeridianPtr &primeMeridian, bool expectEllipsoid); MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node); CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node, const UnitOfMeasure &unitIn, const UnitOfMeasure::Type &unitType, bool isGeocentric, int expectedOrderNum); CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */ const WKTNodeNNPtr &parentNode, const UnitOfMeasure &defaultAngularUnit); GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node, bool forceGeocentricIfNoCs = false); CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node); static UnitOfMeasure guessUnitForParameter(const std::string ¶mName, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit); void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged, std::vector ¶meters, std::vector &values, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit); static std::string getExtensionProj4(const WKTNode::Private *nodeP); static void addExtensionProj4ToProp(const WKTNode::Private *nodeP, PropertyMap &props); ConversionNNPtr buildConversion(const WKTNodeNNPtr &node, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit); static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode); static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode, const char *paramName); ConversionNNPtr buildProjection(const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit); ConversionNNPtr buildProjectionStandard(const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit); const ESRIMethodMapping * getESRIMapping(const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, std::map &mapParamNameToValue); static ConversionNNPtr buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit, const ESRIMethodMapping *esriMapping, std::map &mapParamNameToValue); ConversionNNPtr buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit); ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node); VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode); TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node); EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node); ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node); CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node); DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node); CRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node); BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node); TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode); TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node); DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node); EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node); EngineeringCRSNNPtr buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node); DerivedEngineeringCRSNNPtr buildDerivedEngineeringCRS(const WKTNodeNNPtr &node); ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode); ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node); DerivedParametricCRSNNPtr buildDerivedParametricCRS(const WKTNodeNNPtr &node); DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node); CRSPtr buildCRS(const WKTNodeNNPtr &node); TransformationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node); PointMotionOperationNNPtr buildPointMotionOperation(const WKTNodeNNPtr &node); ConcatenatedOperationNNPtr buildConcatenatedOperation(const WKTNodeNNPtr &node); CoordinateMetadataNNPtr buildCoordinateMetadata(const WKTNodeNNPtr &node); }; //! @endcond // --------------------------------------------------------------------------- WKTParser::WKTParser() : d(std::make_unique()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress WKTParser::~WKTParser() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Set whether parsing should be done in strict mode. */ WKTParser &WKTParser::setStrict(bool strict) { d->strict_ = strict; return *this; } // --------------------------------------------------------------------------- /** \brief Set whether object identifiers should be unset when there is * a contradiction between the definition from WKT and the one from * the database. * * At time of writing, this only applies to the base geographic CRS of a * projected CRS, when comparing its coordinate system. */ WKTParser &WKTParser::setUnsetIdentifiersIfIncompatibleDef(bool unset) { d->unsetIdentifiersIfIncompatibleDef_ = unset; return *this; } // --------------------------------------------------------------------------- /** \brief Return the list of warnings found during parsing. * * \note The list might be non-empty only is setStrict(false) has been called. */ std::list WKTParser::warningList() const { return d->warningList_; } // --------------------------------------------------------------------------- /** \brief Return the list of grammar errors found during parsing. * * Grammar errors are non-compliance issues with respect to the WKT grammar. * * \note The list might be non-empty only is setStrict(false) has been called. * * @since PROJ 9.5 */ std::list WKTParser::grammarErrorList() const { return d->grammarErrorList_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void WKTParser::Private::emitRecoverableWarning(const std::string &errorMsg) { if (strict_) { throw ParsingException(errorMsg); } else { warningList_.push_back(errorMsg); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void WKTParser::Private::emitGrammarError(const std::string &errorMsg) { if (strict_) { throw ParsingException(errorMsg); } else { grammarErrorList_.push_back(errorMsg); } } //! @endcond // --------------------------------------------------------------------------- static double asDouble(const std::string &val) { return c_locale_stod(val); } // --------------------------------------------------------------------------- PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) { throw ParsingException( concat("not enough children in ", nodeName, " node")); } // --------------------------------------------------------------------------- PROJ_NO_RETURN static void ThrowNotRequiredNumberOfChildren(const std::string &nodeName) { throw ParsingException( concat("not required number of children in ", nodeName, " node")); } // --------------------------------------------------------------------------- PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) { throw ParsingException(concat("missing ", nodeName, " node")); } // --------------------------------------------------------------------------- PROJ_NO_RETURN static void ThrowNotExpectedCSType(const std::string &expectedCSType) { throw ParsingException(concat("CS node is not of type ", expectedCSType)); } // --------------------------------------------------------------------------- static ParsingException buildRethrow(const char *funcName, const std::exception &e) { std::string res(funcName); res += ": "; res += e.what(); return ParsingException(res); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) { return ::stripQuotes(node->GP()->value()); } // --------------------------------------------------------------------------- double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) { return io::asDouble(node->GP()->value()); } // --------------------------------------------------------------------------- IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &parentNode, const WKTNodeNNPtr &node, bool tolerant, bool removeInverseOf) { const auto *nodeP = node->GP(); const auto &nodeChildren = nodeP->children(); if (nodeChildren.size() >= 2) { auto codeSpace = stripQuotes(nodeChildren[0]); if (removeInverseOf && starts_with(codeSpace, "INVERSE(") && codeSpace.back() == ')') { codeSpace = codeSpace.substr(strlen("INVERSE(")); codeSpace.resize(codeSpace.size() - 1); } PropertyMap propertiesId; if (nodeChildren.size() >= 3 && nodeChildren[2]->GP()->childrenSize() == 0) { std::string version = stripQuotes(nodeChildren[2]); // IAU + 2015 -> IAU_2015 if (dbContext_) { std::string codeSpaceOut; if (dbContext_->getVersionedAuthority(codeSpace, version, codeSpaceOut)) { codeSpace = std::move(codeSpaceOut); version.clear(); } } if (!version.empty()) { propertiesId.set(Identifier::VERSION_KEY, version); } } auto code = stripQuotes(nodeChildren[1]); // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone // south, we generated a wrong value. Auto-fix that const auto &parentNodeKeyword(parentNode->GP()->value()); if (parentNodeKeyword == WKTConstants::CONVERSION && codeSpace == Identifier::EPSG) { const auto &parentNodeChildren = parentNode->GP()->children(); if (!parentNodeChildren.empty()) { const auto parentNodeName(stripQuotes(parentNodeChildren[0])); if (ci_starts_with(parentNodeName, "UTM Zone ") && parentNodeName.find('S') != std::string::npos) { const int nZone = atoi(parentNodeName.c_str() + strlen("UTM Zone ")); if (nZone >= 1 && nZone <= 60) { code = internal::toString(16100 + nZone); } } } } auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION); auto &uriNode = nodeP->lookForChild(WKTConstants::URI); propertiesId.set(Identifier::CODESPACE_KEY, codeSpace); bool authoritySet = false; /*if (!isNull(citationNode))*/ { const auto *citationNodeP = citationNode->GP(); if (citationNodeP->childrenSize() == 1) { authoritySet = true; propertiesId.set(Identifier::AUTHORITY_KEY, stripQuotes(citationNodeP->children()[0])); } } if (!authoritySet) { propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace); } /*if (!isNull(uriNode))*/ { const auto *uriNodeP = uriNode->GP(); if (uriNodeP->childrenSize() == 1) { propertiesId.set(Identifier::URI_KEY, stripQuotes(uriNodeP->children()[0])); } } return Identifier::create(code, propertiesId); } else if (strict_ || !tolerant) { ThrowNotEnoughChildren(nodeP->value()); } else { std::string msg("not enough children in "); msg += nodeP->value(); msg += " node"; warningList_.emplace_back(std::move(msg)); } return nullptr; } // --------------------------------------------------------------------------- PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node, bool removeInverseOf, bool hasName) { if (properties_.size() >= MAX_PROPERTY_SIZE) { throw ParsingException("MAX_PROPERTY_SIZE reached"); } properties_.push_back(std::make_unique()); auto properties = properties_.back().get(); std::string authNameFromAlias; std::string codeFromAlias; const auto *nodeP = node->GP(); const auto &nodeChildren = nodeP->children(); auto identifiers = ArrayOfBaseObject::create(); for (const auto &subNode : nodeChildren) { const auto &subNodeName(subNode->GP()->value()); if (ci_equal(subNodeName, WKTConstants::ID) || ci_equal(subNodeName, WKTConstants::AUTHORITY)) { auto id = buildId(node, subNode, true, removeInverseOf); if (id) { identifiers->add(NN_NO_CHECK(id)); } } } if (hasName && !nodeChildren.empty()) { const auto &nodeName(nodeP->value()); auto name(stripQuotes(nodeChildren[0])); if (removeInverseOf && starts_with(name, "Inverse of ")) { name = name.substr(strlen("Inverse of ")); } if (ends_with(name, " (deprecated)")) { name.resize(name.size() - strlen(" (deprecated)")); properties->set(common::IdentifiedObject::DEPRECATED_KEY, true); } // Oracle WKT can contain names like // "Reseau Geodesique Francais 1993 (EPSG ID 6171)" // for WKT attributes to the auth_name = "IGN - Paris" // Strip that suffix from the name and assign a true EPSG code to the // object if (identifiers->empty()) { const auto pos = name.find(" (EPSG ID "); if (pos != std::string::npos && name.back() == ')') { const auto code = name.substr(pos + strlen(" (EPSG ID "), name.size() - 1 - pos - strlen(" (EPSG ID ")); name.resize(pos); PropertyMap propertiesId; propertiesId.set(Identifier::CODESPACE_KEY, Identifier::EPSG); propertiesId.set(Identifier::AUTHORITY_KEY, Identifier::EPSG); identifiers->add(Identifier::create(code, propertiesId)); } } const char *tableNameForAlias = nullptr; if (ci_equal(nodeName, WKTConstants::GEOGCS)) { if (starts_with(name, "GCS_")) { esriStyle_ = true; if (name == "GCS_WGS_1984") { name = "WGS 84"; } else if (name == "GCS_unknown") { name = "unknown"; } else { tableNameForAlias = "geodetic_crs"; } } } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) { if (name == "WGS_1984") { name = "WGS 84"; authNameFromAlias = Identifier::EPSG; codeFromAlias = "7030"; } else { tableNameForAlias = "ellipsoid"; } } if (dbContext_ && tableNameForAlias) { std::string outTableName; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto officialName = authFactory->getOfficialNameFromAlias( name, tableNameForAlias, "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { name = std::move(officialName); // Clearing authority for geodetic_crs because of // potential axis order mismatch. if (strcmp(tableNameForAlias, "geodetic_crs") == 0) { authNameFromAlias.clear(); codeFromAlias.clear(); } } } properties->set(IdentifiedObject::NAME_KEY, name); } if (identifiers->empty() && !authNameFromAlias.empty()) { identifiers->add(Identifier::create( codeFromAlias, PropertyMap() .set(Identifier::CODESPACE_KEY, authNameFromAlias) .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); } if (!identifiers->empty()) { properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); } auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK); if (!isNull(remarkNode)) { const auto &remarkChildren = remarkNode->GP()->children(); if (remarkChildren.size() == 1) { properties->set(IdentifiedObject::REMARKS_KEY, stripQuotes(remarkChildren[0])); } else { ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value()); } } ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); for (const auto &subNode : nodeP->children()) { const auto &subNodeName(subNode->GP()->value()); if (ci_equal(subNodeName, WKTConstants::USAGE)) { auto objectDomain = buildObjectDomain(subNode); if (!objectDomain) { throw ParsingException( concat("missing children in ", subNodeName, " node")); } array->add(NN_NO_CHECK(objectDomain)); } } if (!array->empty()) { properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array); } else { auto objectDomain = buildObjectDomain(node); if (objectDomain) { properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain)); } } auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION); if (!isNull(versionNode)) { const auto &versionChildren = versionNode->GP()->children(); if (versionChildren.size() == 1) { properties->set(CoordinateOperation::OPERATION_VERSION_KEY, stripQuotes(versionChildren[0])); } else { ThrowNotRequiredNumberOfChildren(versionNode->GP()->value()); } } return *properties; } // --------------------------------------------------------------------------- ObjectDomainPtr WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE); auto &areaNode = nodeP->lookForChild(WKTConstants::AREA); auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX); auto &verticalExtentNode = nodeP->lookForChild(WKTConstants::VERTICALEXTENT); auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT); if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) || !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) { optional scope; const auto *scopeNodeP = scopeNode->GP(); const auto &scopeChildren = scopeNodeP->children(); if (scopeChildren.size() == 1) { scope = stripQuotes(scopeChildren[0]); } ExtentPtr extent; if (!isNull(areaNode) || !isNull(bboxNode)) { util::optional description; std::vector geogExtent; std::vector verticalExtent; std::vector temporalExtent; if (!isNull(areaNode)) { const auto &areaChildren = areaNode->GP()->children(); if (areaChildren.size() == 1) { description = stripQuotes(areaChildren[0]); } else { ThrowNotRequiredNumberOfChildren(areaNode->GP()->value()); } } if (!isNull(bboxNode)) { const auto &bboxChildren = bboxNode->GP()->children(); if (bboxChildren.size() == 4) { double south, west, north, east; try { south = asDouble(bboxChildren[0]); west = asDouble(bboxChildren[1]); north = asDouble(bboxChildren[2]); east = asDouble(bboxChildren[3]); } catch (const std::exception &) { throw ParsingException(concat("not 4 double values in ", bboxNode->GP()->value(), " node")); } try { auto bbox = GeographicBoundingBox::create(west, south, east, north); geogExtent.emplace_back(bbox); } catch (const std::exception &e) { throw ParsingException(concat("Invalid ", bboxNode->GP()->value(), " node: ") + e.what()); } } else { ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value()); } } if (!isNull(verticalExtentNode)) { const auto &verticalExtentChildren = verticalExtentNode->GP()->children(); const auto verticalExtentChildrenSize = verticalExtentChildren.size(); if (verticalExtentChildrenSize == 2 || verticalExtentChildrenSize == 3) { double min; double max; try { min = asDouble(verticalExtentChildren[0]); max = asDouble(verticalExtentChildren[1]); } catch (const std::exception &) { throw ParsingException( concat("not 2 double values in ", verticalExtentNode->GP()->value(), " node")); } UnitOfMeasure unit = UnitOfMeasure::METRE; if (verticalExtentChildrenSize == 3) { unit = buildUnit(verticalExtentChildren[2], UnitOfMeasure::Type::LINEAR); } verticalExtent.emplace_back(VerticalExtent::create( min, max, util::nn_make_shared(unit))); } else { ThrowNotRequiredNumberOfChildren( verticalExtentNode->GP()->value()); } } if (!isNull(temporalExtentNode)) { const auto &temporalExtentChildren = temporalExtentNode->GP()->children(); if (temporalExtentChildren.size() == 2) { temporalExtent.emplace_back(TemporalExtent::create( stripQuotes(temporalExtentChildren[0]), stripQuotes(temporalExtentChildren[1]))); } else { ThrowNotRequiredNumberOfChildren( temporalExtentNode->GP()->value()); } } extent = Extent::create(description, geogExtent, verticalExtent, temporalExtent) .as_nullable(); } return ObjectDomain::create(scope, extent).as_nullable(); } return nullptr; } // --------------------------------------------------------------------------- UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node, UnitOfMeasure::Type type) { const auto *nodeP = node->GP(); const auto &children = nodeP->children(); if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) || (type == UnitOfMeasure::Type::TIME && children.size() < 1)) { ThrowNotEnoughChildren(nodeP->value()); } try { std::string unitName(stripQuotes(children[0])); PropertyMap properties(buildProperties(node)); auto &idNode = nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY); if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) { emitRecoverableWarning("not enough children in " + idNode->GP()->value() + " node"); } const bool hasValidIdNode = !isNull(idNode) && idNode->GP()->childrenSize() >= 2; const auto &idNodeChildren(idNode->GP()->children()); std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0]) : std::string()); std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1]) : std::string()); bool queryDb = true; if (type == UnitOfMeasure::Type::UNKNOWN) { if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) { type = UnitOfMeasure::Type::LINEAR; unitName = "metre"; if (codeSpace.empty()) { codeSpace = Identifier::EPSG; code = "9001"; queryDb = false; } } else if (ci_equal(unitName, "DEGREE") || ci_equal(unitName, "GRAD")) { type = UnitOfMeasure::Type::ANGULAR; } } if (esriStyle_ && dbContext_ && queryDb) { std::string outTableName; std::string authNameFromAlias; std::string codeFromAlias; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto officialName = authFactory->getOfficialNameFromAlias( unitName, "unit_of_measure", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { unitName = std::move(officialName); codeSpace = std::move(authNameFromAlias); code = std::move(codeFromAlias); } } double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0; constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37; constexpr double REL_ERROR = 1e-10; // Fix common rounding errors if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) < REL_ERROR * convFactor) { convFactor = UnitOfMeasure::DEGREE.conversionToSI(); } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) < REL_ERROR * convFactor) { convFactor = US_FOOT_CONV_FACTOR; } return UnitOfMeasure(unitName, convFactor, type, codeSpace, code); } catch (const std::exception &e) { throw buildRethrow(__FUNCTION__, e); } } // --------------------------------------------------------------------------- // node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node, UnitOfMeasure::Type type) { const auto *nodeP = node->GP(); { auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT); if (!isNull(unitNode)) { return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR); } } { auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT); if (!isNull(unitNode)) { return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR); } } { auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT); if (!isNull(unitNode)) { return buildUnit(unitNode, UnitOfMeasure::Type::SCALE); } } { auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT); if (!isNull(unitNode)) { return buildUnit(unitNode, UnitOfMeasure::Type::TIME); } } { auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY); if (!isNull(unitNode)) { return buildUnit(unitNode, UnitOfMeasure::Type::TIME); } } { auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT); if (!isNull(unitNode)) { return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC); } } { auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT); if (!isNull(unitNode)) { return buildUnit(unitNode, type); } } return UnitOfMeasure::NONE; } // --------------------------------------------------------------------------- EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); const auto &children = nodeP->children(); if (children.size() < 3) { ThrowNotEnoughChildren(nodeP->value()); } try { UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure::METRE; } Length semiMajorAxis(asDouble(children[1]), unit); // Some WKT in the wild use "inf". Cf SPHEROID["unnamed",6370997,"inf"] // in https://zenodo.org/record/3878979#.Y_P4g4CZNH4, // https://zenodo.org/record/5831940#.Y_P4i4CZNH5 // or https://grasswiki.osgeo.org/wiki/Marine_Science const auto &invFlatteningChild = children[2]; if (invFlatteningChild->GP()->value() == "\"inf\"") { emitRecoverableWarning("Inverse flattening = \"inf\" is not " "conformant, but understood"); } Scale invFlattening(invFlatteningChild->GP()->value() == "\"inf\"" ? 0 : asDouble(invFlatteningChild)); const auto ellpsProperties = buildProperties(node); std::string ellpsName; ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName); const auto celestialBody(Ellipsoid::guessBodyName( dbContext_, semiMajorAxis.getSIValue(), ellpsName)); if (invFlattening.getSIValue() == 0) { return Ellipsoid::createSphere(ellpsProperties, semiMajorAxis, celestialBody); } else { return Ellipsoid::createFlattenedSphere( ellpsProperties, semiMajorAxis, invFlattening, celestialBody); } } catch (const std::exception &e) { throw buildRethrow(__FUNCTION__, e); } } // --------------------------------------------------------------------------- PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian( const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) { const auto *nodeP = node->GP(); const auto &children = nodeP->children(); if (children.size() < 2) { ThrowNotEnoughChildren(nodeP->value()); } auto name = stripQuotes(children[0]); UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); if (unit == UnitOfMeasure::NONE) { unit = defaultAngularUnit; if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure::DEGREE; } } try { double angleValue = asDouble(children[1]); // Correct for GDAL WKT1 and WKT1-ESRI departure if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 && unit._isEquivalentTo(UnitOfMeasure::GRAD, util::IComparable::Criterion::EQUIVALENT)) { angleValue = 2.5969213; } else { static const struct { const char *name; int deg; int min; double sec; } primeMeridiansDMS[] = { {"Lisbon", -9, 7, 54.862}, {"Bogota", -74, 4, 51.3}, {"Madrid", -3, 41, 14.55}, {"Rome", 12, 27, 8.4}, {"Bern", 7, 26, 22.5}, {"Jakarta", 106, 48, 27.79}, {"Ferro", -17, 40, 0}, {"Brussels", 4, 22, 4.71}, {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815}, {"Oslo", 10, 43, 22.5}, {"Paris RGS", 2, 20, 13.95}, {"Paris_RGS", 2, 20, 13.95}}; // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS" // unit and a DD.MMSSsss value, but this will likely be changed to // use decimal degree. // Or WKT1 may for example use the Paris RGS decimal degree value // but with a GEOGCS with UNIT["Grad"] for (const auto &pmDef : primeMeridiansDMS) { if (name == pmDef.name) { double dmsAsDecimalValue = (pmDef.deg >= 0 ? 1 : -1) * (std::abs(pmDef.deg) + pmDef.min / 100. + pmDef.sec / 10000.); double dmsAsDecimalDegreeValue = (pmDef.deg >= 0 ? 1 : -1) * (std::abs(pmDef.deg) + pmDef.min / 60. + pmDef.sec / 3600.); if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 || std::fabs(angleValue - dmsAsDecimalDegreeValue) < 1e-8) { angleValue = dmsAsDecimalDegreeValue; unit = UnitOfMeasure::DEGREE; } break; } } } auto &properties = buildProperties(node); if (dbContext_ && esriStyle_) { std::string outTableName; std::string codeFromAlias; std::string authNameFromAlias; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto officialName = authFactory->getOfficialNameFromAlias( name, "prime_meridian", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { properties.set(IdentifiedObject::NAME_KEY, officialName); if (!authNameFromAlias.empty()) { auto identifiers = ArrayOfBaseObject::create(); identifiers->add(Identifier::create( codeFromAlias, PropertyMap() .set(Identifier::CODESPACE_KEY, authNameFromAlias) .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); } } } Angle angle(angleValue, unit); return PrimeMeridian::create(properties, angle); } catch (const std::exception &e) { throw buildRethrow(__FUNCTION__, e); } } // --------------------------------------------------------------------------- optional WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) { auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR); if (anchorNode->GP()->childrenSize() == 1) { return optional( stripQuotes(anchorNode->GP()->children()[0])); } return optional(); } // --------------------------------------------------------------------------- optional WKTParser::Private::getAnchorEpoch(const WKTNodeNNPtr &node) { auto &anchorEpochNode = node->GP()->lookForChild(WKTConstants::ANCHOREPOCH); if (anchorEpochNode->GP()->childrenSize() == 1) { try { double value = asDouble(anchorEpochNode->GP()->children()[0]); return optional( common::Measure(value, common::UnitOfMeasure::YEAR)); } catch (const std::exception &e) { throw buildRethrow(__FUNCTION__, e); } } return optional(); } // --------------------------------------------------------------------------- static const PrimeMeridianNNPtr & fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid, const PrimeMeridianNNPtr &pm) { return (ellipsoid->celestialBody() != Ellipsoid::EARTH && pm.get() == PrimeMeridian::GREENWICH.get()) ? PrimeMeridian::REFERENCE_MERIDIAN : pm; } // --------------------------------------------------------------------------- GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame( const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian, const WKTNodeNNPtr &dynamicNode) { const auto *nodeP = node->GP(); auto &ellipsoidNode = nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); if (isNull(ellipsoidNode)) { ThrowMissing(WKTConstants::ELLIPSOID); } auto &properties = buildProperties(node); // do that before buildEllipsoid() so that esriStyle_ can be set auto name = stripQuotes(nodeP->children()[0]); const auto identifyFromName = [&](const std::string &l_name) { if (dbContext_) { auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto res = authFactory->createObjectsFromName( l_name, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true, 1); if (!res.empty()) { bool foundDatumName = false; const auto &refDatum = res.front(); if (metadata::Identifier::isEquivalentName( l_name.c_str(), refDatum->nameStr().c_str())) { foundDatumName = true; } else if (refDatum->identifiers().size() == 1) { const auto &id = refDatum->identifiers()[0]; const auto aliases = authFactory->databaseContext()->getAliases( *id->codeSpace(), id->code(), refDatum->nameStr(), "geodetic_datum", "not EPSG_OLD"); for (const auto &alias : aliases) { if (metadata::Identifier::isEquivalentName( l_name.c_str(), alias.c_str())) { foundDatumName = true; break; } } } if (foundDatumName) { properties.set(IdentifiedObject::NAME_KEY, refDatum->nameStr()); if (!properties.get(Identifier::CODESPACE_KEY) && refDatum->identifiers().size() == 1) { const auto &id = refDatum->identifiers()[0]; auto identifiers = ArrayOfBaseObject::create(); identifiers->add(Identifier::create( id->code(), PropertyMap() .set(Identifier::CODESPACE_KEY, *id->codeSpace()) .set(Identifier::AUTHORITY_KEY, *id->codeSpace()))); properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); } return true; } } else { // Get official name from database if AUTHORITY is present auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY); if (!isNull(idNode)) { try { auto id = buildId(node, idNode, false, false); auto authFactory2 = AuthorityFactory::create( NN_NO_CHECK(dbContext_), *id->codeSpace()); auto dbDatum = authFactory2->createGeodeticDatum(id->code()); properties.set(IdentifiedObject::NAME_KEY, dbDatum->nameStr()); return true; } catch (const std::exception &) { } } } } return false; }; // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official // name. bool nameSet = false; if (name == "WGS_1984") { nameSet = true; properties.set(IdentifiedObject::NAME_KEY, GeodeticReferenceFrame::EPSG_6326->nameStr()); } // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9 else if (internal::ends_with(name, " ensemble")) { auto massagedName = DatumEnsemble::ensembleNameToNonEnsembleName(name); if (!massagedName.empty()) { nameSet = true; properties.set(IdentifiedObject::NAME_KEY, massagedName); } } // If we got hints this might be a ESRI WKT, then check in the DB to // confirm std::string officialName; std::string authNameFromAlias; std::string codeFromAlias; if (!nameSet && maybeEsriStyle_ && dbContext_ && !(starts_with(name, "D_") || esriStyle_)) { std::string outTableName; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); officialName = authFactory->getOfficialNameFromAlias( name, "geodetic_datum", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { maybeEsriStyle_ = false; esriStyle_ = true; } } if (!nameSet && (starts_with(name, "D_") || esriStyle_)) { esriStyle_ = true; const char *tableNameForAlias = nullptr; if (name == "D_WGS_1984") { name = "World Geodetic System 1984"; authNameFromAlias = Identifier::EPSG; codeFromAlias = "6326"; } else if (name == "D_ETRS_1989") { name = "European Terrestrial Reference System 1989"; authNameFromAlias = Identifier::EPSG; codeFromAlias = "6258"; } else if (name == "D_unknown") { name = "unknown"; } else if (name == "D_Unknown_based_on_WGS_84_ellipsoid") { name = "Unknown based on WGS 84 ellipsoid"; } else { tableNameForAlias = "geodetic_datum"; } bool setNameAndId = true; if (dbContext_ && tableNameForAlias) { if (officialName.empty()) { std::string outTableName; auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), std::string()); officialName = authFactory->getOfficialNameFromAlias( name, tableNameForAlias, "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); } if (officialName.empty()) { if (starts_with(name, "D_")) { // For the case of "D_GDA2020" where there is no D_GDA2020 // ESRI alias, so just try without the D_ prefix. const auto nameWithoutDPrefix = name.substr(2); if (identifyFromName(nameWithoutDPrefix)) { setNameAndId = false; // already done in identifyFromName() } } } else { if (primeMeridian->nameStr() != PrimeMeridian::GREENWICH->nameStr()) { auto nameWithPM = officialName + " (" + primeMeridian->nameStr() + ")"; if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) { officialName = std::move(nameWithPM); } } name = std::move(officialName); } } if (setNameAndId) { properties.set(IdentifiedObject::NAME_KEY, name); if (!authNameFromAlias.empty()) { auto identifiers = ArrayOfBaseObject::create(); identifiers->add(Identifier::create( codeFromAlias, PropertyMap() .set(Identifier::CODESPACE_KEY, authNameFromAlias) .set(Identifier::AUTHORITY_KEY, authNameFromAlias))); properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); } } } else if (!nameSet && name.find('_') != std::string::npos) { // Likely coming from WKT1 identifyFromName(name); } auto ellipsoid = buildEllipsoid(ellipsoidNode); const auto &primeMeridianModified = fixupPrimeMeridan(ellipsoid, primeMeridian); auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84); if (!isNull(TOWGS84Node)) { const auto &TOWGS84Children = TOWGS84Node->GP()->children(); const size_t TOWGS84Size = TOWGS84Children.size(); if (TOWGS84Size == 3 || TOWGS84Size == 7) { try { for (const auto &child : TOWGS84Children) { toWGS84Parameters_.push_back(asDouble(child)); } if (TOWGS84Size == 7 && dbContext_) { dbContext_->toWGS84AutocorrectWrongValues( toWGS84Parameters_[0], toWGS84Parameters_[1], toWGS84Parameters_[2], toWGS84Parameters_[3], toWGS84Parameters_[4], toWGS84Parameters_[5], toWGS84Parameters_[6]); } for (size_t i = TOWGS84Size; i < 7; ++i) { toWGS84Parameters_.push_back(0.0); } } catch (const std::exception &) { throw ParsingException("Invalid TOWGS84 node"); } } else { throw ParsingException("Invalid TOWGS84 node"); } } auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); const auto &extensionChildren = extensionNode->GP()->children(); if (extensionChildren.size() == 2) { if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { datumPROJ4Grids_ = stripQuotes(extensionChildren[1]); } } if (!isNull(dynamicNode)) { double frameReferenceEpoch = 0.0; util::optional modelName; parseDynamic(dynamicNode, frameReferenceEpoch, modelName); return DynamicGeodeticReferenceFrame::create( properties, ellipsoid, getAnchor(node), primeMeridianModified, common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), modelName); } return GeodeticReferenceFrame::create(properties, ellipsoid, getAnchor(node), getAnchorEpoch(node), primeMeridianModified); } // --------------------------------------------------------------------------- DatumEnsembleNNPtr WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node, const PrimeMeridianPtr &primeMeridian, bool expectEllipsoid) { const auto *nodeP = node->GP(); auto &ellipsoidNode = nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID); if (expectEllipsoid && isNull(ellipsoidNode)) { ThrowMissing(WKTConstants::ELLIPSOID); } auto properties = buildProperties(node); std::vector datums; for (const auto &subNode : nodeP->children()) { if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) { if (subNode->GP()->childrenSize() == 0) { throw ParsingException("Invalid MEMBER node"); } if (expectEllipsoid) { datums.emplace_back(GeodeticReferenceFrame::create( buildProperties(subNode), buildEllipsoid(ellipsoidNode), optional(), primeMeridian ? NN_NO_CHECK(primeMeridian) : PrimeMeridian::GREENWICH)); } else { datums.emplace_back( VerticalReferenceFrame::create(buildProperties(subNode))); } } } if (datums.empty() && !nodeP->children().empty()) { auto name = stripQuotes(nodeP->children()[0]); if (dbContext_) { auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto res = authFactory->createObjectsFromName( name, {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, true, 1); if (res.size() == 1) { auto datumEnsemble = dynamic_cast(res.front().get()); if (datumEnsemble) { datums = datumEnsemble->datums(); } } else { throw ParsingException( "No entry for datum ensemble '" + name + "' in database, and no explicit member specified"); } } else { throw ParsingException("Datum ensemble '" + name + "' has no explicit member specified and no " "connection to database"); } } auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY); auto &accuracyNodeChildren = accuracyNode->GP()->children(); if (accuracyNodeChildren.empty()) { ThrowMissing(WKTConstants::ENSEMBLEACCURACY); } auto accuracy = PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value()); try { return DatumEnsemble::create(properties, datums, accuracy); } catch (const util::Exception &e) { throw buildRethrow(__FUNCTION__, e); } } // --------------------------------------------------------------------------- MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); const auto &children = nodeP->children(); if (children.size() < 2) { ThrowNotEnoughChildren(nodeP->value()); } UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR); try { double angleValue = asDouble(children[0]); Angle angle(angleValue, unit); return Meridian::create(angle); } catch (const std::exception &e) { throw buildRethrow(__FUNCTION__, e); } } // --------------------------------------------------------------------------- PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() { throw ParsingException("buildCS: missing UNIT"); } // --------------------------------------------------------------------------- CoordinateSystemAxisNNPtr WKTParser::Private::buildAxis(const WKTNodeNNPtr &node, const UnitOfMeasure &unitIn, const UnitOfMeasure::Type &unitType, bool isGeocentric, int expectedOrderNum) { const auto *nodeP = node->GP(); const auto &children = nodeP->children(); if (children.size() < 2) { ThrowNotEnoughChildren(nodeP->value()); } auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER); if (!isNull(orderNode)) { const auto &orderNodeChildren = orderNode->GP()->children(); if (orderNodeChildren.size() != 1) { ThrowNotEnoughChildren(WKTConstants::ORDER); } const auto &order = orderNodeChildren[0]->GP()->value(); int orderNum; try { orderNum = std::stoi(order); } catch (const std::exception &) { throw ParsingException( concat("buildAxis: invalid ORDER value: ", order)); } if (orderNum != expectedOrderNum) { throw ParsingException( concat("buildAxis: did not get expected ORDER value: ", order)); } } // The axis designation in WK2 can be: "name", "(abbrev)" or "name // (abbrev)" std::string axisDesignation(stripQuotes(children[0])); size_t sepPos = axisDesignation.find(" ("); std::string axisName; std::string abbreviation; if (sepPos != std::string::npos && axisDesignation.back() == ')') { axisName = CoordinateSystemAxis::normalizeAxisName( axisDesignation.substr(0, sepPos)); abbreviation = axisDesignation.substr(sepPos + 2); abbreviation.resize(abbreviation.size() - 1); } else if (!axisDesignation.empty() && axisDesignation[0] == '(' && axisDesignation.back() == ')') { abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2); if (abbreviation == AxisAbbreviation::E) { axisName = AxisName::Easting; } else if (abbreviation == AxisAbbreviation::N) { axisName = AxisName::Northing; } else if (abbreviation == AxisAbbreviation::lat) { axisName = AxisName::Latitude; } else if (abbreviation == AxisAbbreviation::lon) { axisName = AxisName::Longitude; } } else { axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation); if (axisName == AxisName::Latitude) { abbreviation = AxisAbbreviation::lat; } else if (axisName == AxisName::Longitude) { abbreviation = AxisAbbreviation::lon; } else if (axisName == AxisName::Ellipsoidal_height) { abbreviation = AxisAbbreviation::h; } } const std::string &dirString = children[1]->GP()->value(); const AxisDirection *direction = AxisDirection::valueOf(dirString); // WKT2, geocentric CS: axis names are omitted if (axisName.empty()) { if (direction == &AxisDirection::GEOCENTRIC_X && abbreviation == AxisAbbreviation::X) { axisName = AxisName::Geocentric_X; } else if (direction == &AxisDirection::GEOCENTRIC_Y && abbreviation == AxisAbbreviation::Y) { axisName = AxisName::Geocentric_Y; } else if (direction == &AxisDirection::GEOCENTRIC_Z && abbreviation == AxisAbbreviation::Z) { axisName = AxisName::Geocentric_Z; } } // WKT1 if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) { abbreviation = AxisAbbreviation::X; direction = &AxisDirection::GEOCENTRIC_X; } else if (!direction && isGeocentric && axisName == AxisName::Geocentric_Y) { abbreviation = AxisAbbreviation::Y; direction = &AxisDirection::GEOCENTRIC_Y; } else if (isGeocentric && axisName == AxisName::Geocentric_Z && (dirString == AxisDirectionWKT1::NORTH.toString() || dirString == AxisDirectionWKT1::OTHER.toString())) { abbreviation = AxisAbbreviation::Z; direction = &AxisDirection::GEOCENTRIC_Z; } else if (dirString == AxisDirectionWKT1::OTHER.toString()) { direction = &AxisDirection::UNSPECIFIED; } else if (dirString == "UNKNOWN") { // Found in WKT1 of NSIDC's EASE-Grid Sea Ice Age datasets. // Cf https://github.com/OSGeo/gdal/issues/7210 emitRecoverableWarning("UNKNOWN is not a valid direction name."); direction = &AxisDirection::UNSPECIFIED; } if (!direction) { throw ParsingException( concat("unhandled axis direction: ", children[1]->GP()->value())); } UnitOfMeasure unit(buildUnitInSubNode(node)); if (unit == UnitOfMeasure::NONE) { // If no unit in the AXIS node, use the one potentially coming from // the CS. unit = unitIn; if (unit == UnitOfMeasure::NONE && unitType != UnitOfMeasure::Type::NONE && unitType != UnitOfMeasure::Type::TIME) { ThrowParsingExceptionMissingUNIT(); } } auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN); util::optional minVal; auto &axisMinValueNode = nodeP->lookForChild(WKTConstants::AXISMINVALUE); if (!isNull(axisMinValueNode)) { const auto &axisMinValueNodeChildren = axisMinValueNode->GP()->children(); if (axisMinValueNodeChildren.size() != 1) { ThrowNotEnoughChildren(WKTConstants::AXISMINVALUE); } const auto &val = axisMinValueNodeChildren[0]; try { minVal = asDouble(val); } catch (const std::exception &) { throw ParsingException(concat( "buildAxis: invalid AXISMINVALUE value: ", val->GP()->value())); } } util::optional maxVal; auto &axisMaxValueNode = nodeP->lookForChild(WKTConstants::AXISMAXVALUE); if (!isNull(axisMaxValueNode)) { const auto &axisMaxValueNodeChildren = axisMaxValueNode->GP()->children(); if (axisMaxValueNodeChildren.size() != 1) { ThrowNotEnoughChildren(WKTConstants::AXISMAXVALUE); } const auto &val = axisMaxValueNodeChildren[0]; try { maxVal = asDouble(val); } catch (const std::exception &) { throw ParsingException(concat( "buildAxis: invalid AXISMAXVALUE value: ", val->GP()->value())); } } util::optional rangeMeaning; auto &rangeMeaningNode = nodeP->lookForChild(WKTConstants::RANGEMEANING); if (!isNull(rangeMeaningNode)) { const auto &rangeMeaningNodeChildren = rangeMeaningNode->GP()->children(); if (rangeMeaningNodeChildren.size() != 1) { ThrowNotEnoughChildren(WKTConstants::RANGEMEANING); } const std::string &val = rangeMeaningNodeChildren[0]->GP()->value(); const RangeMeaning *meaning = RangeMeaning::valueOf(val); if (meaning == nullptr) { throw ParsingException( concat("buildAxis: invalid RANGEMEANING value: ", val)); } rangeMeaning = util::optional(*meaning); } return CoordinateSystemAxis::create( buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName), abbreviation, *direction, unit, minVal, maxVal, rangeMeaning, !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable() : nullptr); } // --------------------------------------------------------------------------- static const PropertyMap emptyPropertyMap{}; // --------------------------------------------------------------------------- PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) { throw ParsingException(msg); } // --------------------------------------------------------------------------- static ParsingException buildParsingExceptionInvalidAxisCount(const std::string &csType) { return ParsingException( concat("buildCS: invalid CS axis count for ", csType)); } // --------------------------------------------------------------------------- void WKTParser::Private::emitRecoverableMissingUNIT( const std::string &parentNodeName, const UnitOfMeasure &fallbackUnit) { std::string msg("buildCS: missing UNIT in "); msg += parentNodeName; if (!strict_ && fallbackUnit == UnitOfMeasure::METRE) { msg += ". Assuming metre"; } else if (!strict_ && fallbackUnit == UnitOfMeasure::DEGREE) { msg += ". Assuming degree"; } emitRecoverableWarning(msg); } // --------------------------------------------------------------------------- CoordinateSystemNNPtr WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */ const WKTNodeNNPtr &parentNode, const UnitOfMeasure &defaultAngularUnit) { bool isGeocentric = false; std::string csType; const int numberOfAxis = parentNode->countChildrenOfName(WKTConstants::AXIS); int axisCount = numberOfAxis; const auto &parentNodeName = parentNode->GP()->value(); if (!isNull(node)) { const auto *nodeP = node->GP(); const auto &children = nodeP->children(); if (children.size() < 2) { ThrowNotEnoughChildren(nodeP->value()); } csType = children[0]->GP()->value(); try { axisCount = std::stoi(children[1]->GP()->value()); } catch (const std::exception &) { ThrowParsingException(concat("buildCS: invalid CS axis count: ", children[1]->GP()->value())); } } else { const char *csTypeCStr = CartesianCS::WKT2_TYPE; if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) { // csTypeCStr = CartesianCS::WKT2_TYPE; isGeocentric = true; if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure::METRE; emitRecoverableMissingUNIT(parentNodeName, unit); } return CartesianCS::createGeocentric(unit); } } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) { csTypeCStr = EllipsoidalCS::WKT2_TYPE; if (axisCount == 0) { // Missing axis with GEOGCS ? Presumably Long/Lat order // implied auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::ANGULAR); if (unit == UnitOfMeasure::NONE) { unit = defaultAngularUnit; emitRecoverableMissingUNIT(parentNodeName, unit); } // ESRI WKT for geographic 3D CRS auto &linUnitNode = parentNode->GP()->lookForChild(WKTConstants::LINUNIT); if (!isNull(linUnitNode)) { return EllipsoidalCS:: createLongitudeLatitudeEllipsoidalHeight( unit, buildUnit(linUnitNode, UnitOfMeasure::Type::LINEAR)); } // WKT1 --> long/lat return EllipsoidalCS::createLongitudeLatitude(unit); } } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) || ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) { csTypeCStr = EllipsoidalCS::WKT2_TYPE; if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::ANGULAR); if (unit == UnitOfMeasure::NONE) { unit = defaultAngularUnit; } // WKT2 --> presumably lat/long return EllipsoidalCS::createLatitudeLongitude(unit); } } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) || ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) || ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) { csTypeCStr = CartesianCS::WKT2_TYPE; if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure::METRE; if (ci_equal(parentNodeName, WKTConstants::PROJCS)) { emitRecoverableMissingUNIT(parentNodeName, unit); } } return CartesianCS::createEastingNorthing(unit); } } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) || ci_equal(parentNodeName, WKTConstants::VERTCS) || ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) { csTypeCStr = VerticalCS::WKT2_TYPE; bool downDirection = false; if (ci_equal(parentNodeName, WKTConstants::VERTCS)) // ESRI { for (const auto &childNode : parentNode->GP()->children()) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() == 2 && ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) && childNodeChildren[0]->GP()->value() == "\"Direction\"") { const auto ¶mValue = childNodeChildren[1]->GP()->value(); try { double val = asDouble(childNodeChildren[1]); if (val == 1.0) { // ok } else if (val == -1.0) { downDirection = true; } } catch (const std::exception &) { throw ParsingException( concat("unhandled parameter value type : ", paramValue)); } } } } if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure::METRE; if (ci_equal(parentNodeName, WKTConstants::VERT_CS) || ci_equal(parentNodeName, WKTConstants::VERTCS)) { emitRecoverableMissingUNIT(parentNodeName, unit); } } if (downDirection) { return VerticalCS::create( util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, "depth"), "D", AxisDirection::DOWN, unit)); } return VerticalCS::createGravityRelatedHeight(unit); } } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) { if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure::METRE; } return CartesianCS::createEastingNorthing(unit); } else if (axisCount == 1) { csTypeCStr = VerticalCS::WKT2_TYPE; } else if (axisCount == 2 || axisCount == 3) { csTypeCStr = CartesianCS::WKT2_TYPE; } else { throw ParsingException( "buildCS: unexpected AXIS count for LOCAL_CS"); } } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) { csTypeCStr = ParametricCS::WKT2_TYPE; if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::PARAMETRIC); } return ParametricCS::create( emptyPropertyMap, CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown parametric"), std::string(), AxisDirection::UNSPECIFIED, unit)); } } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) { csTypeCStr = TemporalCS::WKT2_2015_TYPE; if (axisCount == 0) { auto unit = buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME); if (unit == UnitOfMeasure::NONE) { unit = UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME); } return DateTimeTemporalCS::create( emptyPropertyMap, CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown temporal"), std::string(), AxisDirection::FUTURE, unit)); } } else { // Shouldn't happen normally throw ParsingException( concat("buildCS: unexpected parent node: ", parentNodeName)); } csType = csTypeCStr; } if (axisCount != 1 && axisCount != 2 && axisCount != 3) { throw buildParsingExceptionInvalidAxisCount(csType); } if (numberOfAxis != axisCount) { throw ParsingException("buildCS: declared number of axis by CS node " "and number of AXIS are inconsistent"); } const auto unitType = ci_equal(csType, EllipsoidalCS::WKT2_TYPE) ? UnitOfMeasure::Type::ANGULAR : ci_equal(csType, OrdinalCS::WKT2_TYPE) ? UnitOfMeasure::Type::NONE : ci_equal(csType, ParametricCS::WKT2_TYPE) ? UnitOfMeasure::Type::PARAMETRIC : ci_equal(csType, CartesianCS::WKT2_TYPE) || ci_equal(csType, VerticalCS::WKT2_TYPE) || ci_equal(csType, AffineCS::WKT2_TYPE) ? UnitOfMeasure::Type::LINEAR : (ci_equal(csType, TemporalCS::WKT2_2015_TYPE) || ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE) || ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE) || ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE)) ? UnitOfMeasure::Type::TIME : UnitOfMeasure::Type::UNKNOWN; UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType); if (unit == UnitOfMeasure::NONE) { if (ci_equal(parentNodeName, WKTConstants::VERT_CS) || ci_equal(parentNodeName, WKTConstants::VERTCS)) { unit = UnitOfMeasure::METRE; emitRecoverableMissingUNIT(parentNodeName, unit); } } std::vector axisList; for (int i = 0; i < axisCount; i++) { axisList.emplace_back( buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i), unit, unitType, isGeocentric, i + 1)); } const PropertyMap &csMap = emptyPropertyMap; if (ci_equal(csType, EllipsoidalCS::WKT2_TYPE)) { if (axisCount == 2) { return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); } else if (axisCount == 3) { return EllipsoidalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } } else if (ci_equal(csType, CartesianCS::WKT2_TYPE)) { if (axisCount == 2) { return CartesianCS::create(csMap, axisList[0], axisList[1]); } else if (axisCount == 3) { return CartesianCS::create(csMap, axisList[0], axisList[1], axisList[2]); } } else if (ci_equal(csType, AffineCS::WKT2_TYPE)) { if (axisCount == 2) { return AffineCS::create(csMap, axisList[0], axisList[1]); } else if (axisCount == 3) { return AffineCS::create(csMap, axisList[0], axisList[1], axisList[2]); } } else if (ci_equal(csType, VerticalCS::WKT2_TYPE)) { if (axisCount == 1) { return VerticalCS::create(csMap, axisList[0]); } } else if (ci_equal(csType, SphericalCS::WKT2_TYPE)) { if (axisCount == 2) { // Extension to ISO19111 to support (planet)-ocentric CS with // geocentric latitude return SphericalCS::create(csMap, axisList[0], axisList[1]); } else if (axisCount == 3) { return SphericalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } } else if (ci_equal(csType, OrdinalCS::WKT2_TYPE)) { // WKT2-2019 return OrdinalCS::create(csMap, axisList); } else if (ci_equal(csType, ParametricCS::WKT2_TYPE)) { if (axisCount == 1) { return ParametricCS::create(csMap, axisList[0]); } } else if (ci_equal(csType, TemporalCS::WKT2_2015_TYPE)) { if (axisCount == 1) { if (isNull( parentNode->GP()->lookForChild(WKTConstants::TIMEUNIT)) && isNull(parentNode->GP()->lookForChild(WKTConstants::UNIT))) { return DateTimeTemporalCS::create(csMap, axisList[0]); } else { // Default to TemporalMeasureCS // TemporalCount could also be possible return TemporalMeasureCS::create(csMap, axisList[0]); } } } else if (ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE)) { if (axisCount == 1) { return DateTimeTemporalCS::create(csMap, axisList[0]); } } else if (ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE)) { if (axisCount == 1) { return TemporalCountCS::create(csMap, axisList[0]); } } else if (ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE)) { if (axisCount == 1) { return TemporalMeasureCS::create(csMap, axisList[0]); } } else { throw ParsingException(concat("unhandled CS type: ", csType)); } throw buildParsingExceptionInvalidAxisCount(csType); } // --------------------------------------------------------------------------- std::string WKTParser::Private::getExtensionProj4(const WKTNode::Private *nodeP) { auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION); const auto &extensionChildren = extensionNode->GP()->children(); if (extensionChildren.size() == 2) { if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { return stripQuotes(extensionChildren[1]); } } return std::string(); } // --------------------------------------------------------------------------- void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP, PropertyMap &props) { const auto extensionProj4(getExtensionProj4(nodeP)); if (!extensionProj4.empty()) { props.set("EXTENSION_PROJ4", extensionProj4); } } // --------------------------------------------------------------------------- GeodeticCRSNNPtr WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node, bool forceGeocentricIfNoCs) { const auto *nodeP = node->GP(); auto &datumNode = nodeP->lookForChild( WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF); auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); if (isNull(datumNode) && isNull(ensembleNode)) { throw ParsingException("Missing DATUM or ENSEMBLE node"); } // Do that now so that esriStyle_ can be set before buildPrimeMeridian() auto props = buildProperties(node); auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); const auto &nodeName = nodeP->value(); if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) && !ci_equal(nodeName, WKTConstants::GEOCCS) && !ci_equal(nodeName, WKTConstants::BASEGEODCRS) && !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { ThrowMissing(WKTConstants::CS_); } auto &primeMeridianNode = nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN); if (isNull(primeMeridianNode)) { // PRIMEM is required in WKT1 if (ci_equal(nodeName, WKTConstants::GEOGCS) || ci_equal(nodeName, WKTConstants::GEOCCS)) { emitRecoverableWarning(nodeName + " should have a PRIMEM node"); } } auto angularUnit = buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS) ? UnitOfMeasure::Type::ANGULAR : UnitOfMeasure::Type::UNKNOWN); if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) { angularUnit = UnitOfMeasure::NONE; } auto primeMeridian = !isNull(primeMeridianNode) ? buildPrimeMeridian(primeMeridianNode, angularUnit) : PrimeMeridian::GREENWICH; if (angularUnit == UnitOfMeasure::NONE) { angularUnit = primeMeridian->longitude().unit(); } addExtensionProj4ToProp(nodeP, props); // No explicit AXIS node ? (WKT1) if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { props.set("IMPLICIT_CS", true); } const std::string crsName = stripQuotes(nodeP->children()[0]); if (esriStyle_ && dbContext_) { std::string outTableName; std::string authNameFromAlias; std::string codeFromAlias; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto officialName = authFactory->getOfficialNameFromAlias( crsName, "geodetic_crs", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { props.set(IdentifiedObject::NAME_KEY, officialName); } } auto datum = !isNull(datumNode) ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode) .as_nullable() : nullptr; auto datumEnsemble = !isNull(ensembleNode) ? buildDatumEnsemble(ensembleNode, primeMeridian, true) .as_nullable() : nullptr; auto cs = buildCS(csNode, node, angularUnit); // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS, // in a few rare cases, this might be a Geocentric CRS, and thus a // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way // to figure that is to resolve the CRS from its code... if (isNull(csNode) && dbContext_ && ci_equal(nodeName, WKTConstants::BASEGEODCRS)) { const auto &nodeChildren = nodeP->children(); for (const auto &subNode : nodeChildren) { const auto &subNodeName(subNode->GP()->value()); if (ci_equal(subNodeName, WKTConstants::ID) || ci_equal(subNodeName, WKTConstants::AUTHORITY)) { auto id = buildId(node, subNode, true, false); if (id) { try { auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), *id->codeSpace()); auto dbCRS = authFactory->createGeodeticCRS(id->code()); cs = dbCRS->coordinateSystem(); } catch (const util::Exception &) { } } } } } if (forceGeocentricIfNoCs && isNull(csNode) && ci_equal(nodeName, WKTConstants::BASEGEODCRS)) { cs = cs::CartesianCS::createGeocentric(UnitOfMeasure::METRE); } auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); if (ellipsoidalCS) { if (ci_equal(nodeName, WKTConstants::GEOCCS)) { throw ParsingException("ellipsoidal CS not expected in GEOCCS"); } try { auto crs = GeographicCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); // In case of missing CS node, or to check it, query the coordinate // system from the DB if possible (typically for the baseCRS of a // ProjectedCRS) if (!crs->identifiers().empty() && dbContext_) { GeographicCRSPtr dbCRS; try { const auto &id = crs->identifiers()[0]; auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), *id->codeSpace()); dbCRS = authFactory->createGeographicCRS(id->code()) .as_nullable(); } catch (const util::Exception &) { } if (dbCRS && (!isNull(csNode) || node->countChildrenOfName(WKTConstants::AXIS) != 0) && !ellipsoidalCS->_isEquivalentTo( dbCRS->coordinateSystem().get(), util::IComparable::Criterion::EQUIVALENT)) { if (unsetIdentifiersIfIncompatibleDef_) { emitRecoverableWarning( "Coordinate system of GeographicCRS in the WKT " "definition is different from the one of the " "authority. Unsetting the identifier to avoid " "confusion"); props.unset(Identifier::CODESPACE_KEY); props.unset(Identifier::AUTHORITY_KEY); props.unset(IdentifiedObject::IDENTIFIERS_KEY); } crs = GeographicCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); } else if (dbCRS) { auto csFromDB = dbCRS->coordinateSystem(); auto csFromDBAltered = csFromDB; if (!isNull(nodeP->lookForChild(WKTConstants::UNIT))) { csFromDBAltered = csFromDB->alterAngularUnit(angularUnit); if (unsetIdentifiersIfIncompatibleDef_ && !csFromDBAltered->_isEquivalentTo( csFromDB.get(), util::IComparable::Criterion::EQUIVALENT)) { emitRecoverableWarning( "Coordinate system of GeographicCRS in the WKT " "definition is different from the one of the " "authority. Unsetting the identifier to avoid " "confusion"); props.unset(Identifier::CODESPACE_KEY); props.unset(Identifier::AUTHORITY_KEY); props.unset(IdentifiedObject::IDENTIFIERS_KEY); } } crs = GeographicCRS::create(props, datum, datumEnsemble, csFromDBAltered); } } return crs; } catch (const util::Exception &e) { throw ParsingException(std::string("buildGeodeticCRS: ") + e.what()); } } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) || ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) || ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) { // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected throw ParsingException(concat("ellipsoidal CS expected, but found ", cs->getWKT2Type(true))); } auto cartesianCS = nn_dynamic_pointer_cast(cs); if (cartesianCS) { if (cartesianCS->axisList().size() != 3) { throw ParsingException( "Cartesian CS for a GeodeticCRS should have 3 axis"); } try { return GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(cartesianCS)); } catch (const util::Exception &e) { throw ParsingException(std::string("buildGeodeticCRS: ") + e.what()); } } auto sphericalCS = nn_dynamic_pointer_cast(cs); if (sphericalCS) { try { return GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(sphericalCS)); } catch (const util::Exception &e) { throw ParsingException(std::string("buildGeodeticCRS: ") + e.what()); } } throw ParsingException( concat("unhandled CS type: ", cs->getWKT2Type(true))); } // --------------------------------------------------------------------------- CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS, WKTConstants::BASEGEOGCRS); // given the constraints enforced on calling code path assert(!isNull(baseGeodCRSNode)); auto &derivingConversionNode = nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); if (isNull(derivingConversionNode)) { ThrowMissing(WKTConstants::DERIVINGCONVERSION); } auto derivingConversion = buildConversion( derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); bool forceGeocentricIfNoCs = false; auto cartesianCS = nn_dynamic_pointer_cast(cs); if (cartesianCS) { if (cartesianCS->axisList().size() != 3) { throw ParsingException( "Cartesian CS for a GeodeticCRS should have 3 axis"); } const int methodCode = derivingConversion->method()->getEPSGCode(); if ((methodCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || methodCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || methodCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || methodCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || methodCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC) && nodeP->lookForChild(WKTConstants::BASEGEODCRS) != nullptr) { forceGeocentricIfNoCs = true; } } auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode, forceGeocentricIfNoCs); auto ellipsoidalCS = nn_dynamic_pointer_cast(cs); if (ellipsoidalCS) { if (ellipsoidalCS->axisList().size() == 3 && baseGeodCRS->coordinateSystem()->axisList().size() == 2) { baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast( baseGeodCRS->promoteTo3D(std::string(), dbContext_))); } return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS, derivingConversion, NN_NO_CHECK(ellipsoidalCS)); } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) { // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected throw ParsingException(concat("ellipsoidal CS expected, but found ", cs->getWKT2Type(true))); } if (cartesianCS) { return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, derivingConversion, NN_NO_CHECK(cartesianCS)); } auto sphericalCS = nn_dynamic_pointer_cast(cs); if (sphericalCS) { return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS, derivingConversion, NN_NO_CHECK(sphericalCS)); } throw ParsingException( concat("unhandled CS type: ", cs->getWKT2Type(true))); } // --------------------------------------------------------------------------- UnitOfMeasure WKTParser::Private::guessUnitForParameter( const std::string ¶mName, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit) { UnitOfMeasure unit; // scale must be first because of 'Scale factor on pseudo standard parallel' if (ci_find(paramName, "scale") != std::string::npos || ci_find(paramName, "scaling factor") != std::string::npos) { unit = UnitOfMeasure::SCALE_UNITY; } else if (ci_find(paramName, "latitude") != std::string::npos || ci_find(paramName, "longitude") != std::string::npos || ci_find(paramName, "meridian") != std::string::npos || ci_find(paramName, "parallel") != std::string::npos || ci_find(paramName, "azimuth") != std::string::npos || ci_find(paramName, "angle") != std::string::npos || ci_find(paramName, "heading") != std::string::npos || ci_find(paramName, "rotation") != std::string::npos) { unit = defaultAngularUnit; } else if (ci_find(paramName, "easting") != std::string::npos || ci_find(paramName, "northing") != std::string::npos || ci_find(paramName, "height") != std::string::npos) { unit = defaultLinearUnit; } return unit; } // --------------------------------------------------------------------------- static bool isEPSGCodeForInterpolationParameter(const OperationParameterNNPtr ¶meter) { const auto &name = parameter->nameStr(); const auto epsgCode = parameter->getEPSGCode(); return name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS || epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS || name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS || epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS; } // --------------------------------------------------------------------------- static bool isIntegerParameter(const OperationParameterNNPtr ¶meter) { return isEPSGCodeForInterpolationParameter(parameter); } // --------------------------------------------------------------------------- void WKTParser::Private::consumeParameters( const WKTNodeNNPtr &node, bool isAbridged, std::vector ¶meters, std::vector &values, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit) { for (const auto &childNode : node->GP()->children()) { const auto &childNodeChildren = childNode->GP()->children(); if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { if (childNodeChildren.size() < 2) { ThrowNotEnoughChildren(childNode->GP()->value()); } parameters.push_back( OperationParameter::create(buildProperties(childNode))); const auto ¶mValue = childNodeChildren[1]->GP()->value(); if (!paramValue.empty() && paramValue[0] == '"') { values.push_back( ParameterValue::create(stripQuotes(childNodeChildren[1]))); } else { try { double val = asDouble(childNodeChildren[1]); auto unit = buildUnitInSubNode(childNode); if (unit == UnitOfMeasure::NONE) { const auto ¶mName = childNodeChildren[0]->GP()->value(); unit = guessUnitForParameter( paramName, defaultLinearUnit, defaultAngularUnit); } if (isAbridged) { const auto ¶mName = parameters.back()->nameStr(); int paramEPSGCode = 0; const auto ¶mIds = parameters.back()->identifiers(); if (paramIds.size() == 1 && ci_equal(*(paramIds[0]->codeSpace()), Identifier::EPSG)) { paramEPSGCode = ::atoi(paramIds[0]->code().c_str()); } const common::UnitOfMeasure *pUnit = nullptr; if (OperationParameterValue::convertFromAbridged( paramName, val, pUnit, paramEPSGCode)) { unit = *pUnit; parameters.back() = OperationParameter::create( buildProperties(childNode) .set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, paramEPSGCode)); } } if (isIntegerParameter(parameters.back())) { values.push_back(ParameterValue::create( std::stoi(childNodeChildren[1]->GP()->value()))); } else { values.push_back( ParameterValue::create(Measure(val, unit))); } } catch (const std::exception &) { throw ParsingException(concat( "unhandled parameter value type : ", paramValue)); } } } else if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETERFILE)) { if (childNodeChildren.size() < 2) { ThrowNotEnoughChildren(childNode->GP()->value()); } parameters.push_back( OperationParameter::create(buildProperties(childNode))); values.push_back(ParameterValue::createFilename( stripQuotes(childNodeChildren[1]))); } } } // --------------------------------------------------------------------------- static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter( DatabaseContextPtr &dbContext, std::vector ¶meters, std::vector &values); ConversionNNPtr WKTParser::Private::buildConversion(const WKTNodeNNPtr &node, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit) { auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD, WKTConstants::PROJECTION); if (isNull(methodNode)) { ThrowMissing(WKTConstants::METHOD); } if (methodNode->GP()->childrenSize() == 0) { ThrowNotEnoughChildren(WKTConstants::METHOD); } std::vector parameters; std::vector values; consumeParameters(node, false, parameters, values, defaultLinearUnit, defaultAngularUnit); auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter( dbContext_, parameters, values); auto &convProps = buildProperties(node); auto &methodProps = buildProperties(methodNode); std::string convName; std::string methodName; if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) && methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) && starts_with(convName, "Inverse of ") && starts_with(methodName, "Inverse of ")) { auto &invConvProps = buildProperties(node, true); auto &invMethodProps = buildProperties(methodNode, true); auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast( Conversion::create(invConvProps, invMethodProps, parameters, values) ->inverse())); if (interpolationCRS) conv->setInterpolationCRS(interpolationCRS); return conv; } auto conv = Conversion::create(convProps, methodProps, parameters, values); if (interpolationCRS) conv->setInterpolationCRS(interpolationCRS); return conv; } // --------------------------------------------------------------------------- static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter( DatabaseContextPtr &dbContext, std::vector ¶meters, std::vector &values) { // Transform EPSG hacky PARAMETER["EPSG code for Interpolation CRS", // crs_epsg_code] into proper interpolation CRS if (dbContext != nullptr) { for (size_t i = 0; i < parameters.size(); ++i) { if (isEPSGCodeForInterpolationParameter(parameters[i])) { const int code = values[i]->integerValue(); try { auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext), Identifier::EPSG); auto interpolationCRS = authFactory ->createGeographicCRS(internal::toString(code)) .as_nullable(); parameters.erase(parameters.begin() + i); values.erase(values.begin() + i); return interpolationCRS; } catch (const util::Exception &) { } } } } return nullptr; } // --------------------------------------------------------------------------- TransformationNNPtr WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD); if (isNull(methodNode)) { ThrowMissing(WKTConstants::METHOD); } if (methodNode->GP()->childrenSize() == 0) { ThrowNotEnoughChildren(WKTConstants::METHOD); } auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) { ThrowMissing(WKTConstants::SOURCECRS); } auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]); if (!sourceCRS) { throw ParsingException("Invalid content in SOURCECRS node"); } auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) { ThrowMissing(WKTConstants::TARGETCRS); } auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]); if (!targetCRS) { throw ParsingException("Invalid content in TARGETCRS node"); } auto &interpolationCRSNode = nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS); CRSPtr interpolationCRS; if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP() ->childrenSize() == 1) { interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]); } std::vector parameters; std::vector values; const auto &defaultLinearUnit = UnitOfMeasure::NONE; const auto &defaultAngularUnit = UnitOfMeasure::NONE; consumeParameters(node, false, parameters, values, defaultLinearUnit, defaultAngularUnit); if (interpolationCRS == nullptr) interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter( dbContext_, parameters, values); std::vector accuracies; auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY); if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) { accuracies.push_back(PositionalAccuracy::create( stripQuotes(accuracyNode->GP()->children()[0]))); } return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), interpolationCRS, buildProperties(methodNode), parameters, values, accuracies); } // --------------------------------------------------------------------------- PointMotionOperationNNPtr WKTParser::Private::buildPointMotionOperation(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD); if (isNull(methodNode)) { ThrowMissing(WKTConstants::METHOD); } if (methodNode->GP()->childrenSize() == 0) { ThrowNotEnoughChildren(WKTConstants::METHOD); } auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); if (sourceCRSNode->GP()->childrenSize() != 1) { ThrowMissing(WKTConstants::SOURCECRS); } auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]); if (!sourceCRS) { throw ParsingException("Invalid content in SOURCECRS node"); } std::vector parameters; std::vector values; const auto &defaultLinearUnit = UnitOfMeasure::NONE; const auto &defaultAngularUnit = UnitOfMeasure::NONE; consumeParameters(node, false, parameters, values, defaultLinearUnit, defaultAngularUnit); std::vector accuracies; auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY); if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) { accuracies.push_back(PositionalAccuracy::create( stripQuotes(accuracyNode->GP()->children()[0]))); } return PointMotionOperation::create( buildProperties(node), NN_NO_CHECK(sourceCRS), buildProperties(methodNode), parameters, values, accuracies); } // --------------------------------------------------------------------------- ConcatenatedOperationNNPtr WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) { ThrowMissing(WKTConstants::SOURCECRS); } auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]); if (!sourceCRS) { throw ParsingException("Invalid content in SOURCECRS node"); } auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) { ThrowMissing(WKTConstants::TARGETCRS); } auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]); if (!targetCRS) { throw ParsingException("Invalid content in TARGETCRS node"); } std::vector operations; for (const auto &childNode : nodeP->children()) { if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) { if (childNode->GP()->childrenSize() != 1) { throw ParsingException("Invalid content in STEP node"); } auto op = nn_dynamic_pointer_cast( build(childNode->GP()->children()[0])); if (!op) { throw ParsingException("Invalid content in STEP node"); } operations.emplace_back(NN_NO_CHECK(op)); } } ConcatenatedOperation::fixSteps( NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operations, dbContext_, /* fixDirectionAllowed = */ true); std::vector accuracies; auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY); if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) { accuracies.push_back(PositionalAccuracy::create( stripQuotes(accuracyNode->GP()->children()[0]))); } try { return ConcatenatedOperation::create(buildProperties(node), operations, accuracies); } catch (const InvalidOperation &e) { throw ParsingException( std::string("Cannot build concatenated operation: ") + e.what()); } } // --------------------------------------------------------------------------- bool WKTParser::Private::hasWebMercPROJ4String( const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) { if (projectionNode->GP()->childrenSize() == 0) { ThrowNotEnoughChildren(WKTConstants::PROJECTION); } const std::string wkt1ProjectionName = stripQuotes(projectionNode->GP()->children()[0]); auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), "Mercator_1SP") && projCRSNode->countChildrenOfName("center_latitude") == 0) { // Hack to detect the hacky way of encodign webmerc in GDAL WKT1 // with a EXTENSION["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"] node if (extensionNode && extensionNode->GP()->childrenSize() == 2 && ci_equal(stripQuotes(extensionNode->GP()->children()[0]), "PROJ4")) { std::string projString = stripQuotes(extensionNode->GP()->children()[1]); if (projString.find("+proj=merc") != std::string::npos && projString.find("+a=6378137") != std::string::npos && projString.find("+b=6378137") != std::string::npos && projString.find("+lon_0=0") != std::string::npos && projString.find("+x_0=0") != std::string::npos && projString.find("+y_0=0") != std::string::npos && projString.find("+nadgrids=@null") != std::string::npos && (projString.find("+lat_ts=") == std::string::npos || projString.find("+lat_ts=0") != std::string::npos) && (projString.find("+k=") == std::string::npos || projString.find("+k=1") != std::string::npos) && (projString.find("+units=") == std::string::npos || projString.find("+units=m") != std::string::npos)) { return true; } } } return false; } // --------------------------------------------------------------------------- static const MethodMapping * selectSphericalOrEllipsoidal(const MethodMapping *mapping, const GeodeticCRSNNPtr &baseGeodCRS) { if (mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL || mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) { mapping = getMapping( baseGeodCRS->ellipsoid()->isSphere() ? EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL : EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA); } else if (mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL || mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) { mapping = getMapping( baseGeodCRS->ellipsoid()->isSphere() ? EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL : EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA); } else if (mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL || mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL) { mapping = getMapping(baseGeodCRS->ellipsoid()->isSphere() ? EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL : EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL); } return mapping; } // --------------------------------------------------------------------------- const ESRIMethodMapping *WKTParser::Private::getESRIMapping( const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, std::map &mapParamNameToValue) { const std::string esriProjectionName = stripQuotes(projectionNode->GP()->children()[0]); // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods // depending // on the parameters / their values const auto esriMappings = getMappingsFromESRI(esriProjectionName); if (esriMappings.empty()) { return nullptr; } // Build a map of present parameters for (const auto &childNode : projCRSNode->GP()->children()) { if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() < 2) { ThrowNotEnoughChildren(WKTConstants::PARAMETER); } const std::string parameterName(stripQuotes(childNodeChildren[0])); const auto ¶mValue = childNodeChildren[1]->GP()->value(); mapParamNameToValue[parameterName] = paramValue; } } // Compare parameters present with the ones expected in the mapping const ESRIMethodMapping *esriMapping = nullptr; int bestMatchCount = -1; for (const auto &mapping : esriMappings) { int matchCount = 0; int unmatchCount = 0; for (const auto *param = mapping->params; param->esri_name; ++param) { auto iter = mapParamNameToValue.find(param->esri_name); if (iter != mapParamNameToValue.end()) { if (param->wkt2_name == nullptr) { bool ok = true; try { if (io::asDouble(param->fixed_value) == io::asDouble(iter->second)) { matchCount++; } else { ok = false; } } catch (const std::exception &) { ok = false; } if (!ok) { matchCount = -1; break; } } else { matchCount++; } } else if (param->is_fixed_value) { mapParamNameToValue[param->esri_name] = param->fixed_value; } else { unmatchCount++; } } if (matchCount > bestMatchCount && !(maybeEsriStyle_ && unmatchCount >= matchCount)) { esriMapping = mapping; bestMatchCount = matchCount; } } return esriMapping; } // --------------------------------------------------------------------------- ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit, const ESRIMethodMapping *esriMapping, std::map &mapParamNameToValue) { std::map mapWKT2NameToESRIName; for (const auto *param = esriMapping->params; param->esri_name; ++param) { if (param->wkt2_name) { mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name; } } const std::string esriProjectionName = stripQuotes(projectionNode->GP()->children()[0]); const char *projectionMethodWkt2Name = esriMapping->wkt2_name; if (ci_equal(esriProjectionName, "Krovak")) { const std::string projCRSName = stripQuotes(projCRSNode->GP()->children()[0]); if (projCRSName.find("_East_North") != std::string::npos) { projectionMethodWkt2Name = EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED; } } const auto *wkt2_mapping = getMapping(projectionMethodWkt2Name); if (ci_equal(esriProjectionName, "Stereographic")) { try { const auto iterLatitudeOfOrigin = mapParamNameToValue.find("Latitude_Of_Origin"); if (iterLatitudeOfOrigin != mapParamNameToValue.end() && std::fabs(io::asDouble(iterLatitudeOfOrigin->second)) == 90.0) { wkt2_mapping = getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); } } catch (const std::exception &) { } } assert(wkt2_mapping); wkt2_mapping = selectSphericalOrEllipsoidal(wkt2_mapping, baseGeodCRS); PropertyMap propertiesMethod; propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name); if (wkt2_mapping->epsg_code != 0) { propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code); propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } std::vector parameters; std::vector values; if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && ci_equal(esriProjectionName, "Plate_Carree")) { // Add a fixed Latitude of 1st parallel = 0 so as to have all // parameters expected by Equidistant Cylindrical. mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] = "Standard_Parallel_1"; mapParamNameToValue["Standard_Parallel_1"] = "0"; } else if ((wkt2_mapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A || wkt2_mapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) && !ci_equal(esriProjectionName, "Rectified_Skew_Orthomorphic_Natural_Origin") && !ci_equal(esriProjectionName, "Rectified_Skew_Orthomorphic_Center")) { // ESRI WKT lacks the angle to skew grid // Take it from the azimuth value mapWKT2NameToESRIName [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth"; } for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) { const auto *paramMapping = wkt2_mapping->params[i]; auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name); if (iter == mapWKT2NameToESRIName.end()) { continue; } const auto &esriParamName = iter->second; auto iter2 = mapParamNameToValue.find(esriParamName); auto mapParamNameToValueEnd = mapParamNameToValue.end(); if (iter2 == mapParamNameToValueEnd) { // In case we don't find a direct match, try the aliases for (iter2 = mapParamNameToValue.begin(); iter2 != mapParamNameToValueEnd; ++iter2) { if (areEquivalentParameters(iter2->first, esriParamName)) { break; } } if (iter2 == mapParamNameToValueEnd) { continue; } } PropertyMap propertiesParameter; propertiesParameter.set(IdentifiedObject::NAME_KEY, paramMapping->wkt2_name); if (paramMapping->epsg_code != 0) { propertiesParameter.set(Identifier::CODE_KEY, paramMapping->epsg_code); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } parameters.push_back(OperationParameter::create(propertiesParameter)); try { double val = io::asDouble(iter2->second); auto unit = guessUnitForParameter( paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit); values.push_back(ParameterValue::create(Measure(val, unit))); } catch (const std::exception &) { throw ParsingException( concat("unhandled parameter value type : ", iter2->second)); } } return Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, esriProjectionName == "Gauss_Kruger" ? "unnnamed (Gauss Kruger)" : "unnamed"), propertiesMethod, parameters, values) ->identify(); } // --------------------------------------------------------------------------- ConversionNNPtr WKTParser::Private::buildProjectionFromESRI( const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit) { std::map mapParamNameToValue; const auto esriMapping = getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue); if (esriMapping == nullptr) { return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit, defaultAngularUnit); } return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit, defaultAngularUnit, esriMapping, mapParamNameToValue); } // --------------------------------------------------------------------------- ConversionNNPtr WKTParser::Private::buildProjection( const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit) { if (projectionNode->GP()->childrenSize() == 0) { ThrowNotEnoughChildren(WKTConstants::PROJECTION); } if (esriStyle_ || maybeEsriStyle_) { return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit, defaultAngularUnit); } return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit, defaultAngularUnit); } // --------------------------------------------------------------------------- std::string WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode, const char *paramName) { for (const auto &childNode : projCRSNode->GP()->children()) { if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() == 2 && metadata::Identifier::isEquivalentName( stripQuotes(childNodeChildren[0]).c_str(), paramName)) { return childNodeChildren[1]->GP()->value(); } } } return std::string(); } // --------------------------------------------------------------------------- ConversionNNPtr WKTParser::Private::buildProjectionStandard( const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit, const UnitOfMeasure &defaultAngularUnit) { std::string wkt1ProjectionName = stripQuotes(projectionNode->GP()->children()[0]); std::vector parameters; std::vector values; bool tryToIdentifyWKT1Method = true; auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION); const auto &extensionChildren = extensionNode->GP()->children(); bool gdal_3026_hack = false; if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(), "Mercator_1SP") && projectionGetParameter(projCRSNode, "center_latitude").empty()) { // Hack for https://trac.osgeo.org/gdal/ticket/3026 std::string lat0( projectionGetParameter(projCRSNode, "latitude_of_origin")); if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") { wkt1ProjectionName = "Mercator_2SP"; gdal_3026_hack = true; } else { // The latitude of origin, which should always be zero, is // missing // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP // definition, // so add it manually. PropertyMap propertiesParameter; propertiesParameter.set(IdentifiedObject::NAME_KEY, "Latitude of natural origin"); propertiesParameter.set(Identifier::CODE_KEY, 8801); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); parameters.push_back( OperationParameter::create(propertiesParameter)); values.push_back( ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE))); } } else if (metadata::Identifier::isEquivalentName( wkt1ProjectionName.c_str(), "Polar_Stereographic")) { std::map mapParameters; for (const auto &childNode : projCRSNode->GP()->children()) { const auto &childNodeChildren = childNode->GP()->children(); if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) && childNodeChildren.size() == 2) { const std::string wkt1ParameterName( stripQuotes(childNodeChildren[0])); try { double val = asDouble(childNodeChildren[1]); auto unit = guessUnitForParameter(wkt1ParameterName, defaultLinearUnit, defaultAngularUnit); mapParameters.insert(std::pair( tolower(wkt1ParameterName), Measure(val, unit))); } catch (const std::exception &) { } } } Measure latitudeOfOrigin = mapParameters["latitude_of_origin"]; Measure centralMeridian = mapParameters["central_meridian"]; Measure scaleFactorFromMap = mapParameters["scale_factor"]; Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE) ? Measure(1.0, UnitOfMeasure::SCALE_UNITY) : scaleFactorFromMap); Measure falseEasting = mapParameters["false_easting"]; Measure falseNorthing = mapParameters["false_northing"]; if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && scaleFactor.getSIValue() == 1.0) { return Conversion::createPolarStereographicVariantB( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), Angle(centralMeridian.value(), centralMeridian.unit()), Length(falseEasting.value(), falseEasting.unit()), Length(falseNorthing.value(), falseNorthing.unit())); } if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE && std::fabs(std::fabs(latitudeOfOrigin.convertToUnit( UnitOfMeasure::DEGREE)) - 90.0) < 1e-10) { return Conversion::createPolarStereographicVariantA( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()), Angle(centralMeridian.value(), centralMeridian.unit()), Scale(scaleFactor.value(), scaleFactor.unit()), Length(falseEasting.value(), falseEasting.unit()), Length(falseNorthing.value(), falseNorthing.unit())); } tryToIdentifyWKT1Method = false; // Import GDAL PROJ4 extension nodes } else if (extensionChildren.size() == 2 && ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) { std::string projString = stripQuotes(extensionChildren[1]); if (starts_with(projString, "+proj=")) { if (projString.find(" +type=crs") == std::string::npos) { projString += " +type=crs"; } try { auto projObj = PROJStringParser().createFromPROJString(projString); auto projObjCrs = nn_dynamic_pointer_cast(projObj); if (projObjCrs) { return projObjCrs->derivingConversion(); } } catch (const io::ParsingException &) { } } } std::string projectionName(std::move(wkt1ProjectionName)); const MethodMapping *mapping = tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr; if (!mapping) { // Sometimes non-WKT1:ESRI looking WKT can actually use WKT1:ESRI // projection definitions std::map mapParamNameToValue; const auto esriMapping = getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue); if (esriMapping != nullptr) { return buildProjectionFromESRI( baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit, defaultAngularUnit, esriMapping, mapParamNameToValue); } } if (mapping) { mapping = selectSphericalOrEllipsoidal(mapping, baseGeodCRS); } else if (metadata::Identifier::isEquivalentName( projectionName.c_str(), "Lambert Conformal Conic")) { // Lambert Conformal Conic or Lambert_Conformal_Conic are respectively // used by Oracle WKT and Trimble for either LCC 1SP or 2SP, so we // have to look at parameters to figure out the variant. bool found2ndStdParallel = false; bool foundScaleFactor = false; for (const auto &childNode : projCRSNode->GP()->children()) { if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() < 2) { ThrowNotEnoughChildren(WKTConstants::PARAMETER); } const std::string wkt1ParameterName( stripQuotes(childNodeChildren[0])); if (metadata::Identifier::isEquivalentName( wkt1ParameterName.c_str(), WKT1_STANDARD_PARALLEL_2)) { found2ndStdParallel = true; } else if (metadata::Identifier::isEquivalentName( wkt1ParameterName.c_str(), WKT1_SCALE_FACTOR)) { foundScaleFactor = true; } } } if (found2ndStdParallel && !foundScaleFactor) { mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); } else if (!found2ndStdParallel && foundScaleFactor) { mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); } else if (found2ndStdParallel && foundScaleFactor) { // Not sure if that happens mapping = getMapping( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN); } } // For Krovak, we need to look at axis to decide between the Krovak and // Krovak East-North Oriented methods if (ci_equal(projectionName, "Krovak") && projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 && &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0), defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 1) ->direction() == &AxisDirection::SOUTH && &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1), defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 2) ->direction() == &AxisDirection::WEST) { mapping = getMapping(EPSG_CODE_METHOD_KROVAK); } PropertyMap propertiesMethod; if (mapping) { projectionName = mapping->wkt2_name; if (mapping->epsg_code != 0) { propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code); propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } } propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName); std::vector foundParameters; if (mapping) { size_t countParams = 0; while (mapping->params[countParams] != nullptr) { ++countParams; } foundParameters.resize(countParams); } for (const auto &childNode : projCRSNode->GP()->children()) { if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() < 2) { ThrowNotEnoughChildren(WKTConstants::PARAMETER); } const auto ¶mValue = childNodeChildren[1]->GP()->value(); PropertyMap propertiesParameter; const std::string wkt1ParameterName( stripQuotes(childNodeChildren[0])); std::string parameterName(wkt1ParameterName); if (gdal_3026_hack) { if (ci_equal(parameterName, "latitude_of_origin")) { parameterName = "standard_parallel_1"; } else if (ci_equal(parameterName, "scale_factor") && paramValue == "1") { continue; } } auto *paramMapping = mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr; if (mapping && mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && ci_equal(parameterName, "latitude_of_origin")) { // Some illegal formulations of Mercator_2SP have a unexpected // latitude_of_origin parameter. We accept it on import, but // do not accept it when exporting to PROJ string, unless it is // zero. // No need to try to update foundParameters[] as this is a // unexpected one. parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; propertiesParameter.set( Identifier::CODE_KEY, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } else if (mapping && paramMapping) { for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) { if (mapping->params[idx] == paramMapping) { foundParameters[idx] = true; break; } } parameterName = paramMapping->wkt2_name; if (paramMapping->epsg_code != 0) { propertiesParameter.set(Identifier::CODE_KEY, paramMapping->epsg_code); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } } propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName); parameters.push_back( OperationParameter::create(propertiesParameter)); try { double val = io::asDouble(paramValue); auto unit = guessUnitForParameter( wkt1ParameterName, defaultLinearUnit, defaultAngularUnit); values.push_back(ParameterValue::create(Measure(val, unit))); } catch (const std::exception &) { throw ParsingException( concat("unhandled parameter value type : ", paramValue)); } } } // Add back important parameters that should normally be present, but // are sometimes missing. Currently we only deal with Scale factor at // natural origin. This is to avoid a default value of 0 to slip in later. // But such WKT should be considered invalid. if (mapping) { for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) { if (!foundParameters[idx] && mapping->params[idx]->epsg_code == EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) { emitRecoverableWarning( "The WKT string lacks a value " "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN ". Default it to 1."); PropertyMap propertiesParameter; propertiesParameter.set( Identifier::CODE_KEY, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); propertiesParameter.set( IdentifiedObject::NAME_KEY, EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); parameters.push_back( OperationParameter::create(propertiesParameter)); values.push_back(ParameterValue::create( Measure(1.0, UnitOfMeasure::SCALE_UNITY))); } } } if (mapping && (mapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A || mapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B)) { // Special case when importing some GDAL WKT of Hotine Oblique Mercator // that have a Azimuth parameter but lacks the Rectified Grid Angle. // We have code in the exportToPROJString() to deal with that situation, // but also adds the rectified grid angle from the azimuth on import. bool foundAngleRecifiedToSkewGrid = false; bool foundAzimuth = false; for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) { if (foundParameters[idx] && mapping->params[idx]->epsg_code == EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) { foundAngleRecifiedToSkewGrid = true; } else if (foundParameters[idx] && mapping->params[idx]->epsg_code == EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) { foundAzimuth = true; } } if (!foundAngleRecifiedToSkewGrid && foundAzimuth) { for (size_t idx = 0; idx < parameters.size(); ++idx) { if (parameters[idx]->getEPSGCode() == EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) { PropertyMap propertiesParameter; propertiesParameter.set( Identifier::CODE_KEY, EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); propertiesParameter.set( IdentifiedObject::NAME_KEY, EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID); parameters.push_back( OperationParameter::create(propertiesParameter)); values.push_back(values[idx]); } } } } return Conversion::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), propertiesMethod, parameters, values) ->identify(); } // --------------------------------------------------------------------------- static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props, const cs::CartesianCSNNPtr &cs) { auto conversion = Conversion::createPopularVisualisationPseudoMercator( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0), Angle(0), Length(0), Length(0)); return ProjectedCRS::create(props, GeographicCRS::EPSG_4326, conversion, cs); } // --------------------------------------------------------------------------- ProjectedCRSNNPtr WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION); auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION); if (isNull(conversionNode) && isNull(projectionNode)) { ThrowMissing(WKTConstants::CONVERSION); } auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS, WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS); if (isNull(baseGeodCRSNode)) { throw ParsingException( "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node"); } auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode); auto props = buildProperties(node); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); const auto &nodeValue = nodeP->value(); if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) && !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) { ThrowMissing(WKTConstants::CS_); } std::string projCRSName = stripQuotes(nodeP->children()[0]); auto cs = [this, &projCRSName, &nodeP, &csNode, &node, &nodeValue, &conversionNode]() -> CoordinateSystemNNPtr { if (isNull(csNode) && ci_equal(nodeValue, WKTConstants::BASEPROJCRS) && !isNull(conversionNode)) { // A BASEPROJCRS (as of WKT2 18-010r11) normally lacks an explicit // CS[] which cause issues to properly instantiate it. So we first // start by trying to identify the BASEPROJCRS by its id or name. // And fallback to exploring the conversion parameters to infer the // CS AXIS unit from the linear parameter unit... Not fully bullet // proof. if (dbContext_) { // Get official name from database if ID is present auto &idNode = nodeP->lookForChild(WKTConstants::ID); if (!isNull(idNode)) { try { auto id = buildId(node, idNode, false, false); auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), *id->codeSpace()); auto projCRS = authFactory->createProjectedCRS(id->code()); return projCRS->coordinateSystem(); } catch (const std::exception &) { } } auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), std::string()); auto res = authFactory->createObjectsFromName( projCRSName, {AuthorityFactory::ObjectType::PROJECTED_CRS}, false, 2); if (res.size() == 1) { auto projCRS = dynamic_cast(res.front().get()); if (projCRS) { return projCRS->coordinateSystem(); } } } auto conv = buildConversion(conversionNode, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE); UnitOfMeasure linearUOM = UnitOfMeasure::NONE; for (const auto &genOpParamvalue : conv->parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meterValue = opParamvalue->parameterValue(); if (parameterValue->type() == operation::ParameterValue::Type::MEASURE) { const auto &measure = parameterValue->value(); const auto &unit = measure.unit(); if (unit.type() == UnitOfMeasure::Type::LINEAR) { if (linearUOM == UnitOfMeasure::NONE) { linearUOM = unit; } else if (linearUOM != unit) { linearUOM = UnitOfMeasure::NONE; break; } } } } } if (linearUOM != UnitOfMeasure::NONE) { return CartesianCS::createEastingNorthing(linearUOM); } } return buildCS(csNode, node, UnitOfMeasure::NONE); }(); auto cartesianCS = nn_dynamic_pointer_cast(cs); if (dbContext_ && ((!esriStyle_ && projCRSName == "ETRF2000-PL / CS92" && baseGeodCRS->nameStr() == "ETRF2000-PL") || (esriStyle_ && projCRSName == "ETRF2000-PL_CS92" && (baseGeodCRS->nameStr() == "GCS_ETRF2000-PL" || baseGeodCRS->nameStr() == "ETRF2000-PL")))) { // Oddity: "ETRF2000-PL / CS92" (EPSG:2180) has switched back to // "ETRS89 / PL-1992" auto authFactoryEPSG = io::AuthorityFactory::create(NN_NO_CHECK(dbContext_), "EPSG"); auto newProjCRS = authFactoryEPSG->createProjectedCRS("2180"); props.set(IdentifiedObject::NAME_KEY, newProjCRS->nameStr()); baseGeodCRS = newProjCRS->baseCRS(); } // In EPSG v12.025, Norway projected systems based on ETRS89 (EPSG:4258) // have switched to use ETRS89-NOR [EUREF89] (EPSG:10875). // Similarly for other ETRS89-like datums in later releases else if (dbContext_ && (((starts_with(projCRSName, "ETRS89 / ") || (esriStyle_ && starts_with(projCRSName, "ETRS_1989_"))) && baseGeodCRS->nameStr() == "ETRS89") || starts_with(projCRSName, "ETRF2000-PL /")) && util::isOfExactType(*(baseGeodCRS.get())) && baseGeodCRS->coordinateSystem()->axisList().size() == 2) { auto authFactoryEPSG = io::AuthorityFactory::create(NN_NO_CHECK(dbContext_), "EPSG"); const auto objCandidates = authFactoryEPSG->createObjectsFromNameEx( projCRSName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, false, // approximateMatch 0, // limit true // useAliases ); for (const auto &[obj, name] : objCandidates) { if (name == projCRSName) { auto candidateProj = dynamic_cast(obj.get()); if (candidateProj && candidateProj->baseCRS()->nameStr() != baseGeodCRS->nameStr() && candidateProj->baseCRS()->_isEquivalentTo( baseGeodCRS.get(), util::IComparable::Criterion:: EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, dbContext_)) { props.set(IdentifiedObject::NAME_KEY, candidateProj->nameStr()); baseGeodCRS = candidateProj->baseCRS(); break; } } } } if (esriStyle_ && dbContext_) { if (cartesianCS) { std::string outTableName; std::string authNameFromAlias; std::string codeFromAlias; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto officialName = authFactory->getOfficialNameFromAlias( projCRSName, "projected_crs", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { // Special case for https://github.com/OSGeo/PROJ/issues/2086 // The name of the CRS to identify is // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501 // whereas it should be // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37; if (projCRSName.find("_FIPS_") != std::string::npos && projCRSName.find("_Feet") == std::string::npos && std::fabs( cartesianCS->axisList()[0]->unit().conversionToSI() - US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) { auto officialNameFromFeet = authFactory->getOfficialNameFromAlias( projCRSName + "_Feet", "projected_crs", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialNameFromFeet.empty()) { officialName = std::move(officialNameFromFeet); } } projCRSName = officialName; props.set(IdentifiedObject::NAME_KEY, officialName); } } } if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode) && cartesianCS) { toWGS84Parameters_.clear(); return createPseudoMercator(props, NN_NO_CHECK(cartesianCS)); } // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated // by older GDAL versions // https://trac.osgeo.org/gdal/changeset/30732 // WGS_1984_Web_Mercator: deprecated ESRI:102113 if (cartesianCS && (metadata::Identifier::isEquivalentName( projCRSName.c_str(), "WGS_84_Pseudo_Mercator") || metadata::Identifier::isEquivalentName( projCRSName.c_str(), "WGS_1984_Web_Mercator"))) { toWGS84Parameters_.clear(); return createPseudoMercator(props, NN_NO_CHECK(cartesianCS)); } // For WKT2, if there is no explicit parameter unit, use metre for linear // units and degree for angular units const UnitOfMeasure linearUnit( !isNull(conversionNode) ? UnitOfMeasure::METRE : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR)); const auto &angularUnit = !isNull(conversionNode) ? UnitOfMeasure::DEGREE : baseGeodCRS->coordinateSystem()->axisList()[0]->unit(); auto conversion = !isNull(conversionNode) ? buildConversion(conversionNode, linearUnit, angularUnit) : buildProjection(baseGeodCRS, node, projectionNode, linearUnit, angularUnit); // No explicit AXIS node ? (WKT1) if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) { props.set("IMPLICIT_CS", true); } if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) { const auto methodCode = conversion->method()->getEPSGCode(); // Krovak south oriented ? if (methodCode == EPSG_CODE_METHOD_KROVAK) { cartesianCS = CartesianCS::create( PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Southing), emptyString, AxisDirection::SOUTH, linearUnit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Westing), emptyString, AxisDirection::WEST, linearUnit)) .as_nullable(); } else if (methodCode == EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A || methodCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) { // It is likely that the ESRI definition of EPSG:32661 (UPS North) & // EPSG:32761 (UPS South) uses the easting-northing order, instead // of the EPSG northing-easting order. // Same for WKT1_GDAL const double lat0 = conversion->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE); if (std::fabs(lat0 - 90) < 1e-10) { cartesianCS = CartesianCS::createNorthPoleEastingSouthNorthingSouth( linearUnit) .as_nullable(); } else if (std::fabs(lat0 - -90) < 1e-10) { cartesianCS = CartesianCS::createSouthPoleEastingNorthNorthingNorth( linearUnit) .as_nullable(); } } else if (methodCode == EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { const double lat_ts = conversion->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, common::UnitOfMeasure::DEGREE); if (lat_ts > 0) { cartesianCS = CartesianCS::createNorthPoleEastingSouthNorthingSouth( linearUnit) .as_nullable(); } else if (lat_ts < 0) { cartesianCS = CartesianCS::createSouthPoleEastingNorthNorthingNorth( linearUnit) .as_nullable(); } } else if (methodCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { cartesianCS = CartesianCS::createWestingSouthing(linearUnit).as_nullable(); } } if (!cartesianCS) { ThrowNotExpectedCSType(CartesianCS::WKT2_TYPE); } if (cartesianCS->axisList().size() == 3 && baseGeodCRS->coordinateSystem()->axisList().size() == 2) { baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast( baseGeodCRS->promoteTo3D(std::string(), dbContext_))); } addExtensionProj4ToProp(nodeP, props); return ProjectedCRS::create(props, baseGeodCRS, conversion, NN_NO_CHECK(cartesianCS)); } // --------------------------------------------------------------------------- void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode, double &frameReferenceEpoch, util::optional &modelName) { auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH); const auto &frameEpochChildren = frameEpochNode->GP()->children(); if (frameEpochChildren.empty()) { ThrowMissing(WKTConstants::FRAMEEPOCH); } try { frameReferenceEpoch = asDouble(frameEpochChildren[0]); } catch (const std::exception &) { throw ParsingException("Invalid FRAMEEPOCH node"); } auto &modelNode = dynamicNode->GP()->lookForChild( WKTConstants::MODEL, WKTConstants::VELOCITYGRID); const auto &modelChildren = modelNode->GP()->children(); if (modelChildren.size() == 1) { modelName = stripQuotes(modelChildren[0]); } } // --------------------------------------------------------------------------- VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame( const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) { if (!isNull(dynamicNode)) { double frameReferenceEpoch = 0.0; util::optional modelName; parseDynamic(dynamicNode, frameReferenceEpoch, modelName); return DynamicVerticalReferenceFrame::create( buildProperties(node), getAnchor(node), optional(), common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR), modelName); } // WKT1 VERT_DATUM has a datum type after the datum name const auto *nodeP = node->GP(); const std::string &name(nodeP->value()); auto &props = buildProperties(node); const auto &children = nodeP->children(); if (esriStyle_ && dbContext_ && !children.empty()) { std::string outTableName; std::string authNameFromAlias; std::string codeFromAlias; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); const std::string datumName = stripQuotes(children[0]); auto officialName = authFactory->getOfficialNameFromAlias( datumName, "vertical_datum", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { props.set(IdentifiedObject::NAME_KEY, officialName); } } if (ci_equal(name, WKTConstants::VERT_DATUM)) { if (children.size() >= 2) { props.set("VERT_DATUM_TYPE", children[1]->GP()->value()); } } return VerticalReferenceFrame::create(props, getAnchor(node), getAnchorEpoch(node)); } // --------------------------------------------------------------------------- TemporalDatumNNPtr WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR); std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN; const auto &calendarChildren = calendarNode->GP()->children(); if (calendarChildren.size() == 1) { calendar = stripQuotes(calendarChildren[0]); } auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN); std::string originStr; const auto &timeOriginNodeChildren = timeOriginNode->GP()->children(); if (timeOriginNodeChildren.size() == 1) { originStr = stripQuotes(timeOriginNodeChildren[0]); } auto origin = DateTime::create(originStr); return TemporalDatum::create(buildProperties(node), origin, calendar); } // --------------------------------------------------------------------------- EngineeringDatumNNPtr WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) { return EngineeringDatum::create(buildProperties(node), getAnchor(node)); } // --------------------------------------------------------------------------- ParametricDatumNNPtr WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) { return ParametricDatum::create(buildProperties(node), getAnchor(node)); } // --------------------------------------------------------------------------- static CRSNNPtr createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS) { CRSPtr sourceTransformationCRS; if (dynamic_cast(targetCRS.get())) { GeographicCRSPtr sourceGeographicCRS = sourceCRS->extractGeographicCRS(); sourceTransformationCRS = sourceGeographicCRS; if (sourceGeographicCRS) { const auto &sourceDatum = sourceGeographicCRS->datum(); if (sourceDatum != nullptr && sourceGeographicCRS->primeMeridian() ->longitude() .getSIValue() != 0.0) { sourceTransformationCRS = GeographicCRS::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, sourceGeographicCRS->nameStr() + " (with Greenwich prime meridian)"), datum::GeodeticReferenceFrame::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, sourceDatum->nameStr() + " (with Greenwich prime meridian)"), sourceDatum->ellipsoid(), util::optional(), datum::PrimeMeridian::GREENWICH), sourceGeographicCRS->coordinateSystem()) .as_nullable(); } } else { auto vertSourceCRS = std::dynamic_pointer_cast(sourceCRS); if (!vertSourceCRS) { throw ParsingException( "Cannot find GeographicCRS or VerticalCRS in sourceCRS"); } const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0]; if (axis->unit() == common::UnitOfMeasure::METRE && &(axis->direction()) == &AxisDirection::UP) { sourceTransformationCRS = sourceCRS; } else { std::string sourceTransformationCRSName( vertSourceCRS->nameStr()); if (ends_with(sourceTransformationCRSName, " (ftUS)")) { sourceTransformationCRSName.resize( sourceTransformationCRSName.size() - strlen(" (ftUS)")); } if (ends_with(sourceTransformationCRSName, " depth")) { sourceTransformationCRSName.resize( sourceTransformationCRSName.size() - strlen(" depth")); } if (!ends_with(sourceTransformationCRSName, " height")) { sourceTransformationCRSName += " height"; } sourceTransformationCRS = VerticalCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, sourceTransformationCRSName), vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(), VerticalCS::createGravityRelatedHeight( common::UnitOfMeasure::METRE)) .as_nullable(); } } } else { sourceTransformationCRS = sourceCRS; } return NN_NO_CHECK(sourceTransformationCRS); } // --------------------------------------------------------------------------- CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); const auto &nodeValue = nodeP->value(); auto &vdatumNode = nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM, WKTConstants::VERTICALDATUM, WKTConstants::VRF); auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE); // like in ESRI VERTCS["WGS_1984",DATUM["D_WGS_1984", // SPHEROID["WGS_1984",6378137.0,298.257223563]], // PARAMETER["Vertical_Shift",0.0], // PARAMETER["Direction",1.0],UNIT["Meter",1.0] auto &geogDatumNode = ci_equal(nodeValue, WKTConstants::VERTCS) ? nodeP->lookForChild(WKTConstants::DATUM) : null_node; if (isNull(vdatumNode) && isNull(geogDatumNode) && isNull(ensembleNode)) { throw ParsingException("Missing VDATUM or ENSEMBLE node"); } for (const auto &childNode : nodeP->children()) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() == 2 && ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) && childNodeChildren[0]->GP()->value() == "\"Vertical_Shift\"") { esriStyle_ = true; break; } } auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC); auto vdatum = !isNull(geogDatumNode) ? VerticalReferenceFrame::create( PropertyMap() .set(IdentifiedObject::NAME_KEY, buildGeodeticReferenceFrame(geogDatumNode, PrimeMeridian::GREENWICH, null_node) ->nameStr()) .set("VERT_DATUM_TYPE", "2002")) .as_nullable() : !isNull(vdatumNode) ? buildVerticalReferenceFrame(vdatumNode, dynamicNode).as_nullable() : nullptr; auto datumEnsemble = !isNull(ensembleNode) ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable() : nullptr; auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) && !ci_equal(nodeValue, WKTConstants::VERTCS) && !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) { ThrowMissing(WKTConstants::CS_); } auto verticalCS = nn_dynamic_pointer_cast( buildCS(csNode, node, UnitOfMeasure::NONE)); if (!verticalCS) { ThrowNotExpectedCSType(VerticalCS::WKT2_TYPE); } if (vdatum && vdatum->getWKT1DatumType() == "2002" && &(verticalCS->axisList()[0]->direction()) == &(AxisDirection::UP)) { verticalCS = VerticalCS::create( util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, "ellipsoidal height"), "h", AxisDirection::UP, verticalCS->axisList()[0]->unit())) .as_nullable(); } auto &props = buildProperties(node); if (esriStyle_ && dbContext_) { std::string outTableName; std::string authNameFromAlias; std::string codeFromAlias; auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); const std::string vertCRSName = stripQuotes(nodeP->children()[0]); auto officialName = authFactory->getOfficialNameFromAlias( vertCRSName, "vertical_crs", "ESRI", false, outTableName, authNameFromAlias, codeFromAlias); if (!officialName.empty()) { props.set(IdentifiedObject::NAME_KEY, officialName); } } // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name, // following conventions from // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf // page 9 if (ci_equal(nodeValue, WKTConstants::VERT_CS) || ci_equal(nodeValue, WKTConstants::VERTCS)) { std::string name; if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) { std::string navd88GeoidName; for (const char *prefix : {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ", "NAVD88 height (ftUS) - "}) { if (starts_with(name, prefix)) { navd88GeoidName = name.substr(strlen(prefix)); auto pos = navd88GeoidName.find_first_of(" ("); if (pos != std::string::npos) { navd88GeoidName.resize(pos); } break; } } // Deal with vertical CRS names like "Geoid 2012A" if (navd88GeoidName.empty() && ci_starts_with(name, "Geoid") && geogCRSOfCompoundCRS_ && starts_with(geogCRSOfCompoundCRS_->nameStr(), "NAD83")) { // Remove spaces navd88GeoidName = replaceAll(name, " ", ""); // Morph "Geoid 20XX[Y]" to "GeoidXX[Y]" if (navd88GeoidName.size() >= 9 && ci_starts_with(navd88GeoidName, "Geoid20") && navd88GeoidName[7] >= '0' && navd88GeoidName[7] <= '9' && navd88GeoidName[8] >= '0' && navd88GeoidName[8] <= '9') { navd88GeoidName = "Geoid" + navd88GeoidName.substr(7); } } if (!navd88GeoidName.empty()) { const auto &axis = verticalCS->axisList()[0]; const auto &dir = axis->direction(); if (dir == cs::AxisDirection::UP) { if (axis->unit() == common::UnitOfMeasure::METRE) { props.set(IdentifiedObject::NAME_KEY, "NAVD88 height"); props.set(Identifier::CODE_KEY, 5703); props.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } else if (axis->unit().name() == "US survey foot") { props.set(IdentifiedObject::NAME_KEY, "NAVD88 height (ftUS)"); props.set(Identifier::CODE_KEY, 6360); props.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } } PropertyMap propsModel; propsModel.set(IdentifiedObject::NAME_KEY, toupper(navd88GeoidName)); PropertyMap propsDatum; propsDatum.set(IdentifiedObject::NAME_KEY, "North American Vertical Datum 1988"); propsDatum.set(Identifier::CODE_KEY, 5103); propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG); vdatum = VerticalReferenceFrame::create(propsDatum).as_nullable(); const auto dummyCRS = VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)); const auto model(Transformation::create( propsModel, dummyCRS, dummyCRS, nullptr, OperationMethod::create( PropertyMap(), std::vector()), {}, {})); props.set("GEOID_MODEL", model); } } } auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL); if (!isNull(geoidModelNode)) { ArrayOfBaseObjectNNPtr arrayModels = ArrayOfBaseObject::create(); for (const auto &childNode : nodeP->children()) { const auto &childNodeChildren = childNode->GP()->children(); if (childNodeChildren.size() >= 1 && ci_equal(childNode->GP()->value(), WKTConstants::GEOIDMODEL)) { auto &propsModel = buildProperties(childNode); const auto dummyCRS = VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)); const auto model(Transformation::create( propsModel, dummyCRS, dummyCRS, nullptr, OperationMethod::create( PropertyMap(), std::vector()), {}, {})); arrayModels->add(model); } } props.set("GEOID_MODEL", arrayModels); } auto crs = nn_static_pointer_cast(VerticalCRS::create( props, vdatum, datumEnsemble, NN_NO_CHECK(verticalCS))); if (!isNull(vdatumNode)) { auto &extensionNode = vdatumNode->lookForChild(WKTConstants::EXTENSION); const auto &extensionChildren = extensionNode->GP()->children(); if (extensionChildren.size() == 2) { if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) { const auto gridName(stripQuotes(extensionChildren[1])); // This is the expansion of EPSG:5703 by old GDAL versions. // See // https://trac.osgeo.org/metacrs/changeset?reponame=&new=2281%40geotiff%2Ftrunk%2Flibgeotiff%2Fcsv%2Fvertcs.override.csv&old=1893%40geotiff%2Ftrunk%2Flibgeotiff%2Fcsv%2Fvertcs.override.csv // It is unlikely that the user really explicitly wants this. if (gridName != "g2003conus.gtx,g2003alaska.gtx," "g2003h01.gtx,g2003p01.gtx" && gridName != "g2012a_conus.gtx,g2012a_alaska.gtx," "g2012a_guam.gtx,g2012a_hawaii.gtx," "g2012a_puertorico.gtx,g2012a_samoa.gtx") { auto geogCRS = geogCRSOfCompoundCRS_ && geogCRSOfCompoundCRS_->primeMeridian() ->longitude() .getSIValue() == 0 && geogCRSOfCompoundCRS_->coordinateSystem() ->axisList()[0] ->unit() == UnitOfMeasure::DEGREE ? geogCRSOfCompoundCRS_->promoteTo3D(std::string(), dbContext_) : GeographicCRS::EPSG_4979; auto sourceTransformationCRS = createBoundCRSSourceTransformationCRS( crs.as_nullable(), geogCRS.as_nullable()); auto transformation = Transformation:: createGravityRelatedHeightToGeographic3D( PropertyMap().set( IdentifiedObject::NAME_KEY, sourceTransformationCRS->nameStr() + " to " + geogCRS->nameStr() + " ellipsoidal height"), sourceTransformationCRS, geogCRS, nullptr, gridName, std::vector()); return nn_static_pointer_cast( BoundCRS::create(crs, geogCRS, transformation)); } } } } return crs; } // --------------------------------------------------------------------------- DerivedVerticalCRSNNPtr WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS); // given the constraints enforced on calling code path assert(!isNull(baseVertCRSNode)); auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode); auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS()); auto &derivingConversionNode = nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); if (isNull(derivingConversionNode)) { ThrowMissing(WKTConstants::DERIVINGCONVERSION); } auto derivingConversion = buildConversion( derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); auto verticalCS = nn_dynamic_pointer_cast(cs); if (!verticalCS) { throw ParsingException( concat("vertical CS expected, but found ", cs->getWKT2Type(true))); } return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS, derivingConversion, NN_NO_CHECK(verticalCS)); } // --------------------------------------------------------------------------- CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) { std::vector components; bool bFirstNode = true; for (const auto &child : node->GP()->children()) { auto crs = buildCRS(child); if (crs) { if (bFirstNode) { geogCRSOfCompoundCRS_ = crs->extractGeographicCRS(); bFirstNode = false; } components.push_back(NN_NO_CHECK(crs)); } } if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) { return CompoundCRS::createLax(buildProperties(node), components, dbContext_); } else { return CompoundCRS::create(buildProperties(node), components); } } // --------------------------------------------------------------------------- static TransformationNNPtr buildTransformationForBoundCRS( DatabaseContextPtr &dbContext, const util::PropertyMap &abridgedNodeProperties, const util::PropertyMap &methodNodeProperties, const CRSNNPtr &sourceCRS, const CRSNNPtr &targetCRS, std::vector ¶meters, std::vector &values) { auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter( dbContext, parameters, values); const auto sourceTransformationCRS( createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS)); auto transformation = Transformation::create( abridgedNodeProperties, sourceTransformationCRS, targetCRS, interpolationCRS, methodNodeProperties, parameters, values, std::vector()); // If the transformation is a "Geographic3D to GravityRelatedHeight" one, // then the sourceCRS is expected to be a GeographicCRS and the target a // VerticalCRS. Due to how things work in a BoundCRS, we have the opposite, // so use our "GravityRelatedHeight to Geographic3D" method instead. if (Transformation::isGeographic3DToGravityRelatedHeight( transformation->method(), true) && dynamic_cast(sourceTransformationCRS.get()) && dynamic_cast(targetCRS.get())) { auto fileParameter = transformation->parameterValue( EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { const auto &filename = fileParameter->valueFile(); transformation = Transformation::createGravityRelatedHeightToGeographic3D( abridgedNodeProperties, sourceTransformationCRS, targetCRS, interpolationCRS, filename, std::vector()); } } return transformation; } // --------------------------------------------------------------------------- BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &abridgedNode = nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION); if (isNull(abridgedNode)) { ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION); } auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD); if (isNull(methodNode)) { ThrowMissing(WKTConstants::METHOD); } if (methodNode->GP()->childrenSize() == 0) { ThrowNotEnoughChildren(WKTConstants::METHOD); } auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS); const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children(); if (sourceCRSNodeChildren.size() != 1) { ThrowNotEnoughChildren(WKTConstants::SOURCECRS); } auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]); if (!sourceCRS) { throw ParsingException("Invalid content in SOURCECRS node"); } auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS); const auto &targetCRSNodeChildren = targetCRSNode->GP()->children(); if (targetCRSNodeChildren.size() != 1) { ThrowNotEnoughChildren(WKTConstants::TARGETCRS); } auto targetCRS = buildCRS(targetCRSNodeChildren[0]); if (!targetCRS) { throw ParsingException("Invalid content in TARGETCRS node"); } std::vector parameters; std::vector values; const auto &defaultLinearUnit = UnitOfMeasure::NONE; const auto &defaultAngularUnit = UnitOfMeasure::NONE; consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit, defaultAngularUnit); const auto nnSourceCRS = NN_NO_CHECK(sourceCRS); const auto nnTargetCRS = NN_NO_CHECK(targetCRS); const auto transformation = buildTransformationForBoundCRS( dbContext_, buildProperties(abridgedNode), buildProperties(methodNode), nnSourceCRS, nnTargetCRS, parameters, values); return BoundCRS::create(buildProperties(node, false, false), NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), transformation); } // --------------------------------------------------------------------------- TemporalCSNNPtr WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) { auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); auto temporalCS = nn_dynamic_pointer_cast(cs); if (!temporalCS) { ThrowNotExpectedCSType(TemporalCS::WKT2_2015_TYPE); } return NN_NO_CHECK(temporalCS); } // --------------------------------------------------------------------------- TemporalCRSNNPtr WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) { auto &datumNode = node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM); if (isNull(datumNode)) { throw ParsingException("Missing TDATUM / TIMEDATUM node"); } return TemporalCRS::create(buildProperties(node), buildTemporalDatum(datumNode), buildTemporalCS(node)); } // --------------------------------------------------------------------------- DerivedTemporalCRSNNPtr WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS); // given the constraints enforced on calling code path assert(!isNull(baseCRSNode)); auto &derivingConversionNode = nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); if (isNull(derivingConversionNode)) { ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); } return DerivedTemporalCRS::create( buildProperties(node), buildTemporalCRS(baseCRSNode), buildConversion(derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE), buildTemporalCS(node)); } // --------------------------------------------------------------------------- EngineeringCRSNNPtr WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM, WKTConstants::ENGINEERINGDATUM); if (isNull(datumNode)) { throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node"); } auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); return EngineeringCRS::create(buildProperties(node), buildEngineeringDatum(datumNode), cs); } // --------------------------------------------------------------------------- EngineeringCRSNNPtr WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) { auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM); auto cs = buildCS(null_node, node, UnitOfMeasure::NONE); auto datum = EngineeringDatum::create( !isNull(datumNode) ? buildProperties(datumNode) : // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL // has a tradition of emitting just LOCAL_CS["foo"] []() { PropertyMap map; map.set(IdentifiedObject::NAME_KEY, UNKNOWN_ENGINEERING_DATUM); return map; }()); return EngineeringCRS::create(buildProperties(node), datum, cs); } // --------------------------------------------------------------------------- DerivedEngineeringCRSNNPtr WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS); // given the constraints enforced on calling code path assert(!isNull(baseEngCRSNode)); auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode); auto &derivingConversionNode = nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); if (isNull(derivingConversionNode)) { ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); } auto derivingConversion = buildConversion( derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS, derivingConversion, cs); } // --------------------------------------------------------------------------- ParametricCSNNPtr WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) { auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE); auto parametricCS = nn_dynamic_pointer_cast(cs); if (!parametricCS) { ThrowNotExpectedCSType(ParametricCS::WKT2_TYPE); } return NN_NO_CHECK(parametricCS); } // --------------------------------------------------------------------------- ParametricCRSNNPtr WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) { auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM, WKTConstants::PARAMETRICDATUM); if (isNull(datumNode)) { throw ParsingException("Missing PDATUM / PARAMETRICDATUM node"); } return ParametricCRS::create(buildProperties(node), buildParametricDatum(datumNode), buildParametricCS(node)); } // --------------------------------------------------------------------------- DerivedParametricCRSNNPtr WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS); // given the constraints enforced on calling code path assert(!isNull(baseParamCRSNode)); auto &derivingConversionNode = nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); if (isNull(derivingConversionNode)) { ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); } return DerivedParametricCRS::create( buildProperties(node), buildParametricCRS(baseParamCRSNode), buildConversion(derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE), buildParametricCS(node)); } // --------------------------------------------------------------------------- DerivedProjectedCRSNNPtr WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS); if (isNull(baseProjCRSNode)) { ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS); } auto baseProjCRS = buildProjectedCRS(baseProjCRSNode); auto &conversionNode = nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION); if (isNull(conversionNode)) { ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION); } auto linearUnit = buildUnitInSubNode(node); const auto &angularUnit = baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit(); auto conversion = buildConversion(conversionNode, linearUnit, angularUnit); auto &csNode = nodeP->lookForChild(WKTConstants::CS_); if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) { ThrowMissing(WKTConstants::CS_); } auto cs = buildCS(csNode, node, UnitOfMeasure::NONE); if (cs->axisList().size() == 3 && baseProjCRS->coordinateSystem()->axisList().size() == 2) { baseProjCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast( baseProjCRS->promoteTo3D(std::string(), dbContext_))); } return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS, conversion, cs); } // --------------------------------------------------------------------------- CoordinateMetadataNNPtr WKTParser::Private::buildCoordinateMetadata(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); const auto &l_children = nodeP->children(); if (l_children.empty()) { ThrowNotEnoughChildren(WKTConstants::COORDINATEMETADATA); } auto crs = buildCRS(l_children[0]); if (!crs) { throw ParsingException("Invalid content in CRS node"); } auto &epochNode = nodeP->lookForChild(WKTConstants::EPOCH); if (!isNull(epochNode)) { const auto &epochChildren = epochNode->GP()->children(); if (epochChildren.empty()) { ThrowMissing(WKTConstants::EPOCH); } double coordinateEpoch; try { coordinateEpoch = asDouble(epochChildren[0]); } catch (const std::exception &) { throw ParsingException("Invalid EPOCH node"); } return CoordinateMetadata::create(NN_NO_CHECK(crs), coordinateEpoch, dbContext_); } return CoordinateMetadata::create(NN_NO_CHECK(crs)); } // --------------------------------------------------------------------------- static bool isGeodeticCRS(const std::string &name) { return ci_equal(name, WKTConstants::GEODCRS) || // WKT2 ci_equal(name, WKTConstants::GEODETICCRS) || // WKT2 ci_equal(name, WKTConstants::GEOGCRS) || // WKT2 2019 ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2019 ci_equal(name, WKTConstants::GEOGCS) || // WKT1 ci_equal(name, WKTConstants::GEOCCS); // WKT1 } // --------------------------------------------------------------------------- CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); const std::string &name(nodeP->value()); const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) { if (!toWGS84Parameters_.empty()) { auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_); toWGS84Parameters_.clear(); return util::nn_static_pointer_cast(ret); } else if (!datumPROJ4Grids_.empty()) { auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_); datumPROJ4Grids_.clear(); return util::nn_static_pointer_cast(ret); } return crs; }; if (isGeodeticCRS(name)) { if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS, WKTConstants::BASEGEODCRS))) { return util::nn_static_pointer_cast( applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node))); } else { return util::nn_static_pointer_cast( applyHorizontalBoundCRSParams(buildGeodeticCRS(node))); } } if (ci_equal(name, WKTConstants::PROJCS) || ci_equal(name, WKTConstants::PROJCRS) || ci_equal(name, WKTConstants::PROJECTEDCRS)) { // Get the EXTENSION "PROJ4" node before attempting to call // buildProjectedCRS() since formulations of WKT1_GDAL from GDAL 2.x // with the netCDF driver and the lack the required UNIT[] node std::string projString = getExtensionProj4(nodeP); if (!projString.empty() && (starts_with(projString, "+proj=ob_tran +o_proj=longlat") || starts_with(projString, "+proj=ob_tran +o_proj=lonlat") || starts_with(projString, "+proj=ob_tran +o_proj=latlong") || starts_with(projString, "+proj=ob_tran +o_proj=latlon"))) { // Those are not a projected CRS, but a DerivedGeographic one... if (projString.find(" +type=crs") == std::string::npos) { projString += " +type=crs"; } try { auto projObj = PROJStringParser().createFromPROJString(projString); auto crs = nn_dynamic_pointer_cast(projObj); if (crs) { return util::nn_static_pointer_cast( applyHorizontalBoundCRSParams(NN_NO_CHECK(crs))); } } catch (const io::ParsingException &) { } } return util::nn_static_pointer_cast( applyHorizontalBoundCRSParams(buildProjectedCRS(node))); } if (ci_equal(name, WKTConstants::VERT_CS) || ci_equal(name, WKTConstants::VERTCS) || ci_equal(name, WKTConstants::VERTCRS) || ci_equal(name, WKTConstants::VERTICALCRS)) { if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) { return util::nn_static_pointer_cast( buildDerivedVerticalCRS(node)); } else { return util::nn_static_pointer_cast(buildVerticalCRS(node)); } } if (ci_equal(name, WKTConstants::COMPD_CS) || ci_equal(name, WKTConstants::COMPOUNDCRS)) { return util::nn_static_pointer_cast(buildCompoundCRS(node)); } if (ci_equal(name, WKTConstants::BOUNDCRS)) { return util::nn_static_pointer_cast(buildBoundCRS(node)); } if (ci_equal(name, WKTConstants::TIMECRS)) { if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) { return util::nn_static_pointer_cast( buildDerivedTemporalCRS(node)); } else { return util::nn_static_pointer_cast(buildTemporalCRS(node)); } } if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) { return util::nn_static_pointer_cast( buildDerivedProjectedCRS(node)); } if (ci_equal(name, WKTConstants::ENGCRS) || ci_equal(name, WKTConstants::ENGINEERINGCRS)) { if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) { return util::nn_static_pointer_cast( buildDerivedEngineeringCRS(node)); } else { return util::nn_static_pointer_cast(buildEngineeringCRS(node)); } } if (ci_equal(name, WKTConstants::LOCAL_CS)) { return util::nn_static_pointer_cast( buildEngineeringCRSFromLocalCS(node)); } if (ci_equal(name, WKTConstants::PARAMETRICCRS)) { if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) { return util::nn_static_pointer_cast( buildDerivedParametricCRS(node)); } else { return util::nn_static_pointer_cast(buildParametricCRS(node)); } } return nullptr; } // --------------------------------------------------------------------------- BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) { const auto *nodeP = node->GP(); const std::string &name(nodeP->value()); auto crs = buildCRS(node); if (crs) { return util::nn_static_pointer_cast(NN_NO_CHECK(crs)); } // Datum handled by caller code WKTParser::createFromWKT() if (ci_equal(name, WKTConstants::ENSEMBLE)) { return util::nn_static_pointer_cast(buildDatumEnsemble( node, PrimeMeridian::GREENWICH, !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID)))); } if (ci_equal(name, WKTConstants::VDATUM) || ci_equal(name, WKTConstants::VERT_DATUM) || ci_equal(name, WKTConstants::VERTICALDATUM) || ci_equal(name, WKTConstants::VRF)) { return util::nn_static_pointer_cast( buildVerticalReferenceFrame(node, null_node)); } if (ci_equal(name, WKTConstants::TDATUM) || ci_equal(name, WKTConstants::TIMEDATUM)) { return util::nn_static_pointer_cast( buildTemporalDatum(node)); } if (ci_equal(name, WKTConstants::EDATUM) || ci_equal(name, WKTConstants::ENGINEERINGDATUM)) { return util::nn_static_pointer_cast( buildEngineeringDatum(node)); } if (ci_equal(name, WKTConstants::PDATUM) || ci_equal(name, WKTConstants::PARAMETRICDATUM)) { return util::nn_static_pointer_cast( buildParametricDatum(node)); } if (ci_equal(name, WKTConstants::ELLIPSOID) || ci_equal(name, WKTConstants::SPHEROID)) { return util::nn_static_pointer_cast(buildEllipsoid(node)); } if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) { auto transf = buildCoordinateOperation(node); const char *prefixes[] = { "PROJ-based operation method: ", "PROJ-based operation method (approximate): "}; for (const char *prefix : prefixes) { if (starts_with(transf->method()->nameStr(), prefix)) { auto projString = transf->method()->nameStr().substr(strlen(prefix)); return util::nn_static_pointer_cast( PROJBasedOperation::create( PropertyMap(), projString, transf->sourceCRS(), transf->targetCRS(), transf->coordinateOperationAccuracies())); } } return util::nn_static_pointer_cast(transf); } if (ci_equal(name, WKTConstants::CONVERSION)) { auto conv = buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE); if (starts_with(conv->method()->nameStr(), "PROJ-based operation method: ")) { auto projString = conv->method()->nameStr().substr( strlen("PROJ-based operation method: ")); return util::nn_static_pointer_cast( PROJBasedOperation::create(PropertyMap(), projString, nullptr, nullptr, {})); } return util::nn_static_pointer_cast(conv); } if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) { return util::nn_static_pointer_cast( buildConcatenatedOperation(node)); } if (ci_equal(name, WKTConstants::POINTMOTIONOPERATION)) { return util::nn_static_pointer_cast( buildPointMotionOperation(node)); } if (ci_equal(name, WKTConstants::ID) || ci_equal(name, WKTConstants::AUTHORITY)) { return util::nn_static_pointer_cast( NN_NO_CHECK(buildId(node, node, false, false))); } if (ci_equal(name, WKTConstants::COORDINATEMETADATA)) { return util::nn_static_pointer_cast( buildCoordinateMetadata(node)); } throw ParsingException(concat("unhandled keyword: ", name)); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress class JSONParser { DatabaseContextPtr dbContext_{}; std::string deformationModelName_{}; static std::string getString(const json &j, const char *key); static json getObject(const json &j, const char *key); static json getArray(const json &j, const char *key); static int getInteger(const json &j, const char *key); static double getNumber(const json &j, const char *key); static UnitOfMeasure getUnit(const json &j, const char *key); static std::string getName(const json &j); static std::string getType(const json &j); static Length getLength(const json &j, const char *key); static Measure getMeasure(const json &j); IdentifierNNPtr buildId(const json &parentJ, const json &j, bool removeInverseOf); static ObjectDomainPtr buildObjectDomain(const json &j); PropertyMap buildProperties(const json &j, bool removeInverseOf = false, bool nameRequired = true); GeographicCRSNNPtr buildGeographicCRS(const json &j); GeodeticCRSNNPtr buildGeodeticCRS(const json &j); ProjectedCRSNNPtr buildProjectedCRS(const json &j); ConversionNNPtr buildConversion(const json &j); DatumEnsembleNNPtr buildDatumEnsemble(const json &j); GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j); VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j); DynamicGeodeticReferenceFrameNNPtr buildDynamicGeodeticReferenceFrame(const json &j); DynamicVerticalReferenceFrameNNPtr buildDynamicVerticalReferenceFrame(const json &j); EllipsoidNNPtr buildEllipsoid(const json &j); PrimeMeridianNNPtr buildPrimeMeridian(const json &j); CoordinateSystemNNPtr buildCS(const json &j); MeridianNNPtr buildMeridian(const json &j); CoordinateSystemAxisNNPtr buildAxis(const json &j); VerticalCRSNNPtr buildVerticalCRS(const json &j); CRSNNPtr buildCRS(const json &j); CompoundCRSNNPtr buildCompoundCRS(const json &j); BoundCRSNNPtr buildBoundCRS(const json &j); TransformationNNPtr buildTransformation(const json &j); PointMotionOperationNNPtr buildPointMotionOperation(const json &j); ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j); CoordinateMetadataNNPtr buildCoordinateMetadata(const json &j); void buildGeodeticDatumOrDatumEnsemble(const json &j, GeodeticReferenceFramePtr &datum, DatumEnsemblePtr &datumEnsemble); static util::optional getAnchor(const json &j) { util::optional anchor; if (j.contains("anchor")) { anchor = getString(j, "anchor"); } return anchor; } static util::optional getAnchorEpoch(const json &j) { if (j.contains("anchor_epoch")) { return util::optional(common::Measure( getNumber(j, "anchor_epoch"), common::UnitOfMeasure::YEAR)); } return util::optional(); } EngineeringDatumNNPtr buildEngineeringDatum(const json &j) { return EngineeringDatum::create(buildProperties(j), getAnchor(j)); } ParametricDatumNNPtr buildParametricDatum(const json &j) { return ParametricDatum::create(buildProperties(j), getAnchor(j)); } TemporalDatumNNPtr buildTemporalDatum(const json &j) { auto calendar = getString(j, "calendar"); auto origin = DateTime::create(j.contains("time_origin") ? getString(j, "time_origin") : std::string()); return TemporalDatum::create(buildProperties(j), origin, calendar); } template util::nn> buildCRS(const json &j, DatumBuilderType f) { auto datum = (this->*f)(getObject(j, "datum")); auto cs = buildCS(getObject(j, "coordinate_system")); auto csCast = util::nn_dynamic_pointer_cast(cs); if (!csCast) { throw ParsingException("coordinate_system not of expected type"); } return TargetCRS::create(buildProperties(j), datum, NN_NO_CHECK(csCast)); } template util::nn> buildDerivedCRS(const json &j) { auto baseCRSObj = create(getObject(j, "base_crs")); auto baseCRS = util::nn_dynamic_pointer_cast(baseCRSObj); if (!baseCRS) { throw ParsingException("base_crs not of expected type"); } auto cs = buildCS(getObject(j, "coordinate_system")); auto csCast = util::nn_dynamic_pointer_cast(cs); if (!csCast) { throw ParsingException("coordinate_system not of expected type"); } auto conv = buildConversion(getObject(j, "conversion")); return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv, NN_NO_CHECK(csCast)); } public: JSONParser() = default; JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) { dbContext_ = dbContext; return *this; } BaseObjectNNPtr create(const json &j); }; // --------------------------------------------------------------------------- std::string JSONParser::getString(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (!v.is_string()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a string"); } return v.get(); } // --------------------------------------------------------------------------- json JSONParser::getObject(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (!v.is_object()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a object"); } return v.get(); } // --------------------------------------------------------------------------- json JSONParser::getArray(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (!v.is_array()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a array"); } return v.get(); } // --------------------------------------------------------------------------- int JSONParser::getInteger(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (!v.is_number()) { throw ParsingException(std::string("The value of \"") + key + "\" should be an integer"); } const double dbl = v.get(); if (!(dbl >= std::numeric_limits::min() && dbl <= std::numeric_limits::max() && static_cast(dbl) == dbl)) { throw ParsingException(std::string("The value of \"") + key + "\" should be an integer"); } return static_cast(dbl); } // --------------------------------------------------------------------------- double JSONParser::getNumber(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (!v.is_number()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a number"); } return v.get(); } // --------------------------------------------------------------------------- UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (v.is_string()) { auto vStr = v.get(); for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE, UnitOfMeasure::SCALE_UNITY}) { if (vStr == unit.name()) return unit; } throw ParsingException("Unknown unit name: " + vStr); } if (!v.is_object()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a string or an object"); } auto typeStr = getType(v); UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN; if (typeStr == "LinearUnit") { type = UnitOfMeasure::Type::LINEAR; } else if (typeStr == "AngularUnit") { type = UnitOfMeasure::Type::ANGULAR; } else if (typeStr == "ScaleUnit") { type = UnitOfMeasure::Type::SCALE; } else if (typeStr == "TimeUnit") { type = UnitOfMeasure::Type::TIME; } else if (typeStr == "ParametricUnit") { type = UnitOfMeasure::Type::PARAMETRIC; } else if (typeStr == "Unit") { type = UnitOfMeasure::Type::UNKNOWN; } else { throw ParsingException("Unsupported value of \"type\""); } auto nameStr = getName(v); auto convFactor = getNumber(v, "conversion_factor"); std::string authorityStr; std::string codeStr; if (v.contains("authority") && v.contains("code")) { authorityStr = getString(v, "authority"); auto code = v["code"]; if (code.is_string()) { codeStr = code.get(); } else if (code.is_number_integer()) { codeStr = internal::toString(code.get()); } else { throw ParsingException("Unexpected type for value of \"code\""); } } return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr); } // --------------------------------------------------------------------------- std::string JSONParser::getName(const json &j) { return getString(j, "name"); } // --------------------------------------------------------------------------- std::string JSONParser::getType(const json &j) { return getString(j, "type"); } // --------------------------------------------------------------------------- Length JSONParser::getLength(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } auto v = j[key]; if (v.is_number()) { return Length(v.get(), UnitOfMeasure::METRE); } if (v.is_object()) { return Length(getMeasure(v)); } throw ParsingException(std::string("The value of \"") + key + "\" should be a number or an object"); } // --------------------------------------------------------------------------- Measure JSONParser::getMeasure(const json &j) { return Measure(getNumber(j, "value"), getUnit(j, "unit")); } // --------------------------------------------------------------------------- ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) { optional scope; if (j.contains("scope")) { scope = getString(j, "scope"); } std::string area; if (j.contains("area")) { area = getString(j, "area"); } std::vector geogExtent; if (j.contains("bbox")) { auto bbox = getObject(j, "bbox"); double south = getNumber(bbox, "south_latitude"); double west = getNumber(bbox, "west_longitude"); double north = getNumber(bbox, "north_latitude"); double east = getNumber(bbox, "east_longitude"); try { geogExtent.emplace_back( GeographicBoundingBox::create(west, south, east, north)); } catch (const std::exception &e) { throw ParsingException( std::string("Invalid bbox node: ").append(e.what())); } } std::vector verticalExtent; if (j.contains("vertical_extent")) { const auto vertical_extent = getObject(j, "vertical_extent"); const auto min = getNumber(vertical_extent, "minimum"); const auto max = getNumber(vertical_extent, "maximum"); const auto unit = vertical_extent.contains("unit") ? getUnit(vertical_extent, "unit") : UnitOfMeasure::METRE; verticalExtent.emplace_back(VerticalExtent::create( min, max, util::nn_make_shared(unit))); } std::vector temporalExtent; if (j.contains("temporal_extent")) { const auto temporal_extent = getObject(j, "temporal_extent"); const auto start = getString(temporal_extent, "start"); const auto end = getString(temporal_extent, "end"); temporalExtent.emplace_back(TemporalExtent::create(start, end)); } if (scope.has_value() || !area.empty() || !geogExtent.empty() || !verticalExtent.empty() || !temporalExtent.empty()) { util::optional description; if (!area.empty()) description = area; ExtentPtr extent; if (description.has_value() || !geogExtent.empty() || !verticalExtent.empty() || !temporalExtent.empty()) { extent = Extent::create(description, geogExtent, verticalExtent, temporalExtent) .as_nullable(); } return ObjectDomain::create(scope, extent).as_nullable(); } return nullptr; } // --------------------------------------------------------------------------- IdentifierNNPtr JSONParser::buildId(const json &parentJ, const json &j, bool removeInverseOf) { PropertyMap propertiesId; auto codeSpace(getString(j, "authority")); if (removeInverseOf && starts_with(codeSpace, "INVERSE(") && codeSpace.back() == ')') { codeSpace = codeSpace.substr(strlen("INVERSE(")); codeSpace.resize(codeSpace.size() - 1); } std::string version; if (j.contains("version")) { auto versionJ = j["version"]; if (versionJ.is_string()) { version = versionJ.get(); } else if (versionJ.is_number()) { const double dblVersion = versionJ.get(); if (dblVersion >= std::numeric_limits::min() && dblVersion <= std::numeric_limits::max() && static_cast(dblVersion) == dblVersion) { version = internal::toString(static_cast(dblVersion)); } else { version = internal::toString(dblVersion, /*precision=*/15); } } else { throw ParsingException("Unexpected type for value of \"version\""); } } // IAU + 2015 -> IAU_2015 if (dbContext_ && !version.empty()) { std::string codeSpaceOut; if (dbContext_->getVersionedAuthority(codeSpace, version, codeSpaceOut)) { codeSpace = std::move(codeSpaceOut); version.clear(); } } propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace); propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace); if (!j.contains("code")) { throw ParsingException("Missing \"code\" key"); } std::string code; auto codeJ = j["code"]; if (codeJ.is_string()) { code = codeJ.get(); } else if (codeJ.is_number_integer()) { code = internal::toString(codeJ.get()); } else { throw ParsingException("Unexpected type for value of \"code\""); } // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone // south, we generated a wrong value. Auto-fix that if (parentJ.contains("type") && getType(parentJ) == "Conversion" && codeSpace == Identifier::EPSG && parentJ.contains("name")) { const auto parentNodeName(getName(parentJ)); if (ci_starts_with(parentNodeName, "UTM Zone ") && parentNodeName.find('S') != std::string::npos) { const int nZone = atoi(parentNodeName.c_str() + strlen("UTM Zone ")); if (nZone >= 1 && nZone <= 60) { code = internal::toString(16100 + nZone); } } } if (!version.empty()) { propertiesId.set(Identifier::VERSION_KEY, version); } if (j.contains("authority_citation")) { propertiesId.set(Identifier::AUTHORITY_KEY, getString(j, "authority_citation")); } if (j.contains("uri")) { propertiesId.set(Identifier::URI_KEY, getString(j, "uri")); } return Identifier::create(code, propertiesId); } // --------------------------------------------------------------------------- PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf, bool nameRequired) { PropertyMap map; if (j.contains("name") || nameRequired) { std::string name(getName(j)); if (removeInverseOf && starts_with(name, "Inverse of ")) { name = name.substr(strlen("Inverse of ")); } map.set(IdentifiedObject::NAME_KEY, name); } if (j.contains("ids")) { auto idsJ = getArray(j, "ids"); auto identifiers = ArrayOfBaseObject::create(); for (const auto &idJ : idsJ) { if (!idJ.is_object()) { throw ParsingException( "Unexpected type for value of \"ids\" child"); } identifiers->add(buildId(j, idJ, removeInverseOf)); } map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); } else if (j.contains("id")) { auto idJ = getObject(j, "id"); auto identifiers = ArrayOfBaseObject::create(); identifiers->add(buildId(j, idJ, removeInverseOf)); map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); } if (j.contains("remarks")) { map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks")); } if (j.contains("usages")) { ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); auto usages = j["usages"]; if (!usages.is_array()) { throw ParsingException("Unexpected type for value of \"usages\""); } for (const auto &usage : usages) { if (!usage.is_object()) { throw ParsingException( "Unexpected type for value of \"usages\" child"); } auto objectDomain = buildObjectDomain(usage); if (!objectDomain) { throw ParsingException("missing children in \"usages\" child"); } array->add(NN_NO_CHECK(objectDomain)); } if (!array->empty()) { map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array); } } else { auto objectDomain = buildObjectDomain(j); if (objectDomain) { map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain)); } } return map; } // --------------------------------------------------------------------------- BaseObjectNNPtr JSONParser::create(const json &j) { if (!j.is_object()) { throw ParsingException("JSON object expected"); } auto type = getString(j, "type"); if (type == "GeographicCRS") { return buildGeographicCRS(j); } if (type == "GeodeticCRS") { return buildGeodeticCRS(j); } if (type == "ProjectedCRS") { return buildProjectedCRS(j); } if (type == "VerticalCRS") { return buildVerticalCRS(j); } if (type == "CompoundCRS") { return buildCompoundCRS(j); } if (type == "BoundCRS") { return buildBoundCRS(j); } if (type == "EngineeringCRS") { return buildCRS(j, &JSONParser::buildEngineeringDatum); } if (type == "ParametricCRS") { return buildCRS(j, &JSONParser::buildParametricDatum); } if (type == "TemporalCRS") { return buildCRS(j, &JSONParser::buildTemporalDatum); } if (type == "DerivedGeodeticCRS") { auto baseCRSObj = create(getObject(j, "base_crs")); auto baseCRS = util::nn_dynamic_pointer_cast(baseCRSObj); if (!baseCRS) { throw ParsingException("base_crs not of expected type"); } auto cs = buildCS(getObject(j, "coordinate_system")); auto conv = buildConversion(getObject(j, "conversion")); auto csCartesian = util::nn_dynamic_pointer_cast(cs); if (csCartesian) return DerivedGeodeticCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv, NN_NO_CHECK(csCartesian)); auto csSpherical = util::nn_dynamic_pointer_cast(cs); if (csSpherical) return DerivedGeodeticCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv, NN_NO_CHECK(csSpherical)); throw ParsingException("coordinate_system not of expected type"); } if (type == "DerivedGeographicCRS") { return buildDerivedCRS(j); } if (type == "DerivedProjectedCRS") { return buildDerivedCRS(j); } if (type == "DerivedVerticalCRS") { return buildDerivedCRS(j); } if (type == "DerivedEngineeringCRS") { return buildDerivedCRS(j); } if (type == "DerivedParametricCRS") { return buildDerivedCRS(j); } if (type == "DerivedTemporalCRS") { return buildDerivedCRS(j); } if (type == "DatumEnsemble") { return buildDatumEnsemble(j); } if (type == "GeodeticReferenceFrame") { return buildGeodeticReferenceFrame(j); } if (type == "VerticalReferenceFrame") { return buildVerticalReferenceFrame(j); } if (type == "DynamicGeodeticReferenceFrame") { return buildDynamicGeodeticReferenceFrame(j); } if (type == "DynamicVerticalReferenceFrame") { return buildDynamicVerticalReferenceFrame(j); } if (type == "EngineeringDatum") { return buildEngineeringDatum(j); } if (type == "ParametricDatum") { return buildParametricDatum(j); } if (type == "TemporalDatum") { return buildTemporalDatum(j); } if (type == "Ellipsoid") { return buildEllipsoid(j); } if (type == "PrimeMeridian") { return buildPrimeMeridian(j); } if (type == "CoordinateSystem") { return buildCS(j); } if (type == "Conversion") { return buildConversion(j); } if (type == "Transformation") { return buildTransformation(j); } if (type == "PointMotionOperation") { return buildPointMotionOperation(j); } if (type == "ConcatenatedOperation") { return buildConcatenatedOperation(j); } if (type == "CoordinateMetadata") { return buildCoordinateMetadata(j); } if (type == "Axis") { return buildAxis(j); } throw ParsingException("Unsupported value of \"type\""); } // --------------------------------------------------------------------------- void JSONParser::buildGeodeticDatumOrDatumEnsemble( const json &j, GeodeticReferenceFramePtr &datum, DatumEnsemblePtr &datumEnsemble) { if (j.contains("datum")) { auto datumJ = getObject(j, "datum"); if (j.contains("deformation_models")) { auto deformationModelsJ = getArray(j, "deformation_models"); if (!deformationModelsJ.empty()) { const auto &deformationModelJ = deformationModelsJ[0]; deformationModelName_ = getString(deformationModelJ, "name"); // We can handle only one for now } } datum = util::nn_dynamic_pointer_cast( create(datumJ)); if (!datum) { throw ParsingException("datum of wrong type"); } deformationModelName_.clear(); } else { datumEnsemble = buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); } } // --------------------------------------------------------------------------- GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { GeodeticReferenceFramePtr datum; DatumEnsemblePtr datumEnsemble; buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble); auto csJ = getObject(j, "coordinate_system"); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); if (!ellipsoidalCS) { throw ParsingException("expected an ellipsoidal CS"); } return GeographicCRS::create(buildProperties(j), datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); } // --------------------------------------------------------------------------- GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) { GeodeticReferenceFramePtr datum; DatumEnsemblePtr datumEnsemble; buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble); auto csJ = getObject(j, "coordinate_system"); auto cs = buildCS(csJ); auto props = buildProperties(j); auto cartesianCS = nn_dynamic_pointer_cast(cs); if (cartesianCS) { if (cartesianCS->axisList().size() != 3) { throw ParsingException( "Cartesian CS for a GeodeticCRS should have 3 axis"); } try { return GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(cartesianCS)); } catch (const util::Exception &e) { throw ParsingException(std::string("buildGeodeticCRS: ") + e.what()); } } auto sphericalCS = nn_dynamic_pointer_cast(cs); if (sphericalCS) { try { return GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(sphericalCS)); } catch (const util::Exception &e) { throw ParsingException(std::string("buildGeodeticCRS: ") + e.what()); } } throw ParsingException("expected a Cartesian or spherical CS"); } // --------------------------------------------------------------------------- ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) { auto jBaseCRS = getObject(j, "base_crs"); auto jBaseCS = getObject(jBaseCRS, "coordinate_system"); auto baseCS = buildCS(jBaseCS); auto baseCRS = dynamic_cast(baseCS.get()) != nullptr ? util::nn_static_pointer_cast( buildGeographicCRS(jBaseCRS)) : buildGeodeticCRS(jBaseCRS); auto csJ = getObject(j, "coordinate_system"); auto cartesianCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); if (!cartesianCS) { throw ParsingException("expected a Cartesian CS"); } auto conv = buildConversion(getObject(j, "conversion")); return ProjectedCRS::create(buildProperties(j), baseCRS, conv, NN_NO_CHECK(cartesianCS)); } // --------------------------------------------------------------------------- VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) { VerticalReferenceFramePtr datum; DatumEnsemblePtr datumEnsemble; if (j.contains("datum")) { auto datumJ = getObject(j, "datum"); if (j.contains("deformation_models")) { auto deformationModelsJ = getArray(j, "deformation_models"); if (!deformationModelsJ.empty()) { const auto &deformationModelJ = deformationModelsJ[0]; deformationModelName_ = getString(deformationModelJ, "name"); // We can handle only one for now } } datum = util::nn_dynamic_pointer_cast( create(datumJ)); if (!datum) { throw ParsingException("datum of wrong type"); } } else { datumEnsemble = buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); } auto csJ = getObject(j, "coordinate_system"); auto verticalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); if (!verticalCS) { throw ParsingException("expected a vertical CS"); } const auto buildGeoidModel = [this, &datum, &datumEnsemble, &verticalCS](const json &geoidModelJ) { auto propsModel = buildProperties(geoidModelJ); const auto dummyCRS = VerticalCRS::create( PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS)); CRSPtr interpolationCRS; if (geoidModelJ.contains("interpolation_crs")) { auto interpolationCRSJ = getObject(geoidModelJ, "interpolation_crs"); interpolationCRS = buildCRS(interpolationCRSJ).as_nullable(); } return Transformation::create( propsModel, dummyCRS, GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored, interpolationCRS, OperationMethod::create(PropertyMap(), std::vector()), {}, {}); }; auto props = buildProperties(j); if (j.contains("geoid_model")) { auto geoidModelJ = getObject(j, "geoid_model"); props.set("GEOID_MODEL", buildGeoidModel(geoidModelJ)); } else if (j.contains("geoid_models")) { auto geoidModelsJ = getArray(j, "geoid_models"); auto geoidModels = ArrayOfBaseObject::create(); for (const auto &geoidModelJ : geoidModelsJ) { geoidModels->add(buildGeoidModel(geoidModelJ)); } props.set("GEOID_MODEL", geoidModels); } return VerticalCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(verticalCS)); } // --------------------------------------------------------------------------- CRSNNPtr JSONParser::buildCRS(const json &j) { auto crs = util::nn_dynamic_pointer_cast(create(j)); if (crs) { return NN_NO_CHECK(crs); } throw ParsingException("Object is not a CRS"); } // --------------------------------------------------------------------------- CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) { auto componentsJ = getArray(j, "components"); std::vector components; for (const auto &componentJ : componentsJ) { if (!componentJ.is_object()) { throw ParsingException( "Unexpected type for a \"components\" child"); } components.push_back(buildCRS(componentJ)); } return CompoundCRS::create(buildProperties(j), components); } // --------------------------------------------------------------------------- ConversionNNPtr JSONParser::buildConversion(const json &j) { auto methodJ = getObject(j, "method"); auto convProps = buildProperties(j); auto methodProps = buildProperties(methodJ); if (!j.contains("parameters")) { return Conversion::create(convProps, methodProps, {}, {}); } auto parametersJ = getArray(j, "parameters"); std::vector parameters; std::vector values; for (const auto ¶m : parametersJ) { if (!param.is_object()) { throw ParsingException( "Unexpected type for a \"parameters\" child"); } parameters.emplace_back( OperationParameter::create(buildProperties(param))); if (isIntegerParameter(parameters.back())) { values.emplace_back( ParameterValue::create(getInteger(param, "value"))); } else { values.emplace_back(ParameterValue::create(getMeasure(param))); } } auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter( dbContext_, parameters, values); std::string convName; std::string methodName; if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) && methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) && starts_with(convName, "Inverse of ") && starts_with(methodName, "Inverse of ")) { auto invConvProps = buildProperties(j, true); auto invMethodProps = buildProperties(methodJ, true); auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast( Conversion::create(invConvProps, invMethodProps, parameters, values) ->inverse())); if (interpolationCRS) conv->setInterpolationCRS(interpolationCRS); return conv; } auto conv = Conversion::create(convProps, methodProps, parameters, values); if (interpolationCRS) conv->setInterpolationCRS(interpolationCRS); return conv; } // --------------------------------------------------------------------------- BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) { auto sourceCRS = buildCRS(getObject(j, "source_crs")); auto targetCRS = buildCRS(getObject(j, "target_crs")); auto transformationJ = getObject(j, "transformation"); auto methodJ = getObject(transformationJ, "method"); auto parametersJ = getArray(transformationJ, "parameters"); std::vector parameters; std::vector values; for (const auto ¶m : parametersJ) { if (!param.is_object()) { throw ParsingException( "Unexpected type for a \"parameters\" child"); } parameters.emplace_back( OperationParameter::create(buildProperties(param))); if (param.contains("value")) { auto v = param["value"]; if (v.is_string()) { values.emplace_back( ParameterValue::createFilename(v.get())); continue; } } values.emplace_back(ParameterValue::create(getMeasure(param))); } const auto transformation = [&]() { // Unofficial extension / mostly for testing purposes. // Allow to explicitly specify the source_crs of the transformation of // the boundCRS if it is not the source_crs of the BoundCRS. Cf // https://github.com/OSGeo/PROJ/issues/3428 use case if (transformationJ.contains("source_crs")) { auto sourceTransformationCRS = buildCRS(getObject(transformationJ, "source_crs")); auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter( dbContext_, parameters, values); return Transformation::create( buildProperties(transformationJ), sourceTransformationCRS, targetCRS, interpolationCRS, buildProperties(methodJ), parameters, values, std::vector()); } return buildTransformationForBoundCRS( dbContext_, buildProperties(transformationJ), buildProperties(methodJ), sourceCRS, targetCRS, parameters, values); }(); return BoundCRS::create(buildProperties(j, /* removeInverseOf= */ false, /* nameRequired=*/false), sourceCRS, targetCRS, transformation); } // --------------------------------------------------------------------------- TransformationNNPtr JSONParser::buildTransformation(const json &j) { auto sourceCRS = buildCRS(getObject(j, "source_crs")); auto targetCRS = buildCRS(getObject(j, "target_crs")); auto methodJ = getObject(j, "method"); auto parametersJ = getArray(j, "parameters"); std::vector parameters; std::vector values; for (const auto ¶m : parametersJ) { if (!param.is_object()) { throw ParsingException( "Unexpected type for a \"parameters\" child"); } parameters.emplace_back( OperationParameter::create(buildProperties(param))); if (param.contains("value")) { auto v = param["value"]; if (v.is_string()) { values.emplace_back( ParameterValue::createFilename(v.get())); continue; } } values.emplace_back(ParameterValue::create(getMeasure(param))); } CRSPtr interpolationCRS; if (j.contains("interpolation_crs")) { interpolationCRS = buildCRS(getObject(j, "interpolation_crs")).as_nullable(); } std::vector accuracies; if (j.contains("accuracy")) { accuracies.push_back( PositionalAccuracy::create(getString(j, "accuracy"))); } return Transformation::create(buildProperties(j), sourceCRS, targetCRS, interpolationCRS, buildProperties(methodJ), parameters, values, accuracies); } // --------------------------------------------------------------------------- PointMotionOperationNNPtr JSONParser::buildPointMotionOperation(const json &j) { auto sourceCRS = buildCRS(getObject(j, "source_crs")); auto methodJ = getObject(j, "method"); auto parametersJ = getArray(j, "parameters"); std::vector parameters; std::vector values; for (const auto ¶m : parametersJ) { if (!param.is_object()) { throw ParsingException( "Unexpected type for a \"parameters\" child"); } parameters.emplace_back( OperationParameter::create(buildProperties(param))); if (param.contains("value")) { auto v = param["value"]; if (v.is_string()) { values.emplace_back( ParameterValue::createFilename(v.get())); continue; } } values.emplace_back(ParameterValue::create(getMeasure(param))); } std::vector accuracies; if (j.contains("accuracy")) { accuracies.push_back( PositionalAccuracy::create(getString(j, "accuracy"))); } return PointMotionOperation::create(buildProperties(j), sourceCRS, buildProperties(methodJ), parameters, values, accuracies); } // --------------------------------------------------------------------------- ConcatenatedOperationNNPtr JSONParser::buildConcatenatedOperation(const json &j) { auto sourceCRS = buildCRS(getObject(j, "source_crs")); auto targetCRS = buildCRS(getObject(j, "target_crs")); auto stepsJ = getArray(j, "steps"); std::vector operations; for (const auto &stepJ : stepsJ) { if (!stepJ.is_object()) { throw ParsingException("Unexpected type for a \"steps\" child"); } auto op = nn_dynamic_pointer_cast(create(stepJ)); if (!op) { throw ParsingException("Invalid content in a \"steps\" child"); } operations.emplace_back(NN_NO_CHECK(op)); } ConcatenatedOperation::fixSteps(sourceCRS, targetCRS, operations, dbContext_, /* fixDirectionAllowed = */ true); std::vector accuracies; if (j.contains("accuracy")) { accuracies.push_back( PositionalAccuracy::create(getString(j, "accuracy"))); } try { return ConcatenatedOperation::create(buildProperties(j), operations, accuracies); } catch (const InvalidOperation &e) { throw ParsingException( std::string("Cannot build concatenated operation: ") + e.what()); } } // --------------------------------------------------------------------------- CoordinateMetadataNNPtr JSONParser::buildCoordinateMetadata(const json &j) { auto crs = buildCRS(getObject(j, "crs")); if (j.contains("coordinateEpoch")) { auto jCoordinateEpoch = j["coordinateEpoch"]; if (jCoordinateEpoch.is_number()) { return CoordinateMetadata::create( crs, jCoordinateEpoch.get(), dbContext_); } throw ParsingException( "Unexpected type for value of \"coordinateEpoch\""); } return CoordinateMetadata::create(crs); } // --------------------------------------------------------------------------- MeridianNNPtr JSONParser::buildMeridian(const json &j) { if (!j.contains("longitude")) { throw ParsingException("Missing \"longitude\" key"); } auto longitude = j["longitude"]; if (longitude.is_number()) { return Meridian::create( Angle(longitude.get(), UnitOfMeasure::DEGREE)); } else if (longitude.is_object()) { return Meridian::create(Angle(getMeasure(longitude))); } throw ParsingException("Unexpected type for value of \"longitude\""); } // --------------------------------------------------------------------------- CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) { auto dirString = getString(j, "direction"); auto abbreviation = getString(j, "abbreviation"); const UnitOfMeasure unit( j.contains("unit") ? getUnit(j, "unit") : UnitOfMeasure(std::string(), 1.0, UnitOfMeasure::Type::NONE)); auto direction = AxisDirection::valueOf(dirString); if (!direction) { throw ParsingException(concat("unhandled axis direction: ", dirString)); } auto meridian = j.contains("meridian") ? buildMeridian(getObject(j, "meridian")).as_nullable() : nullptr; util::optional minVal; if (j.contains("minimum_value")) { minVal = getNumber(j, "minimum_value"); } util::optional maxVal; if (j.contains("maximum_value")) { maxVal = getNumber(j, "maximum_value"); } util::optional rangeMeaning; if (j.contains("range_meaning")) { const auto val = getString(j, "range_meaning"); const RangeMeaning *meaning = RangeMeaning::valueOf(val); if (meaning == nullptr) { throw ParsingException( concat("buildAxis: invalid range_meaning value: ", val)); } rangeMeaning = util::optional(*meaning); } return CoordinateSystemAxis::create(buildProperties(j), abbreviation, *direction, unit, minVal, maxVal, rangeMeaning, meridian); } // --------------------------------------------------------------------------- CoordinateSystemNNPtr JSONParser::buildCS(const json &j) { auto subtype = getString(j, "subtype"); if (!j.contains("axis")) { throw ParsingException("Missing \"axis\" key"); } auto jAxisList = j["axis"]; if (!jAxisList.is_array()) { throw ParsingException("Unexpected type for value of \"axis\""); } std::vector axisList; for (const auto &axis : jAxisList) { if (!axis.is_object()) { throw ParsingException( "Unexpected type for value of a \"axis\" member"); } axisList.emplace_back(buildAxis(axis)); } const PropertyMap &csMap = emptyPropertyMap; const auto axisCount = axisList.size(); if (subtype == EllipsoidalCS::WKT2_TYPE) { if (axisCount == 2) { return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); } if (axisCount == 3) { return EllipsoidalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } throw ParsingException("Expected 2 or 3 axis"); } if (subtype == CartesianCS::WKT2_TYPE) { if (axisCount == 2) { return CartesianCS::create(csMap, axisList[0], axisList[1]); } if (axisCount == 3) { return CartesianCS::create(csMap, axisList[0], axisList[1], axisList[2]); } throw ParsingException("Expected 2 or 3 axis"); } if (subtype == AffineCS::WKT2_TYPE) { if (axisCount == 2) { return AffineCS::create(csMap, axisList[0], axisList[1]); } if (axisCount == 3) { return AffineCS::create(csMap, axisList[0], axisList[1], axisList[2]); } throw ParsingException("Expected 2 or 3 axis"); } if (subtype == VerticalCS::WKT2_TYPE) { if (axisCount == 1) { return VerticalCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == SphericalCS::WKT2_TYPE) { if (axisCount == 2) { // Extension to ISO19111 to support (planet)-ocentric CS with // geocentric latitude return SphericalCS::create(csMap, axisList[0], axisList[1]); } else if (axisCount == 3) { return SphericalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } throw ParsingException("Expected 2 or 3 axis"); } if (subtype == OrdinalCS::WKT2_TYPE) { return OrdinalCS::create(csMap, axisList); } if (subtype == ParametricCS::WKT2_TYPE) { if (axisCount == 1) { return ParametricCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == DateTimeTemporalCS::WKT2_2019_TYPE) { if (axisCount == 1) { return DateTimeTemporalCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == TemporalCountCS::WKT2_2019_TYPE) { if (axisCount == 1) { return TemporalCountCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == TemporalMeasureCS::WKT2_2019_TYPE) { if (axisCount == 1) { return TemporalMeasureCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } throw ParsingException("Unhandled value for subtype"); } // --------------------------------------------------------------------------- DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) { std::vector datums; if (j.contains("members")) { auto membersJ = getArray(j, "members"); const bool hasEllipsoid(j.contains("ellipsoid")); for (const auto &memberJ : membersJ) { if (!memberJ.is_object()) { throw ParsingException( "Unexpected type for value of a \"members\" member"); } auto datumName(getName(memberJ)); bool datumAdded = false; if (dbContext_ && memberJ.contains("id")) { auto id = getObject(memberJ, "id"); auto authority = getString(id, "authority"); auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), authority); auto code = id["code"]; std::string codeStr; if (code.is_string()) { codeStr = code.get(); } else if (code.is_number_integer()) { codeStr = internal::toString(code.get()); } else { throw ParsingException( "Unexpected type for value of \"code\""); } try { datums.push_back(authFactory->createDatum(codeStr)); datumAdded = true; } catch (const std::exception &) { // Silently ignore, as this isn't necessary an error. // If an older PROJ version parses a DatumEnsemble object of // a more recent PROJ version where the datum ensemble got // a new member, it might be unknown from the older PROJ. } } if (dbContext_ && !datumAdded) { auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext_), std::string()); auto list = authFactory->createObjectsFromName( datumName, {AuthorityFactory::ObjectType::DATUM}, false /* approximate=false*/); if (!list.empty()) { auto datum = util::nn_dynamic_pointer_cast(list.front()); if (!datum) throw ParsingException( "DatumEnsemble member is not a datum"); datums.push_back(NN_NO_CHECK(datum)); datumAdded = true; } } if (!datumAdded) { // Fallback if no db match if (hasEllipsoid) { datums.emplace_back(GeodeticReferenceFrame::create( buildProperties(memberJ), buildEllipsoid(getObject(j, "ellipsoid")), optional(), PrimeMeridian::GREENWICH)); } else { datums.emplace_back(VerticalReferenceFrame::create( buildProperties(memberJ))); } } } } else { auto name = getString(j, "name"); if (dbContext_) { auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string()); auto res = authFactory->createObjectsFromName( name, {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, true, 1); if (res.size() == 1) { auto datumEnsemble = dynamic_cast(res.front().get()); if (datumEnsemble) { datums = datumEnsemble->datums(); } } else { throw ParsingException( "No entry for datum ensemble '" + name + "' in database, and no explicit member specified"); } } else { throw ParsingException("Datum ensemble '" + name + "' has no explicit member specified and no " "connection to database"); } } return DatumEnsemble::create( buildProperties(j), datums, PositionalAccuracy::create(getString(j, "accuracy"))); } // --------------------------------------------------------------------------- GeodeticReferenceFrameNNPtr JSONParser::buildGeodeticReferenceFrame(const json &j) { auto ellipsoidJ = getObject(j, "ellipsoid"); auto pm = j.contains("prime_meridian") ? buildPrimeMeridian(getObject(j, "prime_meridian")) : PrimeMeridian::GREENWICH; return GeodeticReferenceFrame::create(buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), getAnchorEpoch(j), pm); } // --------------------------------------------------------------------------- DynamicGeodeticReferenceFrameNNPtr JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { auto ellipsoidJ = getObject(j, "ellipsoid"); auto pm = j.contains("prime_meridian") ? buildPrimeMeridian(getObject(j, "prime_meridian")) : PrimeMeridian::GREENWICH; Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), UnitOfMeasure::YEAR); optional deformationModel; if (j.contains("deformation_model")) { // Before PROJJSON v0.5 / PROJ 9.1 deformationModel = getString(j, "deformation_model"); } else if (!deformationModelName_.empty()) { deformationModel = deformationModelName_; } return DynamicGeodeticReferenceFrame::create( buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm, frameReferenceEpoch, deformationModel); } // --------------------------------------------------------------------------- VerticalReferenceFrameNNPtr JSONParser::buildVerticalReferenceFrame(const json &j) { return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j), getAnchorEpoch(j)); } // --------------------------------------------------------------------------- DynamicVerticalReferenceFrameNNPtr JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), UnitOfMeasure::YEAR); optional deformationModel; if (j.contains("deformation_model")) { // Before PROJJSON v0.5 / PROJ 9.1 deformationModel = getString(j, "deformation_model"); } else if (!deformationModelName_.empty()) { deformationModel = deformationModelName_; } return DynamicVerticalReferenceFrame::create( buildProperties(j), getAnchor(j), util::optional(), frameReferenceEpoch, deformationModel); } // --------------------------------------------------------------------------- PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) { if (!j.contains("longitude")) { throw ParsingException("Missing \"longitude\" key"); } auto longitude = j["longitude"]; if (longitude.is_number()) { return PrimeMeridian::create( buildProperties(j), Angle(longitude.get(), UnitOfMeasure::DEGREE)); } else if (longitude.is_object()) { return PrimeMeridian::create(buildProperties(j), Angle(getMeasure(longitude))); } throw ParsingException("Unexpected type for value of \"longitude\""); } // --------------------------------------------------------------------------- EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) { if (j.contains("semi_major_axis")) { auto semiMajorAxis = getLength(j, "semi_major_axis"); const auto ellpsProperties = buildProperties(j); std::string ellpsName; ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName); const auto celestialBody(Ellipsoid::guessBodyName( dbContext_, semiMajorAxis.getSIValue(), ellpsName)); if (j.contains("semi_minor_axis")) { return Ellipsoid::createTwoAxis(ellpsProperties, semiMajorAxis, getLength(j, "semi_minor_axis"), celestialBody); } else if (j.contains("inverse_flattening")) { return Ellipsoid::createFlattenedSphere( ellpsProperties, semiMajorAxis, Scale(getNumber(j, "inverse_flattening")), celestialBody); } else { throw ParsingException( "Missing semi_minor_axis or inverse_flattening"); } } else if (j.contains("radius")) { auto radius = getLength(j, "radius"); const auto celestialBody( Ellipsoid::guessBodyName(dbContext_, radius.getSIValue())); return Ellipsoid::createSphere(buildProperties(j), radius, celestialBody); } throw ParsingException("Missing semi_major_axis or radius"); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // import a CRS encoded as OGC Best Practice document 11-135. static const char *const crsURLPrefixes[] = { "http://opengis.net/def/crs", "https://opengis.net/def/crs", "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs", "www.opengis.net/def/crs", }; static bool isCRSURL(const std::string &text) { for (const auto crsURLPrefix : crsURLPrefixes) { if (starts_with(text, crsURLPrefix)) { return true; } } return false; } static CRSNNPtr importFromCRSURL(const std::string &text, const DatabaseContextNNPtr &dbContext) { // e.g http://www.opengis.net/def/crs/EPSG/0/4326 std::vector parts; for (const auto crsURLPrefix : crsURLPrefixes) { if (starts_with(text, crsURLPrefix)) { parts = split(text.substr(strlen(crsURLPrefix)), '/'); break; } } // e.g // "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855" if (!parts.empty() && starts_with(parts[0], "-compound?")) { parts = split(text.substr(text.find('?') + 1), '&'); std::map mapParts; for (const auto &part : parts) { const auto queryParam = split(part, '='); if (queryParam.size() != 2) { throw ParsingException("invalid OGC CRS URL"); } try { mapParts[std::stoi(queryParam[0])] = queryParam[1]; } catch (const std::exception &) { throw ParsingException("invalid OGC CRS URL"); } } std::vector components; std::string name; for (size_t i = 1; i <= mapParts.size(); ++i) { const auto iter = mapParts.find(static_cast(i)); if (iter == mapParts.end()) { throw ParsingException("invalid OGC CRS URL"); } components.emplace_back(importFromCRSURL(iter->second, dbContext)); if (!name.empty()) { name += " + "; } name += components.back()->nameStr(); } return CompoundCRS::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, name), components); } if (parts.size() < 4) { throw ParsingException("invalid OGC CRS URL"); } const auto &auth_name = parts[1]; const auto &code = parts[3]; try { auto factoryCRS = AuthorityFactory::create(dbContext, auth_name); return factoryCRS->createCoordinateReferenceSystem(code, true); } catch (...) { const auto &version = parts[2]; if (version.empty() || version == "0") { const auto authoritiesFromAuthName = dbContext->getVersionedAuthoritiesFromName(auth_name); for (const auto &authNameVersioned : authoritiesFromAuthName) { try { auto factoryCRS = AuthorityFactory::create(dbContext, authNameVersioned); return factoryCRS->createCoordinateReferenceSystem(code, true); } catch (...) { } } throw; } std::string authNameWithVersion; if (!dbContext->getVersionedAuthority(auth_name, version, authNameWithVersion)) { throw; } auto factoryCRS = AuthorityFactory::create(dbContext, authNameWithVersion); return factoryCRS->createCoordinateReferenceSystem(code, true); } } // --------------------------------------------------------------------------- /* Import a CRS encoded as WMSAUTO string. * * Note that the WMS 1.3 specification does not include the * units code, while apparently earlier specs do. We try to * guess around this. * * (code derived from GDAL's importFromWMSAUTO()) */ static CRSNNPtr importFromWMSAUTO(const std::string &text) { int nUnitsId = 9001; double dfRefLong; double dfRefLat = 0.0; assert(ci_starts_with(text, "AUTO:")); const auto parts = split(text.substr(strlen("AUTO:")), ','); try { constexpr int AUTO_MOLLWEIDE = 42005; if (parts.size() == 4) { nUnitsId = std::stoi(parts[1]); dfRefLong = c_locale_stod(parts[2]); dfRefLat = c_locale_stod(parts[3]); } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) { nUnitsId = std::stoi(parts[1]); dfRefLong = c_locale_stod(parts[2]); } else if (parts.size() == 3) { dfRefLong = c_locale_stod(parts[1]); dfRefLat = c_locale_stod(parts[2]); } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) { dfRefLong = c_locale_stod(parts[1]); } else { throw ParsingException("invalid WMS AUTO CRS definition"); } const auto getConversion = [dfRefLong, dfRefLat, &parts]() { const int nProjId = std::stoi(parts[0]); switch (nProjId) { case 42001: // Auto UTM if (!(dfRefLong >= -180 && dfRefLong < 180)) { throw ParsingException("invalid WMS AUTO CRS definition: " "invalid longitude"); } return Conversion::createUTM( util::PropertyMap(), static_cast(floor((dfRefLong + 180.0) / 6.0)) + 1, dfRefLat >= 0.0); case 42002: // Auto TM (strangely very UTM-like). return Conversion::createTransverseMercator( util::PropertyMap(), common::Angle(0), common::Angle(dfRefLong), common::Scale(0.9996), common::Length(500000), common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0)); case 42003: // Auto Orthographic. return Conversion::createOrthographic( util::PropertyMap(), common::Angle(dfRefLat), common::Angle(dfRefLong), common::Length(0), common::Length(0)); case 42004: // Auto Equirectangular return Conversion::createEquidistantCylindrical( util::PropertyMap(), common::Angle(dfRefLat), common::Angle(dfRefLong), common::Length(0), common::Length(0)); case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant return Conversion::createMollweide( util::PropertyMap(), common::Angle(dfRefLong), common::Length(0), common::Length(0)); default: throw ParsingException("invalid WMS AUTO CRS definition: " "unsupported projection id"); } }; const auto getUnits = [nUnitsId]() -> const UnitOfMeasure & { switch (nUnitsId) { case 9001: return UnitOfMeasure::METRE; case 9002: return UnitOfMeasure::FOOT; case 9003: return UnitOfMeasure::US_FOOT; default: throw ParsingException("invalid WMS AUTO CRS definition: " "unsupported units code"); } }; return crs::ProjectedCRS::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), crs::GeographicCRS::EPSG_4326, getConversion(), cs::CartesianCS::createEastingNorthing(getUnits())); } catch (const std::exception &) { throw ParsingException("invalid WMS AUTO CRS definition"); } } // --------------------------------------------------------------------------- static BaseObjectNNPtr createFromURNPart(const DatabaseContextPtr &dbContext, const std::string &type, const std::string &authName, const std::string &version, const std::string &code) { if (!dbContext) { throw ParsingException("no database context specified"); } try { auto factory = AuthorityFactory::create(NN_NO_CHECK(dbContext), authName); if (type == "crs") { return factory->createCoordinateReferenceSystem(code); } if (type == "coordinateOperation") { return factory->createCoordinateOperation(code, true); } if (type == "datum") { return factory->createDatum(code); } if (type == "ensemble") { return factory->createDatumEnsemble(code); } if (type == "ellipsoid") { return factory->createEllipsoid(code); } if (type == "meridian") { return factory->createPrimeMeridian(code); } // Extension of OGC URN syntax to CoordinateMetadata if (type == "coordinateMetadata") { return factory->createCoordinateMetadata(code); } throw ParsingException(concat("unhandled object type: ", type)); } catch (...) { if (version.empty()) { const auto authoritiesFromAuthName = dbContext->getVersionedAuthoritiesFromName(authName); for (const auto &authNameVersioned : authoritiesFromAuthName) { try { return createFromURNPart(dbContext, type, authNameVersioned, std::string(), code); } catch (...) { } } throw; } std::string authNameWithVersion; if (!dbContext->getVersionedAuthority(authName, version, authNameWithVersion)) { throw; } return createFromURNPart(dbContext, type, authNameWithVersion, std::string(), code); } } // --------------------------------------------------------------------------- static BaseObjectNNPtr createFromUserInput(const std::string &text, const DatabaseContextPtr &dbContext, bool usePROJ4InitRules, PJ_CONTEXT *ctx, bool ignoreCoordinateEpoch) { std::size_t idxFirstCharNotSpace = text.find_first_not_of(" \t\r\n"); if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) { return createFromUserInput(text.substr(idxFirstCharNotSpace), dbContext, usePROJ4InitRules, ctx, ignoreCoordinateEpoch); } // Parse strings like "ITRF2014 @ 2025.0" const auto posAt = text.find('@'); if (!ignoreCoordinateEpoch && posAt != std::string::npos) { // Try first as if belonged to the name try { return createFromUserInput(text, dbContext, usePROJ4InitRules, ctx, /* ignoreCoordinateEpoch = */ true); } catch (...) { } std::string leftPart = text.substr(0, posAt); while (!leftPart.empty() && leftPart.back() == ' ') leftPart.resize(leftPart.size() - 1); const auto nonSpacePos = text.find_first_not_of(' ', posAt + 1); if (nonSpacePos != std::string::npos) { auto obj = createFromUserInput(leftPart, dbContext, usePROJ4InitRules, ctx, /* ignoreCoordinateEpoch = */ true); auto crs = nn_dynamic_pointer_cast(obj); if (crs) { double epoch; try { epoch = c_locale_stod(text.substr(nonSpacePos)); } catch (const std::exception &) { throw ParsingException("non-numeric value after @"); } try { return CoordinateMetadata::create(NN_NO_CHECK(crs), epoch, dbContext); } catch (const std::exception &e) { throw ParsingException( std::string( "CoordinateMetadata::create() failed with: ") + e.what()); } } } } if (!text.empty() && text[0] == '{') { json j; try { j = json::parse(text); } catch (const std::exception &e) { throw ParsingException(e.what()); } return JSONParser().attachDatabaseContext(dbContext).create(j); } if (!ci_starts_with(text, "step proj=") && !ci_starts_with(text, "step +proj=")) { for (const auto &wktConstant : WKTConstants::constants()) { if (ci_starts_with(text, wktConstant)) { for (auto wkt = text.c_str() + wktConstant.size(); *wkt != '\0'; ++wkt) { if (isspace(static_cast(*wkt))) continue; if (*wkt == '[') { return WKTParser() .attachDatabaseContext(dbContext) .setStrict(false) .createFromWKT(text); } break; } } } } const char *textWithoutPlusPrefix = text.c_str(); if (textWithoutPlusPrefix[0] == '+') textWithoutPlusPrefix++; if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 || text.find(" +proj=") != std::string::npos || text.find(" proj=") != std::string::npos || strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 || text.find(" +init=") != std::string::npos || text.find(" init=") != std::string::npos || strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) { return PROJStringParser() .attachDatabaseContext(dbContext) .attachContext(ctx) .setUsePROJ4InitRules(ctx != nullptr ? (proj_context_get_use_proj4_init_rules( ctx, false) == TRUE) : usePROJ4InitRules) .createFromPROJString(text); } if (isCRSURL(text) && dbContext) { return importFromCRSURL(text, NN_NO_CHECK(dbContext)); } if (ci_starts_with(text, "AUTO:")) { return importFromWMSAUTO(text); } std::vector tokens; if (text.find(' ') == std::string::npos) tokens = split(text, ':'); if (tokens.size() == 2) { if (!dbContext) { throw ParsingException("no database context specified"); } DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext)); const auto &authName = tokens[0]; const auto &code = tokens[1]; auto factory = AuthorityFactory::create(dbContextNNPtr, authName); try { return factory->createCoordinateReferenceSystem(code); } catch (...) { // Convenience for well-known misused code // See https://github.com/OSGeo/PROJ/issues/1730 if (ci_equal(authName, "EPSG") && code == "102100") { factory = AuthorityFactory::create(dbContextNNPtr, "ESRI"); return factory->createCoordinateReferenceSystem(code); } const auto authoritiesFromAuthName = dbContextNNPtr->getVersionedAuthoritiesFromName(authName); for (const auto &authNameVersioned : authoritiesFromAuthName) { factory = AuthorityFactory::create(dbContextNNPtr, authNameVersioned); try { return factory->createCoordinateReferenceSystem(code); } catch (...) { } } const auto allAuthorities = dbContextNNPtr->getAuthorities(); for (const auto &authCandidate : allAuthorities) { if (ci_equal(authCandidate, authName)) { factory = AuthorityFactory::create(dbContextNNPtr, authCandidate); try { return factory->createCoordinateReferenceSystem(code); } catch (...) { // EPSG:4326+3855 auto tokensCode = split(code, '+'); if (tokensCode.size() == 2) { auto crs1(factory->createCoordinateReferenceSystem( tokensCode[0], false)); auto crs2(factory->createCoordinateReferenceSystem( tokensCode[1], false)); return CompoundCRS::createLax( util::PropertyMap().set( IdentifiedObject::NAME_KEY, crs1->nameStr() + " + " + crs2->nameStr()), {crs1, crs2}, dbContext); } throw; } } } throw; } } else if (tokens.size() == 3) { // ESRI:103668+EPSG:5703 ... compound auto tokensCenter = split(tokens[1], '+'); if (tokensCenter.size() == 2) { if (!dbContext) { throw ParsingException("no database context specified"); } DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext)); const auto &authName1 = tokens[0]; const auto &code1 = tokensCenter[0]; const auto &authName2 = tokensCenter[1]; const auto &code2 = tokens[2]; auto factory1 = AuthorityFactory::create(dbContextNNPtr, authName1); auto crs1 = factory1->createCoordinateReferenceSystem(code1, false); auto factory2 = AuthorityFactory::create(dbContextNNPtr, authName2); auto crs2 = factory2->createCoordinateReferenceSystem(code2, false); return CompoundCRS::createLax( util::PropertyMap().set(IdentifiedObject::NAME_KEY, crs1->nameStr() + " + " + crs2->nameStr()), {crs1, crs2}, dbContext); } } if (starts_with(text, "urn:ogc:def:crs,")) { if (!dbContext) { throw ParsingException("no database context specified"); } auto tokensComma = split(text, ','); if (tokensComma.size() == 4 && starts_with(tokensComma[1], "crs:") && starts_with(tokensComma[2], "cs:") && starts_with(tokensComma[3], "coordinateOperation:")) { // OGC 07-092r2: para 7.5.4 // URN combined references for projected or derived CRSs const auto &crsPart = tokensComma[1]; const auto tokensCRS = split(crsPart, ':'); if (tokensCRS.size() != 4) { throw ParsingException( concat("invalid crs component: ", crsPart)); } auto factoryCRS = AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCRS[1]); auto baseCRS = factoryCRS->createCoordinateReferenceSystem(tokensCRS[3], true); const auto &csPart = tokensComma[2]; auto tokensCS = split(csPart, ':'); if (tokensCS.size() != 4) { throw ParsingException( concat("invalid cs component: ", csPart)); } auto factoryCS = AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCS[1]); auto cs = factoryCS->createCoordinateSystem(tokensCS[3]); const auto &opPart = tokensComma[3]; auto tokensOp = split(opPart, ':'); if (tokensOp.size() != 4) { throw ParsingException( concat("invalid coordinateOperation component: ", opPart)); } auto factoryOp = AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]); auto op = factoryOp->createCoordinateOperation(tokensOp[3], true); const auto &baseName = baseCRS->nameStr(); std::string name(baseName); auto geogCRS = util::nn_dynamic_pointer_cast(baseCRS); if (geogCRS && geogCRS->coordinateSystem()->axisList().size() == 3 && baseName.find("3D") == std::string::npos) { name += " (3D)"; } name += " / "; name += op->nameStr(); auto props = util::PropertyMap().set(IdentifiedObject::NAME_KEY, name); if (auto conv = util::nn_dynamic_pointer_cast(op)) { auto convNN = NN_NO_CHECK(conv); if (geogCRS != nullptr) { auto geogCRSNN = NN_NO_CHECK(geogCRS); if (CartesianCSPtr ccs = util::nn_dynamic_pointer_cast(cs)) { return ProjectedCRS::create(props, geogCRSNN, convNN, NN_NO_CHECK(ccs)); } if (EllipsoidalCSPtr ecs = util::nn_dynamic_pointer_cast(cs)) { return DerivedGeographicCRS::create( props, geogCRSNN, convNN, NN_NO_CHECK(ecs)); } } else if (dynamic_cast(baseCRS.get()) && dynamic_cast(cs.get())) { return DerivedGeodeticCRS::create( props, NN_NO_CHECK(util::nn_dynamic_pointer_cast( baseCRS)), convNN, NN_NO_CHECK( util::nn_dynamic_pointer_cast(cs))); } else if (auto pcrs = util::nn_dynamic_pointer_cast( baseCRS)) { return DerivedProjectedCRS::create(props, NN_NO_CHECK(pcrs), convNN, cs); } else if (auto vertBaseCRS = util::nn_dynamic_pointer_cast( baseCRS)) { if (auto vertCS = util::nn_dynamic_pointer_cast(cs)) { const int methodCode = convNN->method()->getEPSGCode(); std::string newName(baseName); std::string unitNameSuffix; for (const char *suffix : {" (ft)", " (ftUS)"}) { if (ends_with(newName, suffix)) { unitNameSuffix = suffix; newName.resize(newName.size() - strlen(suffix)); break; } } bool newNameOk = false; if (methodCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR || methodCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { const auto &unitName = vertCS->axisList()[0]->unit().name(); if (unitName == UnitOfMeasure::METRE.name()) { newNameOk = true; } else if (unitName == UnitOfMeasure::FOOT.name()) { newName += " (ft)"; newNameOk = true; } else if (unitName == UnitOfMeasure::US_FOOT.name()) { newName += " (ftUS)"; newNameOk = true; } } else if (methodCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { if (ends_with(newName, " height")) { newName.resize(newName.size() - strlen(" height")); newName += " depth"; newName += unitNameSuffix; newNameOk = true; } else if (ends_with(newName, " depth")) { newName.resize(newName.size() - strlen(" depth")); newName += " height"; newName += unitNameSuffix; newNameOk = true; } } if (newNameOk) { props.set(IdentifiedObject::NAME_KEY, newName); } return DerivedVerticalCRS::create( props, NN_NO_CHECK(vertBaseCRS), convNN, NN_NO_CHECK(vertCS)); } } } throw ParsingException("unsupported combination of baseCRS, CS " "and coordinateOperation for a " "DerivedCRS"); } // OGC 07-092r2: para 7.5.2 // URN combined references for compound coordinate reference systems std::vector components; std::string name; for (size_t i = 1; i < tokensComma.size(); i++) { tokens = split(tokensComma[i], ':'); if (tokens.size() != 4) { throw ParsingException( concat("invalid crs component: ", tokensComma[i])); } const auto &type = tokens[0]; auto factory = AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]); const auto &code = tokens[3]; if (type == "crs") { auto crs(factory->createCoordinateReferenceSystem(code, false)); components.emplace_back(crs); if (!name.empty()) { name += " + "; } name += crs->nameStr(); } else { throw ParsingException( concat("unexpected object type: ", type)); } } return CompoundCRS::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, name), components); } // OGC 07-092r2: para 7.5.3 // 7.5.3 URN combined references for concatenated operations if (starts_with(text, "urn:ogc:def:coordinateOperation,")) { if (!dbContext) { throw ParsingException("no database context specified"); } auto tokensComma = split(text, ','); std::vector components; for (size_t i = 1; i < tokensComma.size(); i++) { tokens = split(tokensComma[i], ':'); if (tokens.size() != 4) { throw ParsingException(concat( "invalid coordinateOperation component: ", tokensComma[i])); } const auto &type = tokens[0]; auto factory = AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]); const auto &code = tokens[3]; if (type == "coordinateOperation") { auto op(factory->createCoordinateOperation(code, false)); components.emplace_back(op); } else { throw ParsingException( concat("unexpected object type: ", type)); } } return ConcatenatedOperation::createComputeMetadata(components, true); } // urn:ogc:def:crs:EPSG::4326 if (tokens.size() == 7 && tolower(tokens[0]) == "urn") { const std::string type(tokens[3] == "CRS" ? "crs" : tokens[3]); const auto &authName = tokens[4]; const auto &version = tokens[5]; const auto &code = tokens[6]; return createFromURNPart(dbContext, type, authName, version, code); } // urn:ogc:def:crs:OGC::AUTO42001:-117:33 if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" && ci_starts_with(tokens[6], "AUTO")) { const auto textAUTO = text.substr(text.find(":AUTO") + 5); return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ",")); } // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to // above) if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") { const auto &type = tokens[2]; const auto &authName = tokens[3]; const auto &version = tokens[4]; const auto &code = tokens[5]; return createFromURNPart(dbContext, type, authName, version, code); } // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version) if (tokens.size() == 6 && tokens[0] == "urn") { const auto &type = tokens[3]; const auto &authName = tokens[4]; const auto &code = tokens[5]; return createFromURNPart(dbContext, type, authName, std::string(), code); } if (dbContext) { auto factory = AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); const auto searchObject = [&factory]( const std::string &objectName, bool approximateMatch, const std::vector &objectTypes) -> IdentifiedObjectPtr { constexpr size_t limitResultCount = 10; auto res = factory->createObjectsFromName( objectName, objectTypes, approximateMatch, limitResultCount); if (res.size() == 1) { return res.front().as_nullable(); } if (res.size() > 1) { if (objectTypes.size() == 1 && objectTypes[0] == AuthorityFactory::ObjectType::CRS) { for (size_t ndim = 2; ndim <= 3; ndim++) { for (const auto &obj : res) { auto crs = dynamic_cast(obj.get()); if (crs && crs->coordinateSystem()->axisList().size() == ndim) { return obj.as_nullable(); } } } } // If there's exactly only one object whose name is equivalent // to the user input, return it. for (int pass = 0; pass <= 1; ++pass) { IdentifiedObjectPtr identifiedObj; for (const auto &obj : res) { if (Identifier::isEquivalentName( obj->nameStr().c_str(), objectName.c_str(), /* biggerDifferencesAllowed = */ pass == 1)) { if (identifiedObj == nullptr) { identifiedObj = obj.as_nullable(); } else { identifiedObj = nullptr; break; } } } if (identifiedObj) { return identifiedObj; } } std::string msg("several objects matching this name: "); bool first = true; for (const auto &obj : res) { if (msg.size() > 200) { msg += ", ..."; break; } if (!first) { msg += ", "; } first = false; msg += obj->nameStr(); } throw ParsingException(msg); } return nullptr; }; const auto searchCRS = [&searchObject](const std::string &objectName) { const auto objectTypes = std::vector{ AuthorityFactory::ObjectType::CRS}; { constexpr bool approximateMatch = false; auto ret = searchObject(objectName, approximateMatch, objectTypes); if (ret) return ret; } constexpr bool approximateMatch = true; return searchObject(objectName, approximateMatch, objectTypes); }; // strings like "WGS 84 + EGM96 height" CompoundCRSPtr compoundCRS; try { const auto tokensCompound = split(text, " + "); if (tokensCompound.size() == 2) { auto obj1 = searchCRS(tokensCompound[0]); auto obj2 = searchCRS(tokensCompound[1]); auto crs1 = std::dynamic_pointer_cast(obj1); auto crs2 = std::dynamic_pointer_cast(obj2); if (crs1 && crs2) { compoundCRS = CompoundCRS::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, crs1->nameStr() + " + " + crs2->nameStr()), {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)}) .as_nullable(); } } } catch (const std::exception &) { } // First pass: exact match on CRS objects // Second pass: exact match on other objects // Third pass: approximate match on CRS objects // Fourth pass: approximate match on other objects // But only allow approximate matching if the size of the text is // large enough (>= 5), otherwise we get a lot of false positives: // "foo" -> "Amersfoort", "bar" -> "Barbados 1938" // Also only accept approximate matching if the ratio between the // input and match size is not too small, so that "omerc" doesn't match // with "WGS 84 / Pseudo-Mercator" const int maxNumberPasses = text.size() <= 4 ? 2 : 4; for (int pass = 0; pass < maxNumberPasses; ++pass) { const bool approximateMatch = (pass >= 2); auto ret = searchObject( text, approximateMatch, (pass == 0 || pass == 2) ? std::vector< AuthorityFactory::ObjectType>{AuthorityFactory:: ObjectType::CRS} : std::vector{ AuthorityFactory::ObjectType::ELLIPSOID, AuthorityFactory::ObjectType::DATUM, AuthorityFactory::ObjectType::DATUM_ENSEMBLE, AuthorityFactory::ObjectType::COORDINATE_OPERATION}); if (ret) { if (!approximateMatch || ret->nameStr().size() < 2 * text.size()) return NN_NO_CHECK(ret); } if (compoundCRS) { if (!approximateMatch || compoundCRS->nameStr().size() < 2 * text.size()) return NN_NO_CHECK(compoundCRS); } } } throw ParsingException("unrecognized format / unknown name"); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a sub-class of BaseObject from a user specified text. * * The text can be a: *
    *
  • WKT string
  • *
  • PROJ string
  • *
  • database code, prefixed by its authority. e.g. "EPSG:4326"
  • *
  • OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326", * "urn:ogc:def:coordinateOperation:EPSG::1671", * "urn:ogc:def:ellipsoid:EPSG::7001" * or "urn:ogc:def:datum:EPSG::6326"
  • *
  • OGC URN combining references for compound coordinate reference systems * e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717" * We also accept a custom abbreviated syntax EPSG:2393+5717 * or ESRI:103668+EPSG:5703 *
  • *
  • OGC URN combining references for references for projected or derived * CRSs * e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)" * "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031" *
  • *
  • Extension of OGC URN for CoordinateMetadata. * e.g. * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"
  • *
  • OGC URN combining references for concatenated operations * e.g. * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"
  • *
  • OGC URL for a single CRS. e.g. * "http://www.opengis.net/def/crs/EPSG/0/4326"
  • *
  • OGC URL for a compound * CRS. e.g * "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855"
  • *
  • an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.
  • *
  • a CRS name and a coordinate epoch, separated with '@'. For example * "ITRF2014@2025.0". (added in PROJ 9.2)
  • *
  • a compound CRS made from two object names separated with " + ". * e.g. "WGS 84 + EGM96 height"
  • *
  • PROJJSON string
  • *
* * @param text One of the above mentioned text format * @param dbContext Database context, or nullptr (in which case database * lookups will not work) * @param usePROJ4InitRules When set to true, * init=epsg:XXXX syntax will be allowed and will be interpreted according to * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude * order and will expect/output coordinates in radians. ProjectedCRS will have * easting, northing axis order (except the ones with Transverse Mercator South * Orientated projection). In that mode, the epsg:XXXX syntax will be also * interpreted the same way. * @throw ParsingException if the string cannot be parsed. */ BaseObjectNNPtr createFromUserInput(const std::string &text, const DatabaseContextPtr &dbContext, bool usePROJ4InitRules) { return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr, /* ignoreCoordinateEpoch = */ false); } // --------------------------------------------------------------------------- /** \brief Instantiate a sub-class of BaseObject from a user specified text. * * The text can be a: *
    *
  • WKT string
  • *
  • PROJ string
  • *
  • database code, prefixed by its authority. e.g. "EPSG:4326"
  • *
  • OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326", * "urn:ogc:def:coordinateOperation:EPSG::1671", * "urn:ogc:def:ellipsoid:EPSG::7001" * or "urn:ogc:def:datum:EPSG::6326"
  • *
  • OGC URN combining references for compound coordinate reference systems * e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717" * We also accept a custom abbreviated syntax EPSG:2393+5717 *
  • *
  • OGC URN combining references for references for projected or derived * CRSs * e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)" * "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031" *
  • *
  • Extension of OGC URN for CoordinateMetadata. * e.g. * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"
  • *
  • OGC URN combining references for concatenated operations * e.g. * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"
  • *
  • an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.
  • *
  • a compound CRS made from two object names separated with " + ". * e.g. "WGS 84 + EGM96 height"
  • *
  • PROJJSON string
  • *
* * @param text One of the above mentioned text format * @param ctx PROJ context * @throw ParsingException if the string cannot be parsed. */ BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) { DatabaseContextPtr dbContext; try { if (ctx != nullptr) { // Only connect to proj.db if needed if (text.find("proj=") == std::string::npos || text.find("init=") != std::string::npos) { dbContext = ctx->get_cpp_context()->getDatabaseContext().as_nullable(); } } } catch (const std::exception &) { } return createFromUserInput(text, dbContext, false, ctx, /* ignoreCoordinateEpoch = */ false); } // --------------------------------------------------------------------------- /** \brief Instantiate a sub-class of BaseObject from a WKT string. * * By default, validation is strict (to the extent of the checks that are * actually implemented. Currently only WKT1 strict grammar is checked), and * any issue detected will cause an exception to be thrown, unless * setStrict(false) is called priorly. * * In non-strict mode, non-fatal issues will be recovered and simply listed * in warningList(). This does not prevent more severe errors to cause an * exception to be thrown. * * @throw ParsingException if the string cannot be parsed. */ BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) { const auto dialect = guessDialect(wkt); d->maybeEsriStyle_ = (dialect == WKTGuessedDialect::WKT1_ESRI); if (d->maybeEsriStyle_) { if (wkt.find("PARAMETER[\"X_Scale\",") != std::string::npos) { d->esriStyle_ = true; d->maybeEsriStyle_ = false; } } const auto build = [this, &wkt]() -> BaseObjectNNPtr { size_t indexEnd; WKTNodeNNPtr root = WKTNode::createFrom(wkt, 0, 0, indexEnd); const std::string &name(root->GP()->value()); if (ci_equal(name, WKTConstants::DATUM) || ci_equal(name, WKTConstants::GEODETICDATUM) || ci_equal(name, WKTConstants::TRF)) { auto primeMeridian = PrimeMeridian::GREENWICH; if (indexEnd < wkt.size()) { indexEnd = skipSpace(wkt, indexEnd); if (indexEnd < wkt.size() && wkt[indexEnd] == ',') { ++indexEnd; indexEnd = skipSpace(wkt, indexEnd); if (indexEnd < wkt.size() && ci_starts_with(wkt.c_str() + indexEnd, WKTConstants::PRIMEM.c_str())) { primeMeridian = d->buildPrimeMeridian( WKTNode::createFrom(wkt, indexEnd, 0, indexEnd), UnitOfMeasure::DEGREE); } } } return d->buildGeodeticReferenceFrame(root, primeMeridian, null_node); } else if (ci_equal(name, WKTConstants::GEOGCS) || ci_equal(name, WKTConstants::PROJCS)) { // Parse implicit compoundCRS from ESRI that is // "PROJCS[...],VERTCS[...]" or "GEOGCS[...],VERTCS[...]" if (indexEnd < wkt.size()) { indexEnd = skipSpace(wkt, indexEnd); if (indexEnd < wkt.size() && wkt[indexEnd] == ',') { ++indexEnd; indexEnd = skipSpace(wkt, indexEnd); if (indexEnd < wkt.size() && ci_starts_with(wkt.c_str() + indexEnd, WKTConstants::VERTCS.c_str())) { auto horizCRS = d->buildCRS(root); if (horizCRS) { auto vertCRS = d->buildVerticalCRS(WKTNode::createFrom( wkt, indexEnd, 0, indexEnd)); return CompoundCRS::createLax( util::PropertyMap().set( IdentifiedObject::NAME_KEY, horizCRS->nameStr() + " + " + vertCRS->nameStr()), {NN_NO_CHECK(horizCRS), vertCRS}, d->dbContext_); } } } } } return d->build(root); }; auto obj = build(); if (dialect == WKTGuessedDialect::WKT1_GDAL || dialect == WKTGuessedDialect::WKT1_ESRI) { auto errorMsg = pj_wkt1_parse(wkt); if (!errorMsg.empty()) { d->emitGrammarError(errorMsg); } } else if (dialect == WKTGuessedDialect::WKT2_2015 || dialect == WKTGuessedDialect::WKT2_2019) { auto errorMsg = pj_wkt2_parse(wkt); if (!errorMsg.empty()) { d->emitGrammarError(errorMsg); } } return obj; } // --------------------------------------------------------------------------- /** \brief Attach a database context, to allow queries in it if needed. */ WKTParser & WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { d->dbContext_ = dbContext; return *this; } // --------------------------------------------------------------------------- /** \brief Guess the "dialect" of the WKT string. */ WKTParser::WKTGuessedDialect WKTParser::guessDialect(const std::string &inputWkt) noexcept { // cppcheck complains (rightly) that the method could be static (void)this; std::string wkt = inputWkt; std::size_t idxFirstCharNotSpace = wkt.find_first_not_of(" \t\r\n"); if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) { wkt = wkt.substr(idxFirstCharNotSpace); } if (ci_starts_with(wkt, WKTConstants::VERTCS)) { return WKTGuessedDialect::WKT1_ESRI; } const std::string *const wkt1_keywords[] = { &WKTConstants::GEOCCS, &WKTConstants::GEOGCS, &WKTConstants::COMPD_CS, &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS}; for (const auto &pointerKeyword : wkt1_keywords) { if (ci_starts_with(wkt, *pointerKeyword)) { if ((ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos || (!ci_starts_with(wkt, WKTConstants::LOCAL_CS) && ci_find(wkt, "AXIS[") == std::string::npos && ci_find(wkt, "AUTHORITY[") == std::string::npos)) && // WKT1:GDAL and WKT1:ESRI have both a // Hotine_Oblique_Mercator_Azimuth_Center If providing a // WKT1:GDAL without AXIS, we may wrongly detect it as WKT1:ESRI // and skip the rectified_grid_angle parameter cf // https://github.com/OSGeo/PROJ/issues/3279 ci_find(wkt, "PARAMETER[\"rectified_grid_angle") == std::string::npos) { return WKTGuessedDialect::WKT1_ESRI; } return WKTGuessedDialect::WKT1_GDAL; } } const std::string *const wkt2_2019_only_keywords[] = { &WKTConstants::GEOGCRS, // contained in previous one // &WKTConstants::BASEGEOGCRS, &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE, &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL, &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE, &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS, &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF, &WKTConstants::POINTMOTIONOPERATION}; for (const auto &pointerKeyword : wkt2_2019_only_keywords) { auto pos = ci_find(wkt, *pointerKeyword); if (pos != std::string::npos && wkt[pos + pointerKeyword->size()] == '[') { return WKTGuessedDialect::WKT2_2019; } } static const char *const wkt2_2019_only_substrings[] = { "CS[TemporalDateTime,", "CS[TemporalCount,", "CS[TemporalMeasure,", }; for (const auto &substrings : wkt2_2019_only_substrings) { if (ci_find(wkt, substrings) != std::string::npos) { return WKTGuessedDialect::WKT2_2019; } } for (const auto &wktConstant : WKTConstants::constants()) { if (ci_starts_with(wkt, wktConstant)) { for (auto wktPtr = wkt.c_str() + wktConstant.size(); *wktPtr != '\0'; ++wktPtr) { if (isspace(static_cast(*wktPtr))) continue; if (*wktPtr == '[') { return WKTGuessedDialect::WKT2_2015; } break; } } } return WKTGuessedDialect::NOT_WKT; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress FormattingException::FormattingException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- FormattingException::FormattingException(const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- FormattingException::FormattingException(const FormattingException &) = default; // --------------------------------------------------------------------------- FormattingException::~FormattingException() = default; // --------------------------------------------------------------------------- void FormattingException::Throw(const char *msg) { throw FormattingException(msg); } // --------------------------------------------------------------------------- void FormattingException::Throw(const std::string &msg) { throw FormattingException(msg); } // --------------------------------------------------------------------------- ParsingException::ParsingException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- ParsingException::ParsingException(const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- ParsingException::ParsingException(const ParsingException &) = default; // --------------------------------------------------------------------------- ParsingException::~ParsingException() = default; // --------------------------------------------------------------------------- IPROJStringExportable::~IPROJStringExportable() = default; // --------------------------------------------------------------------------- std::string IPROJStringExportable::exportToPROJString( PROJStringFormatter *formatter) const { const bool bIsCRS = dynamic_cast(this) != nullptr; if (bIsCRS) { formatter->setCRSExport(true); } _exportToPROJString(formatter); if (formatter->getAddNoDefs() && bIsCRS) { if (!formatter->hasParam("no_defs")) { formatter->addParam("no_defs"); } } if (bIsCRS) { if (!formatter->hasParam("type")) { formatter->addParam("type", "crs"); } formatter->setCRSExport(false); } return formatter->toString(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Step { std::string name{}; bool isInit = false; bool inverted{false}; struct KeyValue { std::string key{}; std::string value{}; bool usedByParser = false; // only for PROJStringParser used explicit KeyValue(const std::string &keyIn) : key(keyIn) {} KeyValue(const char *keyIn, const std::string &valueIn); KeyValue(const std::string &keyIn, const std::string &valueIn) : key(keyIn), value(valueIn) {} // cppcheck-suppress functionStatic bool keyEquals(const char *otherKey) const noexcept { return key == otherKey; } // cppcheck-suppress functionStatic bool equals(const char *otherKey, const char *otherVal) const noexcept { return key == otherKey && value == otherVal; } bool operator==(const KeyValue &other) const noexcept { return key == other.key && value == other.value; } bool operator!=(const KeyValue &other) const noexcept { return key != other.key || value != other.value; } }; std::vector paramValues{}; bool hasKey(const char *keyName) const { for (const auto &kv : paramValues) { if (kv.key == keyName) { return true; } } return false; } }; Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn) : key(keyIn), value(valueIn) {} struct PROJStringFormatter::Private { PROJStringFormatter::Convention convention_ = PROJStringFormatter::Convention::PROJ_5; std::vector toWGS84Parameters_{}; std::string vDatumExtension_{}; std::string geoidCRSValue_{}; std::string hDatumExtension_{}; crs::GeographicCRSPtr geogCRSOfCompoundCRS_{}; std::list steps_{}; std::vector globalParamValues_{}; struct InversionStackElt { std::list::iterator startIter{}; bool iterValid = false; bool currentInversionState = false; }; std::vector inversionStack_{InversionStackElt()}; bool omitProjLongLatIfPossible_ = false; std::vector omitZUnitConversion_{false}; std::vector omitHorizontalConversionInVertTransformation_{false}; DatabaseContextPtr dbContext_{}; bool useApproxTMerc_ = false; bool addNoDefs_ = true; bool coordOperationOptimizations_ = false; bool crsExport_ = false; bool legacyCRSToCRSContext_ = false; bool multiLine_ = false; bool normalizeOutput_ = false; int indentWidth_ = 2; int indentLevel_ = 0; int maxLineLength_ = 80; std::string result_{}; // cppcheck-suppress functionStatic void appendToResult(const char *str); // cppcheck-suppress functionStatic void addStep(); }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PROJStringFormatter::PROJStringFormatter(Convention conventionIn, const DatabaseContextPtr &dbContext) : d(std::make_unique()) { d->convention_ = conventionIn; d->dbContext_ = dbContext; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PROJStringFormatter::~PROJStringFormatter() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Constructs a new formatter. * * A formatter can be used only once (its internal state is mutated) * * Its default behavior can be adjusted with the different setters. * * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5 * @param dbContext Database context (can help to find alternative grid names). * May be nullptr * @return new formatter. */ PROJStringFormatterNNPtr PROJStringFormatter::create(Convention conventionIn, DatabaseContextPtr dbContext) { return NN_NO_CHECK(PROJStringFormatter::make_unique( conventionIn, dbContext)); } // --------------------------------------------------------------------------- /** \brief Set whether approximate Transverse Mercator or UTM should be used */ void PROJStringFormatter::setUseApproxTMerc(bool flag) { d->useApproxTMerc_ = flag; } // --------------------------------------------------------------------------- /** \brief Whether to use multi line output or not. */ PROJStringFormatter & PROJStringFormatter::setMultiLine(bool multiLine) noexcept { d->multiLine_ = multiLine; return *this; } // --------------------------------------------------------------------------- /** \brief Set number of spaces for each indentation level (defaults to 2). */ PROJStringFormatter & PROJStringFormatter::setIndentationWidth(int width) noexcept { d->indentWidth_ = width; return *this; } // --------------------------------------------------------------------------- /** \brief Set the maximum size of a line (when multiline output is enable). * Can be set to 0 for unlimited length. */ PROJStringFormatter & PROJStringFormatter::setMaxLineLength(int maxLineLength) noexcept { d->maxLineLength_ = maxLineLength; return *this; } // --------------------------------------------------------------------------- /** \brief Returns the PROJ string. */ const std::string &PROJStringFormatter::toString() const { assert(d->inversionStack_.size() == 1); d->result_.clear(); auto &steps = d->steps_; if (d->normalizeOutput_) { // Sort +key=value options of each step in lexicographic order. for (auto &step : steps) { std::sort(step.paramValues.begin(), step.paramValues.end(), [](const Step::KeyValue &a, const Step::KeyValue &b) { return a.key < b.key; }); } } for (auto iter = steps.begin(); iter != steps.end();) { // Remove no-op helmert auto &step = *iter; const auto paramCount = step.paramValues.size(); if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) && step.paramValues[0].equals("x", "0") && step.paramValues[1].equals("y", "0") && step.paramValues[2].equals("z", "0") && (paramCount == 3 || (step.paramValues[3].equals("rx", "0") && step.paramValues[4].equals("ry", "0") && step.paramValues[5].equals("rz", "0") && step.paramValues[6].equals("s", "0") && step.paramValues[7].keyEquals("convention")))) { iter = steps.erase(iter); } else if (d->coordOperationOptimizations_ && step.name == "unitconvert" && paramCount == 2 && step.paramValues[0].keyEquals("xy_in") && step.paramValues[1].keyEquals("xy_out") && step.paramValues[0].value == step.paramValues[1].value) { iter = steps.erase(iter); } else if (step.name == "push" && step.inverted) { step.name = "pop"; step.inverted = false; ++iter; } else if (step.name == "pop" && step.inverted) { step.name = "push"; step.inverted = false; ++iter; } else if (step.name == "noop" && steps.size() > 1) { iter = steps.erase(iter); } else { ++iter; } } for (auto &step : steps) { if (!step.inverted) { continue; } const auto paramCount = step.paramValues.size(); // axisswap order=2,1 (or 1,-2) is its own inverse if (step.name == "axisswap" && paramCount == 1 && (step.paramValues[0].equals("order", "2,1") || step.paramValues[0].equals("order", "1,-2"))) { step.inverted = false; continue; } // axisswap inv order=2,-1 ==> axisswap order -2,1 if (step.name == "axisswap" && paramCount == 1 && step.paramValues[0].equals("order", "2,-1")) { step.inverted = false; step.paramValues[0] = Step::KeyValue("order", "-2,1"); continue; } // axisswap order=1,2,-3 is its own inverse if (step.name == "axisswap" && paramCount == 1 && step.paramValues[0].equals("order", "1,2,-3")) { step.inverted = false; continue; } // handle unitconvert inverse if (step.name == "unitconvert" && paramCount == 2 && step.paramValues[0].keyEquals("xy_in") && step.paramValues[1].keyEquals("xy_out")) { std::swap(step.paramValues[0].value, step.paramValues[1].value); step.inverted = false; continue; } if (step.name == "unitconvert" && paramCount == 2 && step.paramValues[0].keyEquals("z_in") && step.paramValues[1].keyEquals("z_out")) { std::swap(step.paramValues[0].value, step.paramValues[1].value); step.inverted = false; continue; } if (step.name == "unitconvert" && paramCount == 4 && step.paramValues[0].keyEquals("xy_in") && step.paramValues[1].keyEquals("z_in") && step.paramValues[2].keyEquals("xy_out") && step.paramValues[3].keyEquals("z_out")) { std::swap(step.paramValues[0].value, step.paramValues[2].value); std::swap(step.paramValues[1].value, step.paramValues[3].value); step.inverted = false; continue; } // set does the same in forward and inverse paths if (step.name == "set") { step.inverted = false; } } { auto iterCur = steps.begin(); if (iterCur != steps.end()) { ++iterCur; } while (iterCur != steps.end()) { assert(iterCur != steps.begin()); auto iterPrev = std::prev(iterCur); auto &prevStep = *iterPrev; auto &curStep = *iterCur; const auto curStepParamCount = curStep.paramValues.size(); const auto prevStepParamCount = prevStep.paramValues.size(); const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() { iterCur = steps.erase(iterPrev, std::next(iterCur)); if (iterCur != steps.begin()) iterCur = std::prev(iterCur); if (iterCur == steps.begin() && iterCur != steps.end()) ++iterCur; }; // longlat (or its inverse) with ellipsoid only is a no-op // do that only for an internal step if (std::next(iterCur) != steps.end() && curStep.name == "longlat" && curStepParamCount == 1 && curStep.paramValues[0].keyEquals("ellps")) { iterCur = steps.erase(iterCur); continue; } // push v_x followed by pop v_x is a no-op. if (curStep.name == "pop" && prevStep.name == "push" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 1 && prevStepParamCount == 1 && curStep.paramValues[0].key == prevStep.paramValues[0].key) { deletePrevAndCurIter(); continue; } // pop v_x followed by push v_x is, almost, a no-op. For our // purposes, // we consider it as a no-op for better pipeline optimizations. if (curStep.name == "push" && prevStep.name == "pop" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 1 && prevStepParamCount == 1 && curStep.paramValues[0].key == prevStep.paramValues[0].key) { deletePrevAndCurIter(); continue; } // unitconvert (xy) followed by its inverse is a no-op if (curStep.name == "unitconvert" && prevStep.name == "unitconvert" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 2 && prevStepParamCount == 2 && curStep.paramValues[0].keyEquals("xy_in") && prevStep.paramValues[0].keyEquals("xy_in") && curStep.paramValues[1].keyEquals("xy_out") && prevStep.paramValues[1].keyEquals("xy_out") && curStep.paramValues[0].value == prevStep.paramValues[1].value && curStep.paramValues[1].value == prevStep.paramValues[0].value) { deletePrevAndCurIter(); continue; } // unitconvert (z) followed by its inverse is a no-op if (curStep.name == "unitconvert" && prevStep.name == "unitconvert" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 2 && prevStepParamCount == 2 && curStep.paramValues[0].keyEquals("z_in") && prevStep.paramValues[0].keyEquals("z_in") && curStep.paramValues[1].keyEquals("z_out") && prevStep.paramValues[1].keyEquals("z_out") && curStep.paramValues[0].value == prevStep.paramValues[1].value && curStep.paramValues[1].value == prevStep.paramValues[0].value) { deletePrevAndCurIter(); continue; } // unitconvert (xyz) followed by its inverse is a no-op if (curStep.name == "unitconvert" && prevStep.name == "unitconvert" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 4 && prevStepParamCount == 4 && curStep.paramValues[0].keyEquals("xy_in") && prevStep.paramValues[0].keyEquals("xy_in") && curStep.paramValues[1].keyEquals("z_in") && prevStep.paramValues[1].keyEquals("z_in") && curStep.paramValues[2].keyEquals("xy_out") && prevStep.paramValues[2].keyEquals("xy_out") && curStep.paramValues[3].keyEquals("z_out") && prevStep.paramValues[3].keyEquals("z_out") && curStep.paramValues[0].value == prevStep.paramValues[2].value && curStep.paramValues[1].value == prevStep.paramValues[3].value && curStep.paramValues[2].value == prevStep.paramValues[0].value && curStep.paramValues[3].value == prevStep.paramValues[1].value) { deletePrevAndCurIter(); continue; } const auto deletePrevIter = [&steps, &iterPrev, &iterCur]() { steps.erase(iterPrev, iterCur); if (iterCur != steps.begin()) iterCur = std::prev(iterCur); if (iterCur == steps.begin()) ++iterCur; }; // combine unitconvert (xy) and unitconvert (z) bool changeDone = false; for (int k = 0; k < 2; ++k) { auto &first = (k == 0) ? curStep : prevStep; auto &second = (k == 0) ? prevStep : curStep; if (first.name == "unitconvert" && second.name == "unitconvert" && !first.inverted && !second.inverted && first.paramValues.size() == 2 && second.paramValues.size() == 2 && second.paramValues[0].keyEquals("xy_in") && second.paramValues[1].keyEquals("xy_out") && first.paramValues[0].keyEquals("z_in") && first.paramValues[1].keyEquals("z_out")) { const std::string xy_in(second.paramValues[0].value); const std::string xy_out(second.paramValues[1].value); const std::string z_in(first.paramValues[0].value); const std::string z_out(first.paramValues[1].value); iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("xy_in", xy_in)); iterCur->paramValues.emplace_back( Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("xy_out", xy_out)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); changeDone = true; break; } } if (changeDone) { continue; } // +step +proj=unitconvert +xy_in=X1 +xy_out=X2 // +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2 // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2 for (int k = 0; k < 2; ++k) { auto &first = (k == 0) ? curStep : prevStep; auto &second = (k == 0) ? prevStep : curStep; if (first.name == "unitconvert" && second.name == "unitconvert" && !first.inverted && !second.inverted && first.paramValues.size() == 4 && second.paramValues.size() == 2 && first.paramValues[0].keyEquals("xy_in") && first.paramValues[1].keyEquals("z_in") && first.paramValues[2].keyEquals("xy_out") && first.paramValues[3].keyEquals("z_out") && second.paramValues[0].keyEquals("xy_in") && second.paramValues[1].keyEquals("xy_out") && first.paramValues[0].value == second.paramValues[1].value && first.paramValues[2].value == second.paramValues[0].value) { const std::string z_in(first.paramValues[1].value); const std::string z_out(first.paramValues[3].value); if (z_in != z_out) { iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); } else { deletePrevAndCurIter(); } changeDone = true; break; } } if (changeDone) { continue; } // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2 // +step +proj=unitconvert +z_in=Z2 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 // +z_out=Z3 if (prevStep.name == "unitconvert" && curStep.name == "unitconvert" && !prevStep.inverted && !curStep.inverted && prevStep.paramValues.size() == 4 && curStep.paramValues.size() == 2 && prevStep.paramValues[0].keyEquals("xy_in") && prevStep.paramValues[1].keyEquals("z_in") && prevStep.paramValues[2].keyEquals("xy_out") && prevStep.paramValues[3].keyEquals("z_out") && curStep.paramValues[0].keyEquals("z_in") && curStep.paramValues[1].keyEquals("z_out") && prevStep.paramValues[3].value == curStep.paramValues[0].value) { const std::string xy_in(prevStep.paramValues[0].value); const std::string z_in(prevStep.paramValues[1].value); const std::string xy_out(prevStep.paramValues[2].value); const std::string z_out(curStep.paramValues[1].value); iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("xy_in", xy_in)); iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("xy_out", xy_out)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); continue; } // +step +proj=unitconvert +z_in=Z1 +z_out=Z2 // +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X2 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 // +z_out=Z3 if (prevStep.name == "unitconvert" && curStep.name == "unitconvert" && !prevStep.inverted && !curStep.inverted && prevStep.paramValues.size() == 2 && curStep.paramValues.size() == 4 && prevStep.paramValues[0].keyEquals("z_in") && prevStep.paramValues[1].keyEquals("z_out") && curStep.paramValues[0].keyEquals("xy_in") && curStep.paramValues[1].keyEquals("z_in") && curStep.paramValues[2].keyEquals("xy_out") && curStep.paramValues[3].keyEquals("z_out") && prevStep.paramValues[1].value == curStep.paramValues[1].value) { const std::string xy_in(curStep.paramValues[0].value); const std::string z_in(prevStep.paramValues[0].value); const std::string xy_out(curStep.paramValues[2].value); const std::string z_out(curStep.paramValues[3].value); iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("xy_in", xy_in)); iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("xy_out", xy_out)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); continue; } // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2 // +step +proj=unitconvert +xy_in=X2 +xy_out=X3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 // +z_out=Z2 if (prevStep.name == "unitconvert" && curStep.name == "unitconvert" && !prevStep.inverted && !curStep.inverted && prevStep.paramValues.size() == 4 && curStep.paramValues.size() == 2 && prevStep.paramValues[0].keyEquals("xy_in") && prevStep.paramValues[1].keyEquals("z_in") && prevStep.paramValues[2].keyEquals("xy_out") && prevStep.paramValues[3].keyEquals("z_out") && curStep.paramValues[0].keyEquals("xy_in") && curStep.paramValues[1].keyEquals("xy_out") && prevStep.paramValues[2].value == curStep.paramValues[0].value) { const std::string xy_in(prevStep.paramValues[0].value); const std::string z_in(prevStep.paramValues[1].value); const std::string xy_out(curStep.paramValues[1].value); const std::string z_out(prevStep.paramValues[3].value); iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("xy_in", xy_in)); iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("xy_out", xy_out)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); continue; } // clang-format off // A bit odd. Used to simplify geog3d_feet -> EPSG:6318+6360 // of https://github.com/OSGeo/PROJ/issues/3938 // where we get originally // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=us-ft // +step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m // and want it simplified as: // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=deg +z_out=us-ft // // More generally: // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2 // +step +proj=unitconvert +xy_in=X2 +z_in=Z3 +xy_out=X3 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 +z_out=Z2 // clang-format on if (prevStep.name == "unitconvert" && curStep.name == "unitconvert" && !prevStep.inverted && !curStep.inverted && prevStep.paramValues.size() == 4 && curStep.paramValues.size() == 4 && prevStep.paramValues[0].keyEquals("xy_in") && prevStep.paramValues[1].keyEquals("z_in") && prevStep.paramValues[2].keyEquals("xy_out") && prevStep.paramValues[3].keyEquals("z_out") && curStep.paramValues[0].keyEquals("xy_in") && curStep.paramValues[1].keyEquals("z_in") && curStep.paramValues[2].keyEquals("xy_out") && curStep.paramValues[3].keyEquals("z_out") && prevStep.paramValues[2].value == curStep.paramValues[0].value && curStep.paramValues[1].value == curStep.paramValues[3].value) { const std::string xy_in(prevStep.paramValues[0].value); const std::string z_in(prevStep.paramValues[1].value); const std::string xy_out(curStep.paramValues[2].value); const std::string z_out(prevStep.paramValues[3].value); iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("xy_in", xy_in)); iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("xy_out", xy_out)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); continue; } // clang-format off // Variant of above // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z1 // +step +proj=unitconvert +xy_in=X2 +z_in=Z2 +xy_out=X3 +z_out=Z3 // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X3 +z_out=Z3 // clang-format on if (prevStep.name == "unitconvert" && curStep.name == "unitconvert" && !prevStep.inverted && !curStep.inverted && prevStep.paramValues.size() == 4 && curStep.paramValues.size() == 4 && prevStep.paramValues[0].keyEquals("xy_in") && prevStep.paramValues[1].keyEquals("z_in") && prevStep.paramValues[2].keyEquals("xy_out") && prevStep.paramValues[3].keyEquals("z_out") && curStep.paramValues[0].keyEquals("xy_in") && curStep.paramValues[1].keyEquals("z_in") && curStep.paramValues[2].keyEquals("xy_out") && curStep.paramValues[3].keyEquals("z_out") && prevStep.paramValues[1].value == prevStep.paramValues[3].value && curStep.paramValues[0].value == prevStep.paramValues[2].value) { const std::string xy_in(prevStep.paramValues[0].value); const std::string z_in(curStep.paramValues[1].value); const std::string xy_out(curStep.paramValues[2].value); const std::string z_out(curStep.paramValues[3].value); iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("xy_in", xy_in)); iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in)); iterCur->paramValues.emplace_back( Step::KeyValue("xy_out", xy_out)); iterCur->paramValues.emplace_back( Step::KeyValue("z_out", z_out)); deletePrevIter(); continue; } // unitconvert (1), axisswap order=2,1, unitconvert(2) ==> // axisswap order=2,1, unitconvert (1), unitconvert(2) which // will get further optimized by previous case if (std::next(iterCur) != steps.end() && prevStep.name == "unitconvert" && curStep.name == "axisswap" && curStepParamCount == 1 && curStep.paramValues[0].equals("order", "2,1")) { auto iterNext = std::next(iterCur); auto &nextStep = *iterNext; if (nextStep.name == "unitconvert") { std::swap(*iterPrev, *iterCur); ++iterCur; continue; } } // axisswap order=2,1 followed by itself is a no-op if (curStep.name == "axisswap" && prevStep.name == "axisswap" && curStepParamCount == 1 && prevStepParamCount == 1 && curStep.paramValues[0].equals("order", "2,1") && prevStep.paramValues[0].equals("order", "2,1")) { deletePrevAndCurIter(); continue; } // axisswap order=2,-1 followed by axisswap order=-2,1 is a no-op if (curStep.name == "axisswap" && prevStep.name == "axisswap" && curStepParamCount == 1 && prevStepParamCount == 1 && !prevStep.inverted && prevStep.paramValues[0].equals("order", "2,-1") && !curStep.inverted && curStep.paramValues[0].equals("order", "-2,1")) { deletePrevAndCurIter(); continue; } // axisswap order=2,-1 followed by axisswap order=1,-2 is // equivalent to axisswap order=2,1 if (curStep.name == "axisswap" && prevStep.name == "axisswap" && curStepParamCount == 1 && prevStepParamCount == 1 && !prevStep.inverted && prevStep.paramValues[0].equals("order", "2,-1") && !curStep.inverted && curStep.paramValues[0].equals("order", "1,-2")) { prevStep.inverted = false; prevStep.paramValues[0] = Step::KeyValue("order", "2,1"); // Delete this iter iterCur = steps.erase(iterCur); continue; } // axisswap order=2,1 followed by axisswap order=2,-1 is // equivalent to axisswap order=1,-2 // Same for axisswap order=-2,1 followed by axisswap order=2,1 if (curStep.name == "axisswap" && prevStep.name == "axisswap" && curStepParamCount == 1 && prevStepParamCount == 1 && ((prevStep.paramValues[0].equals("order", "2,1") && !curStep.inverted && curStep.paramValues[0].equals("order", "2,-1")) || (prevStep.paramValues[0].equals("order", "-2,1") && !prevStep.inverted && curStep.paramValues[0].equals("order", "2,1")))) { prevStep.inverted = false; prevStep.paramValues[0] = Step::KeyValue("order", "1,-2"); // Delete this iter iterCur = steps.erase(iterCur); continue; } // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can // suppress axisswap if (std::next(iterCur) != steps.end() && prevStep.name == "axisswap" && curStep.name == "unitconvert" && prevStepParamCount == 1 && prevStep.paramValues[0].equals("order", "2,1")) { auto iterNext = std::next(iterCur); auto &nextStep = *iterNext; if (nextStep.name == "axisswap" && nextStep.paramValues.size() == 1 && nextStep.paramValues[0].equals("order", "2,1")) { steps.erase(iterPrev); steps.erase(iterNext); // Coverity complains about invalid usage of iterCur // due to the above erase(iterNext). To the best of our // understanding, this is a false-positive. // coverity[use_iterator] if (iterCur != steps.begin()) iterCur = std::prev(iterCur); if (iterCur == steps.begin()) ++iterCur; continue; } } // "+proj=set +v_4=X" followed by "+proj=set +v_4=X +omit_fwd" // can be optimized as "+proj=set +v_4=X" if (curStep.name == "set" && prevStep.name == "set" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 2 && prevStepParamCount == 1 && curStep.paramValues[0].keyEquals("v_4") && prevStep.paramValues[0].keyEquals("v_4") && curStep.paramValues[1].keyEquals("omit_fwd") && curStep.paramValues[0].value == prevStep.paramValues[0].value) { iterCur = steps.erase(iterCur); continue; } // "+proj=set +v_4=X +omit_inv" followed by "+proj=set +v_4=X" // can be optimized as "+proj=set +v_4=X" if (curStep.name == "set" && prevStep.name == "set" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 1 && prevStepParamCount == 2 && curStep.paramValues[0].keyEquals("v_4") && prevStep.paramValues[0].keyEquals("v_4") && prevStep.paramValues[1].keyEquals("omit_inv") && curStep.paramValues[0].value == prevStep.paramValues[0].value) { deletePrevIter(); continue; } // for practical purposes WGS84 and GRS80 ellipsoids are // equivalents (cartesian transform between both lead to differences // of the order of 1e-14 deg..). // No need to do a cart roundtrip for that... // and actually IGNF uses the GRS80 definition for the WGS84 datum if (curStep.name == "cart" && prevStep.name == "cart" && curStep.inverted == !prevStep.inverted && curStepParamCount == 1 && prevStepParamCount == 1 && ((curStep.paramValues[0].equals("ellps", "WGS84") && prevStep.paramValues[0].equals("ellps", "GRS80")) || (curStep.paramValues[0].equals("ellps", "GRS80") && prevStep.paramValues[0].equals("ellps", "WGS84")))) { deletePrevAndCurIter(); continue; } if (curStep.name == "helmert" && prevStep.name == "helmert" && !curStep.inverted && !prevStep.inverted && curStepParamCount == 3 && curStepParamCount == prevStepParamCount) { std::map leftParamsMap; std::map rightParamsMap; try { for (const auto &kv : prevStep.paramValues) { leftParamsMap[kv.key] = c_locale_stod(kv.value); } for (const auto &kv : curStep.paramValues) { rightParamsMap[kv.key] = c_locale_stod(kv.value); } } catch (const std::invalid_argument &) { break; } const std::string x("x"); const std::string y("y"); const std::string z("z"); if (leftParamsMap.find(x) != leftParamsMap.end() && leftParamsMap.find(y) != leftParamsMap.end() && leftParamsMap.find(z) != leftParamsMap.end() && rightParamsMap.find(x) != rightParamsMap.end() && rightParamsMap.find(y) != rightParamsMap.end() && rightParamsMap.find(z) != rightParamsMap.end()) { const double xSum = leftParamsMap[x] + rightParamsMap[x]; const double ySum = leftParamsMap[y] + rightParamsMap[y]; const double zSum = leftParamsMap[z] + rightParamsMap[z]; if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) { deletePrevAndCurIter(); } else { prevStep.paramValues[0] = Step::KeyValue("x", internal::toString(xSum)); prevStep.paramValues[1] = Step::KeyValue("y", internal::toString(ySum)); prevStep.paramValues[2] = Step::KeyValue("z", internal::toString(zSum)); // Delete this iter iterCur = steps.erase(iterCur); } continue; } } // Helmert followed by its inverse is a no-op if (curStep.name == "helmert" && prevStep.name == "helmert" && !curStep.inverted && !prevStep.inverted && curStepParamCount == prevStepParamCount) { std::set leftParamsSet; std::set rightParamsSet; std::map leftParamsMap; std::map rightParamsMap; for (const auto &kv : prevStep.paramValues) { leftParamsSet.insert(kv.key); leftParamsMap[kv.key] = kv.value; } for (const auto &kv : curStep.paramValues) { rightParamsSet.insert(kv.key); rightParamsMap[kv.key] = kv.value; } if (leftParamsSet == rightParamsSet) { bool doErase = true; try { for (const auto ¶m : leftParamsSet) { if (param == "convention" || param == "t_epoch" || param == "t_obs") { if (leftParamsMap[param] != rightParamsMap[param]) { doErase = false; break; } } else if (c_locale_stod(leftParamsMap[param]) != -c_locale_stod(rightParamsMap[param])) { doErase = false; break; } } } catch (const std::invalid_argument &) { break; } if (doErase) { deletePrevAndCurIter(); continue; } } } // The following should be optimized as a no-op // +step +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=-0.35 // +rz=-0.736 +s=0 +convention=coordinate_frame // +step +inv +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=0.35 // +rz=0.736 +s=0 +convention=position_vector if (curStep.name == "helmert" && prevStep.name == "helmert" && ((curStep.inverted && !prevStep.inverted) || (!curStep.inverted && prevStep.inverted)) && curStepParamCount == prevStepParamCount) { std::set leftParamsSet; std::set rightParamsSet; std::map leftParamsMap; std::map rightParamsMap; for (const auto &kv : prevStep.paramValues) { leftParamsSet.insert(kv.key); leftParamsMap[kv.key] = kv.value; } for (const auto &kv : curStep.paramValues) { rightParamsSet.insert(kv.key); rightParamsMap[kv.key] = kv.value; } if (leftParamsSet == rightParamsSet) { bool doErase = true; try { for (const auto ¶m : leftParamsSet) { if (param == "convention") { // Convention must be different if (leftParamsMap[param] == rightParamsMap[param]) { doErase = false; break; } } else if (param == "rx" || param == "ry" || param == "rz" || param == "drx" || param == "dry" || param == "drz") { // Rotational parameters should have opposite // value if (c_locale_stod(leftParamsMap[param]) != -c_locale_stod(rightParamsMap[param])) { doErase = false; break; } } else { // Non rotational parameters should have the // same value if (leftParamsMap[param] != rightParamsMap[param]) { doErase = false; break; } } } } catch (const std::invalid_argument &) { break; } if (doErase) { deletePrevAndCurIter(); continue; } } } // Optimize patterns like Krovak (South West) to Krovak East North // (also applies to Modified Krovak) // +step +inv +proj=krovak +axis=swu +lat_0=49.5 // +lon_0=24.8333333333333 // +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel // +step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 // +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel // as: // +step +proj=axisswap +order=-2,-1 // Also applies for the symmetrical case where +axis=swu is on the // second step. if (curStep.inverted != prevStep.inverted && curStep.name == prevStep.name && ((curStepParamCount + 1 == prevStepParamCount && prevStep.paramValues[0].equals("axis", "swu")) || (prevStepParamCount + 1 == curStepParamCount && curStep.paramValues[0].equals("axis", "swu")))) { const auto &swStep = (curStepParamCount < prevStepParamCount) ? prevStep : curStep; const auto &enStep = (curStepParamCount < prevStepParamCount) ? curStep : prevStep; // Check if all remaining parameters (except leading axis=swu // in swStep) are identical. bool allSame = true; for (size_t j = 0; j < std::min(curStepParamCount, prevStepParamCount); j++) { if (enStep.paramValues[j] != swStep.paramValues[j + 1]) { allSame = false; break; } } if (allSame) { iterCur->inverted = false; iterCur->name = "axisswap"; iterCur->paramValues.clear(); iterCur->paramValues.emplace_back( Step::KeyValue("order", "-2,-1")); deletePrevIter(); continue; } } // detect a step and its inverse if (curStep.inverted != prevStep.inverted && curStep.name == prevStep.name && curStepParamCount == prevStepParamCount) { bool allSame = true; for (size_t j = 0; j < curStepParamCount; j++) { if (curStep.paramValues[j] != prevStep.paramValues[j]) { allSame = false; break; } } if (allSame) { deletePrevAndCurIter(); continue; } } ++iterCur; } } { auto iterCur = steps.begin(); if (iterCur != steps.end()) { ++iterCur; } while (iterCur != steps.end()) { assert(iterCur != steps.begin()); auto iterPrev = std::prev(iterCur); auto &prevStep = *iterPrev; auto &curStep = *iterCur; const auto curStepParamCount = curStep.paramValues.size(); const auto prevStepParamCount = prevStep.paramValues.size(); // +step +proj=hgridshift +grids=grid_A // +step +proj=vgridshift [...] <== curStep // +step +inv +proj=hgridshift +grids=grid_A // ==> // +step +proj=push +v_1 +v_2 // +step +proj=hgridshift +grids=grid_A +omit_inv // +step +proj=vgridshift [...] // +step +inv +proj=hgridshift +grids=grid_A +omit_fwd // +step +proj=pop +v_1 +v_2 if (std::next(iterCur) != steps.end() && prevStep.name == "hgridshift" && prevStepParamCount == 1 && curStep.name == "vgridshift") { auto iterNext = std::next(iterCur); auto &nextStep = *iterNext; if (nextStep.name == "hgridshift" && nextStep.inverted != prevStep.inverted && nextStep.paramValues.size() == 1 && prevStep.paramValues[0] == nextStep.paramValues[0]) { Step pushStep; pushStep.name = "push"; pushStep.paramValues.emplace_back("v_1"); pushStep.paramValues.emplace_back("v_2"); steps.insert(iterPrev, pushStep); prevStep.paramValues.emplace_back("omit_inv"); nextStep.paramValues.emplace_back("omit_fwd"); Step popStep; popStep.name = "pop"; popStep.paramValues.emplace_back("v_1"); popStep.paramValues.emplace_back("v_2"); steps.insert(std::next(iterNext), popStep); continue; } } // +step +proj=unitconvert +xy_in=rad +xy_out=deg // +step +proj=axisswap +order=2,1 // +step +proj=push +v_1 +v_2 // +step +proj=axisswap +order=2,1 // +step +proj=unitconvert +xy_in=deg +xy_out=rad // +step +proj=vgridshift ... // +step +proj=unitconvert +xy_in=rad +xy_out=deg // +step +proj=axisswap +order=2,1 // +step +proj=pop +v_1 +v_2 // ==> // +step +proj=vgridshift ... // +step +proj=unitconvert +xy_in=rad +xy_out=deg // +step +proj=axisswap +order=2,1 if (prevStep.name == "unitconvert" && prevStepParamCount == 2 && prevStep.paramValues[0].equals("xy_in", "rad") && prevStep.paramValues[1].equals("xy_out", "deg") && curStep.name == "axisswap" && curStepParamCount == 1 && curStep.paramValues[0].equals("order", "2,1")) { auto iterNext = std::next(iterCur); bool ok = false; if (iterNext != steps.end()) { auto &nextStep = *iterNext; if (nextStep.name == "push" && nextStep.paramValues.size() == 2 && nextStep.paramValues[0].keyEquals("v_1") && nextStep.paramValues[1].keyEquals("v_2")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "axisswap" && nextStep.paramValues.size() == 1 && nextStep.paramValues[0].equals("order", "2,1")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "unitconvert" && nextStep.paramValues.size() == 2 && nextStep.paramValues[0].equals("xy_in", "deg") && nextStep.paramValues[1].equals("xy_out", "rad")) { ok = true; iterNext = std::next(iterNext); } } auto iterVgridshift = iterNext; ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "vgridshift") { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "unitconvert" && nextStep.paramValues.size() == 2 && nextStep.paramValues[0].equals("xy_in", "rad") && nextStep.paramValues[1].equals("xy_out", "deg")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "axisswap" && nextStep.paramValues.size() == 1 && nextStep.paramValues[0].equals("order", "2,1")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "pop" && nextStep.paramValues.size() == 2 && nextStep.paramValues[0].keyEquals("v_1") && nextStep.paramValues[1].keyEquals("v_2")) { ok = true; // iterNext = std::next(iterNext); } } if (ok) { steps.erase(iterPrev, iterVgridshift); steps.erase(iterNext, std::next(iterNext)); iterPrev = std::prev(iterVgridshift); iterCur = iterVgridshift; continue; } } // +step +proj=axisswap +order=2,1 // +step +proj=unitconvert +xy_in=deg +xy_out=rad // +step +proj=vgridshift ... // +step +proj=unitconvert +xy_in=rad +xy_out=deg // +step +proj=axisswap +order=2,1 // +step +proj=push +v_1 +v_2 // +step +proj=axisswap +order=2,1 // +step +proj=unitconvert +xy_in=deg +xy_out=rad // ==> // +step +proj=push +v_1 +v_2 // +step +proj=axisswap +order=2,1 // +step +proj=unitconvert +xy_in=deg +xy_out=rad // +step +proj=vgridshift ... if (prevStep.name == "axisswap" && prevStepParamCount == 1 && prevStep.paramValues[0].equals("order", "2,1") && curStep.name == "unitconvert" && curStepParamCount == 2 && !curStep.inverted && curStep.paramValues[0].equals("xy_in", "deg") && curStep.paramValues[1].equals("xy_out", "rad")) { auto iterNext = std::next(iterCur); bool ok = false; auto iterVgridshift = iterNext; if (iterNext != steps.end()) { auto &nextStep = *iterNext; if (nextStep.name == "vgridshift") { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "unitconvert" && !nextStep.inverted && nextStep.paramValues.size() == 2 && nextStep.paramValues[0].equals("xy_in", "rad") && nextStep.paramValues[1].equals("xy_out", "deg")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "axisswap" && nextStep.paramValues.size() == 1 && nextStep.paramValues[0].equals("order", "2,1")) { ok = true; iterNext = std::next(iterNext); } } auto iterPush = iterNext; ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "push" && nextStep.paramValues.size() == 2 && nextStep.paramValues[0].keyEquals("v_1") && nextStep.paramValues[1].keyEquals("v_2")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "axisswap" && nextStep.paramValues.size() == 1 && nextStep.paramValues[0].equals("order", "2,1")) { ok = true; iterNext = std::next(iterNext); } } ok &= iterNext != steps.end(); if (ok) { ok = false; auto &nextStep = *iterNext; if (nextStep.name == "unitconvert" && nextStep.paramValues.size() == 2 && !nextStep.inverted && nextStep.paramValues[0].equals("xy_in", "deg") && nextStep.paramValues[1].equals("xy_out", "rad")) { ok = true; // iterNext = std::next(iterNext); } } if (ok) { Step stepVgridshift(*iterVgridshift); steps.erase(iterPrev, iterPush); steps.insert(std::next(iterNext), std::move(stepVgridshift)); iterPrev = iterPush; iterCur = std::next(iterPush); continue; } } ++iterCur; } } { auto iterCur = steps.begin(); if (iterCur != steps.end()) { ++iterCur; } while (iterCur != steps.end()) { assert(iterCur != steps.begin()); auto iterPrev = std::prev(iterCur); auto &prevStep = *iterPrev; auto &curStep = *iterCur; const auto curStepParamCount = curStep.paramValues.size(); const auto prevStepParamCount = prevStep.paramValues.size(); const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() { iterCur = steps.erase(iterPrev, std::next(iterCur)); if (iterCur != steps.begin()) iterCur = std::prev(iterCur); if (iterCur == steps.begin() && iterCur != steps.end()) ++iterCur; }; // axisswap order=2,1 followed by itself is a no-op if (curStep.name == "axisswap" && prevStep.name == "axisswap" && curStepParamCount == 1 && prevStepParamCount == 1 && curStep.paramValues[0].equals("order", "2,1") && prevStep.paramValues[0].equals("order", "2,1")) { deletePrevAndCurIter(); continue; } // detect a step and its inverse if (curStep.inverted != prevStep.inverted && curStep.name == prevStep.name && curStepParamCount == prevStepParamCount) { bool allSame = true; for (size_t j = 0; j < curStepParamCount; j++) { if (curStep.paramValues[j] != prevStep.paramValues[j]) { allSame = false; break; } } if (allSame) { deletePrevAndCurIter(); continue; } } ++iterCur; } } if (steps.size() > 1 || (steps.size() == 1 && (steps.front().inverted || steps.front().hasKey("omit_inv") || steps.front().hasKey("omit_fwd") || !d->globalParamValues_.empty()))) { d->appendToResult("+proj=pipeline"); for (const auto ¶mValue : d->globalParamValues_) { d->appendToResult("+"); d->result_ += paramValue.key; if (!paramValue.value.empty()) { d->result_ += '='; d->result_ += pj_double_quote_string_param_if_needed(paramValue.value); } } if (d->multiLine_) { d->indentLevel_++; } } for (const auto &step : steps) { std::string curLine; if (!d->result_.empty()) { if (d->multiLine_) { curLine = std::string(static_cast(d->indentLevel_) * d->indentWidth_, ' '); curLine += "+step"; } else { curLine = " +step"; } } if (step.inverted) { curLine += " +inv"; } if (!step.name.empty()) { if (!curLine.empty()) curLine += ' '; curLine += step.isInit ? "+init=" : "+proj="; curLine += step.name; } for (const auto ¶mValue : step.paramValues) { std::string newKV = "+"; newKV += paramValue.key; if (!paramValue.value.empty()) { newKV += '='; newKV += pj_double_quote_string_param_if_needed(paramValue.value); } if (d->maxLineLength_ > 0 && d->multiLine_ && curLine.size() + newKV.size() > static_cast(d->maxLineLength_)) { if (!d->result_.empty()) d->result_ += '\n'; d->result_ += curLine; curLine = std::string(static_cast(d->indentLevel_) * d->indentWidth_ + strlen("+step "), ' '); } else { if (!curLine.empty()) curLine += ' '; } curLine += newKV; } if (d->multiLine_ && !d->result_.empty()) d->result_ += '\n'; d->result_ += curLine; } if (d->result_.empty()) { d->appendToResult("+proj=noop"); } return d->result_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PROJStringFormatter::Convention PROJStringFormatter::convention() const { return d->convention_; } // --------------------------------------------------------------------------- // Return the number of steps in the pipeline. // Note: this value will change after calling toString() that will run // optimizations. size_t PROJStringFormatter::getStepCount() const { return d->steps_.size(); } // --------------------------------------------------------------------------- bool PROJStringFormatter::getUseApproxTMerc() const { return d->useApproxTMerc_; } // --------------------------------------------------------------------------- void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) { d->coordOperationOptimizations_ = enable; } // --------------------------------------------------------------------------- void PROJStringFormatter::Private::appendToResult(const char *str) { if (!result_.empty()) { result_ += ' '; } result_ += str; } // --------------------------------------------------------------------------- static void PROJStringSyntaxParser(const std::string &projString, std::vector &steps, std::vector &globalParamValues, std::string &title) { std::vector tokens; bool hasProj = false; bool hasInit = false; bool hasPipeline = false; std::string projStringModified(projString); // Special case for "+title=several words +foo=bar" if (starts_with(projStringModified, "+title=") && projStringModified.size() > 7 && projStringModified[7] != '"') { const auto plusPos = projStringModified.find(" +", 1); const auto spacePos = projStringModified.find(' '); if (plusPos != std::string::npos && spacePos != std::string::npos && spacePos < plusPos) { std::string tmp("+title="); tmp += pj_double_quote_string_param_if_needed( projStringModified.substr(7, plusPos - 7)); tmp += projStringModified.substr(plusPos); projStringModified = std::move(tmp); } } size_t argc = pj_trim_argc(&projStringModified[0]); char **argv = pj_trim_argv(argc, &projStringModified[0]); for (size_t i = 0; i < argc; i++) { std::string token(argv[i]); if (!hasPipeline && token == "proj=pipeline") { hasPipeline = true; } else if (!hasProj && starts_with(token, "proj=")) { hasProj = true; } else if (!hasInit && starts_with(token, "init=")) { hasInit = true; } tokens.emplace_back(token); } free(argv); if (!hasPipeline) { if (hasProj || hasInit) { steps.push_back(Step()); } for (auto &word : tokens) { if (starts_with(word, "proj=") && !hasInit && steps.back().name.empty()) { assert(hasProj); auto stepName = word.substr(strlen("proj=")); steps.back().name = std::move(stepName); } else if (starts_with(word, "init=")) { assert(hasInit); auto initName = word.substr(strlen("init=")); steps.back().name = std::move(initName); steps.back().isInit = true; } else if (word == "inv") { if (!steps.empty()) { steps.back().inverted = true; } } else if (starts_with(word, "title=")) { title = word.substr(strlen("title=")); } else if (word != "step") { const auto pos = word.find('='); const auto key = word.substr(0, pos); Step::KeyValue pair( (pos != std::string::npos) ? Step::KeyValue(key, word.substr(pos + 1)) : Step::KeyValue(key)); if (steps.empty()) { globalParamValues.push_back(std::move(pair)); } else { steps.back().paramValues.push_back(std::move(pair)); } } } return; } bool inPipeline = false; bool invGlobal = false; for (auto &word : tokens) { if (word == "proj=pipeline") { if (inPipeline) { throw ParsingException("nested pipeline not supported"); } inPipeline = true; } else if (word == "step") { if (!inPipeline) { throw ParsingException("+step found outside pipeline"); } steps.push_back(Step()); } else if (word == "inv") { if (steps.empty()) { invGlobal = true; } else { steps.back().inverted = true; } } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") && steps.back().name.empty()) { auto stepName = word.substr(strlen("proj=")); steps.back().name = std::move(stepName); } else if (inPipeline && !steps.empty() && starts_with(word, "init=") && steps.back().name.empty()) { auto initName = word.substr(strlen("init=")); steps.back().name = std::move(initName); steps.back().isInit = true; } else if (!inPipeline && starts_with(word, "title=")) { title = word.substr(strlen("title=")); } else { const auto pos = word.find('='); auto key = word.substr(0, pos); Step::KeyValue pair((pos != std::string::npos) ? Step::KeyValue(key, word.substr(pos + 1)) : Step::KeyValue(key)); if (steps.empty()) { globalParamValues.emplace_back(std::move(pair)); } else { steps.back().paramValues.emplace_back(std::move(pair)); } } } if (invGlobal && !steps.empty()) { for (auto &step : steps) { step.inverted = !step.inverted; } std::reverse(steps.begin(), steps.end()); } } // --------------------------------------------------------------------------- void PROJStringFormatter::ingestPROJString( const std::string &str) // throw ParsingException { std::vector steps; std::string title; PROJStringSyntaxParser(str, steps, d->globalParamValues_, title); d->steps_.insert(d->steps_.end(), steps.begin(), steps.end()); } // --------------------------------------------------------------------------- void PROJStringFormatter::setCRSExport(bool b) { d->crsExport_ = b; } // --------------------------------------------------------------------------- bool PROJStringFormatter::getCRSExport() const { return d->crsExport_; } // --------------------------------------------------------------------------- void PROJStringFormatter::startInversion() { PROJStringFormatter::Private::InversionStackElt elt; elt.startIter = d->steps_.end(); if (elt.startIter != d->steps_.begin()) { elt.iterValid = true; --elt.startIter; // point to the last valid element } else { elt.iterValid = false; } elt.currentInversionState = !d->inversionStack_.back().currentInversionState; d->inversionStack_.push_back(elt); } // --------------------------------------------------------------------------- void PROJStringFormatter::stopInversion() { assert(!d->inversionStack_.empty()); auto startIter = d->inversionStack_.back().startIter; if (!d->inversionStack_.back().iterValid) { startIter = d->steps_.begin(); } else { ++startIter; // advance after the last valid element we marked above } // Invert the inversion status of the steps between the start point and // the current end of steps for (auto iter = startIter; iter != d->steps_.end(); ++iter) { iter->inverted = !iter->inverted; for (auto ¶mValue : iter->paramValues) { if (paramValue.key == "omit_fwd") paramValue.key = "omit_inv"; else if (paramValue.key == "omit_inv") paramValue.key = "omit_fwd"; } } // And reverse the order of steps in that range as well. std::reverse(startIter, d->steps_.end()); d->inversionStack_.pop_back(); } // --------------------------------------------------------------------------- bool PROJStringFormatter::isInverted() const { return d->inversionStack_.back().currentInversionState; } // --------------------------------------------------------------------------- void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); } // --------------------------------------------------------------------------- void PROJStringFormatter::addStep(const char *stepName) { d->addStep(); d->steps_.back().name.assign(stepName); } // --------------------------------------------------------------------------- void PROJStringFormatter::addStep(const std::string &stepName) { d->addStep(); d->steps_.back().name = stepName; } // --------------------------------------------------------------------------- void PROJStringFormatter::setCurrentStepInverted(bool inverted) { assert(!d->steps_.empty()); d->steps_.back().inverted = inverted; } // --------------------------------------------------------------------------- bool PROJStringFormatter::hasParam(const char *paramName) const { if (!d->steps_.empty()) { for (const auto ¶mValue : d->steps_.back().paramValues) { if (paramValue.keyEquals(paramName)) { return true; } } } return false; } // --------------------------------------------------------------------------- void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; } // --------------------------------------------------------------------------- bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; } // --------------------------------------------------------------------------- void PROJStringFormatter::addParam(const std::string ¶mName) { if (d->steps_.empty()) { d->addStep(); } d->steps_.back().paramValues.push_back(Step::KeyValue(paramName)); } // --------------------------------------------------------------------------- void PROJStringFormatter::addParam(const char *paramName, int val) { addParam(std::string(paramName), val); } void PROJStringFormatter::addParam(const std::string ¶mName, int val) { addParam(paramName, internal::toString(val)); } // --------------------------------------------------------------------------- static std::string formatToString(double val, double precision) { if (std::abs(val * 10 - std::round(val * 10)) < precision) { // For the purpose of // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561 // Latitude of natural of origin to be properly rounded from 55 grad // to // 49.5 deg val = std::round(val * 10) / 10; } return normalizeSerializedString(internal::toString(val)); } // --------------------------------------------------------------------------- void PROJStringFormatter::addParam(const char *paramName, double val) { addParam(std::string(paramName), val); } void PROJStringFormatter::addParam(const std::string ¶mName, double val) { if (paramName == "dt") { addParam(paramName, normalizeSerializedString(internal::toString(val, 7))); } else { addParam(paramName, formatToString(val, 1e-8)); } } // --------------------------------------------------------------------------- void PROJStringFormatter::addParam(const char *paramName, const std::vector &vals) { std::string paramValue; for (size_t i = 0; i < vals.size(); ++i) { if (i > 0) { paramValue += ','; } paramValue += formatToString(vals[i], 1e-8); } addParam(paramName, paramValue); } // --------------------------------------------------------------------------- void PROJStringFormatter::addParam(const char *paramName, const char *val) { addParam(std::string(paramName), val); } void PROJStringFormatter::addParam(const char *paramName, const std::string &val) { addParam(std::string(paramName), val); } void PROJStringFormatter::addParam(const std::string ¶mName, const char *val) { addParam(paramName, std::string(val)); } // --------------------------------------------------------------------------- void PROJStringFormatter::addParam(const std::string ¶mName, const std::string &val) { if (d->steps_.empty()) { d->addStep(); } d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val)); } // --------------------------------------------------------------------------- void PROJStringFormatter::setTOWGS84Parameters( const std::vector ¶ms) { d->toWGS84Parameters_ = params; } // --------------------------------------------------------------------------- const std::vector &PROJStringFormatter::getTOWGS84Parameters() const { return d->toWGS84Parameters_; } // --------------------------------------------------------------------------- std::set PROJStringFormatter::getUsedGridNames() const { std::set res; for (const auto &step : d->steps_) { for (const auto ¶m : step.paramValues) { if (param.keyEquals("grids") || param.keyEquals("file")) { const auto gridNames = split(param.value, ","); for (const auto &gridName : gridNames) { res.insert(gridName); } } } } return res; } // --------------------------------------------------------------------------- bool PROJStringFormatter::requiresPerCoordinateInputTime() const { for (const auto &step : d->steps_) { if (step.name == "set" && !step.inverted) { for (const auto ¶m : step.paramValues) { if (param.keyEquals("v_4")) { return false; } } } else if (step.name == "helmert") { for (const auto ¶m : step.paramValues) { if (param.keyEquals("t_epoch")) { return true; } } } else if (step.name == "deformation") { for (const auto ¶m : step.paramValues) { if (param.keyEquals("t_epoch")) { return true; } } } else if (step.name == "defmodel") { return true; } } return false; } // --------------------------------------------------------------------------- void PROJStringFormatter::setVDatumExtension(const std::string &filename, const std::string &geoidCRSValue) { d->vDatumExtension_ = filename; d->geoidCRSValue_ = geoidCRSValue; } // --------------------------------------------------------------------------- const std::string &PROJStringFormatter::getVDatumExtension() const { return d->vDatumExtension_; } // --------------------------------------------------------------------------- const std::string &PROJStringFormatter::getGeoidCRSValue() const { return d->geoidCRSValue_; } // --------------------------------------------------------------------------- void PROJStringFormatter::setHDatumExtension(const std::string &filename) { d->hDatumExtension_ = filename; } // --------------------------------------------------------------------------- const std::string &PROJStringFormatter::getHDatumExtension() const { return d->hDatumExtension_; } // --------------------------------------------------------------------------- void PROJStringFormatter::setGeogCRSOfCompoundCRS( const crs::GeographicCRSPtr &crs) { d->geogCRSOfCompoundCRS_ = crs; } // --------------------------------------------------------------------------- const crs::GeographicCRSPtr & PROJStringFormatter::getGeogCRSOfCompoundCRS() const { return d->geogCRSOfCompoundCRS_; } // --------------------------------------------------------------------------- void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) { assert(d->omitProjLongLatIfPossible_ ^ omit); d->omitProjLongLatIfPossible_ = omit; } // --------------------------------------------------------------------------- bool PROJStringFormatter::omitProjLongLatIfPossible() const { return d->omitProjLongLatIfPossible_; } // --------------------------------------------------------------------------- void PROJStringFormatter::pushOmitZUnitConversion() { d->omitZUnitConversion_.push_back(true); } // --------------------------------------------------------------------------- void PROJStringFormatter::popOmitZUnitConversion() { assert(d->omitZUnitConversion_.size() > 1); d->omitZUnitConversion_.pop_back(); } // --------------------------------------------------------------------------- bool PROJStringFormatter::omitZUnitConversion() const { return d->omitZUnitConversion_.back(); } // --------------------------------------------------------------------------- void PROJStringFormatter::pushOmitHorizontalConversionInVertTransformation() { d->omitHorizontalConversionInVertTransformation_.push_back(true); } // --------------------------------------------------------------------------- void PROJStringFormatter::popOmitHorizontalConversionInVertTransformation() { assert(d->omitHorizontalConversionInVertTransformation_.size() > 1); d->omitHorizontalConversionInVertTransformation_.pop_back(); } // --------------------------------------------------------------------------- bool PROJStringFormatter::omitHorizontalConversionInVertTransformation() const { return d->omitHorizontalConversionInVertTransformation_.back(); } // --------------------------------------------------------------------------- void PROJStringFormatter::setLegacyCRSToCRSContext(bool legacyContext) { d->legacyCRSToCRSContext_ = legacyContext; } // --------------------------------------------------------------------------- bool PROJStringFormatter::getLegacyCRSToCRSContext() const { return d->legacyCRSToCRSContext_; } // --------------------------------------------------------------------------- /** Asks for a "normalized" output during toString(), aimed at comparing two * strings for equivalence. * * This consists for now in sorting the +key=value option in lexicographic * order. */ PROJStringFormatter &PROJStringFormatter::setNormalizeOutput() { d->normalizeOutput_ = true; return *this; } // --------------------------------------------------------------------------- const DatabaseContextPtr &PROJStringFormatter::databaseContext() const { return d->dbContext_; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PROJStringParser::Private { DatabaseContextPtr dbContext_{}; PJ_CONTEXT *ctx_{}; bool usePROJ4InitRules_ = false; std::vector warningList_{}; std::string projString_{}; std::vector steps_{}; std::vector globalParamValues_{}; std::string title_{}; bool ignoreNadgrids_ = false; template // cppcheck-suppress functionStatic bool hasParamValue(Step &step, const T key) { for (auto &pair : globalParamValues_) { if (ci_equal(pair.key, key)) { pair.usedByParser = true; return true; } } for (auto &pair : step.paramValues) { if (ci_equal(pair.key, key)) { pair.usedByParser = true; return true; } } return false; } template // cppcheck-suppress functionStatic const std::string &getGlobalParamValue(T key) { for (auto &pair : globalParamValues_) { if (ci_equal(pair.key, key)) { pair.usedByParser = true; return pair.value; } } return emptyString; } template // cppcheck-suppress functionStatic const std::string &getParamValue(Step &step, const T key) { for (auto &pair : globalParamValues_) { if (ci_equal(pair.key, key)) { pair.usedByParser = true; return pair.value; } } for (auto &pair : step.paramValues) { if (ci_equal(pair.key, key)) { pair.usedByParser = true; return pair.value; } } return emptyString; } static const std::string &getParamValueK(Step &step) { for (auto &pair : step.paramValues) { if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) { pair.usedByParser = true; return pair.value; } } return emptyString; } // cppcheck-suppress functionStatic bool hasUnusedParameters(const Step &step) const { if (steps_.size() == 1) { for (const auto &pair : step.paramValues) { if (pair.key != "no_defs" && !pair.usedByParser) { return true; } } } return false; } // cppcheck-suppress functionStatic std::string guessBodyName(double a); PrimeMeridianNNPtr buildPrimeMeridian(Step &step); GeodeticReferenceFrameNNPtr buildDatum(Step &step, const std::string &title); GeodeticCRSNNPtr buildGeodeticCRS(int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis); GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert); CRSNNPtr buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geogCRS, int iUnitConvert, int iAxisSwap); CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs); UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName, const std::string &toMeterParamName); enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE }; std::vector processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap, AxisType axisType, bool ignorePROJAxis); EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis); SphericalCSNNPtr buildSphericalCS(int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis); }; //! @endcond // --------------------------------------------------------------------------- PROJStringParser::PROJStringParser() : d(std::make_unique()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PROJStringParser::~PROJStringParser() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Attach a database context, to allow queries in it if needed. */ PROJStringParser & PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) { d->dbContext_ = dbContext; return *this; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) { d->ctx_ = ctx; return *this; } //! @endcond // --------------------------------------------------------------------------- /** \brief Set how init=epsg:XXXX syntax should be interpreted. * * @param enable When set to true, * init=epsg:XXXX syntax will be allowed and will be interpreted according to * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude * order and will expect/output coordinates in radians. ProjectedCRS will have * easting, northing axis order (except the ones with Transverse Mercator South * Orientated projection). */ PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) { d->usePROJ4InitRules_ = enable; return *this; } // --------------------------------------------------------------------------- /** \brief Return the list of warnings found during parsing. */ std::vector PROJStringParser::warningList() const { return d->warningList_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- static const struct LinearUnitDesc { const char *projName; const char *convToMeter; const char *name; int epsgCode; } linearUnitDescs[] = { {"mm", "0.001", "millimetre", 1025}, {"cm", "0.01", "centimetre", 1033}, {"m", "1.0", "metre", 9001}, {"meter", "1.0", "metre", 9001}, // alternative {"metre", "1.0", "metre", 9001}, // alternative {"ft", "0.3048", "foot", 9002}, {"us-ft", "0.3048006096012192", "US survey foot", 9003}, {"fath", "1.8288", "fathom", 9014}, {"kmi", "1852", "nautical mile", 9030}, {"us-ch", "20.11684023368047", "US survey chain", 9033}, {"us-mi", "1609.347218694437", "US survey mile", 9035}, {"km", "1000.0", "kilometre", 9036}, {"ind-ft", "0.30479841", "Indian foot (1937)", 9081}, {"ind-yd", "0.91439523", "Indian yard (1937)", 9085}, {"mi", "1609.344", "Statute mile", 9093}, {"yd", "0.9144", "yard", 9096}, {"ch", "20.1168", "chain", 9097}, {"link", "0.201168", "link", 9098}, {"dm", "0.1", "decimetre", 0}, // no EPSG equivalent {"in", "0.0254", "inch", 0}, // no EPSG equivalent {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent {"ind-ch", "20.11669506", "Indian chain", 0}, // no EPSG equivalent }; static const LinearUnitDesc *getLinearUnits(const std::string &projName) { for (const auto &desc : linearUnitDescs) { if (desc.projName == projName) return &desc; } return nullptr; } static const LinearUnitDesc *getLinearUnits(double toMeter) { for (const auto &desc : linearUnitDescs) { if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) < 1e-10 * toMeter) { return &desc; } } return nullptr; } // --------------------------------------------------------------------------- static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) { std::string unitsCode; if (unitsMatch->epsgCode) { std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << unitsMatch->epsgCode; unitsCode = buffer.str(); } return UnitOfMeasure( unitsMatch->name, c_locale_stod(unitsMatch->convToMeter), UnitOfMeasure::Type::LINEAR, unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode); } // --------------------------------------------------------------------------- static UnitOfMeasure _buildUnit(double to_meter_value) { // TODO: look-up in EPSG catalog if (to_meter_value == 0) { throw ParsingException("invalid unit value"); } return UnitOfMeasure("unknown", to_meter_value, UnitOfMeasure::Type::LINEAR); } // --------------------------------------------------------------------------- UnitOfMeasure PROJStringParser::Private::buildUnit(Step &step, const std::string &unitsParamName, const std::string &toMeterParamName) { UnitOfMeasure unit = UnitOfMeasure::METRE; const LinearUnitDesc *unitsMatch = nullptr; const auto &projUnits = getParamValue(step, unitsParamName); if (!projUnits.empty()) { unitsMatch = getLinearUnits(projUnits); if (unitsMatch == nullptr) { throw ParsingException("unhandled " + unitsParamName + "=" + projUnits); } } const auto &toMeter = getParamValue(step, toMeterParamName); if (!toMeter.empty()) { double to_meter_value; try { to_meter_value = c_locale_stod(toMeter); } catch (const std::invalid_argument &) { throw ParsingException("invalid value for " + toMeterParamName); } unitsMatch = getLinearUnits(to_meter_value); if (unitsMatch == nullptr) { unit = _buildUnit(to_meter_value); } } if (unitsMatch) { unit = _buildUnit(unitsMatch); } return unit; } // --------------------------------------------------------------------------- static const struct DatumDesc { const char *projName; const char *gcsName; int gcsCode; const char *datumName; int datumCode; const char *ellipsoidName; int ellipsoidCode; double a; double rf; } datumDescs[] = { {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121, "GRS 1980", 7019, 6378137, 298.257222101}, {"potsdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314, "Bessel 1841", 7004, 6377397.155, 299.1528128}, {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011, 6378249.2, 293.4660213}, {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312, "Bessel 1841", 7004, 6377397.155, 299.1528128}, {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002, 6377340.189, 299.3249646}, {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272, "International 1924", 7022, 6378388, 297}, {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001, 6377563.396, 299.3249646}, }; // --------------------------------------------------------------------------- static bool isGeographicStep(const std::string &name) { return name == "longlat" || name == "lonlat" || name == "latlong" || name == "latlon"; } // --------------------------------------------------------------------------- static bool isGeocentricStep(const std::string &name) { return name == "geocent" || name == "cart"; } // --------------------------------------------------------------------------- static bool isTopocentricStep(const std::string &name) { return name == "topocentric"; } // --------------------------------------------------------------------------- static bool isProjectedStep(const std::string &name) { if (name == "etmerc" || name == "utm" || !getMappingsFromPROJName(name).empty()) { return true; } // IMPROVE ME: have a better way of distinguishing projections from // other // transformations. if (name == "pipeline" || name == "geoc" || name == "deformation" || name == "helmert" || name == "hgridshift" || name == "molodensky" || name == "vgridshift") { return false; } const auto *operations = proj_list_operations(); for (int i = 0; operations[i].id != nullptr; ++i) { if (name == operations[i].id) { return true; } } return false; } // --------------------------------------------------------------------------- static PropertyMap createMapWithUnknownName() { return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown"); } // --------------------------------------------------------------------------- PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) { PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH; const auto &pmStr = getParamValue(step, "pm"); if (!pmStr.empty()) { char *end; double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG; if (pmValue != HUGE_VAL && *end == '\0') { pm = PrimeMeridian::create(createMapWithUnknownName(), Angle(pmValue)); } else { bool found = false; if (pmStr == "paris") { found = true; pm = PrimeMeridian::PARIS; } auto proj_prime_meridians = proj_list_prime_meridians(); for (int i = 0; !found && proj_prime_meridians[i].id != nullptr; i++) { if (pmStr == proj_prime_meridians[i].id) { found = true; std::string name = static_cast(::toupper(pmStr[0])) + pmStr.substr(1); pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) * RAD_TO_DEG; pm = PrimeMeridian::create( PropertyMap().set(IdentifiedObject::NAME_KEY, name), Angle(pmValue)); break; } } if (!found) { throw ParsingException("unknown pm " + pmStr); } } } return pm; } // --------------------------------------------------------------------------- std::string PROJStringParser::Private::guessBodyName(double a) { auto ret = Ellipsoid::guessBodyName(dbContext_, a); if (ret == NON_EARTH_BODY && dbContext_ == nullptr && ctx_ != nullptr) { dbContext_ = ctx_->get_cpp_context()->getDatabaseContext().as_nullable(); if (dbContext_) { ret = Ellipsoid::guessBodyName(dbContext_, a); } } return ret; } // --------------------------------------------------------------------------- GeodeticReferenceFrameNNPtr PROJStringParser::Private::buildDatum(Step &step, const std::string &title) { std::string ellpsStr = getParamValue(step, "ellps"); const auto &datumStr = getParamValue(step, "datum"); const auto &RStr = getParamValue(step, "R"); const auto &aStr = getParamValue(step, "a"); const auto &bStr = getParamValue(step, "b"); const auto &rfStr = getParamValue(step, "rf"); const auto &fStr = getParamValue(step, "f"); const auto &esStr = getParamValue(step, "es"); const auto &eStr = getParamValue(step, "e"); double a = -1.0; double b = -1.0; double rf = -1.0; const util::optional optionalEmptyString{}; const bool numericParamPresent = !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() || !fStr.empty() || !esStr.empty() || !eStr.empty(); if (!numericParamPresent && ellpsStr.empty() && datumStr.empty() && (step.name == "krovak" || step.name == "mod_krovak")) { ellpsStr = "bessel"; } PrimeMeridianNNPtr pm(buildPrimeMeridian(step)); PropertyMap grfMap; const auto &nadgrids = getParamValue(step, "nadgrids"); const auto &towgs84 = getParamValue(step, "towgs84"); std::string datumNameSuffix; if (!nadgrids.empty()) { datumNameSuffix = " using nadgrids=" + nadgrids; } else if (!towgs84.empty()) { datumNameSuffix = " using towgs84=" + towgs84; } // It is arguable that we allow the prime meridian of a datum defined by // its name to be overridden, but this is found at least in a regression // test // of GDAL. So let's keep the ellipsoid part of the datum in that case and // use the specified prime meridian. const auto overridePmIfNeeded = [&pm, &datumNameSuffix](const GeodeticReferenceFrameNNPtr &grf) { if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) { return grf; } else { return GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, UNKNOWN_BASED_ON + grf->ellipsoid()->nameStr() + " ellipsoid" + datumNameSuffix), grf->ellipsoid(), grf->anchorDefinition(), pm); } }; // R take precedence if (!RStr.empty()) { double R; try { R = c_locale_stod(RStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid R value"); } auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), Length(R), guessBodyName(R)); return GeodeticReferenceFrame::create( grfMap.set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" + datumNameSuffix : title), ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); } if (!datumStr.empty()) { auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap, &optionalEmptyString, &pm]() { if (datumStr == "WGS84") { return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326); } else if (datumStr == "NAD83") { return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269); } else if (datumStr == "NAD27") { return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267); } else { for (const auto &datumDesc : datumDescs) { if (datumStr == datumDesc.projName) { (void)datumDesc.gcsName; // to please cppcheck (void)datumDesc.gcsCode; // to please cppcheck auto ellipsoid = Ellipsoid::createFlattenedSphere( grfMap .set(IdentifiedObject::NAME_KEY, datumDesc.ellipsoidName) .set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, datumDesc.ellipsoidCode), Length(datumDesc.a), Scale(datumDesc.rf)); return GeodeticReferenceFrame::create( grfMap .set(IdentifiedObject::NAME_KEY, datumDesc.datumName) .set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, datumDesc.datumCode), ellipsoid, optionalEmptyString, pm); } } } throw ParsingException("unknown datum " + datumStr); }(); if (!numericParamPresent) { return l_datum; } a = l_datum->ellipsoid()->semiMajorAxis().getSIValue(); rf = l_datum->ellipsoid()->computedInverseFlattening(); } else if (!ellpsStr.empty()) { auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, &pm, &datumNameSuffix]() { if (ellpsStr == "WGS84") { return GeodeticReferenceFrame::create( grfMap.set(IdentifiedObject::NAME_KEY, title.empty() ? std::string(UNKNOWN_BASED_ON) .append("WGS 84 ellipsoid") .append(datumNameSuffix) : title), Ellipsoid::WGS84, optionalEmptyString, pm); } else if (ellpsStr == "GRS80") { return GeodeticReferenceFrame::create( grfMap.set(IdentifiedObject::NAME_KEY, title.empty() ? std::string(UNKNOWN_BASED_ON) .append("GRS 1980 ellipsoid") .append(datumNameSuffix) : title), Ellipsoid::GRS1980, optionalEmptyString, pm); } else { auto proj_ellps = proj_list_ellps(); for (int i = 0; proj_ellps[i].id != nullptr; i++) { if (ellpsStr == proj_ellps[i].id) { assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); const double a_iter = c_locale_stod(proj_ellps[i].major + 2); EllipsoidPtr ellipsoid; PropertyMap ellpsMap; if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { const double b_iter = c_locale_stod(proj_ellps[i].ell + 2); ellipsoid = Ellipsoid::createTwoAxis( ellpsMap.set(IdentifiedObject::NAME_KEY, proj_ellps[i].name), Length(a_iter), Length(b_iter)) .as_nullable(); } else { assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); const double rf_iter = c_locale_stod(proj_ellps[i].ell + 3); ellipsoid = Ellipsoid::createFlattenedSphere( ellpsMap.set(IdentifiedObject::NAME_KEY, proj_ellps[i].name), Length(a_iter), Scale(rf_iter)) .as_nullable(); } return GeodeticReferenceFrame::create( grfMap.set(IdentifiedObject::NAME_KEY, title.empty() ? std::string(UNKNOWN_BASED_ON) .append(proj_ellps[i].name) .append(" ellipsoid") .append(datumNameSuffix) : title), NN_NO_CHECK(ellipsoid), optionalEmptyString, pm); } } throw ParsingException("unknown ellipsoid " + ellpsStr); } }(); if (!numericParamPresent) { return l_datum; } a = l_datum->ellipsoid()->semiMajorAxis().getSIValue(); if (l_datum->ellipsoid()->semiMinorAxis().has_value()) { b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue(); } else { rf = l_datum->ellipsoid()->computedInverseFlattening(); } } if (!aStr.empty()) { try { a = c_locale_stod(aStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid a value"); } } const auto createGRF = [&grfMap, &title, &optionalEmptyString, &datumNameSuffix, &pm](const EllipsoidNNPtr &ellipsoid) { std::string datumName(title); if (title.empty()) { if (ellipsoid->nameStr() != "unknown") { datumName = UNKNOWN_BASED_ON; datumName += ellipsoid->nameStr(); datumName += " ellipsoid"; } else { datumName = "unknown"; } datumName += datumNameSuffix; } return GeodeticReferenceFrame::create( grfMap.set(IdentifiedObject::NAME_KEY, datumName), ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm)); }; if (a > 0 && (b > 0 || !bStr.empty())) { if (!bStr.empty()) { try { b = c_locale_stod(bStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid b value"); } } auto ellipsoid = Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a), Length(b), guessBodyName(a)) ->identify(); return createGRF(ellipsoid); } else if (a > 0 && (rf >= 0 || !rfStr.empty())) { if (!rfStr.empty()) { try { rf = c_locale_stod(rfStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid rf value"); } } auto ellipsoid = Ellipsoid::createFlattenedSphere( createMapWithUnknownName(), Length(a), Scale(rf), guessBodyName(a)) ->identify(); return createGRF(ellipsoid); } else if (a > 0 && !fStr.empty()) { double f; try { f = c_locale_stod(fStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid f value"); } auto ellipsoid = Ellipsoid::createFlattenedSphere( createMapWithUnknownName(), Length(a), Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) ->identify(); return createGRF(ellipsoid); } else if (a > 0 && !eStr.empty()) { double e; try { e = c_locale_stod(eStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid e value"); } double alpha = asin(e); /* angular eccentricity */ double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */ auto ellipsoid = Ellipsoid::createFlattenedSphere( createMapWithUnknownName(), Length(a), Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) ->identify(); return createGRF(ellipsoid); } else if (a > 0 && !esStr.empty()) { double es; try { es = c_locale_stod(esStr); } catch (const std::invalid_argument &) { throw ParsingException("Invalid es value"); } double f = 1 - sqrt(1 - es); auto ellipsoid = Ellipsoid::createFlattenedSphere( createMapWithUnknownName(), Length(a), Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a)) ->identify(); return createGRF(ellipsoid); } // If only a is specified, create a sphere if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() && esStr.empty()) { auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(), Length(a), guessBodyName(a)); return createGRF(ellipsoid); } if (!bStr.empty() && aStr.empty()) { throw ParsingException("b found, but a missing"); } if (!rfStr.empty() && aStr.empty()) { throw ParsingException("rf found, but a missing"); } if (!fStr.empty() && aStr.empty()) { throw ParsingException("f found, but a missing"); } if (!eStr.empty() && aStr.empty()) { throw ParsingException("e found, but a missing"); } if (!esStr.empty() && aStr.empty()) { throw ParsingException("es found, but a missing"); } return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326); } // --------------------------------------------------------------------------- static const MeridianPtr nullMeridian{}; static CoordinateSystemAxisNNPtr createAxis(const std::string &name, const std::string &abbreviation, const AxisDirection &direction, const common::UnitOfMeasure &unit, const MeridianPtr &meridian = nullMeridian) { return CoordinateSystemAxis::create( PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation, direction, unit, meridian); } std::vector PROJStringParser::Private::processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap, AxisType axisType, bool ignorePROJAxis) { assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap")); const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR; const bool isSpherical = isGeographic && hasParamValue(step, "geoc"); const auto &eastName = isSpherical ? "Planetocentric longitude" : isGeographic ? AxisName::Longitude : AxisName::Easting; const auto &eastAbbev = isSpherical ? "V" : isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E; const auto &eastDir = isGeographic ? AxisDirection::EAST : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH : (axisType == AxisType::SOUTH_POLE) ? AxisDirection::NORTH : AxisDirection::EAST; CoordinateSystemAxisNNPtr east = createAxis( eastName, eastAbbev, eastDir, unit, (!isGeographic && (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE)) ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable() : nullMeridian); const auto &northName = isSpherical ? "Planetocentric latitude" : isGeographic ? AxisName::Latitude : AxisName::Northing; const auto &northAbbev = isSpherical ? "U" : isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N; const auto &northDir = isGeographic ? AxisDirection::NORTH : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH /*: (axisType == AxisType::SOUTH_POLE) ? AxisDirection::NORTH*/ : AxisDirection::NORTH; const CoordinateSystemAxisNNPtr north = createAxis( northName, northAbbev, northDir, unit, isGeographic ? nullMeridian : (axisType == AxisType::NORTH_POLE) ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable() : (axisType == AxisType::SOUTH_POLE) ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)).as_nullable() : nullMeridian); CoordinateSystemAxisNNPtr west = createAxis(isSpherical ? "Planetocentric longitude" : isGeographic ? AxisName::Longitude : AxisName::Westing, isSpherical ? "V" : isGeographic ? AxisAbbreviation::lon : std::string(), AxisDirection::WEST, unit); CoordinateSystemAxisNNPtr south = createAxis(isSpherical ? "Planetocentric latitude" : isGeographic ? AxisName::Latitude : AxisName::Southing, isSpherical ? "U" : isGeographic ? AxisAbbreviation::lat : std::string(), AxisDirection::SOUTH, unit); std::vector axis{east, north}; const auto &axisStr = getParamValue(step, "axis"); if (!ignorePROJAxis && !axisStr.empty()) { if (axisStr.size() == 3) { for (int i = 0; i < 2; i++) { if (axisStr[i] == 'n') { axis[i] = north; } else if (axisStr[i] == 's') { axis[i] = south; } else if (axisStr[i] == 'e') { axis[i] = east; } else if (axisStr[i] == 'w') { axis[i] = west; } else { throw ParsingException("Unhandled axis=" + axisStr); } } } else { throw ParsingException("Unhandled axis=" + axisStr); } } else if (iAxisSwap >= 0) { auto &stepAxisSwap = steps_[iAxisSwap]; const auto &orderStr = getParamValue(stepAxisSwap, "order"); auto orderTab = split(orderStr, ','); if (orderTab.size() != 2) { throw ParsingException("Unhandled order=" + orderStr); } if (stepAxisSwap.inverted) { throw ParsingException("Unhandled +inv for +proj=axisswap"); } for (size_t i = 0; i < 2; i++) { if (orderTab[i] == "1") { axis[i] = east; } else if (orderTab[i] == "-1") { axis[i] = west; } else if (orderTab[i] == "2") { axis[i] = north; } else if (orderTab[i] == "-2") { axis[i] = south; } else { throw ParsingException("Unhandled order=" + orderStr); } } } else if ((step.name == "krovak" || step.name == "mod_krovak") && hasParamValue(step, "czech")) { axis[0] = std::move(west); axis[1] = std::move(south); } return axis; } // --------------------------------------------------------------------------- EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS( int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) { auto &step = steps_[iStep]; assert(iUnitConvert < 0 || ci_equal(steps_[iUnitConvert].name, "unitconvert")); UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; if (iUnitConvert >= 0) { auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); if (stepUnitConvert.inverted) { std::swap(xy_in, xy_out); } if (iUnitConvert < iStep) { std::swap(xy_in, xy_out); } if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" || (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) { throw ParsingException("unhandled values for xy_in and/or xy_out"); } if (*xy_out == "rad") { angularUnit = UnitOfMeasure::RADIAN; } else if (*xy_out == "grad") { angularUnit = UnitOfMeasure::GRAD; } } std::vector axis = processAxisSwap( step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis); CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Ellipsoidal_height), AxisAbbreviation::h, AxisDirection::UP, buildUnit(step, "vunits", "vto_meter")); return (!hasParamValue(step, "geoidgrids") && (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter"))) ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up) : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]); } // --------------------------------------------------------------------------- SphericalCSNNPtr PROJStringParser::Private::buildSphericalCS( int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) { auto &step = steps_[iStep]; assert(iUnitConvert < 0 || ci_equal(steps_[iUnitConvert].name, "unitconvert")); UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; if (iUnitConvert >= 0) { auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); if (stepUnitConvert.inverted) { std::swap(xy_in, xy_out); } if (iUnitConvert < iStep) { std::swap(xy_in, xy_out); } if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" || (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) { throw ParsingException("unhandled values for xy_in and/or xy_out"); } if (*xy_out == "rad") { angularUnit = UnitOfMeasure::RADIAN; } else if (*xy_out == "grad") { angularUnit = UnitOfMeasure::GRAD; } } std::vector axis = processAxisSwap( step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis); return SphericalCS::create(emptyPropertyMap, axis[0], axis[1]); } // --------------------------------------------------------------------------- static double getNumericValue(const std::string ¶mValue, bool *pHasError = nullptr) { bool success; double value = c_locale_stod(paramValue, success); if (pHasError) *pHasError = !success; return value; } // --------------------------------------------------------------------------- namespace { template inline void ignoreRetVal(T) {} } // namespace GeodeticCRSNNPtr PROJStringParser::Private::buildGeodeticCRS( int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) { auto &step = steps_[iStep]; const bool l_isGeographicStep = isGeographicStep(step.name); const auto &title = l_isGeographicStep ? title_ : emptyString; // units=m is often found in the wild. // No need to create a extension string for this ignoreRetVal(hasParamValue(step, "units")); auto datum = buildDatum(step, title); auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); if (l_isGeographicStep && (hasUnusedParameters(step) || getNumericValue(getParamValue(step, "lon_0")) != 0.0)) { props.set("EXTENSION_PROJ4", projString_); } props.set("IMPLICIT_CS", true); if (!hasParamValue(step, "geoc")) { auto cs = buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis); return GeographicCRS::create(props, datum, cs); } else { auto cs = buildSphericalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis); return GeodeticCRS::create(props, datum, cs); } } // --------------------------------------------------------------------------- GeodeticCRSNNPtr PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) { auto &step = steps_[iStep]; assert(isGeocentricStep(step.name) || isTopocentricStep(step.name)); assert(iUnitConvert < 0 || ci_equal(steps_[iUnitConvert].name, "unitconvert")); const auto &title = title_; auto datum = buildDatum(step, title); UnitOfMeasure unit = buildUnit(step, "units", ""); if (iUnitConvert >= 0) { auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); const std::string *z_in = &getParamValue(stepUnitConvert, "z_in"); const std::string *z_out = &getParamValue(stepUnitConvert, "z_out"); if (stepUnitConvert.inverted) { std::swap(xy_in, xy_out); std::swap(z_in, z_out); } if (xy_in->empty() || xy_out->empty() || *xy_in != "m" || *z_in != "m" || *xy_out != *z_out) { throw ParsingException( "unhandled values for xy_in, z_in, xy_out or z_out"); } const LinearUnitDesc *unitsMatch = nullptr; try { double to_meter_value = c_locale_stod(*xy_out); unitsMatch = getLinearUnits(to_meter_value); if (unitsMatch == nullptr) { unit = _buildUnit(to_meter_value); } } catch (const std::invalid_argument &) { unitsMatch = getLinearUnits(*xy_out); if (!unitsMatch) { throw ParsingException( "unhandled values for xy_in, z_in, xy_out or z_out"); } unit = _buildUnit(unitsMatch); } } auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); auto cs = CartesianCS::createGeocentric(unit); if (hasUnusedParameters(step)) { props.set("EXTENSION_PROJ4", projString_); } return GeodeticCRS::create(props, datum, cs); } // --------------------------------------------------------------------------- CRSNNPtr PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs) { auto &step = steps_[iStep]; const auto &nadgrids = getParamValue(step, "nadgrids"); const auto &towgs84 = getParamValue(step, "towgs84"); // nadgrids has the priority over towgs84 if (!ignoreNadgrids_ && !nadgrids.empty()) { crs = BoundCRS::createFromNadgrids(crs, nadgrids); } else if (!towgs84.empty()) { std::vector towgs84Values; const auto tokens = split(towgs84, ','); for (const auto &str : tokens) { try { towgs84Values.push_back(c_locale_stod(str)); } catch (const std::invalid_argument &) { throw ParsingException("Non numerical value in towgs84 clause"); } } if (towgs84Values.size() == 7 && dbContext_) { if (dbContext_->toWGS84AutocorrectWrongValues( towgs84Values[0], towgs84Values[1], towgs84Values[2], towgs84Values[3], towgs84Values[4], towgs84Values[5], towgs84Values[6])) { for (auto &pair : step.paramValues) { if (ci_equal(pair.key, "towgs84")) { pair.value.clear(); for (int i = 0; i < 7; ++i) { if (i > 0) pair.value += ','; pair.value += internal::toString(towgs84Values[i]); } break; } } } } crs = BoundCRS::createFromTOWGS84(crs, towgs84Values); } const auto &geoidgrids = getParamValue(step, "geoidgrids"); if (!geoidgrids.empty()) { auto vdatum = VerticalReferenceFrame::create( PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown using geoidgrids=" + geoidgrids)); const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter"); auto vcrs = VerticalCRS::create(createMapWithUnknownName(), vdatum, VerticalCS::createGravityRelatedHeight(unit)); CRSNNPtr geogCRS = GeographicCRS::EPSG_4979; // default const auto &geoid_crs = getParamValue(step, "geoid_crs"); if (!geoid_crs.empty()) { if (geoid_crs == "WGS84") { // nothing to do } else if (geoid_crs == "horizontal_crs") { auto geogCRSOfCompoundCRS = crs->extractGeographicCRS(); if (geogCRSOfCompoundCRS && geogCRSOfCompoundCRS->primeMeridian() ->longitude() .getSIValue() == 0 && geogCRSOfCompoundCRS->coordinateSystem() ->axisList()[0] ->unit() == UnitOfMeasure::DEGREE) { geogCRS = geogCRSOfCompoundCRS->promoteTo3D(std::string(), nullptr); } else if (geogCRSOfCompoundCRS) { auto geogCRSOfCompoundCRSDatum = geogCRSOfCompoundCRS->datumNonNull(nullptr); geogCRS = GeographicCRS::create( createMapWithUnknownName(), datum::GeodeticReferenceFrame::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, geogCRSOfCompoundCRSDatum->nameStr() + " (with Greenwich prime meridian)"), geogCRSOfCompoundCRSDatum->ellipsoid(), util::optional(), datum::PrimeMeridian::GREENWICH), EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight( UnitOfMeasure::DEGREE, UnitOfMeasure::METRE)); } } else { throw ParsingException("Unsupported value for geoid_crs: " "should be 'WGS84' or 'horizontal_crs'"); } } auto transformation = Transformation::createGravityRelatedHeightToGeographic3D( PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown to " + geogCRS->nameStr() + " ellipsoidal height"), VerticalCRS::create(createMapWithUnknownName(), vdatum, VerticalCS::createGravityRelatedHeight( common::UnitOfMeasure::METRE)), geogCRS, nullptr, geoidgrids, std::vector()); auto boundvcrs = BoundCRS::create(vcrs, geogCRS, transformation); crs = CompoundCRS::create(createMapWithUnknownName(), std::vector{crs, boundvcrs}); } return crs; } // --------------------------------------------------------------------------- static double getAngularValue(const std::string ¶mValue, bool *pHasError = nullptr) { char *endptr = nullptr; double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG; if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) { if (pHasError) *pHasError = true; return 0.0; } if (pHasError) *pHasError = false; return value; } // --------------------------------------------------------------------------- static bool is_in_stringlist(const std::string &str, const char *stringlist) { if (str.empty()) return false; const char *haystack = stringlist; while (true) { const char *res = strstr(haystack, str.c_str()); if (res == nullptr) return false; if ((res == stringlist || res[-1] == ',') && (res[str.size()] == ',' || res[str.size()] == '\0')) return true; haystack += str.size(); } } // --------------------------------------------------------------------------- CRSNNPtr PROJStringParser::Private::buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geodCRS, int iUnitConvert, int iAxisSwap) { auto &step = steps_[iStep]; const auto mappings = getMappingsFromPROJName(step.name); const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0]; bool foundStrictlyMatchingMapping = false; if (mappings.size() >= 2) { // To distinguish for example +ortho from +ortho +f=0 bool allMappingsHaveAuxParam = true; for (const auto *mappingIter : mappings) { if (mappingIter->proj_name_aux == nullptr) { allMappingsHaveAuxParam = false; } if (mappingIter->proj_name_aux != nullptr && strchr(mappingIter->proj_name_aux, '=') == nullptr && hasParamValue(step, mappingIter->proj_name_aux)) { foundStrictlyMatchingMapping = true; mapping = mappingIter; break; } else if (mappingIter->proj_name_aux != nullptr && strchr(mappingIter->proj_name_aux, '=') != nullptr) { const auto tokens = split(mappingIter->proj_name_aux, '='); if (tokens.size() == 2 && getParamValue(step, tokens[0]) == tokens[1]) { foundStrictlyMatchingMapping = true; mapping = mappingIter; break; } } } if (allMappingsHaveAuxParam && !foundStrictlyMatchingMapping) { mapping = nullptr; } } if (mapping && !foundStrictlyMatchingMapping) { mapping = selectSphericalOrEllipsoidal(mapping, geodCRS); } assert(isProjectedStep(step.name)); assert(iUnitConvert < 0 || ci_equal(steps_[iUnitConvert].name, "unitconvert")); const auto &title = title_; if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo( geodCRS->primeMeridian()->longitude(), util::IComparable::Criterion::EQUIVALENT)) { throw ParsingException("inconsistent pm values between projectedCRS " "and its base geographicalCRS"); } auto axisType = AxisType::REGULAR; bool bWebMercator = false; std::string webMercatorName("WGS 84 / Pseudo-Mercator"); if (step.name == "tmerc" && ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) || (iAxisSwap > 0 && getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) { mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED); } else if (step.name == "etmerc") { mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); } else if (step.name == "lcc") { const auto &lat_0 = getParamValue(step, "lat_0"); const auto &lat_1 = getParamValue(step, "lat_1"); const auto &lat_2 = getParamValue(step, "lat_2"); const auto &k = getParamValueK(step); if (lat_2.empty() && !lat_0.empty() && !lat_1.empty()) { if (lat_0 == lat_1 || // For some reason with gcc 5.3.1-14ubuntu2 32bit, the following // comparison returns false even if lat_0 == lat_1. Smells like // a compiler bug getAngularValue(lat_0) == getAngularValue(lat_1)) { mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); } else { mapping = getMapping( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B); } } else if (!k.empty() && getNumericValue(k) != 1.0) { mapping = getMapping( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN); } else { mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP); } } else if (step.name == "aeqd" && hasParamValue(step, "guam")) { mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION); } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") { mapping = getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X); } else if (step.name == "geos") { mapping = getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y); } else if (step.name == "omerc") { if (hasParamValue(step, "no_rot")) { mapping = nullptr; } else if (hasParamValue(step, "no_uoff") || hasParamValue(step, "no_off")) { mapping = getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A); } else if (hasParamValue(step, "lat_1") && hasParamValue(step, "lon_1") && hasParamValue(step, "lat_2") && hasParamValue(step, "lon_2")) { mapping = getMapping( PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN); } else { mapping = getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); } } else if (step.name == "somerc") { mapping = getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B); if (!hasParamValue(step, "alpha") && !hasParamValue(step, "gamma") && !hasParamValue(step, "lonc")) { step.paramValues.emplace_back(Step::KeyValue("alpha", "90")); step.paramValues.emplace_back(Step::KeyValue("gamma", "90")); step.paramValues.emplace_back( Step::KeyValue("lonc", getParamValue(step, "lon_0"))); } } else if (step.name == "krovak" && ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" && !hasParamValue(step, "czech")) || (iAxisSwap > 0 && getParamValue(steps_[iAxisSwap], "order") == "-2,-1" && !hasParamValue(step, "czech")))) { mapping = getMapping(EPSG_CODE_METHOD_KROVAK); } else if (step.name == "krovak" && iAxisSwap < 0 && hasParamValue(step, "czech") && !hasParamValue(step, "axis")) { mapping = getMapping(EPSG_CODE_METHOD_KROVAK); } else if (step.name == "mod_krovak" && ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" && !hasParamValue(step, "czech")) || (iAxisSwap > 0 && getParamValue(steps_[iAxisSwap], "order") == "-2,-1" && !hasParamValue(step, "czech")))) { mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED); } else if (step.name == "mod_krovak" && iAxisSwap < 0 && hasParamValue(step, "czech") && !hasParamValue(step, "axis")) { mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED); } else if (step.name == "merc") { if (hasParamValue(step, "a") && hasParamValue(step, "b") && getParamValue(step, "a") == getParamValue(step, "b") && (!hasParamValue(step, "lat_ts") || getAngularValue(getParamValue(step, "lat_ts")) == 0.0) && getNumericValue(getParamValueK(step)) == 1.0 && getParamValue(step, "nadgrids") == "@null") { mapping = getMapping( EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR); for (size_t i = 0; i < step.paramValues.size(); ++i) { if (ci_equal(step.paramValues[i].key, "nadgrids")) { ignoreNadgrids_ = true; break; } } if (getNumericValue(getParamValue(step, "a")) == 6378137 && getAngularValue(getParamValue(step, "lon_0")) == 0.0 && getAngularValue(getParamValue(step, "lat_0")) == 0.0 && getAngularValue(getParamValue(step, "x_0")) == 0.0 && getAngularValue(getParamValue(step, "y_0")) == 0.0) { bWebMercator = true; if (hasParamValue(step, "units") && getParamValue(step, "units") != "m") { webMercatorName += " (unit " + getParamValue(step, "units") + ')'; } } } else if (hasParamValue(step, "lat_ts")) { if (hasParamValue(step, "R_C") && !geodCRS->ellipsoid()->isSphere() && getAngularValue(getParamValue(step, "lat_ts")) != 0) { throw ParsingException("lat_ts != 0 not supported for " "spherical Mercator on an ellipsoid"); } mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); } else if (hasParamValue(step, "R_C")) { const auto &k = getParamValueK(step); if (!k.empty() && getNumericValue(k) != 1.0) { if (geodCRS->ellipsoid()->isSphere()) { mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); } else { throw ParsingException( "k_0 != 1 not supported for spherical Mercator on an " "ellipsoid"); } } else { mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_SPHERICAL); } } else { mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A); } } else if (step.name == "stere") { if (hasParamValue(step, "lat_0") && std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - 90.0) < 1e-10) { const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); if (lat_0 > 0) { axisType = AxisType::NORTH_POLE; } else { axisType = AxisType::SOUTH_POLE; } const auto &lat_ts = getParamValue(step, "lat_ts"); const auto &k = getParamValueK(step); if (!lat_ts.empty() && std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 && !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) { throw ParsingException("lat_ts != lat_0 and k != 1 not " "supported for Polar Stereographic"); } if (!lat_ts.empty() && (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) { mapping = getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B); } else { mapping = getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A); } } else { mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC); } } else if (step.name == "laea") { if (hasParamValue(step, "lat_0") && std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) - 90.0) < 1e-10) { const double lat_0 = getAngularValue(getParamValue(step, "lat_0")); if (lat_0 > 0) { axisType = AxisType::NORTH_POLE; } else { axisType = AxisType::SOUTH_POLE; } } } else if (step.name == "ortho") { const std::string &k = getParamValueK(step); if ((!k.empty() && getNumericValue(k) != 1.0) || (hasParamValue(step, "alpha") && getNumericValue(getParamValue(step, "alpha")) != 0.0)) { mapping = getMapping(EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC); } } UnitOfMeasure unit = buildUnit(step, "units", "to_meter"); if (iUnitConvert >= 0) { auto &stepUnitConvert = steps_[iUnitConvert]; const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); if (stepUnitConvert.inverted) { std::swap(xy_in, xy_out); } if (xy_in->empty() || xy_out->empty() || *xy_in != "m") { if (step.name != "ob_tran") { throw ParsingException( "unhandled values for xy_in and/or xy_out"); } } const LinearUnitDesc *unitsMatch = nullptr; try { double to_meter_value = c_locale_stod(*xy_out); unitsMatch = getLinearUnits(to_meter_value); if (unitsMatch == nullptr) { unit = _buildUnit(to_meter_value); } } catch (const std::invalid_argument &) { unitsMatch = getLinearUnits(*xy_out); if (!unitsMatch) { if (step.name != "ob_tran") { throw ParsingException( "unhandled values for xy_in and/or xy_out"); } } else { unit = _buildUnit(unitsMatch); } } } ConversionPtr conv; auto mapWithUnknownName = createMapWithUnknownName(); if (step.name == "utm") { const int zone = std::atoi(getParamValue(step, "zone").c_str()); const bool north = !hasParamValue(step, "south"); conv = Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable(); } else if (mapping) { auto methodMap = PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name); if (mapping->epsg_code) { methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG) .set(Identifier::CODE_KEY, mapping->epsg_code); } std::vector parameters; std::vector values; for (int i = 0; mapping->params[i] != nullptr; i++) { const auto *param = mapping->params[i]; std::string proj_name(param->proj_name ? param->proj_name : ""); const std::string *paramValue = (proj_name == "k" || proj_name == "k_0") ? &getParamValueK(step) : !proj_name.empty() ? &getParamValue(step, proj_name) : &emptyString; double value = 0; if (!paramValue->empty()) { bool hasError = false; if (param->unit_type == UnitOfMeasure::Type::ANGULAR) { value = getAngularValue(*paramValue, &hasError); } else { value = getNumericValue(*paramValue, &hasError); } if (hasError) { throw ParsingException("invalid value for " + proj_name); } } // For omerc, if gamma is missing, the default value is // alpha else if (step.name == "omerc" && proj_name == "gamma") { paramValue = &getParamValue(step, "alpha"); if (!paramValue->empty()) { value = getAngularValue(*paramValue); } } else if (step.name == "krovak" || step.name == "mod_krovak") { // Keep it in sync with defaults of krovak.cpp if (param->epsg_code == EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE) { value = 49.5; } else if (param->epsg_code == EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) { value = 24.833333333333333333; } else if (param->epsg_code == EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) { value = 30.28813975277777776; } else if ( param->epsg_code == EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) { value = 78.5; } else if ( param->epsg_code == EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL) { value = 0.9999; } } else if (step.name == "cea" && proj_name == "lat_ts") { paramValue = &getParamValueK(step); if (!paramValue->empty()) { bool hasError = false; const double k = getNumericValue(*paramValue, &hasError); if (hasError) { throw ParsingException("invalid value for k/k_0"); } if (k >= 0 && k <= 1) { const double es = geodCRS->ellipsoid()->squaredEccentricity(); if (es < 0 || es == 1) { throw ParsingException("Invalid flattening"); } value = Angle(acos(k * sqrt((1 - es) / (1 - k * k * es))), UnitOfMeasure::RADIAN) .convertToUnit(UnitOfMeasure::DEGREE); } else { throw ParsingException("k/k_0 should be in [0,1]"); } } } else if (param->unit_type == UnitOfMeasure::Type::SCALE) { value = 1; } else if (step.name == "peirce_q" && proj_name == "lat_0") { value = 90; } PropertyMap propertiesParameter; propertiesParameter.set(IdentifiedObject::NAME_KEY, param->wkt2_name); if (param->epsg_code) { propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code); propertiesParameter.set(Identifier::CODESPACE_KEY, Identifier::EPSG); } parameters.push_back( OperationParameter::create(propertiesParameter)); // In PROJ convention, angular parameters are always in degree // and linear parameters always in metre. double valRounded = param->unit_type == UnitOfMeasure::Type::LINEAR ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit) : value; if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) { valRounded = std::round(valRounded); } values.push_back(ParameterValue::create( Measure(valRounded, param->unit_type == UnitOfMeasure::Type::ANGULAR ? UnitOfMeasure::DEGREE : param->unit_type == UnitOfMeasure::Type::LINEAR ? unit : param->unit_type == UnitOfMeasure::Type::SCALE ? UnitOfMeasure::SCALE_UNITY : UnitOfMeasure::NONE))); } if (step.name == "tmerc" && hasParamValue(step, "approx")) { methodMap.set("proj_method", "tmerc approx"); } else if (step.name == "utm" && hasParamValue(step, "approx")) { methodMap.set("proj_method", "utm approx"); } conv = Conversion::create(mapWithUnknownName, methodMap, parameters, values) .as_nullable(); } else { std::vector parameters; std::vector values; std::string methodName = "PROJ " + step.name; for (const auto ¶m : step.paramValues) { if (is_in_stringlist(param.key, "wktext,no_defs,datum,ellps,a,b,R,f,rf," "towgs84,nadgrids,geoidgrids," "units,to_meter,vunits,vto_meter,type")) { continue; } if (param.value.empty()) { methodName += " " + param.key; } else if (isalpha(param.value[0])) { methodName += " " + param.key + "=" + param.value; } else { parameters.push_back(OperationParameter::create( PropertyMap().set(IdentifiedObject::NAME_KEY, param.key))); bool hasError = false; if (is_in_stringlist(param.key, "x_0,y_0,h,h_0")) { double value = getNumericValue(param.value, &hasError); values.push_back(ParameterValue::create( Measure(value, UnitOfMeasure::METRE))); } else if (is_in_stringlist( param.key, "k,k_0," "north_square,south_square," // rhealpix "n,m," // sinu "q," // urm5 "path,lsat," // lsat "W,M," // hammer "aperture,resolution," // isea )) { double value = getNumericValue(param.value, &hasError); values.push_back(ParameterValue::create( Measure(value, UnitOfMeasure::SCALE_UNITY))); } else { double value = getAngularValue(param.value, &hasError); values.push_back(ParameterValue::create( Measure(value, UnitOfMeasure::DEGREE))); } if (hasError) { throw ParsingException("invalid value for " + param.key); } } } conv = Conversion::create( mapWithUnknownName, PropertyMap().set(IdentifiedObject::NAME_KEY, methodName), parameters, values) .as_nullable(); for (const char *substr : {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat", "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) { if (starts_with(methodName, substr)) { auto geogCRS = util::nn_dynamic_pointer_cast(geodCRS); if (geogCRS) { return DerivedGeographicCRS::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), NN_NO_CHECK(geogCRS), NN_NO_CHECK(conv), buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false)); } } } } std::vector axis = processAxisSwap(step, unit, iAxisSwap, axisType, false); auto csGeodCRS = geodCRS->coordinateSystem(); auto cs = csGeodCRS->axisList().size() == 2 ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1]) : CartesianCS::create(emptyPropertyMap, axis[0], axis[1], csGeodCRS->axisList()[2]); if (isTopocentricStep(step.name)) { cs = CartesianCS::create( emptyPropertyMap, createAxis("topocentric East", "U", AxisDirection::EAST, unit), createAxis("topocentric North", "V", AxisDirection::NORTH, unit), createAxis("topocentric Up", "W", AxisDirection::UP, unit)); } auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); if (hasUnusedParameters(step)) { props.set("EXTENSION_PROJ4", projString_); } props.set("IMPLICIT_CS", true); CRSNNPtr crs = bWebMercator ? createPseudoMercator( props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs) : ProjectedCRS::create(props, geodCRS, NN_NO_CHECK(conv), cs); return crs; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const metadata::ExtentPtr nullExtent{}; static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) { const auto &domains = crs->domains(); if (!domains.empty()) { return domains[0]->domainOfValidity(); } return nullExtent; } //! @endcond namespace { struct PJContextHolder { PJ_CONTEXT *ctx_; bool bFree_; PJContextHolder(PJ_CONTEXT *ctx, bool bFree) : ctx_(ctx), bFree_(bFree) {} ~PJContextHolder() { if (bFree_) proj_context_destroy(ctx_); } PJContextHolder(const PJContextHolder &) = delete; PJContextHolder &operator=(const PJContextHolder &) = delete; }; } // namespace // --------------------------------------------------------------------------- /** \brief Instantiate a sub-class of BaseObject from a PROJ string. * * The projString must contain +type=crs for the object to be detected as a * CRS instead of a CoordinateOperation. * * @throw ParsingException if the string cannot be parsed. */ BaseObjectNNPtr PROJStringParser::createFromPROJString(const std::string &projString) { // In some abnormal situations involving init=epsg:XXXX syntax, we could // have infinite loop if (d->ctx_ && d->ctx_->projStringParserCreateFromPROJStringRecursionCounter == 2) { throw ParsingException( "Infinite recursion in PROJStringParser::createFromPROJString()"); } d->steps_.clear(); d->title_.clear(); d->globalParamValues_.clear(); d->projString_ = projString; PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_, d->title_); if (d->steps_.empty()) { const auto &vunits = d->getGlobalParamValue("vunits"); const auto &vto_meter = d->getGlobalParamValue("vto_meter"); if (!vunits.empty() || !vto_meter.empty()) { Step fakeStep; if (!vunits.empty()) { fakeStep.paramValues.emplace_back( Step::KeyValue("vunits", vunits)); } if (!vto_meter.empty()) { fakeStep.paramValues.emplace_back( Step::KeyValue("vto_meter", vto_meter)); } auto vdatum = VerticalReferenceFrame::create(createMapWithUnknownName()); auto vcrs = VerticalCRS::create( createMapWithUnknownName(), vdatum, VerticalCS::createGravityRelatedHeight( d->buildUnit(fakeStep, "vunits", "vto_meter"))); return vcrs; } } const bool isGeocentricCRS = ((d->steps_.size() == 1 && d->getParamValue(d->steps_[0], "type") == "crs") || (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) && !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name); const bool isTopocentricCRS = (d->steps_.size() == 1 && isTopocentricStep(d->steps_[0].name) && d->getParamValue(d->steps_[0], "type") == "crs"); // +init=xxxx:yyyy syntax if (d->steps_.size() == 1 && d->steps_[0].isInit && !d->steps_[0].inverted) { auto ctx = d->ctx_ ? d->ctx_ : proj_context_create(); if (!ctx) { throw ParsingException("out of memory"); } PJContextHolder contextHolder(ctx, ctx != d->ctx_); // Those used to come from a text init file // We only support them in compatibility mode const std::string &stepName = d->steps_[0].name; if (ci_starts_with(stepName, "epsg:") || ci_starts_with(stepName, "IGNF:")) { struct BackupContextErrno { PJ_CONTEXT *m_ctxt = nullptr; int m_last_errno = 0; explicit BackupContextErrno(PJ_CONTEXT *ctxtIn) : m_ctxt(ctxtIn), m_last_errno(m_ctxt->last_errno) { m_ctxt->debug_level = PJ_LOG_ERROR; } ~BackupContextErrno() { m_ctxt->last_errno = m_last_errno; } BackupContextErrno(const BackupContextErrno &) = delete; BackupContextErrno & operator=(const BackupContextErrno &) = delete; }; BackupContextErrno backupContextErrno(ctx); bool usePROJ4InitRules = d->usePROJ4InitRules_; if (!usePROJ4InitRules) { usePROJ4InitRules = proj_context_get_use_proj4_init_rules(ctx, FALSE) == TRUE; } if (!usePROJ4InitRules) { throw ParsingException("init=epsg:/init=IGNF: syntax not " "supported in non-PROJ4 emulation mode"); } char unused[256]; std::string initname(stepName); initname.resize(initname.find(':')); int file_found = pj_find_file(ctx, initname.c_str(), unused, sizeof(unused)); if (!file_found) { auto obj = createFromUserInput(stepName, d->dbContext_, true); auto crs = dynamic_cast(obj.get()); bool hasSignificantParamValues = false; bool hasOver = false; for (const auto &kv : d->steps_[0].paramValues) { if (kv.key == "over") { hasOver = true; } else if (!((kv.key == "type" && kv.value == "crs") || kv.key == "wktext" || kv.key == "no_defs")) { hasSignificantParamValues = true; break; } } if (crs && !hasSignificantParamValues) { PropertyMap properties; properties.set(IdentifiedObject::NAME_KEY, d->title_.empty() ? crs->nameStr() : d->title_); if (hasOver) { properties.set("OVER", true); } const auto &extent = getExtent(crs); if (extent) { properties.set( common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } auto geogCRS = dynamic_cast(crs); if (geogCRS) { const auto &cs = geogCRS->coordinateSystem(); // Override with longitude latitude in degrees return GeographicCRS::create( properties, geogCRS->datum(), geogCRS->datumEnsemble(), cs->axisList().size() == 2 ? EllipsoidalCS::createLongitudeLatitude( UnitOfMeasure::DEGREE) : EllipsoidalCS:: createLongitudeLatitudeEllipsoidalHeight( UnitOfMeasure::DEGREE, cs->axisList()[2]->unit())); } auto projCRS = dynamic_cast(crs); if (projCRS) { // Override with easting northing order const auto conv = projCRS->derivingConversion(); if (conv->method()->getEPSGCode() != EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { return ProjectedCRS::create( properties, projCRS->baseCRS(), conv, CartesianCS::createEastingNorthing( projCRS->coordinateSystem() ->axisList()[0] ->unit())); } } return obj; } auto projStringExportable = dynamic_cast(crs); if (projStringExportable) { std::string expanded; if (!d->title_.empty()) { expanded = "title="; expanded += pj_double_quote_string_param_if_needed(d->title_); } for (const auto &pair : d->steps_[0].paramValues) { if (!expanded.empty()) expanded += ' '; expanded += '+'; expanded += pair.key; if (!pair.value.empty()) { expanded += '='; expanded += pj_double_quote_string_param_if_needed( pair.value); } } expanded += ' '; expanded += projStringExportable->exportToPROJString( PROJStringFormatter::create().get()); return createFromPROJString(expanded); } } } paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str()); if (!init) { throw ParsingException("out of memory"); } ctx->projStringParserCreateFromPROJStringRecursionCounter++; paralist *list = pj_expand_init(ctx, init); ctx->projStringParserCreateFromPROJStringRecursionCounter--; if (!list) { free(init); throw ParsingException("cannot expand " + projString); } std::string expanded; if (!d->title_.empty()) { expanded = "title=" + pj_double_quote_string_param_if_needed(d->title_); } bool first = true; bool has_init_term = false; for (auto t = list; t;) { if (!expanded.empty()) { expanded += ' '; } if (first) { // first parameter is the init= itself first = false; } else if (starts_with(t->param, "init=")) { has_init_term = true; } else { expanded += t->param; } auto n = t->next; free(t); t = n; } for (const auto &pair : d->steps_[0].paramValues) { expanded += " +"; expanded += pair.key; if (!pair.value.empty()) { expanded += '='; expanded += pj_double_quote_string_param_if_needed(pair.value); } } if (!has_init_term) { return createFromPROJString(expanded); } } int iFirstGeogStep = -1; int iSecondGeogStep = -1; int iProjStep = -1; int iFirstUnitConvert = -1; int iSecondUnitConvert = -1; int iFirstAxisSwap = -1; int iSecondAxisSwap = -1; bool unexpectedStructure = d->steps_.empty(); for (int i = 0; i < static_cast(d->steps_.size()); i++) { const auto &stepName = d->steps_[i].name; if (isGeographicStep(stepName)) { if (iFirstGeogStep < 0) { iFirstGeogStep = i; } else if (iSecondGeogStep < 0) { iSecondGeogStep = i; } else { unexpectedStructure = true; break; } } else if (ci_equal(stepName, "unitconvert")) { if (iFirstUnitConvert < 0) { iFirstUnitConvert = i; } else if (iSecondUnitConvert < 0) { iSecondUnitConvert = i; } else { unexpectedStructure = true; break; } } else if (ci_equal(stepName, "axisswap")) { if (iFirstAxisSwap < 0) { iFirstAxisSwap = i; } else if (iSecondAxisSwap < 0) { iSecondAxisSwap = i; } else { unexpectedStructure = true; break; } } else if (isProjectedStep(stepName)) { if (iProjStep >= 0) { unexpectedStructure = true; break; } iProjStep = i; } else { unexpectedStructure = true; break; } } if (!d->steps_.empty()) { // CRS candidate if ((d->steps_.size() == 1 && d->getParamValue(d->steps_[0], "type") != "crs") || (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) { unexpectedStructure = true; } } struct Logger { std::string msg{}; // cppcheck-suppress functionStatic void setMessage(const char *msgIn) noexcept { try { msg = msgIn; } catch (const std::exception &) { } } static void log(void *user_data, int level, const char *msg) { if (level == PJ_LOG_ERROR) { static_cast(user_data)->setMessage(msg); } } }; // If the structure is not recognized, then try to instantiate the // pipeline, and if successful, wrap it in a PROJBasedOperation Logger logger; bool valid; auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create(); if (!pj_context) { throw ParsingException("out of memory"); } // Backup error logger and level, and install temporary handler auto old_logger = pj_context->logger; auto old_logger_app_data = pj_context->logger_app_data; auto log_level = proj_log_level(pj_context, PJ_LOG_ERROR); proj_log_func(pj_context, &logger, Logger::log); if (pj_context != d->ctx_) { proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_); } pj_context->projStringParserCreateFromPROJStringRecursionCounter++; auto pj = pj_create_internal( pj_context, (projString.find("type=crs") != std::string::npos ? projString + " +disable_grid_presence_check" : projString) .c_str()); pj_context->projStringParserCreateFromPROJStringRecursionCounter--; valid = pj != nullptr; // Restore initial error logger and level proj_log_level(pj_context, log_level); pj_context->logger = old_logger; pj_context->logger_app_data = old_logger_app_data; // Remove parameters not understood by PROJ. if (valid && d->steps_.size() == 1) { std::vector newParamValues{}; std::set foundKeys; auto &step = d->steps_[0]; for (auto &kv : step.paramValues) { bool recognizedByPROJ = false; if (foundKeys.find(kv.key) != foundKeys.end()) { continue; } foundKeys.insert(kv.key); if ((step.name == "krovak" || step.name == "mod_krovak") && kv.key == "alpha") { // We recognize it in our CRS parsing code recognizedByPROJ = true; } else { for (auto cur = pj->params; cur; cur = cur->next) { const char *equal = strchr(cur->param, '='); if (equal && static_cast(equal - cur->param) == kv.key.size()) { if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) == 0) { recognizedByPROJ = (cur->used == 1); break; } } else if (strcmp(cur->param, kv.key.c_str()) == 0) { recognizedByPROJ = (cur->used == 1); break; } } } if (!recognizedByPROJ && kv.key == "geoid_crs") { for (auto &pair : step.paramValues) { if (ci_equal(pair.key, "geoidgrids")) { recognizedByPROJ = true; break; } } } if (recognizedByPROJ) { newParamValues.emplace_back(kv); } } step.paramValues = std::move(newParamValues); d->projString_.clear(); if (!step.name.empty()) { d->projString_ += step.isInit ? "+init=" : "+proj="; d->projString_ += step.name; } for (const auto ¶mValue : step.paramValues) { if (!d->projString_.empty()) { d->projString_ += ' '; } d->projString_ += '+'; d->projString_ += paramValue.key; if (!paramValue.value.empty()) { d->projString_ += '='; d->projString_ += pj_double_quote_string_param_if_needed(paramValue.value); } } } proj_destroy(pj); if (!valid) { const int l_errno = proj_context_errno(pj_context); std::string msg("Error " + toString(l_errno) + " (" + proj_errno_string(l_errno) + ")"); if (!logger.msg.empty()) { msg += ": "; msg += logger.msg; } logger.msg = std::move(msg); } if (pj_context != d->ctx_) { proj_context_destroy(pj_context); } if (!valid) { throw ParsingException(logger.msg); } if (isGeocentricCRS) { // First run is dry run to mark all recognized/unrecognized tokens for (int iter = 0; iter < 2; iter++) { auto obj = d->buildBoundOrCompoundCRSIfNeeded( 0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert") ? 1 : -1)); if (iter == 1) { return nn_static_pointer_cast(obj); } } } if (isTopocentricCRS) { // First run is dry run to mark all recognized/unrecognized tokens for (int iter = 0; iter < 2; iter++) { auto obj = d->buildBoundOrCompoundCRSIfNeeded( 0, d->buildProjectedCRS(0, d->buildGeocentricCRS(0, -1), -1, -1)); if (iter == 1) { return nn_static_pointer_cast(obj); } } } if (!unexpectedStructure) { if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted && iSecondGeogStep < 0 && iProjStep < 0 && (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) && (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) { // First run is dry run to mark all recognized/unrecognized tokens for (int iter = 0; iter < 2; iter++) { auto obj = d->buildBoundOrCompoundCRSIfNeeded( 0, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert, iFirstAxisSwap, false)); if (iter == 1) { return nn_static_pointer_cast(obj); } } } if (iProjStep >= 0 && !d->steps_[iProjStep].inverted && (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) && iSecondGeogStep < 0) { if (iFirstGeogStep < 0) iFirstGeogStep = iProjStep; // First run is dry run to mark all recognized/unrecognized tokens for (int iter = 0; iter < 2; iter++) { auto obj = d->buildBoundOrCompoundCRSIfNeeded( iProjStep, d->buildProjectedCRS( iProjStep, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert < iFirstGeogStep ? iFirstUnitConvert : -1, iFirstAxisSwap < iFirstGeogStep ? iFirstAxisSwap : -1, true), iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert : iFirstUnitConvert, iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap : iFirstAxisSwap)); if (iter == 1) { return nn_static_pointer_cast(obj); } } } } auto props = PropertyMap(); if (!d->title_.empty()) { props.set(IdentifiedObject::NAME_KEY, d->title_); } return operation::SingleOperation::createPROJBased(props, projString, nullptr, nullptr, {}); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct JSONFormatter::Private { CPLJSonStreamingWriter writer_{nullptr, nullptr}; DatabaseContextPtr dbContext_{}; std::vector stackHasId_{false}; std::vector outputIdStack_{true}; bool allowIDInImmediateChild_ = false; bool omitTypeInImmediateChild_ = false; bool abridgedTransformation_ = false; bool abridgedTransformationWriteSourceCRS_ = false; std::string schema_ = PROJJSON_DEFAULT_VERSION; // cppcheck-suppress functionStatic void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); } // cppcheck-suppress functionStatic void popOutputId() { outputIdStack_.pop_back(); } }; //! @endcond // --------------------------------------------------------------------------- /** \brief Constructs a new formatter. * * A formatter can be used only once (its internal state is mutated) * * @return new formatter. */ JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue DatabaseContextPtr dbContext) { auto ret = NN_NO_CHECK(JSONFormatter::make_unique()); ret->d->dbContext_ = std::move(dbContext); return ret; } // --------------------------------------------------------------------------- /** \brief Whether to use multi line output or not. */ JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept { d->writer_.SetPrettyFormatting(multiLine); return *this; } // --------------------------------------------------------------------------- /** \brief Set number of spaces for each indentation level (defaults to 4). */ JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept { d->writer_.SetIndentationSize(width); return *this; } // --------------------------------------------------------------------------- /** \brief Set the value of the "$schema" key in the top level object. * * If set to empty string, it will not be written. */ JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept { d->schema_ = schema; return *this; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress JSONFormatter::JSONFormatter() : d(std::make_unique()) {} // --------------------------------------------------------------------------- JSONFormatter::~JSONFormatter() = default; // --------------------------------------------------------------------------- CPLJSonStreamingWriter *JSONFormatter::writer() const { return &(d->writer_); } // --------------------------------------------------------------------------- const DatabaseContextPtr &JSONFormatter::databaseContext() const { return d->dbContext_; } // --------------------------------------------------------------------------- bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); } // --------------------------------------------------------------------------- bool JSONFormatter::outputUsage(bool calledBeforeObjectContext) const { return outputId() && d->outputIdStack_.size() == (calledBeforeObjectContext ? 1U : 2U); } // --------------------------------------------------------------------------- void JSONFormatter::setAllowIDInImmediateChild() { d->allowIDInImmediateChild_ = true; } // --------------------------------------------------------------------------- void JSONFormatter::setOmitTypeInImmediateChild() { d->omitTypeInImmediateChild_ = true; } // --------------------------------------------------------------------------- JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter, const char *objectType, bool hasId) : m_formatter(formatter) { m_formatter.d->writer_.StartObj(); if (m_formatter.d->outputIdStack_.size() == 1 && !m_formatter.d->schema_.empty()) { m_formatter.d->writer_.AddObjKey("$schema"); m_formatter.d->writer_.Add(m_formatter.d->schema_); } if (objectType && !m_formatter.d->omitTypeInImmediateChild_) { m_formatter.d->writer_.AddObjKey("type"); m_formatter.d->writer_.Add(objectType); } m_formatter.d->omitTypeInImmediateChild_ = false; // All intermediate nodes shouldn't have ID if a parent has an ID // unless explicitly enabled. if (m_formatter.d->allowIDInImmediateChild_) { m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]); m_formatter.d->allowIDInImmediateChild_ = false; } else { m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] && !m_formatter.d->stackHasId_.back()); } m_formatter.d->stackHasId_.push_back(hasId || m_formatter.d->stackHasId_.back()); } // --------------------------------------------------------------------------- JSONFormatter::ObjectContext::~ObjectContext() { m_formatter.d->writer_.EndObj(); m_formatter.d->stackHasId_.pop_back(); m_formatter.d->popOutputId(); } // --------------------------------------------------------------------------- void JSONFormatter::setAbridgedTransformation(bool outputIn) { d->abridgedTransformation_ = outputIn; } // --------------------------------------------------------------------------- bool JSONFormatter::abridgedTransformation() const { return d->abridgedTransformation_; } // --------------------------------------------------------------------------- void JSONFormatter::setAbridgedTransformationWriteSourceCRS(bool writeCRS) { d->abridgedTransformationWriteSourceCRS_ = writeCRS; } // --------------------------------------------------------------------------- bool JSONFormatter::abridgedTransformationWriteSourceCRS() const { return d->abridgedTransformationWriteSourceCRS_; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the serialized JSON. */ const std::string &JSONFormatter::toString() const { return d->writer_.GetString(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress IJSONExportable::~IJSONExportable() = default; // --------------------------------------------------------------------------- std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const { _exportToJSON(formatter); return formatter->toString(); } //! @endcond } // namespace io NS_PROJ_END proj-9.8.1/src/iso19111/operation/000775 001750 001750 00000000000 15166171735 016433 5ustar00eveneven000000 000000 proj-9.8.1/src/iso19111/operation/coordinateoperation_internal.hpp000664 001750 001750 00000023427 15166171715 025116 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #error This file should only be included from a PROJ cpp file #endif #ifndef COORDINATEOPERATION_INTERNAL_HH_INCLUDED #define COORDINATEOPERATION_INTERNAL_HH_INCLUDED #include "proj/coordinateoperation.hpp" #include //! @cond Doxygen_Suppress NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- bool isAxisOrderReversal(int methodEPSGCode); // --------------------------------------------------------------------------- class InverseCoordinateOperation; /** Shared pointer of InverseCoordinateOperation */ using InverseCoordinateOperationPtr = std::shared_ptr; /** Non-null shared pointer of InverseCoordinateOperation */ using InverseCoordinateOperationNNPtr = util::nn; /** \brief Inverse operation of a CoordinateOperation. * * This is used when there is no straightforward way of building another * subclass of CoordinateOperation that models the inverse operation. */ class InverseCoordinateOperation : virtual public CoordinateOperation { public: InverseCoordinateOperation( const CoordinateOperationNNPtr &forwardOperationIn, bool wktSupportsInversion); ~InverseCoordinateOperation() override; void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override; CoordinateOperationNNPtr inverse() const override; const CoordinateOperationNNPtr &forwardOperation() const { return forwardOperation_; } protected: CoordinateOperationNNPtr forwardOperation_; bool wktSupportsInversion_; void setPropertiesFromForward(); }; // --------------------------------------------------------------------------- /** \brief Inverse of a conversion. */ class InverseConversion : public Conversion, public InverseCoordinateOperation { public: explicit InverseConversion(const ConversionNNPtr &forward); ~InverseConversion() override; void _exportToWKT(io::WKTFormatter *formatter) const override { Conversion::_exportToWKT(formatter); } void _exportToJSON(io::JSONFormatter *formatter) const override { Conversion::_exportToJSON(formatter); } void _exportToPROJString(io::PROJStringFormatter *formatter) const override { InverseCoordinateOperation::_exportToPROJString(formatter); } bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override { return InverseCoordinateOperation::_isEquivalentTo(other, criterion, dbContext); } CoordinateOperationNNPtr inverse() const override { return InverseCoordinateOperation::inverse(); } ConversionNNPtr inverseAsConversion() const; #ifdef _MSC_VER // To avoid a warning C4250: 'osgeo::proj::operation::InverseConversion': // inherits // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' // via dominance std::set gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const override { return SingleOperation::gridsNeeded(databaseContext, considerKnownGridsAsAvailable); } #endif static CoordinateOperationNNPtr create(const ConversionNNPtr &forward); CoordinateOperationNNPtr _shallowClone() const override; }; // --------------------------------------------------------------------------- /** \brief Inverse of a transformation. */ class InverseTransformation : public Transformation, public InverseCoordinateOperation { public: explicit InverseTransformation(const TransformationNNPtr &forward); ~InverseTransformation() override; void _exportToWKT(io::WKTFormatter *formatter) const override; void _exportToPROJString(io::PROJStringFormatter *formatter) const override { return InverseCoordinateOperation::_exportToPROJString(formatter); } void _exportToJSON(io::JSONFormatter *formatter) const override { Transformation::_exportToJSON(formatter); } bool _isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion = util::IComparable::Criterion::STRICT, const io::DatabaseContextPtr &dbContext = nullptr) const override { return InverseCoordinateOperation::_isEquivalentTo(other, criterion, dbContext); } CoordinateOperationNNPtr inverse() const override { return InverseCoordinateOperation::inverse(); } TransformationNNPtr inverseAsTransformation() const; #ifdef _MSC_VER // To avoid a warning C4250: // 'osgeo::proj::operation::InverseTransformation': inherits // 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded' // via dominance std::set gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const override { return SingleOperation::gridsNeeded(databaseContext, considerKnownGridsAsAvailable); } #endif static TransformationNNPtr create(const TransformationNNPtr &forward); CoordinateOperationNNPtr _shallowClone() const override; }; // --------------------------------------------------------------------------- class PROJBasedOperation; /** Shared pointer of PROJBasedOperation */ using PROJBasedOperationPtr = std::shared_ptr; /** Non-null shared pointer of PROJBasedOperation */ using PROJBasedOperationNNPtr = util::nn; /** \brief A PROJ-string based coordinate operation. */ class PROJBasedOperation : public SingleOperation { public: ~PROJBasedOperation() override; void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) CoordinateOperationNNPtr inverse() const override; static PROJBasedOperationNNPtr create(const util::PropertyMap &properties, const std::string &PROJString, const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, const std::vector &accuracies); static PROJBasedOperationNNPtr create(const util::PropertyMap &properties, const io::IPROJStringExportableNNPtr &projExportable, bool inverse, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::CRSPtr &interpolationCRS, const std::vector &accuracies, bool hasRoughTransformation); std::set gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const override; protected: PROJBasedOperation(const PROJBasedOperation &) = default; explicit PROJBasedOperation(const OperationMethodNNPtr &methodIn); void _exportToPROJString(io::PROJStringFormatter *formatter) const override; // throw(FormattingException) void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(FormattingException) CoordinateOperationNNPtr _shallowClone() const override; INLINED_MAKE_SHARED private: std::string projString_{}; io::IPROJStringExportablePtr projStringExportable_{}; bool inverse_ = false; }; // --------------------------------------------------------------------------- class InvalidOperationEmptyIntersection : public InvalidOperation { public: explicit InvalidOperationEmptyIntersection(const std::string &message); InvalidOperationEmptyIntersection( const InvalidOperationEmptyIntersection &other); ~InvalidOperationEmptyIntersection() override; }; } // namespace operation NS_PROJ_END //! @endcond #endif // COORDINATEOPERATION_INTERNAL_HH_INCLUDED proj-9.8.1/src/iso19111/operation/vectorofvaluesparams.cpp000664 001750 001750 00000011234 15166171715 023411 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "vectorofvaluesparams.hpp" // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static std::vector buildParameterValueFromMeasure( const std::initializer_list &list) { std::vector res; for (const auto &v : list) { res.emplace_back(ParameterValue::create(v)); } return res; } VectorOfValues::VectorOfValues(std::initializer_list list) : std::vector(buildParameterValueFromMeasure(list)) {} // This way, we disable inlining of destruction, and save a lot of space VectorOfValues::~VectorOfValues() = default; VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3) { return VectorOfValues{ParameterValue::create(m1), ParameterValue::create(m2), ParameterValue::create(m3)}; } VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4) { return VectorOfValues{ ParameterValue::create(m1), ParameterValue::create(m2), ParameterValue::create(m3), ParameterValue::create(m4)}; } VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4, const common::Measure &m5) { return VectorOfValues{ ParameterValue::create(m1), ParameterValue::create(m2), ParameterValue::create(m3), ParameterValue::create(m4), ParameterValue::create(m5), }; } VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4, const common::Measure &m5, const common::Measure &m6) { return VectorOfValues{ ParameterValue::create(m1), ParameterValue::create(m2), ParameterValue::create(m3), ParameterValue::create(m4), ParameterValue::create(m5), ParameterValue::create(m6), }; } VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4, const common::Measure &m5, const common::Measure &m6, const common::Measure &m7) { return VectorOfValues{ ParameterValue::create(m1), ParameterValue::create(m2), ParameterValue::create(m3), ParameterValue::create(m4), ParameterValue::create(m5), ParameterValue::create(m6), ParameterValue::create(m7), }; } // This way, we disable inlining of destruction, and save a lot of space VectorOfParameters::~VectorOfParameters() = default; //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/transformation.cpp000664 001750 001750 00000246574 15166171715 022225 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "coordinateoperation_internal.hpp" #include "coordinateoperation_private.hpp" #include "esriparammappings.hpp" #include "operationmethod_private.hpp" #include "oputils.hpp" #include "parammappings.hpp" #include "vectorofvaluesparams.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // M_PI // clang-format on #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Transformation::Private { TransformationPtr forwardOperation_{}; static TransformationNNPtr registerInv(const Transformation *thisIn, TransformationNNPtr invTransform); }; //! @endcond // --------------------------------------------------------------------------- Transformation::Transformation( const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies) : SingleOperation(methodIn), d(std::make_unique()) { setParameterValues(values); setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn); setAccuracies(accuracies); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Transformation::~Transformation() = default; //! @endcond // --------------------------------------------------------------------------- Transformation::Transformation(const Transformation &other) : CoordinateOperation(other), SingleOperation(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- /** \brief Return the source crs::CRS of the transformation. * * @return the source CRS. */ const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN { return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_; } // --------------------------------------------------------------------------- /** \brief Return the target crs::CRS of the transformation. * * @return the target CRS. */ const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN { return CoordinateOperation::getPrivate()->strongRef_->targetCRS_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TransformationNNPtr Transformation::shallowClone() const { auto transf = Transformation::nn_make_shared(*this); transf->assignSelf(transf); transf->setCRSs(this, false); if (transf->d->forwardOperation_) { transf->d->forwardOperation_ = transf->d->forwardOperation_->shallowClone().as_nullable(); } return transf; } CoordinateOperationNNPtr Transformation::_shallowClone() const { return util::nn_static_pointer_cast(shallowClone()); } // --------------------------------------------------------------------------- TransformationNNPtr Transformation::promoteTo3D(const std::string &, const io::DatabaseContextPtr &dbContext) const { auto transf = shallowClone(); transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext), targetCRS()->promoteTo3D(std::string(), dbContext), interpolationCRS()); return transf; } // --------------------------------------------------------------------------- TransformationNNPtr Transformation::demoteTo2D(const std::string &, const io::DatabaseContextPtr &dbContext) const { auto transf = shallowClone(); transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext), targetCRS()->demoteTo2D(std::string(), dbContext), interpolationCRS()); return transf; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return the TOWGS84 parameters of the transformation. * * If this transformation uses Coordinate Frame Rotation, Position Vector * transformation or Geocentric translations, a vector of 7 double values * using the Position Vector convention (EPSG:9606) is returned. Those values * can be used as the value of the WKT1 TOWGS84 parameter or * PROJ +towgs84 parameter. * * @param canThrowException if true, an exception is thrown if the method fails, * otherwise an empty vector is returned in case of failure. * @return a vector of 7 values if valid, otherwise a io::FormattingException * is thrown. * @throws io::FormattingException in case of error, if canThrowException is * true */ std::vector Transformation::getTOWGS84Parameters( bool canThrowException) const // throw(io::FormattingException) { // GDAL WKT1 assumes EPSG:9606 / Position Vector convention bool sevenParamsTransform = false; bool threeParamsTransform = false; bool invertRotSigns = false; const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const int methodEPSGCode = l_method->getEPSGCode(); const auto paramCount = parameterValues().size(); if ((paramCount == 7 && ci_find(methodName, "Coordinate Frame") != std::string::npos) || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) { sevenParamsTransform = true; invertRotSigns = true; } else if ((paramCount == 7 && ci_find(methodName, "Position Vector") != std::string::npos) || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { sevenParamsTransform = true; invertRotSigns = false; } else if ((paramCount == 3 && ci_find(methodName, "Geocentric translations") != std::string::npos) || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { threeParamsTransform = true; } if (threeParamsTransform || sevenParamsTransform) { std::vector params(7, 0.0); bool foundX = false; bool foundY = false; bool foundZ = false; bool foundRotX = false; bool foundRotY = false; bool foundRotZ = false; bool foundScale = false; const double rotSign = invertRotSigns ? -1.0 : 1.0; const auto fixNegativeZero = [](double x) { if (x == 0.0) return 0.0; return x; }; for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); const auto epsg_code = parameter->getEPSGCode(); const auto &l_parameterValue = opParamvalue->parameterValue(); if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { const auto &measure = l_parameterValue->value(); if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { params[0] = measure.getSIValue(); foundX = true; } else if (epsg_code == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { params[1] = measure.getSIValue(); foundY = true; } else if (epsg_code == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { params[2] = measure.getSIValue(); foundZ = true; } else if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { params[3] = fixNegativeZero( rotSign * measure.convertToUnit( common::UnitOfMeasure::ARC_SECOND)); foundRotX = true; } else if (epsg_code == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { params[4] = fixNegativeZero( rotSign * measure.convertToUnit( common::UnitOfMeasure::ARC_SECOND)); foundRotY = true; } else if (epsg_code == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { params[5] = fixNegativeZero( rotSign * measure.convertToUnit( common::UnitOfMeasure::ARC_SECOND)); foundRotZ = true; } else if (epsg_code == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { params[6] = measure.convertToUnit( common::UnitOfMeasure::PARTS_PER_MILLION); foundScale = true; } } } } if (foundX && foundY && foundZ && (threeParamsTransform || (foundRotX && foundRotY && foundRotZ && foundScale))) { return params; } else { if (!canThrowException) return {}; throw io::FormattingException( "Missing required parameter values in transformation"); } } #if 0 if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS || methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { auto offsetLat = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); auto offsetLong = parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); auto offsetHeight = parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 && offsetHeight.getSIValue() == 0.0) { std::vector params(7, 0.0); return params; } } #endif if (!canThrowException) return {}; throw io::FormattingException( "Transformation cannot be formatted as WKT1 TOWGS84 parameters"); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a transformation from a vector of GeneralParameterValue. * * @param properties See \ref general_properties. At minimum the name should be * defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param interpolationCRSIn Interpolation CRS (might be null) * @param methodIn Operation method. * @param values Vector of GeneralOperationParameterNNPtr. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ TransformationNNPtr Transformation::create( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies) { if (methodIn->parameters().size() != values.size()) { throw InvalidOperation( "Inconsistent number of parameters and parameter values"); } auto transf = Transformation::nn_make_shared( sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values, accuracies); transf->assignSelf(transf); transf->setProperties(properties); std::string name; if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) && ci_find(name, "ballpark") != std::string::npos) { transf->setHasBallparkTransformation(true); } return transf; } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation and its OperationMethod. * * @param propertiesTransformation The \ref general_properties of the * Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param interpolationCRSIn Interpolation CRS (might be null) * @param propertiesOperationMethod The \ref general_properties of the * OperationMethod. * At minimum the name should be defined. * @param parameters Vector of parameters of the operation method. * @param values Vector of ParameterValueNNPtr. Constraint: * values.size() == parameters.size() * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ TransformationNNPtr Transformation::create(const util::PropertyMap &propertiesTransformation, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const util::PropertyMap &propertiesOperationMethod, const std::vector ¶meters, const std::vector &values, const std::vector &accuracies) // throw InvalidOperation { OperationMethodNNPtr op( OperationMethod::create(propertiesOperationMethod, parameters)); if (parameters.size() != values.size()) { throw InvalidOperation( "Inconsistent number of parameters and parameter values"); } std::vector generalParameterValues; generalParameterValues.reserve(values.size()); for (size_t i = 0; i < values.size(); i++) { generalParameterValues.push_back( OperationParameterValue::create(parameters[i], values[i])); } return create(propertiesTransformation, sourceCRSIn, targetCRSIn, interpolationCRSIn, op, generalParameterValues, accuracies); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- static TransformationNNPtr createSevenParamsTransform( const util::PropertyMap &properties, const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, const std::vector &accuracies) { return Transformation::create( properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), }, createParams(common::Length(translationXMetre), common::Length(translationYMetre), common::Length(translationZMetre), common::Angle(rotationXArcSecond, common::UnitOfMeasure::ARC_SECOND), common::Angle(rotationYArcSecond, common::UnitOfMeasure::ARC_SECOND), common::Angle(rotationZArcSecond, common::UnitOfMeasure::ARC_SECOND), common::Scale(scaleDifferencePPM, common::UnitOfMeasure::PARTS_PER_MILLION)), accuracies); } // --------------------------------------------------------------------------- static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, bool &isGeocentric, bool &isGeog2D, bool &isGeog3D) { auto sourceCRSGeod = dynamic_cast(sourceCRSIn.get()); auto targetCRSGeod = dynamic_cast(targetCRSIn.get()); isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() && targetCRSGeod && targetCRSGeod->isGeocentric(); if (isGeocentric) { isGeog2D = false; isGeog3D = false; return; } isGeocentric = false; auto sourceCRSGeog = dynamic_cast(sourceCRSIn.get()); auto targetCRSGeog = dynamic_cast(targetCRSIn.get()); if (!(sourceCRSGeog || (sourceCRSGeod && sourceCRSGeod->isSphericalPlanetocentric())) || !(targetCRSGeog || (targetCRSGeod && targetCRSGeod->isSphericalPlanetocentric()))) { throw InvalidOperation("Inconsistent CRS type"); } const auto nSrcAxisCount = sourceCRSGeod->coordinateSystem()->axisList().size(); const auto nTargetAxisCount = targetCRSGeod->coordinateSystem()->axisList().size(); isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2; isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2; } // --------------------------------------------------------------------------- static int useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties, int nDefaultOperationMethodEPSGCode) { const auto *operationMethodEPSGCode = properties.get("OPERATION_METHOD_EPSG_CODE"); if (operationMethodEPSGCode) { const auto boxedValue = dynamic_cast( (*operationMethodEPSGCode).get()); if (boxedValue && boxedValue->type() == util::BoxedValue::Type::INTEGER) { return boxedValue->integerValue(); } } return nDefaultOperationMethodEPSGCode; } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Geocentric Translations method. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createGeocentricTranslations( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, const std::vector &accuracies) { bool isGeocentric; bool isGeog2D; bool isGeog3D; getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, isGeog3D); return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( properties, isGeocentric ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC : isGeog2D ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), }, createParams(common::Length(translationXMetre), common::Length(translationYMetre), common::Length(translationZMetre)), accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Position vector transformation * method. * * This is similar to createCoordinateFrameRotation(), except that the sign of * the rotation terms is inverted. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param rotationXArcSecond Value of the Rotation_X parameter (in * arc-second). * @param rotationYArcSecond Value of the Rotation_Y parameter (in * arc-second). * @param rotationZArcSecond Value of the Rotation_Z parameter (in * arc-second). * @param scaleDifferencePPM Value of the Scale_Difference parameter (in * parts-per-million). * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createPositionVector( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, const std::vector &accuracies) { bool isGeocentric; bool isGeog2D; bool isGeog3D; getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, isGeog3D); return createSevenParamsTransform( properties, createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( properties, isGeocentric ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)), sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, translationZMetre, rotationXArcSecond, rotationYArcSecond, rotationZArcSecond, scaleDifferencePPM, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Coordinate Frame Rotation method. * * This is similar to createPositionVector(), except that the sign of * the rotation terms is inverted. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param rotationXArcSecond Value of the Rotation_X parameter (in * arc-second). * @param rotationYArcSecond Value of the Rotation_Y parameter (in * arc-second). * @param rotationZArcSecond Value of the Rotation_Z parameter (in * arc-second). * @param scaleDifferencePPM Value of the Scale_Difference parameter (in * parts-per-million). * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createCoordinateFrameRotation( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, const std::vector &accuracies) { bool isGeocentric; bool isGeog2D; bool isGeog3D; getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, isGeog3D); return createSevenParamsTransform( properties, createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( properties, isGeocentric ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)), sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, translationZMetre, rotationXArcSecond, rotationYArcSecond, rotationZArcSecond, scaleDifferencePPM, accuracies); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static TransformationNNPtr createFifteenParamsTransform( const util::PropertyMap &properties, const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, double rateTranslationX, double rateTranslationY, double rateTranslationZ, double rateRotationX, double rateRotationY, double rateRotationZ, double rateScaleDifference, double referenceEpochYear, const std::vector &accuracies) { return Transformation::create( properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties, VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH), }, VectorOfValues{ common::Length(translationXMetre), common::Length(translationYMetre), common::Length(translationZMetre), common::Angle(rotationXArcSecond, common::UnitOfMeasure::ARC_SECOND), common::Angle(rotationYArcSecond, common::UnitOfMeasure::ARC_SECOND), common::Angle(rotationZArcSecond, common::UnitOfMeasure::ARC_SECOND), common::Scale(scaleDifferencePPM, common::UnitOfMeasure::PARTS_PER_MILLION), common::Measure(rateTranslationX, common::UnitOfMeasure::METRE_PER_YEAR), common::Measure(rateTranslationY, common::UnitOfMeasure::METRE_PER_YEAR), common::Measure(rateTranslationZ, common::UnitOfMeasure::METRE_PER_YEAR), common::Measure(rateRotationX, common::UnitOfMeasure::ARC_SECOND_PER_YEAR), common::Measure(rateRotationY, common::UnitOfMeasure::ARC_SECOND_PER_YEAR), common::Measure(rateRotationZ, common::UnitOfMeasure::ARC_SECOND_PER_YEAR), common::Measure(rateScaleDifference, common::UnitOfMeasure::PPM_PER_YEAR), common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR), }, accuracies); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Time Dependent position vector * transformation method. * * This is similar to createTimeDependentCoordinateFrameRotation(), except that * the sign of * the rotation terms is inverted. * * This method is defined as *
* EPSG:1053. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param rotationXArcSecond Value of the Rotation_X parameter (in * arc-second). * @param rotationYArcSecond Value of the Rotation_Y parameter (in * arc-second). * @param rotationZArcSecond Value of the Rotation_Z parameter (in * arc-second). * @param scaleDifferencePPM Value of the Scale_Difference parameter (in * parts-per-million). * @param rateTranslationX Value of the rate of change of X-axis translation (in * metre/year) * @param rateTranslationY Value of the rate of change of Y-axis translation (in * metre/year) * @param rateTranslationZ Value of the rate of change of Z-axis translation (in * metre/year) * @param rateRotationX Value of the rate of change of X-axis rotation (in * arc-second/year) * @param rateRotationY Value of the rate of change of Y-axis rotation (in * arc-second/year) * @param rateRotationZ Value of the rate of change of Z-axis rotation (in * arc-second/year) * @param rateScaleDifference Value of the rate of change of scale difference * (in PPM/year) * @param referenceEpochYear Parameter reference epoch (in decimal year) * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createTimeDependentPositionVector( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, double rateTranslationX, double rateTranslationY, double rateTranslationZ, double rateRotationX, double rateRotationY, double rateRotationZ, double rateScaleDifference, double referenceEpochYear, const std::vector &accuracies) { bool isGeocentric; bool isGeog2D; bool isGeog3D; getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, isGeog3D); return createFifteenParamsTransform( properties, createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( properties, isGeocentric ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC : isGeog2D ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)), sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, translationZMetre, rotationXArcSecond, rotationYArcSecond, rotationZArcSecond, scaleDifferencePPM, rateTranslationX, rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Time Dependent Position coordinate * frame rotation transformation method. * * This is similar to createTimeDependentPositionVector(), except that the sign * of * the rotation terms is inverted. * * This method is defined as * * EPSG:1056. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param rotationXArcSecond Value of the Rotation_X parameter (in * arc-second). * @param rotationYArcSecond Value of the Rotation_Y parameter (in * arc-second). * @param rotationZArcSecond Value of the Rotation_Z parameter (in * arc-second). * @param scaleDifferencePPM Value of the Scale_Difference parameter (in * parts-per-million). * @param rateTranslationX Value of the rate of change of X-axis translation (in * metre/year) * @param rateTranslationY Value of the rate of change of Y-axis translation (in * metre/year) * @param rateTranslationZ Value of the rate of change of Z-axis translation (in * metre/year) * @param rateRotationX Value of the rate of change of X-axis rotation (in * arc-second/year) * @param rateRotationY Value of the rate of change of Y-axis rotation (in * arc-second/year) * @param rateRotationZ Value of the rate of change of Z-axis rotation (in * arc-second/year) * @param rateScaleDifference Value of the rate of change of scale difference * (in PPM/year) * @param referenceEpochYear Parameter reference epoch (in decimal year) * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double rotationXArcSecond, double rotationYArcSecond, double rotationZArcSecond, double scaleDifferencePPM, double rateTranslationX, double rateTranslationY, double rateTranslationZ, double rateRotationX, double rateRotationY, double rateRotationZ, double rateScaleDifference, double referenceEpochYear, const std::vector &accuracies) { bool isGeocentric; bool isGeog2D; bool isGeog3D; getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D, isGeog3D); return createFifteenParamsTransform( properties, createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent( properties, isGeocentric ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC : isGeog2D ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)), sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre, translationZMetre, rotationXArcSecond, rotationYArcSecond, rotationZArcSecond, scaleDifferencePPM, rateTranslationX, rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY, rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static TransformationNNPtr _createMolodensky( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode, double translationXMetre, double translationYMetre, double translationZMetre, double semiMajorAxisDifferenceMetre, double flattingDifference, const std::vector &accuracies) { return Transformation::create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(methodEPSGCode), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE), }, createParams( common::Length(translationXMetre), common::Length(translationYMetre), common::Length(translationZMetre), common::Length(semiMajorAxisDifferenceMetre), common::Measure(flattingDifference, common::UnitOfMeasure::NONE)), accuracies); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Molodensky method. * * @see createAbridgedMolodensky() for a related method. * * This method is defined as * * EPSG:9604. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param semiMajorAxisDifferenceMetre The difference between the semi-major * axis values of the ellipsoids used in the target and source CRS (in metre). * @param flattingDifference The difference between the flattening values of * the ellipsoids used in the target and source CRS. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ TransformationNNPtr Transformation::createMolodensky( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double semiMajorAxisDifferenceMetre, double flattingDifference, const std::vector &accuracies) { return _createMolodensky( properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY, translationXMetre, translationYMetre, translationZMetre, semiMajorAxisDifferenceMetre, flattingDifference, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with Abridged Molodensky method. * * @see createdMolodensky() for a related method. * * This method is defined as * * EPSG:9605. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param translationXMetre Value of the Translation_X parameter (in metre). * @param translationYMetre Value of the Translation_Y parameter (in metre). * @param translationZMetre Value of the Translation_Z parameter (in metre). * @param semiMajorAxisDifferenceMetre The difference between the semi-major * axis values of the ellipsoids used in the target and source CRS (in metre). * @param flattingDifference The difference between the flattening values of * the ellipsoids used in the target and source CRS. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ TransformationNNPtr Transformation::createAbridgedMolodensky( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, double translationXMetre, double translationYMetre, double translationZMetre, double semiMajorAxisDifferenceMetre, double flattingDifference, const std::vector &accuracies) { return _createMolodensky(properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, translationXMetre, translationYMetre, translationZMetre, semiMajorAxisDifferenceMetre, flattingDifference, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation from TOWGS84 parameters. * * This is a helper of createPositionVector() with the source CRS being the * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326 * * @param sourceCRSIn Source CRS. * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z) * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference) * passed to createPositionVector() * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ TransformationNNPtr Transformation::createTOWGS84( const crs::CRSNNPtr &sourceCRSIn, const std::vector &TOWGS84Parameters) // throw InvalidOperation { if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) { throw InvalidOperation( "Invalid number of elements in TOWGS84Parameters"); } auto transformSourceGeodCRS = sourceCRSIn->extractGeodeticCRS(); if (!transformSourceGeodCRS) { throw InvalidOperation( "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation"); } util::PropertyMap properties; properties.set(common::IdentifiedObject::NAME_KEY, concat("Transformation from ", transformSourceGeodCRS->nameStr(), " to WGS84")); auto targetCRS = dynamic_cast( transformSourceGeodCRS.get()) || transformSourceGeodCRS->isSphericalPlanetocentric() ? util::nn_static_pointer_cast( crs::GeographicCRS::EPSG_4326) : util::nn_static_pointer_cast( crs::GeodeticCRS::EPSG_4978); crs::CRSNNPtr transformSourceCRS = NN_NO_CHECK(transformSourceGeodCRS); if (TOWGS84Parameters.size() == 3) { return createGeocentricTranslations( properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], {}); } return createPositionVector( properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], TOWGS84Parameters[3], TOWGS84Parameters[4], TOWGS84Parameters[5], TOWGS84Parameters[6], {}); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with NTv2 method. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param filename NTv2 filename. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createNTv2( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const std::string &filename, const std::vector &accuracies) { return create(properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2), VectorOfParameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}, VectorOfValues{ParameterValue::createFilename(filename)}, accuracies); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static TransformationNNPtr _createGravityRelatedHeightToGeographic3D( const util::PropertyMap &properties, bool inverse, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const std::string &filename, const std::vector &accuracies) { return Transformation::create( properties, sourceCRSIn, targetCRSIn, interpolationCRSIn, util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D), VectorOfParameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}, VectorOfValues{ParameterValue::createFilename(filename)}, accuracies); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a transformation from GravityRelatedHeight to * Geographic3D * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param interpolationCRSIn Interpolation CRS. (might be null) * @param filename GRID filename. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn, const std::string &filename, const std::vector &accuracies) { return _createGravityRelatedHeightToGeographic3D( properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn, filename, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with method VERTCON * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param filename GRID filename. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createVERTCON( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const std::string &filename, const std::vector &accuracies) { return create(properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON), VectorOfParameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)}, VectorOfValues{ParameterValue::createFilename(filename)}, accuracies); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static inline std::vector buildAccuracyZero() { return std::vector{ metadata::PositionalAccuracy::create("0")}; } // --------------------------------------------------------------------------- //! @endcond /** \brief Instantiate a transformation with method Longitude rotation * * This method is defined as * * EPSG:9601. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param offset Longitude offset to add. * @return new Transformation. */ TransformationNNPtr Transformation::createLongitudeRotation( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) { return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero()); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with method Geographic 2D offsets * * This method is defined as * * EPSG:9619. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param offsetLat Latitude offset to add. * @param offsetLong Longitude offset to add. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createGeographic2DOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, const common::Angle &offsetLong, const std::vector &accuracies) { return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, VectorOfValues{offsetLat, offsetLong}, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with method Geographic 3D offsets * * This method is defined as * * EPSG:9660. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param offsetLat Latitude offset to add. * @param offsetLong Longitude offset to add. * @param offsetHeight Height offset to add. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createGeographic3DOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight, const std::vector &accuracies) { return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, VectorOfValues{offsetLat, offsetLong, offsetHeight}, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with method Geographic 2D with * height * offsets * * This method is defined as * * EPSG:9618. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param offsetLat Latitude offset to add. * @param offsetLong Longitude offset to add. * @param offsetHeight Geoid undulation to add. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight, const std::vector &accuracies) { return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode( EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_HEIGHT)}, VectorOfValues{offsetLat, offsetLong, offsetHeight}, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with method Cartesian grid offsets * * This method is defined as * * EPSG:9656. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param eastingOffset Easting offset to add. * @param northingOffset Northing offset to add. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @since PROJ 9.5.0 */ TransformationNNPtr Transformation::createCartesianGridOffsets( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Length &eastingOffset, const common::Length &northingOffset, const std::vector &accuracies) { return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_EASTING_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_NORTHING_OFFSET)}, VectorOfValues{eastingOffset, northingOffset}, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation with method Vertical Offset. * * This method is defined as * * EPSG:9616. * * @param properties See \ref general_properties of the Transformation. * At minimum the name should be defined. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param offsetHeight Geoid undulation to add. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. */ TransformationNNPtr Transformation::createVerticalOffset( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight, const std::vector &accuracies) { return create(properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET), VectorOfParameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, VectorOfValues{offsetHeight}, accuracies); } // --------------------------------------------------------------------------- /** \brief Instantiate a transformation based on the Change of Vertical Unit * method. * * This method is defined as * * EPSG:1069 [DEPRECATED]. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param sourceCRSIn Source CRS. * @param targetCRSIn Target CRS. * @param factor Conversion factor * @param accuracies Vector of positional accuracy (might be empty). * @return a new Transformation. */ TransformationNNPtr Transformation::createChangeVerticalUnit( const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor, const std::vector &accuracies) { return create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), VectorOfParameters{ createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), }, VectorOfValues{ factor, }, accuracies); } // --------------------------------------------------------------------------- // to avoid -0... static double negate(double val) { if (val != 0) { return -val; } return 0.0; } // --------------------------------------------------------------------------- static CoordinateOperationPtr createApproximateInverseIfPossible(const Transformation *op) { bool sevenParamsTransform = false; bool fifteenParamsTransform = false; const auto &method = op->method(); const auto &methodName = method->nameStr(); const int methodEPSGCode = method->getEPSGCode(); const auto paramCount = op->parameterValues().size(); const bool isPositionVector = ci_find(methodName, "Position Vector") != std::string::npos; const bool isCoordinateFrame = ci_find(methodName, "Coordinate Frame") != std::string::npos; // See end of "2.4.3.3 Helmert 7-parameter transformations" // in EPSG 7-2 guidance // For practical purposes, the inverse of 7- or 15-parameters Helmert // can be obtained by using the forward method with all parameters // negated // (except reference epoch!) // So for WKT export use that. But for PROJ string, we use the +inv flag // so as to get "perfect" round-tripability. if ((paramCount == 7 && isCoordinateFrame && !isTimeDependent(methodName)) || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) { sevenParamsTransform = true; } else if ( (paramCount == 15 && isCoordinateFrame && isTimeDependent(methodName)) || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { fifteenParamsTransform = true; } else if ((paramCount == 7 && isPositionVector && !isTimeDependent(methodName)) || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { sevenParamsTransform = true; } else if ( (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { fifteenParamsTransform = true; } if (sevenParamsTransform || fifteenParamsTransform) { double neg_x = negate(op->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); double neg_y = negate(op->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); double neg_z = negate(op->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); double neg_rx = negate( op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND)); double neg_ry = negate( op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND)); double neg_rz = negate( op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND)); double neg_scaleDiff = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, common::UnitOfMeasure::PARTS_PER_MILLION)); auto methodProperties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, methodName); int method_epsg_code = method->getEPSGCode(); if (method_epsg_code) { methodProperties .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, method_epsg_code); } bool exactInverse = (neg_rx == 0 && neg_ry == 0 && neg_rz == 0 && neg_scaleDiff == 0); if (fifteenParamsTransform) { double neg_rate_x = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, common::UnitOfMeasure::METRE_PER_YEAR)); double neg_rate_y = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, common::UnitOfMeasure::METRE_PER_YEAR)); double neg_rate_z = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, common::UnitOfMeasure::METRE_PER_YEAR)); double neg_rate_rx = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); double neg_rate_ry = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); double neg_rate_rz = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND_PER_YEAR)); double neg_rate_scaleDiff = negate(op->parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, common::UnitOfMeasure::PPM_PER_YEAR)); double referenceEpochYear = op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, common::UnitOfMeasure::YEAR); exactInverse &= (neg_rate_rx == 0 && neg_rate_ry == 0 && neg_rate_rz == 0 && neg_rate_scaleDiff == 0); return util::nn_static_pointer_cast( createFifteenParamsTransform( createPropertiesForInverse(op, false, !exactInverse), methodProperties, op->targetCRS(), op->sourceCRS(), neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z, neg_rate_rx, neg_rate_ry, neg_rate_rz, neg_rate_scaleDiff, referenceEpochYear, op->coordinateOperationAccuracies())) .as_nullable(); } else { return util::nn_static_pointer_cast( createSevenParamsTransform( createPropertiesForInverse(op, false, !exactInverse), methodProperties, op->targetCRS(), op->sourceCRS(), neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz, neg_scaleDiff, op->coordinateOperationAccuracies())) .as_nullable(); } } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TransformationNNPtr Transformation::Private::registerInv(const Transformation *thisIn, TransformationNNPtr invTransform) { invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable(); invTransform->setHasBallparkTransformation( thisIn->hasBallparkTransformation()); invTransform->setRequiresPerCoordinateInputTime( thisIn->requiresPerCoordinateInputTime()); return invTransform; } //! @endcond // --------------------------------------------------------------------------- CoordinateOperationNNPtr Transformation::inverse() const { return inverseAsTransformation(); } // --------------------------------------------------------------------------- TransformationNNPtr Transformation::inverseAsTransformation() const { if (d->forwardOperation_) { return NN_NO_CHECK(d->forwardOperation_); } const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const int methodEPSGCode = l_method->getEPSGCode(); const auto &l_sourceCRS = sourceCRS(); const auto &l_targetCRS = targetCRS(); // For geocentric translation, the inverse is exactly the negation of // the parameters. if ((ci_find(methodName, "Geocentric translations") != std::string::npos && ci_find(methodName, "grid") == std::string::npos) || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { double x = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); double y = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); double z = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); auto properties = createPropertiesForInverse(this, false, false); return Private::registerInv( this, create(properties, l_targetCRS, l_sourceCRS, nullptr, createMethodMapNameEPSGCode( useOperationMethodEPSGCodeIfPresent( properties, methodEPSGCode)), VectorOfParameters{ createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION), createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION), }, createParams(common::Length(negate(x)), common::Length(negate(y)), common::Length(negate(z))), coordinateOperationAccuracies())); } if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { double x = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); double y = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); double z = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); double da = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); double df = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { return Private::registerInv( this, createAbridgedMolodensky( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, negate(x), negate(y), negate(z), negate(da), negate(df), coordinateOperationAccuracies())); } else { return Private::registerInv( this, createMolodensky(createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, negate(x), negate(y), negate(z), negate(da), negate(df), coordinateOperationAccuracies())); } } if (isLongitudeRotation()) { const auto &offset = parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); const common::Angle newOffset(negate(offset.value()), offset.unit()); return Private::registerInv( this, createLongitudeRotation( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, newOffset)); } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { const auto &offsetLat = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); const common::Angle newOffsetLat(negate(offsetLat.value()), offsetLat.unit()); const auto &offsetLong = parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); const common::Angle newOffsetLong(negate(offsetLong.value()), offsetLong.unit()); return Private::registerInv( this, createGeographic2DOffsets( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, coordinateOperationAccuracies())); } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { const auto &offsetLat = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); const common::Angle newOffsetLat(negate(offsetLat.value()), offsetLat.unit()); const auto &offsetLong = parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); const common::Angle newOffsetLong(negate(offsetLong.value()), offsetLong.unit()); const auto &offsetHeight = parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); const common::Length newOffsetHeight(negate(offsetHeight.value()), offsetHeight.unit()); return Private::registerInv( this, createGeographic3DOffsets( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight, coordinateOperationAccuracies())); } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { const auto &offsetLat = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET); const common::Angle newOffsetLat(negate(offsetLat.value()), offsetLat.unit()); const auto &offsetLong = parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET); const common::Angle newOffsetLong(negate(offsetLong.value()), offsetLong.unit()); const auto &offsetHeight = parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_HEIGHT); const common::Length newOffsetHeight(negate(offsetHeight.value()), offsetHeight.unit()); return Private::registerInv( this, createGeographic2DWithHeightOffsets( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight, coordinateOperationAccuracies())); } if (methodEPSGCode == EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS) { const auto &eastingOffset = parameterValueMeasure(EPSG_CODE_PARAMETER_EASTING_OFFSET); const common::Length newEastingOffset(negate(eastingOffset.value()), eastingOffset.unit()); const auto &northingOffset = parameterValueMeasure(EPSG_CODE_PARAMETER_NORTHING_OFFSET); const common::Length newNorthingOffset(negate(northingOffset.value()), northingOffset.unit()); return Private::registerInv( this, createCartesianGridOffsets( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, newEastingOffset, newNorthingOffset, coordinateOperationAccuracies())); } if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { const auto &offsetHeight = parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); const common::Length newOffsetHeight(negate(offsetHeight.value()), offsetHeight.unit()); return Private::registerInv( this, createVerticalOffset(createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, newOffsetHeight, coordinateOperationAccuracies())); } if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { const double convFactor = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); // coverity[divide_by_zero] const double invConvFactor = convFactor == 0.0 ? 0.0 : 1.0 / convFactor; return Private::registerInv( this, createChangeVerticalUnit( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, common::Scale(invConvFactor), coordinateOperationAccuracies())); } #ifdef notdef // We don't need that currently, but we might... if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { return Private::registerInv( this, createHeightDepthReversal( createPropertiesForInverse(this, false, false), l_targetCRS, l_sourceCRS, coordinateOperationAccuracies())); } #endif return InverseTransformation::create(NN_NO_CHECK( util::nn_dynamic_pointer_cast(shared_from_this()))); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- InverseTransformation::InverseTransformation(const TransformationNNPtr &forward) : Transformation( forward->targetCRS(), forward->sourceCRS(), forward->interpolationCRS(), OperationMethod::create(createPropertiesForInverse(forward->method()), forward->method()->parameters()), forward->parameterValues(), forward->coordinateOperationAccuracies()), InverseCoordinateOperation(forward, true) { setPropertiesFromForward(); } // --------------------------------------------------------------------------- InverseTransformation::~InverseTransformation() = default; // --------------------------------------------------------------------------- TransformationNNPtr InverseTransformation::create(const TransformationNNPtr &forward) { auto conv = util::nn_make_shared(forward); conv->assignSelf(conv); return conv; } // --------------------------------------------------------------------------- TransformationNNPtr InverseTransformation::inverseAsTransformation() const { return NN_NO_CHECK( util::nn_dynamic_pointer_cast(forwardOperation_)); } // --------------------------------------------------------------------------- void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const { auto approxInverse = createApproximateInverseIfPossible( util::nn_dynamic_pointer_cast(forwardOperation_).get()); if (approxInverse) { approxInverse->_exportToWKT(formatter); } else { Transformation::_exportToWKT(formatter); } } // --------------------------------------------------------------------------- CoordinateOperationNNPtr InverseTransformation::_shallowClone() const { auto op = InverseTransformation::nn_make_shared( inverseAsTransformation()->shallowClone()); op->assignSelf(op); op->setCRSs(this, false); return util::nn_static_pointer_cast(op); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Transformation::_exportToWKT(io::WKTFormatter *formatter) const { exportTransformationToWKT(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Transformation::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext( formatter->abridgedTransformation() ? "AbridgedTransformation" : "Transformation", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } if (!formatter->abridgedTransformation()) { writer->AddObjKey("source_crs"); formatter->setAllowIDInImmediateChild(); sourceCRS()->_exportToJSON(formatter); writer->AddObjKey("target_crs"); formatter->setAllowIDInImmediateChild(); targetCRS()->_exportToJSON(formatter); const auto &l_interpolationCRS = interpolationCRS(); if (l_interpolationCRS) { writer->AddObjKey("interpolation_crs"); formatter->setAllowIDInImmediateChild(); l_interpolationCRS->_exportToJSON(formatter); } } else { if (formatter->abridgedTransformationWriteSourceCRS()) { writer->AddObjKey("source_crs"); formatter->setAllowIDInImmediateChild(); sourceCRS()->_exportToJSON(formatter); } } writer->AddObjKey("method"); formatter->setOmitTypeInImmediateChild(); formatter->setAllowIDInImmediateChild(); method()->_exportToJSON(formatter); writer->AddObjKey("parameters"); { auto parametersContext(writer->MakeArrayContext(false)); for (const auto &genOpParamvalue : parameterValues()) { formatter->setAllowIDInImmediateChild(); formatter->setOmitTypeInImmediateChild(); genOpParamvalue->_exportToJSON(formatter); } } if (!formatter->abridgedTransformation()) { if (!coordinateOperationAccuracies().empty()) { writer->AddObjKey("accuracy"); writer->Add(coordinateOperationAccuracies()[0]->value()); } } if (formatter->abridgedTransformation()) { if (formatter->outputId()) { formatID(formatter); } } else { ObjectUsage::baseExportToJSON(formatter); } } //! @endcond // --------------------------------------------------------------------------- void Transformation::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(FormattingException) { if (formatter->convention() == io::PROJStringFormatter::Convention::PROJ_4) { throw io::FormattingException( "Transformation cannot be exported as a PROJ.4 string"); } if (exportToPROJStringGeneric(formatter)) { return; } throw io::FormattingException("Unimplemented " + nameStr()); } } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/singleoperation.cpp000664 001750 001750 00000703267 15166171715 022356 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/crs_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "coordinateoperation_internal.hpp" #include "coordinateoperation_private.hpp" #include "operationmethod_private.hpp" #include "oputils.hpp" #include "parammappings.hpp" #include "vectorofvaluesparams.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // M_PI // clang-format on #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( const std::string &message) : InvalidOperation(message) {} InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( const InvalidOperationEmptyIntersection &) = default; InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- GridDescription::GridDescription() : shortName{}, fullName{}, packageName{}, url{}, directDownload(false), openLicense(false), available(false) {} GridDescription::~GridDescription() = default; GridDescription::GridDescription(const GridDescription &) = default; GridDescription::GridDescription(GridDescription &&other) noexcept : shortName(std::move(other.shortName)), fullName(std::move(other.fullName)), packageName(std::move(other.packageName)), url(std::move(other.url)), directDownload(other.directDownload), openLicense(other.openLicense), available(other.available) {} //! @endcond // --------------------------------------------------------------------------- CoordinateOperation::CoordinateOperation() : d(std::make_unique()) {} // --------------------------------------------------------------------------- CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) : ObjectUsage(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateOperation::~CoordinateOperation() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the version of the coordinate transformation (i.e. * instantiation * due to the stochastic nature of the parameters). * * Mandatory when describing a coordinate transformation or point motion * operation, and should not be supplied for a coordinate conversion. * * @return version or empty. */ const util::optional & CoordinateOperation::operationVersion() const { return d->operationVersion_; } // --------------------------------------------------------------------------- /** \brief Return estimate(s) of the impact of this coordinate operation on * point accuracy. * * Gives position error estimates for target coordinates of this coordinate * operation, assuming no errors in source coordinates. * * @return estimate(s) or empty vector. */ const std::vector & CoordinateOperation::coordinateOperationAccuracies() const { return d->coordinateOperationAccuracies_; } // --------------------------------------------------------------------------- /** \brief Return the source CRS of this coordinate operation. * * This should not be null, expect for of a derivingConversion of a DerivedCRS * when the owning DerivedCRS has been destroyed. * * @return source CRS, or null. */ const crs::CRSPtr CoordinateOperation::sourceCRS() const { return d->sourceCRSWeak_.lock(); } // --------------------------------------------------------------------------- /** \brief Return the target CRS of this coordinate operation. * * This should not be null, expect for of a derivingConversion of a DerivedCRS * when the owning DerivedCRS has been destroyed. * * @return target CRS, or null. */ const crs::CRSPtr CoordinateOperation::targetCRS() const { return d->targetCRSWeak_.lock(); } // --------------------------------------------------------------------------- /** \brief Return the interpolation CRS of this coordinate operation. * * @return interpolation CRS, or null. */ const crs::CRSPtr &CoordinateOperation::interpolationCRS() const { return d->interpolationCRS_; } // --------------------------------------------------------------------------- /** \brief Return the source epoch of coordinates. * * @return source epoch of coordinates, or empty. */ const util::optional & CoordinateOperation::sourceCoordinateEpoch() const { return *(d->sourceCoordinateEpoch_); } // --------------------------------------------------------------------------- /** \brief Return the target epoch of coordinates. * * @return target epoch of coordinates, or empty. */ const util::optional & CoordinateOperation::targetCoordinateEpoch() const { return *(d->targetCoordinateEpoch_); } // --------------------------------------------------------------------------- void CoordinateOperation::setWeakSourceTargetCRS( std::weak_ptr sourceCRSIn, std::weak_ptr targetCRSIn) { d->sourceCRSWeak_ = std::move(sourceCRSIn); d->targetCRSWeak_ = std::move(targetCRSIn); } // --------------------------------------------------------------------------- void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn) { d->strongRef_ = std::make_unique(sourceCRSIn, targetCRSIn); d->sourceCRSWeak_ = sourceCRSIn.as_nullable(); d->targetCRSWeak_ = targetCRSIn.as_nullable(); d->interpolationCRS_ = interpolationCRSIn; } // --------------------------------------------------------------------------- void CoordinateOperation::setCRSsUpdateInverse( const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn) { setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn); auto invCO = dynamic_cast(this); if (invCO) { invCO->forwardOperation()->setCRSs(targetCRSIn, sourceCRSIn, interpolationCRSIn); } auto transf = dynamic_cast(this); if (transf) { transf->inverseAsTransformation()->setCRSs(targetCRSIn, sourceCRSIn, interpolationCRSIn); } auto concat = dynamic_cast(this); if (concat) { auto first = concat->operations().front().get(); auto &firstTarget(first->targetCRS()); if (firstTarget) { first->setCRSsUpdateInverse(sourceCRSIn, NN_NO_CHECK(firstTarget), first->interpolationCRS()); } auto last = concat->operations().back().get(); auto &lastSource(last->sourceCRS()); if (lastSource) { last->setCRSsUpdateInverse(NN_NO_CHECK(lastSource), targetCRSIn, last->interpolationCRS()); } } } // --------------------------------------------------------------------------- void CoordinateOperation::setInterpolationCRS( const crs::CRSPtr &interpolationCRSIn) { d->interpolationCRS_ = interpolationCRSIn; } // --------------------------------------------------------------------------- void CoordinateOperation::setCRSs(const CoordinateOperation *in, bool inverseSourceTarget) { auto l_sourceCRS = in->sourceCRS(); auto l_targetCRS = in->targetCRS(); if (l_sourceCRS && l_targetCRS) { auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS); auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); if (inverseSourceTarget) { setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS()); } else { setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS()); } } } // --------------------------------------------------------------------------- void CoordinateOperation::setSourceCoordinateEpoch( const util::optional &epoch) { d->sourceCoordinateEpoch_ = std::make_shared>(epoch); if (epoch) { auto invOp = dynamic_cast(this); if (invOp) invOp->forwardOperation()->setTargetCoordinateEpoch(epoch); } } // --------------------------------------------------------------------------- void CoordinateOperation::setTargetCoordinateEpoch( const util::optional &epoch) { d->targetCoordinateEpoch_ = std::make_shared>(epoch); if (epoch) { auto invOp = dynamic_cast(this); if (invOp) invOp->forwardOperation()->setSourceCoordinateEpoch(epoch); } } // --------------------------------------------------------------------------- void CoordinateOperation::setAccuracies( const std::vector &accuracies) { d->coordinateOperationAccuracies_ = accuracies; } // --------------------------------------------------------------------------- /** \brief Return whether a coordinate operation can be instantiated as * a PROJ pipeline, checking in particular that referenced grids are * available. */ bool CoordinateOperation::isPROJInstantiable( const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const { try { exportToPROJString(io::PROJStringFormatter::create().get()); } catch (const std::exception &) { return false; } for (const auto &gridDesc : gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) { // Grid name starting with @ are considered as optional. if (!gridDesc.available && (gridDesc.shortName.empty() || gridDesc.shortName[0] != '@')) { return false; } } return true; } // --------------------------------------------------------------------------- /** \brief Return whether a coordinate operation has a "ballpark" * transformation, * that is a very approximate one, due to lack of more accurate transformations. * * Typically a null geographic offset between two horizontal datum, or a * null vertical offset (or limited to unit changes) between two vertical * datum. Errors of several tens to one hundred meters might be expected, * compared to more accurate transformations. */ bool CoordinateOperation::hasBallparkTransformation() const { return d->hasBallparkTransformation_; } // --------------------------------------------------------------------------- void CoordinateOperation::setHasBallparkTransformation(bool b) { d->hasBallparkTransformation_ = b; } // --------------------------------------------------------------------------- /** \brief Return whether a coordinate operation requires coordinate tuples * to have a valid input time for the coordinate transformation to succeed. * (this applies for the forward direction) * * Note: in the case of a time-dependent Helmert transformation, this function * will return true, but when executing proj_trans(), execution will still * succeed if the time information is missing, due to the transformation central * epoch being used as a fallback. * * @since 9.5 */ bool CoordinateOperation::requiresPerCoordinateInputTime() const { return d->requiresPerCoordinateInputTime_ && !d->sourceCoordinateEpoch_->has_value(); } // --------------------------------------------------------------------------- void CoordinateOperation::setRequiresPerCoordinateInputTime(bool b) { d->requiresPerCoordinateInputTime_ = b; } // --------------------------------------------------------------------------- void CoordinateOperation::setProperties( const util::PropertyMap &properties) // throw(InvalidValueTypeException) { ObjectUsage::setProperties(properties); properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_); } // --------------------------------------------------------------------------- /** \brief Return a variation of the current coordinate operation whose axis * order is the one expected for visualization purposes. */ CoordinateOperationNNPtr CoordinateOperation::normalizeForVisualization() const { auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); if (!l_sourceCRS || !l_targetCRS) { throw util::UnsupportedOperationException( "Cannot retrieve source or target CRS"); } const bool swapSource = l_sourceCRS->mustAxisOrderBeSwitchedForVisualization(); const bool swapTarget = l_targetCRS->mustAxisOrderBeSwitchedForVisualization(); auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); if (!swapSource && !swapTarget) { return l_this; } std::vector subOps; if (swapSource) { auto op = Conversion::createAxisOrderReversal(false); op->setCRSs(l_sourceCRS->normalizeForVisualization(), NN_NO_CHECK(l_sourceCRS), nullptr); subOps.emplace_back(op); } subOps.emplace_back(l_this); if (swapTarget) { auto op = Conversion::createAxisOrderReversal(false); op->setCRSs(NN_NO_CHECK(l_targetCRS), l_targetCRS->normalizeForVisualization(), nullptr); subOps.emplace_back(op); } util::PropertyMap properties; // The domain(s) are unchanged addDomains(properties, this); addModifiedIdentifier(properties, this, /* inverse = */ false, /* derivedFrom = */ true); std::string name = nameStr(); if (!name.empty()) { name += NORMALIZED_AXIS_ORDER_SUFFIX_STR; properties.set(common::IdentifiedObject::NAME_KEY, name); } const std::string &l_remarks = remarks(); if (!l_remarks.empty()) { properties.set(common::IdentifiedObject::REMARKS_KEY, l_remarks); } std::vector flattenOps; for (const auto &subOp : subOps) { auto subOpConcat = dynamic_cast(subOp.get()); if (subOpConcat) { auto subSubOps = subOpConcat->operations(); for (const auto &subSubOp : subSubOps) { flattenOps.emplace_back(subSubOp); } } else { flattenOps.emplace_back(subOp); } } return ConcatenatedOperation::create(properties, flattenOps, coordinateOperationAccuracies()); } // --------------------------------------------------------------------------- /** \brief Return a coordinate transformer for this operation. * * The returned coordinate transformer is tied to the provided context, * and should only be called by the thread "owning" the passed context. * It should not be used after the context has been destroyed. * * @param ctx Execution context to which the transformer will be tied to. * If null, the default context will be used (only safe for * single-threaded applications). * @return a new CoordinateTransformer instance. * @since 9.3 * @throw UnsupportedOperationException if the transformer cannot be * instantiated. */ CoordinateTransformerNNPtr CoordinateOperation::coordinateTransformer(PJ_CONTEXT *ctx) const { auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); return CoordinateTransformer::create(l_this, ctx); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateOperationNNPtr CoordinateOperation::shallowClone() const { return _shallowClone(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateTransformer::Private { PJ *pj_; }; //! @endcond // --------------------------------------------------------------------------- CoordinateTransformer::CoordinateTransformer() : d(std::make_unique()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateTransformer::~CoordinateTransformer() { if (d->pj_) { proj_assign_context(d->pj_, pj_get_default_ctx()); proj_destroy(d->pj_); } } //! @endcond // --------------------------------------------------------------------------- CoordinateTransformerNNPtr CoordinateTransformer::create(const CoordinateOperationNNPtr &op, PJ_CONTEXT *ctx) { auto transformer = NN_NO_CHECK( CoordinateTransformer::make_unique()); // pj_obj_create does not sanitize the context if (ctx == nullptr) ctx = pj_get_default_ctx(); transformer->d->pj_ = pj_obj_create(ctx, op); if (transformer->d->pj_ == nullptr) throw util::UnsupportedOperationException( "Cannot instantiate transformer"); return transformer; } // --------------------------------------------------------------------------- /** Transforms a coordinate tuple. * * PJ_COORD is a union of many structures. In the context of this method, * it is prudent to only use the v[] array, with the understanding that * the expected input values should be passed in the order and the unit of * the successive axis of the input CRS. Similarly the values returned in the * v[] array of the output PJ_COORD are in the order and the unit of the * successive axis of the output CRS. * For coordinate operations involving a time-dependent operation, * coord.v[3] is the decimal year of the coordinate epoch of the input (or * HUGE_VAL to indicate none) * * If an error occurs, HUGE_VAL is returned in the .v[0] member of the output * coordinate tuple. * * Example how to transform coordinates from EPSG:4326 (WGS 84 * latitude/longitude) to EPSG:32631 (WGS 84 / UTM zone 31N). \code{.cpp} auto authFactory = AuthorityFactory::create(DatabaseContext::create(), std::string()); auto coord_op_ctxt = CoordinateOperationContext::create( authFactory, nullptr, 0.0); auto authFactoryEPSG = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); auto list = CoordinateOperationFactory::create()->createOperations( authFactoryEPSG->createCoordinateReferenceSystem("4326"), authFactoryEPSG->createCoordinateReferenceSystem("32631"), coord_op_ctxt); ASSERT_TRUE(!list.empty()); PJ_CONTEXT* ctx = proj_context_create(); auto transformer = list[0]->coordinateTransformer(ctx); PJ_COORD c; c.v[0] = 49; // latitude in degree c.v[1] = 2; // longitude in degree c.v[2] = 0; c.v[3] = HUGE_VAL; c = transformer->transform(c); EXPECT_NEAR(c.v[0], 426857.98771728, 1e-8); // easting in metre EXPECT_NEAR(c.v[1], 5427937.52346492, 1e-8); // northing in metre proj_context_destroy(ctx); \endcode */ PJ_COORD CoordinateTransformer::transform(PJ_COORD coord) { return proj_trans(d->pj_, PJ_FWD, coord); } // --------------------------------------------------------------------------- OperationMethod::OperationMethod() : d(std::make_unique()) {} // --------------------------------------------------------------------------- OperationMethod::OperationMethod(const OperationMethod &other) : IdentifiedObject(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress OperationMethod::~OperationMethod() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the formula(s) or procedure used by this coordinate operation * method. * * This may be a reference to a publication (in which case use * formulaCitation()). * * Note that the operation method may not be analytic, in which case this * attribute references or contains the procedure, not an analytic formula. * * @return the formula, or empty. */ const util::optional &OperationMethod::formula() PROJ_PURE_DEFN { return d->formula_; } // --------------------------------------------------------------------------- /** \brief Return a reference to a publication giving the formula(s) or * procedure * used by the coordinate operation method. * * @return the formula citation, or empty. */ const util::optional & OperationMethod::formulaCitation() PROJ_PURE_DEFN { return d->formulaCitation_; } // --------------------------------------------------------------------------- /** \brief Return the parameters of this operation method. * * @return the parameters. */ const std::vector & OperationMethod::parameters() PROJ_PURE_DEFN { return d->parameters_; } // --------------------------------------------------------------------------- /** \brief Instantiate a operation method from a vector of * GeneralOperationParameter. * * @param properties See \ref general_properties. At minimum the name should be * defined. * @param parameters Vector of GeneralOperationParameterNNPtr. * @return a new OperationMethod. */ OperationMethodNNPtr OperationMethod::create( const util::PropertyMap &properties, const std::vector ¶meters) { OperationMethodNNPtr method( OperationMethod::nn_make_shared()); method->assignSelf(method); method->setProperties(properties); method->d->parameters_ = parameters; properties.getStringValue("proj_method", method->d->projMethodOverride_); return method; } // --------------------------------------------------------------------------- /** \brief Instantiate a operation method from a vector of OperationParameter. * * @param properties See \ref general_properties. At minimum the name should be * defined. * @param parameters Vector of OperationParameterNNPtr. * @return a new OperationMethod. */ OperationMethodNNPtr OperationMethod::create( const util::PropertyMap &properties, const std::vector ¶meters) { std::vector parametersGeneral; parametersGeneral.reserve(parameters.size()); for (const auto &p : parameters) { parametersGeneral.push_back(p); } return create(properties, parametersGeneral); } // --------------------------------------------------------------------------- /** \brief Return the EPSG code, either directly, or through the name * @return code, or 0 if not found */ int OperationMethod::getEPSGCode() PROJ_PURE_DEFN { int epsg_code = IdentifiedObject::getEPSGCode(); if (epsg_code == 0) { auto l_name = nameStr(); if (ends_with(l_name, " (3D)")) { l_name.resize(l_name.size() - strlen(" (3D)")); } size_t nMethodNameCodes = 0; const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); for (size_t i = 0; i < nMethodNameCodes; ++i) { const auto &tuple = methodNameCodes[i]; if (metadata::Identifier::isEquivalentName(l_name.c_str(), tuple.name)) { return tuple.epsg_code; } } } return epsg_code; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::METHOD : io::WKTConstants::PROJECTION, !identifiers().empty()); std::string l_name(nameStr()); if (!isWKT2) { const MethodMapping *mapping = getMapping(this); if (mapping == nullptr) { l_name = replaceAll(l_name, " ", "_"); } else { if (l_name == PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { l_name = "Geostationary_Satellite"; } else { if (mapping->wkt1_name == nullptr) { throw io::FormattingException( std::string("Unsupported conversion method: ") + mapping->wkt2_name); } l_name = mapping->wkt1_name; } } } formatter->addQuotedString(l_name); if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void OperationMethod::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext("OperationMethod", !identifiers().empty())); writer->AddObjKey("name"); writer->Add(nameStr()); if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool OperationMethod::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherOM = dynamic_cast(other); if (otherOM == nullptr || !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } // TODO test formula and formulaCitation const auto ¶ms = parameters(); const auto &otherParams = otherOM->parameters(); const auto paramsSize = params.size(); if (paramsSize != otherParams.size()) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { for (size_t i = 0; i < paramsSize; i++) { if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion, dbContext)) { return false; } } } else { std::vector candidateIndices(paramsSize, true); for (size_t i = 0; i < paramsSize; i++) { bool found = false; for (size_t j = 0; j < paramsSize; j++) { if (candidateIndices[j] && params[i]->_isEquivalentTo(otherParams[j].get(), criterion, dbContext)) { candidateIndices[j] = false; found = true; break; } } if (!found) { return false; } } } return true; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeneralParameterValue::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeneralParameterValue::GeneralParameterValue() : d(nullptr) {} // --------------------------------------------------------------------------- GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &) : d(nullptr) {} //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeneralParameterValue::~GeneralParameterValue() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct OperationParameterValue::Private { OperationParameterNNPtr parameter; ParameterValueNNPtr parameterValue; Private(const OperationParameterNNPtr ¶meterIn, const ParameterValueNNPtr &valueIn) : parameter(parameterIn), parameterValue(valueIn) {} }; //! @endcond // --------------------------------------------------------------------------- OperationParameterValue::OperationParameterValue( const OperationParameterValue &other) : GeneralParameterValue(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- OperationParameterValue::OperationParameterValue( const OperationParameterNNPtr ¶meterIn, const ParameterValueNNPtr &valueIn) : GeneralParameterValue(), d(std::make_unique(parameterIn, valueIn)) {} // --------------------------------------------------------------------------- /** \brief Instantiate a OperationParameterValue. * * @param parameterIn Parameter (definition). * @param valueIn Parameter value. * @return a new OperationParameterValue. */ OperationParameterValueNNPtr OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, const ParameterValueNNPtr &valueIn) { return OperationParameterValue::nn_make_shared( parameterIn, valueIn); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress OperationParameterValue::~OperationParameterValue() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the parameter (definition) * * @return the parameter (definition). */ const OperationParameterNNPtr & OperationParameterValue::parameter() PROJ_PURE_DEFN { return d->parameter; } // --------------------------------------------------------------------------- /** \brief Return the parameter value. * * @return the parameter value. */ const ParameterValueNNPtr & OperationParameterValue::parameterValue() PROJ_PURE_DEFN { return d->parameterValue; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void OperationParameterValue::_exportToWKT( // cppcheck-suppress passedByValue io::WKTFormatter *formatter) const { _exportToWKT(formatter, nullptr); } void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter, const MethodMapping *mapping) const { const ParamMapping *paramMapping = mapping ? getMapping(mapping, d->parameter) : nullptr; if (paramMapping && paramMapping->wkt1_name == nullptr) { return; } const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) { formatter->startNode(io::WKTConstants::PARAMETERFILE, !parameter()->identifiers().empty()); } else { formatter->startNode(io::WKTConstants::PARAMETER, !parameter()->identifiers().empty()); } if (paramMapping) { formatter->addQuotedString(paramMapping->wkt1_name); } else { formatter->addQuotedString(parameter()->nameStr()); } parameterValue()->_exportToWKT(formatter); if (formatter->outputId()) { parameter()->formatID(formatter); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void OperationParameterValue::_exportToJSON( io::JSONFormatter *formatter) const { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext( "ParameterValue", !parameter()->identifiers().empty())); writer->AddObjKey("name"); writer->Add(parameter()->nameStr()); const auto &l_value(parameterValue()); const auto value_type = l_value->type(); if (value_type == ParameterValue::Type::MEASURE) { writer->AddObjKey("value"); writer->Add(l_value->value().value(), 15); writer->AddObjKey("unit"); const auto &l_unit(l_value->value().unit()); if (l_unit == common::UnitOfMeasure::METRE || l_unit == common::UnitOfMeasure::DEGREE || l_unit == common::UnitOfMeasure::SCALE_UNITY) { writer->Add(l_unit.name()); } else { l_unit._exportToJSON(formatter); } } else if (value_type == ParameterValue::Type::FILENAME) { writer->AddObjKey("value"); writer->Add(l_value->valueFile()); } else if (value_type == ParameterValue::Type::INTEGER) { writer->AddObjKey("value"); writer->Add(l_value->integerValue()); } if (formatter->outputId()) { parameter()->formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** Utility method used on WKT2 import to convert from abridged transformation * to "normal" transformation parameters. */ bool OperationParameterValue::convertFromAbridged( const std::string ¶mName, double &val, const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) || paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { unit = &common::UnitOfMeasure::METRE; paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION; return true; } else if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) || paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { unit = &common::UnitOfMeasure::METRE; paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION; return true; } else if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) || paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { unit = &common::UnitOfMeasure::METRE; paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION; return true; } else if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) || paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { unit = &common::UnitOfMeasure::ARC_SECOND; paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION; return true; } else if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) || paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { unit = &common::UnitOfMeasure::ARC_SECOND; paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION; return true; } else if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) || paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { unit = &common::UnitOfMeasure::ARC_SECOND; paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION; return true; } else if (metadata::Identifier::isEquivalentName( paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) || paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { val = (val - 1.0) * 1e6; unit = &common::UnitOfMeasure::PARTS_PER_MILLION; paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE; return true; } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool OperationParameterValue::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherOPV = dynamic_cast(other); if (otherOPV == nullptr) { return false; } if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion, dbContext)) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { return d->parameterValue->_isEquivalentTo( otherOPV->d->parameterValue.get(), criterion); } if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(), criterion, dbContext)) { return true; } if (d->parameter->getEPSGCode() == EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE || d->parameter->getEPSGCode() == EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) { if (parameterValue()->type() == ParameterValue::Type::MEASURE && otherOPV->parameterValue()->type() == ParameterValue::Type::MEASURE) { const double a = std::fmod(parameterValue()->value().convertToUnit( common::UnitOfMeasure::DEGREE) + 360.0, 360.0); const double b = std::fmod(otherOPV->parameterValue()->value().convertToUnit( common::UnitOfMeasure::DEGREE) + 360.0, 360.0); return std::fabs(a - b) <= 1e-10 * std::fabs(a); } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeneralOperationParameter::Private {}; //! @endcond // --------------------------------------------------------------------------- GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {} // --------------------------------------------------------------------------- GeneralOperationParameter::GeneralOperationParameter( const GeneralOperationParameter &other) : IdentifiedObject(other), d(nullptr) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeneralOperationParameter::~GeneralOperationParameter() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct OperationParameter::Private {}; //! @endcond // --------------------------------------------------------------------------- OperationParameter::OperationParameter() : d(nullptr) {} // --------------------------------------------------------------------------- OperationParameter::OperationParameter(const OperationParameter &other) : GeneralOperationParameter(other), d(nullptr) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress OperationParameter::~OperationParameter() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a OperationParameter. * * @param properties See \ref general_properties. At minimum the name should be * defined. * @return a new OperationParameter. */ OperationParameterNNPtr OperationParameter::create(const util::PropertyMap &properties) { OperationParameterNNPtr op( OperationParameter::nn_make_shared()); op->assignSelf(op); op->setProperties(properties); return op; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool OperationParameter::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherOP = dynamic_cast(other); if (otherOP == nullptr) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); } if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return true; } auto l_epsgCode = getEPSGCode(); return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode(); } //! @endcond // --------------------------------------------------------------------------- void OperationParameter::_exportToWKT(io::WKTFormatter *) const {} // --------------------------------------------------------------------------- /** \brief Return the name of a parameter designed by its EPSG code * @return name, or nullptr if not found */ const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept { size_t nParamNameCodes = 0; const auto paramNameCodes = getParamNameCodes(nParamNameCodes); for (size_t i = 0; i < nParamNameCodes; ++i) { const auto &tuple = paramNameCodes[i]; if (tuple.epsg_code == epsg_code) { return tuple.name; } } return nullptr; } // --------------------------------------------------------------------------- /** \brief Return the EPSG code, either directly, or through the name * @return code, or 0 if not found */ int OperationParameter::getEPSGCode() PROJ_PURE_DEFN { int epsg_code = IdentifiedObject::getEPSGCode(); if (epsg_code == 0) { const auto &l_name = nameStr(); size_t nParamNameCodes = 0; const auto paramNameCodes = getParamNameCodes(nParamNameCodes); for (size_t i = 0; i < nParamNameCodes; ++i) { const auto &tuple = paramNameCodes[i]; if (metadata::Identifier::isEquivalentName(l_name.c_str(), tuple.name)) { return tuple.epsg_code; } } if (metadata::Identifier::isEquivalentName(l_name.c_str(), "Latitude of origin")) { return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; } if (metadata::Identifier::isEquivalentName(l_name.c_str(), "Scale factor")) { return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN; } } return epsg_code; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct SingleOperation::Private { std::vector parameterValues_{}; OperationMethodNNPtr method_; explicit Private(const OperationMethodNNPtr &methodIn) : method_(methodIn) {} }; //! @endcond // --------------------------------------------------------------------------- SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) : d(std::make_unique(methodIn)) { const int methodEPSGCode = d->method_->getEPSGCode(); const auto &methodName = d->method_->nameStr(); setRequiresPerCoordinateInputTime( isTimeDependent(methodName) || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D); } // --------------------------------------------------------------------------- SingleOperation::SingleOperation(const SingleOperation &other) : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) CoordinateOperation(other), #endif d(std::make_unique(*other.d)) { } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress SingleOperation::~SingleOperation() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the parameter values. * * @return the parameter values. */ const std::vector & SingleOperation::parameterValues() PROJ_PURE_DEFN { return d->parameterValues_; } // --------------------------------------------------------------------------- /** \brief Return the operation method associated to the operation. * * @return the operation method. */ const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN { return d->method_; } // --------------------------------------------------------------------------- void SingleOperation::setParameterValues( const std::vector &values) { d->parameterValues_ = values; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const ParameterValuePtr nullParameterValue; //! @endcond /** \brief Return the parameter value corresponding to a parameter name or * EPSG code * * @param paramName the parameter name (or empty, in which case epsg_code * should be non zero) * @param epsg_code the parameter EPSG code (possibly zero) * @return the value, or nullptr if not found. */ const ParameterValuePtr & SingleOperation::parameterValue(const std::string ¶mName, int epsg_code) const noexcept { if (epsg_code) { for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); if (parameter->getEPSGCode() == epsg_code) { return opParamvalue->parameterValue(); } } } } for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); if (metadata::Identifier::isEquivalentName( paramName.c_str(), parameter->nameStr().c_str())) { return opParamvalue->parameterValue(); } } } for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); if (areEquivalentParameters(paramName, parameter->nameStr())) { return opParamvalue->parameterValue(); } } } return nullParameterValue; } // --------------------------------------------------------------------------- /** \brief Return the parameter value corresponding to a EPSG code * * @param epsg_code the parameter EPSG code * @return the value, or nullptr if not found. */ const ParameterValuePtr & SingleOperation::parameterValue(int epsg_code) const noexcept { for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); if (parameter->getEPSGCode() == epsg_code) { return opParamvalue->parameterValue(); } } } return nullParameterValue; } // --------------------------------------------------------------------------- /** \brief Return the parameter value, as a measure, corresponding to a * parameter name or EPSG code * * @param paramName the parameter name (or empty, in which case epsg_code * should be non zero) * @param epsg_code the parameter EPSG code (possibly zero) * @return the measure, or the empty Measure() object if not found. */ const common::Measure & SingleOperation::parameterValueMeasure(const std::string ¶mName, int epsg_code) const noexcept { const auto &val = parameterValue(paramName, epsg_code); if (val && val->type() == ParameterValue::Type::MEASURE) { return val->value(); } return nullMeasure; } /** \brief Return the parameter value, as a measure, corresponding to a * EPSG code * * @param epsg_code the parameter EPSG code * @return the measure, or the empty Measure() object if not found. */ const common::Measure & SingleOperation::parameterValueMeasure(int epsg_code) const noexcept { const auto &val = parameterValue(epsg_code); if (val && val->type() == ParameterValue::Type::MEASURE) { return val->value(); } return nullMeasure; } //! @cond Doxygen_Suppress double SingleOperation::parameterValueNumericAsSI(int epsg_code) const noexcept { const auto &val = parameterValue(epsg_code); if (val && val->type() == ParameterValue::Type::MEASURE) { return val->value().getSIValue(); } return 0.0; } double SingleOperation::parameterValueNumeric( int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept { const auto &val = parameterValue(epsg_code); if (val && val->type() == ParameterValue::Type::MEASURE) { return val->value().convertToUnit(targetUnit); } return 0.0; } double SingleOperation::parameterValueNumeric( const char *param_name, const common::UnitOfMeasure &targetUnit) const noexcept { const auto &val = parameterValue(param_name, 0); if (val && val->type() == ParameterValue::Type::MEASURE) { return val->value().convertToUnit(targetUnit); } return 0.0; } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a PROJ-based single operation. * * \note The operation might internally be a pipeline chaining several * operations. * The use of the SingleOperation modeling here is mostly to be able to get * the PROJ string as a parameter. * * @param properties Properties * @param PROJString the PROJ string. * @param sourceCRS source CRS (might be null). * @param targetCRS target CRS (might be null). * @param accuracies Vector of positional accuracy (might be empty). * @return the new instance */ SingleOperationNNPtr SingleOperation::createPROJBased( const util::PropertyMap &properties, const std::string &PROJString, const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, const std::vector &accuracies) { return util::nn_static_pointer_cast( PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS, accuracies)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool SingleOperation::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { return _isEquivalentTo(other, criterion, dbContext, false); } bool SingleOperation::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext, bool inOtherDirection) const { auto otherSO = dynamic_cast(other); if (otherSO == nullptr || (criterion == util::IComparable::Criterion::STRICT && !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const int methodEPSGCode = d->method_->getEPSGCode(); const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode(); bool equivalentMethods = (criterion == util::IComparable::Criterion::EQUIVALENT && methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) || d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion, dbContext); if (!equivalentMethods && criterion == util::IComparable::Criterion::EQUIVALENT) { if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && otherMethodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || (otherMethodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && otherMethodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || (otherMethodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) || (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { auto geodCRS = dynamic_cast(sourceCRS().get()); auto otherGeodCRS = dynamic_cast( otherSO->sourceCRS().get()); if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() && otherGeodCRS->ellipsoid()->isSphere()) { equivalentMethods = true; } } } if (!equivalentMethods) { if (criterion == util::IComparable::Criterion::EQUIVALENT) { const auto isTOWGS84Transf = [](int code) { return code == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || code == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || code == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D || code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D || code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND; }; // Translation vs (PV or CF) // or different PV vs CF convention if (isTOWGS84Transf(methodEPSGCode) && isTOWGS84Transf(otherMethodEPSGCode)) { auto transf = static_cast(this); auto otherTransf = static_cast(otherSO); auto params = transf->getTOWGS84Parameters(true); auto otherParams = otherTransf->getTOWGS84Parameters(true); assert(params.size() == 7); assert(otherParams.size() == 7); for (size_t i = 0; i < 7; i++) { if (std::fabs(params[i] - otherParams[i]) > 1e-10 * std::fabs(params[i])) { return false; } } return true; } // _1SP methods can sometimes be equivalent to _2SP ones // Check it by using convertToOtherMethod() if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && otherMethodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { // Convert from 2SP to 1SP as the other direction has more // degree of liberties. return otherSO->_isEquivalentTo(this, criterion, dbContext); } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && otherMethodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) || (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && otherMethodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) || (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && otherMethodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { auto conv = dynamic_cast(this); if (conv) { auto eqConv = conv->convertToOtherMethod(otherMethodEPSGCode); if (eqConv) { return eqConv->_isEquivalentTo(other, criterion, dbContext); } } } } return false; } const auto &values = d->parameterValues_; const auto &otherValues = otherSO->d->parameterValues_; const auto valuesSize = values.size(); const auto otherValuesSize = otherValues.size(); if (criterion == util::IComparable::Criterion::STRICT) { if (valuesSize != otherValuesSize) { return false; } for (size_t i = 0; i < valuesSize; i++) { if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion, dbContext)) { return false; } } return true; } std::vector candidateIndices(otherValuesSize, true); bool equivalent = true; bool foundMissingArgs = valuesSize != otherValuesSize; for (size_t i = 0; equivalent && i < valuesSize; i++) { auto opParamvalue = dynamic_cast(values[i].get()); if (!opParamvalue) return false; equivalent = false; bool sameNameDifferentValue = false; for (size_t j = 0; j < otherValuesSize; j++) { if (candidateIndices[j] && values[i]->_isEquivalentTo(otherValues[j].get(), criterion, dbContext)) { candidateIndices[j] = false; equivalent = true; break; } else if (candidateIndices[j]) { auto otherOpParamvalue = dynamic_cast( otherValues[j].get()); if (!otherOpParamvalue) return false; sameNameDifferentValue = opParamvalue->parameter()->_isEquivalentTo( otherOpParamvalue->parameter().get(), criterion, dbContext); if (sameNameDifferentValue) { candidateIndices[j] = false; break; } } } if (!equivalent && methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { // For LCC_2SP, the standard parallels can be switched and // this will result in the same result. const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); if (paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL || paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { auto value_1st = parameterValue( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); auto value_2nd = parameterValue( EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL); if (value_1st && value_2nd) { equivalent = value_1st->_isEquivalentTo( otherSO ->parameterValue( EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) .get(), criterion, dbContext) && value_2nd->_isEquivalentTo( otherSO ->parameterValue( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) .get(), criterion, dbContext); } } } if (equivalent) { continue; } if (sameNameDifferentValue) { break; } // If there are parameters in this method not found in the other one, // check that they are set to a default neutral value, that is 1 // for scale, and 0 otherwise. foundMissingArgs = true; const auto &value = opParamvalue->parameterValue(); if (value->type() != ParameterValue::Type::MEASURE) { break; } if (value->value().unit().type() == common::UnitOfMeasure::Type::SCALE) { equivalent = value->value().getSIValue() == 1.0; } else { equivalent = value->value().getSIValue() == 0.0; } } // In the case the arguments don't perfectly match, try the reverse // check. if (equivalent && foundMissingArgs && !inOtherDirection) { return otherSO->_isEquivalentTo(this, criterion, dbContext, true); } // Equivalent formulations of 2SP can have different parameters // Then convert to 1SP and compare. if (!equivalent && methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { auto conv = dynamic_cast(this); auto otherConv = dynamic_cast(other); if (conv && otherConv) { auto thisAs1SP = conv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); auto otherAs1SP = otherConv->convertToOtherMethod( EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); if (thisAs1SP && otherAs1SP) { equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(), criterion, dbContext); } } } return equivalent; } //! @endcond // --------------------------------------------------------------------------- std::set SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const { std::set res; for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto &value = opParamvalue->parameterValue(); if (value->type() == ParameterValue::Type::FILENAME) { const auto gridNames = split(value->valueFile(), ","); for (const auto &gridName : gridNames) { GridDescription desc; desc.shortName = gridName; if (databaseContext) { databaseContext->lookForGridInfo( desc.shortName, considerKnownGridsAsAvailable, desc.fullName, desc.packageName, desc.url, desc.directDownload, desc.openLicense, desc.available); } res.insert(std::move(desc)); } } } } return res; } // --------------------------------------------------------------------------- /** \brief Validate the parameters used by a coordinate operation. * * Return whether the method is known or not, or a list of missing or extra * parameters for the operations recognized by this implementation. */ std::list SingleOperation::validateParameters() const { std::list res; const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const auto methodEPSGCode = l_method->getEPSGCode(); const auto findMapping = [methodEPSGCode, &methodName]( const MethodMapping *mappings, size_t mappingCount) -> const MethodMapping * { if (methodEPSGCode != 0) { for (size_t i = 0; i < mappingCount; ++i) { const auto &mapping = mappings[i]; if (methodEPSGCode == mapping.epsg_code) { return &mapping; } } } for (size_t i = 0; i < mappingCount; ++i) { const auto &mapping = mappings[i]; if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, methodName.c_str())) { return &mapping; } } return nullptr; }; size_t nProjectionMethodMappings = 0; const auto projectionMethodMappings = getProjectionMethodMappings(nProjectionMethodMappings); const MethodMapping *methodMapping = findMapping(projectionMethodMappings, nProjectionMethodMappings); if (methodMapping == nullptr) { size_t nOtherMethodMappings = 0; const auto otherMethodMappings = getOtherMethodMappings(nOtherMethodMappings); methodMapping = findMapping(otherMethodMappings, nOtherMethodMappings); } if (!methodMapping) { res.emplace_back("Unknown method " + methodName); return res; } if (methodMapping->wkt2_name != methodName) { if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name, methodName.c_str())) { std::string msg("Method name "); msg += methodName; msg += " is equivalent to official "; msg += methodMapping->wkt2_name; msg += " but not strictly equal"; res.emplace_back(msg); } else { std::string msg("Method name "); msg += methodName; msg += ", matched to "; msg += methodMapping->wkt2_name; msg += ", through its EPSG code has not an equivalent name"; res.emplace_back(msg); } } if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) { std::string msg("Method of EPSG code "); msg += toString(methodEPSGCode); msg += " does not match official code ("; msg += toString(methodMapping->epsg_code); msg += ')'; res.emplace_back(msg); } // Check if expected parameters are found for (int i = 0; methodMapping->params && methodMapping->params[i] != nullptr; ++i) { const auto *paramMapping = methodMapping->params[i]; const OperationParameterValue *opv = nullptr; for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); if ((paramMapping->epsg_code != 0 && parameter->getEPSGCode() == paramMapping->epsg_code) || ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { opv = opParamvalue; break; } } } if (!opv) { if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) && paramMapping == ¶mLatitudeNatOrigin) { // extension of EPSG used by GDAL/PROJ, so we should not // warn on its absence. continue; } std::string msg("Cannot find expected parameter "); msg += paramMapping->wkt2_name; res.emplace_back(msg); continue; } const auto ¶meter = opv->parameter(); if (paramMapping->wkt2_name != parameter->nameStr()) { if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { std::string msg("Parameter name "); msg += parameter->nameStr(); msg += " is equivalent to official "; msg += paramMapping->wkt2_name; msg += " but not strictly equal"; res.emplace_back(msg); } else { std::string msg("Parameter name "); msg += parameter->nameStr(); msg += ", matched to "; msg += paramMapping->wkt2_name; msg += ", through its EPSG code has not an equivalent name"; res.emplace_back(msg); } } const auto paramEPSGCode = parameter->getEPSGCode(); if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) { std::string msg("Parameter of EPSG code "); msg += toString(paramEPSGCode); msg += " does not match official code ("; msg += toString(paramMapping->epsg_code); msg += ')'; res.emplace_back(msg); } } // Check if there are extra parameters for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶meter = opParamvalue->parameter(); if (!getMapping(methodMapping, parameter)) { std::string msg("Parameter "); msg += parameter->nameStr(); msg += " found but not expected for this method"; res.emplace_back(msg); } } } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool SingleOperation::isLongitudeRotation() const { return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string nullString; static const std::string &_getNTv1Filename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); const auto &methodName = l_method->nameStr(); if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 || (allowInverse && ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) { const auto &fileParameter = op->parameterValue( EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } // static const std::string &_getNTv2Filename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 || (allowInverse && ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) { const auto &fileParameter = op->parameterValue( EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const std::string &Transformation::getPROJ4NadgridsCompatibleFilename() const { const std::string &filename = _getNTv2Filename(this, false); if (!filename.empty()) { return filename; } if (method()->getEPSGCode() == EPSG_CODE_METHOD_NADCON) { const auto &latitudeFileParameter = parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); const auto &longitudeFileParameter = parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); if (latitudeFileParameter && latitudeFileParameter->type() == ParameterValue::Type::FILENAME && longitudeFileParameter && longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { return latitudeFileParameter->valueFile(); } } if (ci_equal(method()->nameStr(), PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF)) { const auto &fileParameter = parameterValue( EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string &_getCTABLE2Filename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); const auto &methodName = l_method->nameStr(); if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) || (allowInverse && ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) { const auto &fileParameter = op->parameterValue( EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string & _getHorizontalShiftGTIFFFilename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); const auto &methodName = l_method->nameStr(); if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) || ci_equal(methodName, PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF) || (allowInverse && ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF)) || (allowInverse && ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF))) { { const auto &fileParameter = op->parameterValue( EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } { const auto &fileParameter = op->parameterValue( PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE, 0); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string & _getGeocentricTranslationFilename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); const auto &methodName = l_method->nameStr(); if (l_method->getEPSGCode() == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN || (allowInverse && ci_equal( methodName, INVERSE_OF + EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN))) { const auto &fileParameter = op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string & _getGeographic3DOffsetByVelocityGridFilename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); const auto &methodName = l_method->nameStr(); if (l_method->getEPSGCode() == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL || (allowInverse && ci_equal( methodName, INVERSE_OF + EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL))) { const auto &fileParameter = op->parameterValue( EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string & _getVerticalOffsetByVelocityGridFilename(const SingleOperation *op, bool allowInverse) { const auto &l_method = op->method(); const auto &methodName = l_method->nameStr(); if (l_method->getEPSGCode() == EPSG_CODE_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL || (allowInverse && ci_equal( methodName, INVERSE_OF + EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL))) { const auto &fileParameter = op->parameterValue( EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string & _getHeightToGeographic3DFilename(const SingleOperation *op, bool allowInverse) { const auto &methodName = op->method()->nameStr(); if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) || (allowInverse && ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) { const auto &fileParameter = op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool Transformation::isGeographic3DToGravityRelatedHeight( const OperationMethodNNPtr &method, bool allowInverse) { const auto &methodName = method->nameStr(); static const char *const methodCodes[] = { "1025", // Geographic3D to GravityRelatedHeight (EGM2008) "1030", // Geographic3D to GravityRelatedHeight (NZgeoid) "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire) "1047", // Geographic3D to GravityRelatedHeight (Gravsoft) "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2) "1050", // Geographic3D to GravityRelatedHeight (CI) "1059", // Geographic3D to GravityRelatedHeight (PNG) "1088", // Geog3D to Geog2D+GravityRelatedHeight (gtx) "1060", // Geographic3D to GravityRelatedHeight (CGG2013) "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire) "1073", // Geographic3D to GravityRelatedHeight (IGN2009) "1081", // Geographic3D to GravityRelatedHeight (BEV AT) "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2) "1089", // Geog3D to Geog2D+GravityRelatedHeight (BEV AT) "1090", // Geog3D to Geog2D+GravityRelatedHeight (CGG 2013) "1091", // Geog3D to Geog2D+GravityRelatedHeight (CI) "1092", // Geog3D to Geog2D+GravityRelatedHeight (EGM2008) "1093", // Geog3D to Geog2D+GravityRelatedHeight (Gravsoft) "1094", // Geog3D to Geog2D+GravityRelatedHeight (IGN1997) "1095", // Geog3D to Geog2D+GravityRelatedHeight (IGN2009) "1096", // Geog3D to Geog2D+GravityRelatedHeight (OSGM15-Ire) "1097", // Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB) "1098", // Geog3D to Geog2D+GravityRelatedHeight (SA 2010) "1100", // Geog3D to Geog2D+GravityRelatedHeight (PL txt) "1103", // Geog3D to Geog2D+GravityRelatedHeight (EGM) "1105", // Geog3D to Geog2D+GravityRelatedHeight (ITAL2005) "1109", // Geographic3D to Depth (Gravsoft) "1110", // Geog3D to Geog2D+Depth (Gravsoft) "1115", // Geog3D to Geog2D+Depth (txt) "1118", // Geog3D to Geog2D+GravityRelatedHeight (ISG) "1122", // Geog3D to Geog2D+Depth (gtx) "1124", // Geog3D to Geog2D+GravityRelatedHeight (gtg) "1126", // Vertical change by geoid grid difference (NRCan) "1127", // Geographic3D to Depth (gtg) "1128", // Geog3D to Geog2D+Depth (gtg) "1135", // Geog3D to Geog2D+GravityRelatedHeight (NGS bin) "9661", // Geographic3D to GravityRelatedHeight (EGM) "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98) "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB) "9664", // Geographic3D to GravityRelatedHeight (IGN1997) "9665", // Geographic3D to GravityRelatedHeight (US .gtx) "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx) }; if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) { return true; } if (allowInverse && ci_find(methodName, INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) { return true; } for (const auto &code : methodCodes) { for (const auto &idSrc : method->identifiers()) { const auto &srcAuthName = *(idSrc->codeSpace()); const auto &srcCode = idSrc->code(); if (ci_equal(srcAuthName, "EPSG") && srcCode == code) { return true; } if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") && srcCode == code) { return true; } } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const std::string &Transformation::getHeightToGeographic3DFilename() const { const std::string &ret = _getHeightToGeographic3DFilename(this, false); if (!ret.empty()) return ret; if (isGeographic3DToGravityRelatedHeight(method(), false)) { const auto &fileParameter = parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { return fileParameter->valueFile(); } } return nullString; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createSimilarPropertiesOperation(const CoordinateOperationNNPtr &obj) { util::PropertyMap map; // The domain(s) are unchanged addDomains(map, obj.get()); const std::string &forwardName = obj->nameStr(); if (!forwardName.empty()) { map.set(common::IdentifiedObject::NAME_KEY, forwardName); } const std::string &remarks = obj->remarks(); if (!remarks.empty()) { map.set(common::IdentifiedObject::REMARKS_KEY, remarks); } addModifiedIdentifier(map, obj.get(), false, true); return map; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static TransformationNNPtr createNTv1(const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, const std::string &filename, const std::vector &accuracies) { const VectorOfParameters parameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; const VectorOfValues values{ParameterValue::createFilename(filename)}; return Transformation::create( properties, sourceCRSIn, targetCRSIn, nullptr, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1), parameters, values, accuracies); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) { util::PropertyMap map; const std::string &forwardName = obj->nameStr(); if (!forwardName.empty()) { map.set(common::IdentifiedObject::NAME_KEY, forwardName); } { auto ar = util::ArrayOfBaseObject::create(); for (const auto &idSrc : obj->identifiers()) { const auto &srcAuthName = *(idSrc->codeSpace()); const auto &srcCode = idSrc->code(); auto idsProp = util::PropertyMap().set( metadata::Identifier::CODESPACE_KEY, srcAuthName); ar->add(metadata::Identifier::create(srcCode, idsProp)); } if (!ar->empty()) { map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); } } return map; } //! @endcond // --------------------------------------------------------------------------- static bool isRegularVerticalGridMethod(int methodEPSGCode, bool &reverseOffsetSign) { if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NRCAN_BYN || methodEPSGCode == EPSG_CODE_METHOD_VERTICALCHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN) { // NRCAN vertical shift grids use a reverse convention from other // grids: the value in the grid is the value to subtract from the // source vertical CRS to get the target value. reverseOffsetSign = true; return true; } reverseOffsetSign = false; return methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD || methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT || methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX || methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_ASC || methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTG || methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_PL_TXT; } // --------------------------------------------------------------------------- /** \brief Return an equivalent transformation to the current one, but using * PROJ alternative grid names. */ TransformationNNPtr SingleOperation::substitutePROJAlternativeGridNames( io::DatabaseContextNNPtr databaseContext) const { auto self = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); const auto &l_method = method(); const int methodEPSGCode = l_method->getEPSGCode(); std::string projFilename; std::string projGridFormat; bool inverseDirection = false; const auto &NTv1Filename = _getNTv1Filename(this, false); const auto &NTv2Filename = _getNTv2Filename(this, false); std::string lasFilename; if (methodEPSGCode == EPSG_CODE_METHOD_NADCON || methodEPSGCode == EPSG_CODE_METHOD_NADCON5_2D || methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D) { const auto &latitudeFileParameter = parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); const auto &longitudeFileParameter = parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); if (latitudeFileParameter && latitudeFileParameter->type() == ParameterValue::Type::FILENAME && longitudeFileParameter && longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { lasFilename = latitudeFileParameter->valueFile(); } } const auto &horizontalGridName = !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() ? NTv2Filename : lasFilename; const auto l_interpolationCRS = interpolationCRS(); if (!horizontalGridName.empty() && databaseContext->lookForGridAlternative( horizontalGridName, projFilename, projGridFormat, inverseDirection)) { if (horizontalGridName == projFilename) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " + projFilename + " not supported"); } return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const auto &l_accuracies = coordinateOperationAccuracies(); if (projGridFormat == "GTiff") { const VectorOfParameters parameters{ methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D ? OperationParameter::create(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE)) : createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; auto methodProperties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, (methodEPSGCode == EPSG_CODE_METHOD_NADCON5_2D || methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D) ? PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF : PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF); const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; if (inverseDirection) { return Transformation::create( createPropertiesForInverse(self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, l_interpolationCRS, methodProperties, parameters, values, l_accuracies) ->inverseAsTransformation(); } else { return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, methodProperties, parameters, values, l_accuracies); } } else if (projGridFormat == "NTv1") { if (inverseDirection) { return createNTv1(createPropertiesForInverse( self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, projFilename, l_accuracies) ->inverseAsTransformation(); } else { return createNTv1(createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, projFilename, l_accuracies); } } else if (projGridFormat == "NTv2") { if (inverseDirection) { return Transformation::createNTv2( createPropertiesForInverse(self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, projFilename, l_accuracies) ->inverseAsTransformation(); } else { return Transformation::createNTv2( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, projFilename, l_accuracies); } } else if (projGridFormat == "CTable2") { const VectorOfParameters parameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; auto methodProperties = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, PROJ_WKT2_NAME_METHOD_CTABLE2); const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; if (inverseDirection) { return Transformation::create( createPropertiesForInverse(self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, l_interpolationCRS, methodProperties, parameters, values, l_accuracies) ->inverseAsTransformation(); } else { return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, methodProperties, parameters, values, l_accuracies); } } } if (Transformation::isGeographic3DToGravityRelatedHeight(method(), false)) { const auto &fileParameter = parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { const auto &filename = fileParameter->valueFile(); if (databaseContext->lookForGridAlternative( filename, projFilename, projGridFormat, inverseDirection)) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " "Geographic3DToGravityRelatedHeight not supported"); } if (filename == projFilename) { return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException( "Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException( "Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const VectorOfParameters parameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; #ifdef disabled_for_now if (inverseDirection) { return Transformation::create( createPropertiesForInverse( self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()) ->inverseAsTransformation(); } else #endif { return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } } } const auto &geocentricTranslationFilename = _getGeocentricTranslationFilename(this, false); if (!geocentricTranslationFilename.empty()) { if (databaseContext->lookForGridAlternative( geocentricTranslationFilename, projFilename, projGridFormat, inverseDirection)) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " "GeocentricTranslation not supported"); } if (geocentricTranslationFilename == projFilename) { return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const VectorOfParameters parameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } const auto &geographic3DOffsetByVelocityGridFilename = _getGeographic3DOffsetByVelocityGridFilename(this, false); if (!geographic3DOffsetByVelocityGridFilename.empty()) { if (databaseContext->lookForGridAlternative( geographic3DOffsetByVelocityGridFilename, projFilename, projGridFormat, inverseDirection)) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " "Geographic3DOFffsetByVelocityGrid not supported"); } if (geographic3DOffsetByVelocityGridFilename == projFilename) { return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const VectorOfParameters parameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } const auto &verticalOffsetByVelocityGridFilename = _getVerticalOffsetByVelocityGridFilename(this, false); if (!verticalOffsetByVelocityGridFilename.empty()) { if (databaseContext->lookForGridAlternative( verticalOffsetByVelocityGridFilename, projFilename, projGridFormat, inverseDirection)) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " "VerticalOffsetByVelocityGrid not supported"); } if (verticalOffsetByVelocityGridFilename == projFilename) { return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const VectorOfParameters parameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } bool reverseOffsetSign = false; if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON || isRegularVerticalGridMethod(methodEPSGCode, reverseOffsetSign)) { int parameterCode = EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE; auto fileParameter = parameterValue( EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, parameterCode); if (!fileParameter) { parameterCode = EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE; fileParameter = parameterValue( EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE, parameterCode); } if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { const auto &filename = fileParameter->valueFile(); if (databaseContext->lookForGridAlternative( filename, projFilename, projGridFormat, inverseDirection)) { if (filename == projFilename) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " + projFilename + " not supported"); } return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException( "Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException( "Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const VectorOfParameters parameters{ createOpParamNameEPSGCode(parameterCode)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; if (inverseDirection) { return Transformation::create( createPropertiesForInverse( self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()) ->inverseAsTransformation(); } else { return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } } } static const struct { int methodEPSGCode; int gridFilenameParamEPSGCode; const char *gridFilenameParamName; } gridTransformations[] = { {EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE}, {EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, EPSG_NAME_PARAMETER_TIN_OFFSET_FILE}, {EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, EPSG_NAME_PARAMETER_TIN_OFFSET_FILE}, {EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, EPSG_NAME_PARAMETER_TIN_OFFSET_FILE}, }; for (const auto &gridTransf : gridTransformations) { if (methodEPSGCode == gridTransf.methodEPSGCode) { auto fileParameter = parameterValue(gridTransf.gridFilenameParamName, gridTransf.gridFilenameParamEPSGCode); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { const auto &filename = fileParameter->valueFile(); if (databaseContext->lookForGridAlternative( filename, projFilename, projGridFormat, inverseDirection)) { if (filename == projFilename) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction for " + projFilename + " not supported"); } return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException( "Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException( "Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); const VectorOfParameters parameters{ createOpParamNameEPSGCode( gridTransf.gridFilenameParamEPSGCode)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; if (inverseDirection) { return Transformation::create( createPropertiesForInverse( self.as_nullable().get(), true, false), l_targetCRS, l_sourceCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()) ->inverseAsTransformation(); } else { return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } } break; } } if (methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG) { auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME && databaseContext->lookForGridAlternative( fileParameter->valueFile(), projFilename, projGridFormat, inverseDirection)) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction not supported"); } if (fileParameter->valueFile() == projFilename) { return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); std::vector parameters; std::vector values; for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (!opParamvalue) { throw util::UnsupportedOperationException( "non OperationParameterValue parameter value"); } const auto ¶meter = opParamvalue->parameter(); parameters.push_back(parameter); if (parameter->getEPSGCode() == EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE) { values.push_back( ParameterValue::createFilename(projFilename)); } else { values.push_back(opParamvalue->parameterValue()); } } return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) { std::string projFilenameGTF; std::string projFilenamePMV; auto fileParameterGTF = parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); auto fileParameterPMV = parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (fileParameterGTF && fileParameterGTF->type() == ParameterValue::Type::FILENAME && databaseContext->lookForGridAlternative( fileParameterGTF->valueFile(), projFilenameGTF, projGridFormat, inverseDirection) && fileParameterPMV && fileParameterPMV->type() == ParameterValue::Type::FILENAME && databaseContext->lookForGridAlternative( fileParameterPMV->valueFile(), projFilenamePMV, projGridFormat, inverseDirection)) { if (inverseDirection) { throw util::UnsupportedOperationException( "Inverse direction not supported"); } if (fileParameterGTF->valueFile() == projFilenameGTF && fileParameterPMV->valueFile() == projFilenamePMV) { return self; } const auto l_sourceCRSNull = sourceCRS(); const auto l_targetCRSNull = targetCRS(); if (l_sourceCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing sourceCRS"); } if (l_targetCRSNull == nullptr) { throw util::UnsupportedOperationException("Missing targetCRS"); } auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); std::vector parameters; std::vector values; for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (!opParamvalue) { throw util::UnsupportedOperationException( "non OperationParameterValue parameter value"); } const auto ¶meter = opParamvalue->parameter(); parameters.push_back(parameter); if (parameter->getEPSGCode() == EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE) { values.push_back( ParameterValue::createFilename(projFilenameGTF)); } else if ( parameter->getEPSGCode() == EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE) { values.push_back( ParameterValue::createFilename(projFilenamePMV)); } else { values.push_back(opParamvalue->parameterValue()); } } return Transformation::create( createSimilarPropertiesOperation(self), l_sourceCRS, l_targetCRS, l_interpolationCRS, createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } } return self; } //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- InvalidOperation::InvalidOperation(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- InvalidOperation::InvalidOperation(const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- InvalidOperation::InvalidOperation(const InvalidOperation &) = default; // --------------------------------------------------------------------------- InvalidOperation::~InvalidOperation() = default; //! @endcond // --------------------------------------------------------------------------- GeneralParameterValueNNPtr SingleOperation::createOperationParameterValueFromInterpolationCRS( int methodEPSGCode, int crsEPSGCode) { util::PropertyMap propertiesParameter; propertiesParameter.set( common::IdentifiedObject::NAME_KEY, methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE ? EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS : EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS); propertiesParameter.set( metadata::Identifier::CODE_KEY, methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE ? EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS : EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS); propertiesParameter.set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG); return OperationParameterValue::create( OperationParameter::create(propertiesParameter), ParameterValue::create(crsEPSGCode)); } // --------------------------------------------------------------------------- void SingleOperation::exportTransformationToWKT( io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { throw io::FormattingException( "Transformation can only be exported to WKT2"); } if (formatter->abridgedTransformation()) { formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION, !identifiers().empty()); } else { formatter->startNode(io::WKTConstants::COORDINATEOPERATION, !identifiers().empty()); } formatter->addQuotedString(nameStr()); if (formatter->use2019Keywords()) { const auto &version = operationVersion(); if (version.has_value()) { formatter->startNode(io::WKTConstants::VERSION, false); formatter->addQuotedString(*version); formatter->endNode(); } } if (!formatter->abridgedTransformation()) { exportSourceCRSAndTargetCRSToWKT(this, formatter); } const auto &l_method = method(); l_method->_exportToWKT(formatter); bool hasInterpolationCRSParameter = false; for (const auto ¶mValue : parameterValues()) { const auto opParamvalue = dynamic_cast(paramValue.get()); const int paramEPSGCode = opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0; if (paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS || paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) { hasInterpolationCRSParameter = true; } paramValue->_exportToWKT(formatter, nullptr); } const auto l_interpolationCRS = interpolationCRS(); if (formatter->abridgedTransformation()) { // If we have an interpolation CRS that has a EPSG code, then // we can export it as a PARAMETER[] if (!hasInterpolationCRSParameter && l_interpolationCRS) { const auto code = l_interpolationCRS->getEPSGCode(); if (code != 0) { const auto methodEPSGCode = l_method->getEPSGCode(); createOperationParameterValueFromInterpolationCRS( methodEPSGCode, code) ->_exportToWKT(formatter, nullptr); } } } else { if (l_interpolationCRS) { formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false); interpolationCRS()->_exportToWKT(formatter); formatter->endNode(); } if (!coordinateOperationAccuracies().empty()) { formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); formatter->add(coordinateOperationAccuracies()[0]->value()); formatter->endNode(); } } ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // If crs is a geographic CRS, or a compound CRS of a geographic CRS, // or a compoundCRS of a bound CRS of a geographic CRS, return that // geographic CRS static crs::GeographicCRSPtr extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) { auto geogCRS = util::nn_dynamic_pointer_cast(crs); if (!geogCRS) { auto compoundCRS = util::nn_dynamic_pointer_cast(crs); if (compoundCRS) { const auto &components = compoundCRS->componentReferenceSystems(); if (!components.empty()) { geogCRS = util::nn_dynamic_pointer_cast( components[0]); if (!geogCRS) { auto boundCRS = util::nn_dynamic_pointer_cast( components[0]); if (boundCRS) { geogCRS = util::nn_dynamic_pointer_cast( boundCRS->baseCRS()); } } } } else { auto boundCRS = util::nn_dynamic_pointer_cast(crs); if (boundCRS) { geogCRS = util::nn_dynamic_pointer_cast( boundCRS->baseCRS()); } } } return geogCRS; } // --------------------------------------------------------------------------- [[noreturn]] static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) { throw io::FormattingException(concat("Can apply ", std::string(trfrm_name), " only to GeodeticCRS / " "GeographicCRS")); } // --------------------------------------------------------------------------- static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter, const crs::CRSNNPtr &crs, bool addPushV3, const char *trfrm_name) { auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); if (sourceCRSGeog) { formatter->startInversion(); sourceCRSGeog->_exportToPROJString(formatter); formatter->stopInversion(); if (util::isOfExactType( *(sourceCRSGeog.get()))) { const auto derivedGeogCRS = dynamic_cast( sourceCRSGeog.get()); // The export of a DerivedGeographicCRS in non-CRS mode adds // unit conversion and axis swapping to the base CRS. // We must compensate for that formatter->startInversion(); formatter->startInversion(); derivedGeogCRS->baseCRS()->addAngularUnitConvertAndAxisSwap( formatter); formatter->stopInversion(); } if (addPushV3) { formatter->addStep("push"); formatter->addParam("v_3"); } formatter->addStep("cart"); sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); } else { auto sourceCRSGeod = dynamic_cast(crs.get()); if (!sourceCRSGeod) { ThrowExceptionNotGeodeticGeographic(trfrm_name); } formatter->startInversion(); sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); formatter->stopInversion(); } } // --------------------------------------------------------------------------- static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter, const crs::CRSNNPtr &crs, bool addPopV3, const char *trfrm_name) { auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); if (targetCRSGeog) { formatter->addStep("cart"); formatter->setCurrentStepInverted(true); targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); if (addPopV3) { formatter->addStep("pop"); formatter->addParam("v_3"); } if (util::isOfExactType( *(targetCRSGeog.get()))) { // The export of a DerivedGeographicCRS in non-CRS mode adds // unit conversion and axis swapping to the base CRS. // We must compensate for that formatter->startInversion(); const auto derivedGeogCRS = dynamic_cast( targetCRSGeog.get()); derivedGeogCRS->baseCRS()->addAngularUnitConvertAndAxisSwap( formatter); } targetCRSGeog->_exportToPROJString(formatter); } else { auto targetCRSGeod = dynamic_cast(crs.get()); if (!targetCRSGeod) { ThrowExceptionNotGeodeticGeographic(trfrm_name); } targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); } } //! @endcond // --------------------------------------------------------------------------- /* static */ void SingleOperation::exportToPROJStringChangeVerticalUnit( io::PROJStringFormatter *formatter, double convFactor) { const auto uom = common::UnitOfMeasure(std::string(), convFactor, common::UnitOfMeasure::Type::LINEAR) .exportToPROJString(); const std::string reverse_uom( convFactor == 0.0 ? std::string() : common::UnitOfMeasure(std::string(), 1.0 / convFactor, common::UnitOfMeasure::Type::LINEAR) .exportToPROJString()); if (uom == "m") { // do nothing } else if (!uom.empty()) { formatter->addStep("unitconvert"); formatter->addParam("z_in", uom); formatter->addParam("z_out", "m"); } else if (!reverse_uom.empty()) { formatter->addStep("unitconvert"); formatter->addParam("z_in", "m"); formatter->addParam("z_out", reverse_uom); } else if (fabs(convFactor - common::UnitOfMeasure::FOOT.conversionToSI() / common::UnitOfMeasure::US_FOOT.conversionToSI()) < 1e-10) { formatter->addStep("unitconvert"); formatter->addParam("z_in", "ft"); formatter->addParam("z_out", "us-ft"); } else if (fabs(convFactor - common::UnitOfMeasure::US_FOOT.conversionToSI() / common::UnitOfMeasure::FOOT.conversionToSI()) < 1e-10) { formatter->addStep("unitconvert"); formatter->addParam("z_in", "us-ft"); formatter->addParam("z_out", "ft"); } else { formatter->addStep("affine"); formatter->addParam("s33", convFactor); } } // --------------------------------------------------------------------------- bool SingleOperation::exportToPROJStringGeneric( io::PROJStringFormatter *formatter) const { const int methodEPSGCode = method()->getEPSGCode(); if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) { const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value(); const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value(); const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value(); const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value(); const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value(); const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value(); // Do not mess with axis unit and order for that transformation formatter->addStep("affine"); formatter->addParam("xoff", A0); formatter->addParam("s11", A1); formatter->addParam("s12", A2); formatter->addParam("yoff", B0); formatter->addParam("s21", B1); formatter->addParam("s22", B2); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION) { const double XT0 = parameterValueMeasure( EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS) .value(); const double YT0 = parameterValueMeasure( EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS) .value(); const double M = parameterValueMeasure( EPSG_CODE_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES) .value(); const double q = parameterValueNumeric( EPSG_CODE_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES, common::UnitOfMeasure::RADIAN); // Do not mess with axis unit and order for that transformation formatter->addStep("affine"); formatter->addParam("xoff", XT0); formatter->addParam("s11", M * cos(q)); formatter->addParam("s12", M * sin(q)); formatter->addParam("yoff", YT0); formatter->addParam("s21", -M * sin(q)); formatter->addParam("s22", M * cos(q)); return true; } if (isAxisOrderReversal(methodEPSGCode)) { formatter->addStep("axisswap"); formatter->addParam("order", "2,1"); auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (sourceCRSGeog && targetCRSGeog) { const auto &unitSrc = sourceCRSGeog->coordinateSystem()->axisList()[0]->unit(); const auto &unitDst = targetCRSGeog->coordinateSystem()->axisList()[0]->unit(); if (!unitSrc._isEquivalentTo( unitDst, util::IComparable::Criterion::EQUIVALENT)) { formatter->addStep("unitconvert"); auto projUnit = unitSrc.exportToPROJString(); if (projUnit.empty()) { formatter->addParam("xy_in", unitSrc.conversionToSI()); } else { formatter->addParam("xy_in", projUnit); } projUnit = unitDst.exportToPROJString(); if (projUnit.empty()) { formatter->addParam("xy_out", unitDst.conversionToSI()); } else { formatter->addParam("xy_out", projUnit); } } } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { auto sourceCRSGeod = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeod) { auto sourceCRSCompound = dynamic_cast(sourceCRS().get()); if (sourceCRSCompound) { sourceCRSGeod = dynamic_cast( sourceCRSCompound->componentReferenceSystems() .front() .get()); } } auto targetCRSGeod = dynamic_cast(targetCRS().get()); if (!targetCRSGeod) { auto targetCRSCompound = dynamic_cast(targetCRS().get()); if (targetCRSCompound) { targetCRSGeod = dynamic_cast( targetCRSCompound->componentReferenceSystems() .front() .get()); } } if (sourceCRSGeod && targetCRSGeod) { auto sourceCRSGeog = dynamic_cast(sourceCRSGeod); auto targetCRSGeog = dynamic_cast(targetCRSGeod); bool isSrcGeocentric = sourceCRSGeod->isGeocentric(); bool isSrcGeographic = sourceCRSGeog != nullptr; bool isTargetGeocentric = targetCRSGeod->isGeocentric(); bool isTargetGeographic = targetCRSGeog != nullptr; if ((isSrcGeocentric && isTargetGeographic) || (isSrcGeographic && isTargetGeocentric)) { formatter->startInversion(); sourceCRSGeod->_exportToPROJString(formatter); formatter->stopInversion(); targetCRSGeod->_exportToPROJString(formatter); return true; } } throw io::FormattingException("Invalid nature of source and/or " "targetCRS for Geographic/Geocentric " "conversion"); } if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { const double convFactor = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); exportToPROJStringChangeVerticalUnit(formatter, convFactor); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { formatter->addStep("axisswap"); formatter->addParam("order", "1,2,-3"); return true; } formatter->setCoordinateOperationOptimizations(true); bool positionVectorConvention = true; bool sevenParamsTransform = false; bool threeParamsTransform = false; bool fifteenParamsTransform = false; bool fullMatrix = false; const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF); const auto paramCount = parameterValues().size(); const bool l_isTimeDependent = isTimeDependent(methodName); const bool isPositionVector = ci_find(methodName, "Position Vector") != std::string::npos || ci_find(methodName, "PV") != std::string::npos; const bool isCoordinateFrame = ci_find(methodName, "Coordinate Frame") != std::string::npos || ci_find(methodName, "CF") != std::string::npos; if (methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D) { positionVectorConvention = false; sevenParamsTransform = true; fullMatrix = true; } else if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) { positionVectorConvention = false; sevenParamsTransform = true; } else if ( (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { positionVectorConvention = false; fifteenParamsTransform = true; } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { sevenParamsTransform = true; } else if ( (paramCount == 15 && isPositionVector && l_isTimeDependent) || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { fifteenParamsTransform = true; } else if ((paramCount == 3 && ci_find(methodName, "Geocentric translations") != std::string::npos) || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { threeParamsTransform = true; } if (threeParamsTransform || sevenParamsTransform || fifteenParamsTransform) { double x = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); double y = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); double z = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); auto sourceCRSGeog = dynamic_cast(l_sourceCRS.get()); auto targetCRSGeog = dynamic_cast(l_targetCRS.get()); const bool sourceIsCompound = !sourceCRSGeog && dynamic_cast(l_sourceCRS.get()); const bool targetIsCompound = !targetCRSGeog && dynamic_cast(l_targetCRS.get()); const bool addPushPopV3 = (((sourceCRSGeog && sourceCRSGeog->coordinateSystem()->axisList().size() == 2) || (targetCRSGeog && targetCRSGeog->coordinateSystem()->axisList().size() == 2)) || sourceIsCompound || targetIsCompound) && // Below check is for example for // EPSG:10905 ("ETRS89/DREF91/2016 to Asse 2025 + Asse 2025 height // (1)") whose target CRS is a compound CRS !(sourceCRSGeog && sourceCRSGeog->coordinateSystem()->axisList().size() == 3 && targetIsCompound) && !(targetCRSGeog && targetCRSGeog->coordinateSystem()->axisList().size() == 3 && sourceIsCompound); if (l_sourceCRS) { setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), addPushPopV3, "Helmert"); } double sourceYear = sourceCoordinateEpoch().has_value() ? getRoundedEpochInDecimalYear( sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)) : 0; double targetYear = targetCoordinateEpoch().has_value() ? getRoundedEpochInDecimalYear( targetCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)) : 0; if (sourceYear > 0 && targetYear == 0) targetYear = sourceYear; else if (targetYear > 0 && sourceYear == 0) sourceYear = targetYear; if (sourceYear > 0) { formatter->addStep("set"); formatter->addParam("v_4", sourceYear); } formatter->addStep("helmert"); if (fullMatrix) formatter->addParam("exact"); formatter->addParam("x", x); formatter->addParam("y", y); formatter->addParam("z", z); if (sevenParamsTransform || fifteenParamsTransform) { double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double scaleDiff = parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, common::UnitOfMeasure::PARTS_PER_MILLION); formatter->addParam("rx", rx); formatter->addParam("ry", ry); formatter->addParam("rz", rz); formatter->addParam("s", scaleDiff); if (fifteenParamsTransform) { double rate_x = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, common::UnitOfMeasure::METRE_PER_YEAR); double rate_y = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, common::UnitOfMeasure::METRE_PER_YEAR); double rate_z = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, common::UnitOfMeasure::METRE_PER_YEAR); double rate_rx = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND_PER_YEAR); double rate_ry = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND_PER_YEAR); double rate_rz = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND_PER_YEAR); double rate_scaleDiff = parameterValueNumeric( EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, common::UnitOfMeasure::PPM_PER_YEAR); double referenceEpochYear = parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, common::UnitOfMeasure::YEAR); formatter->addParam("dx", rate_x); formatter->addParam("dy", rate_y); formatter->addParam("dz", rate_z); formatter->addParam("drx", rate_rx); formatter->addParam("dry", rate_ry); formatter->addParam("drz", rate_rz); formatter->addParam("ds", rate_scaleDiff); formatter->addParam("t_epoch", referenceEpochYear); } if (positionVectorConvention) { formatter->addParam("convention", "position_vector"); } else { formatter->addParam("convention", "coordinate_frame"); } } if (targetYear > 0) { formatter->addStep("set"); formatter->addParam("v_4", targetYear); } if (l_targetCRS) { setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), addPushPopV3, "Helmert"); } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) { positionVectorConvention = isPositionVector || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D; double x = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); double y = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); double z = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double scaleDiff = parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, common::UnitOfMeasure::PARTS_PER_MILLION); double px = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT); double py = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT); double pz = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT); bool addPushPopV3 = (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D); auto l_sourceCRS = sourceCRS(); if (l_sourceCRS) { setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), addPushPopV3, "Molodensky-Badekas"); } formatter->addStep("molobadekas"); formatter->addParam("x", x); formatter->addParam("y", y); formatter->addParam("z", z); formatter->addParam("rx", rx); formatter->addParam("ry", ry); formatter->addParam("rz", rz); formatter->addParam("s", scaleDiff); formatter->addParam("px", px); formatter->addParam("py", py); formatter->addParam("pz", pz); if (positionVectorConvention) { formatter->addParam("convention", "position_vector"); } else { formatter->addParam("convention", "coordinate_frame"); } auto l_targetCRS = targetCRS(); if (l_targetCRS) { setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), addPushPopV3, "Molodensky-Badekas"); } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { double x = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); double y = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); double z = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); double da = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); double df = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { throw io::FormattingException( "Can apply Molodensky only to GeographicCRS"); } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { throw io::FormattingException( "Can apply Molodensky only to GeographicCRS"); } formatter->startInversion(); sourceCRSGeog->_exportToPROJString(formatter); formatter->stopInversion(); formatter->addStep("molodensky"); sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->addParam("dx", x); formatter->addParam("dy", y); formatter->addParam("dz", z); formatter->addParam("da", da); formatter->addParam("df", df); if (ci_find(methodName, "Abridged") != std::string::npos || methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { formatter->addParam("abridged"); } targetCRSGeog->_exportToPROJString(formatter); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { double offsetLat = parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, common::UnitOfMeasure::ARC_SECOND); double offsetLong = parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, common::UnitOfMeasure::ARC_SECOND); auto l_sourceCRS = sourceCRS(); auto sourceCRSGeog = l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_sourceCRS)) : nullptr; if (!sourceCRSGeog) { throw io::FormattingException( "Can apply Geographic 2D offsets only to GeographicCRS"); } auto l_targetCRS = targetCRS(); auto targetCRSGeog = l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_targetCRS)) : nullptr; if (!targetCRSGeog) { throw io::FormattingException( "Can apply Geographic 2D offsets only to GeographicCRS"); } formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); if (offsetLat != 0.0 || offsetLong != 0.0) { formatter->addStep("geogoffset"); formatter->addParam("dlat", offsetLat); formatter->addParam("dlon", offsetLong); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { double offsetLat = parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, common::UnitOfMeasure::ARC_SECOND); double offsetLong = parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, common::UnitOfMeasure::ARC_SECOND); double offsetHeight = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { auto boundCRS = dynamic_cast(sourceCRS().get()); if (boundCRS) { sourceCRSGeog = dynamic_cast( boundCRS->baseCRS().get()); } if (!sourceCRSGeog) { throw io::FormattingException( "Can apply Geographic 3D offsets only to GeographicCRS"); } } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { auto boundCRS = dynamic_cast(targetCRS().get()); if (boundCRS) { targetCRSGeog = dynamic_cast( boundCRS->baseCRS().get()); } if (!targetCRSGeog) { throw io::FormattingException( "Can apply Geographic 3D offsets only to GeographicCRS"); } } formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { formatter->addStep("geogoffset"); formatter->addParam("dlat", offsetLat); formatter->addParam("dlon", offsetLong); formatter->addParam("dh", offsetHeight); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { double offsetLat = parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, common::UnitOfMeasure::ARC_SECOND); double offsetLong = parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, common::UnitOfMeasure::ARC_SECOND); double offsetHeight = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT); auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { auto sourceCRSCompound = dynamic_cast(sourceCRS().get()); if (sourceCRSCompound) { sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get(); } if (!sourceCRSGeog) { throw io::FormattingException("Can apply Geographic 2D with " "height offsets only to " "GeographicCRS / CompoundCRS"); } } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { auto targetCRSCompound = dynamic_cast(targetCRS().get()); if (targetCRSCompound) { targetCRSGeog = targetCRSCompound->extractGeographicCRS().get(); } if (!targetCRSGeog) { throw io::FormattingException("Can apply Geographic 2D with " "height offsets only to " "GeographicCRS / CompoundCRS"); } } formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { formatter->addStep("geogoffset"); formatter->addParam("dlat", offsetLat); formatter->addParam("dlon", offsetLong); formatter->addParam("dh", offsetHeight); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS) { double eastingOffset = parameterValueNumeric( EPSG_CODE_PARAMETER_EASTING_OFFSET, common::UnitOfMeasure::METRE); double northingOffset = parameterValueNumeric( EPSG_CODE_PARAMETER_NORTHING_OFFSET, common::UnitOfMeasure::METRE); const auto checkIfCompatEngineeringCRS = [](const crs::CRSPtr &crs) { auto engineeringCRS = dynamic_cast(crs.get()); if (engineeringCRS) { auto cs = dynamic_cast( engineeringCRS->coordinateSystem().get()); if (!cs) { throw io::FormattingException( "Can apply Cartesian grid offsets only to " "EngineeringCRS with CartesianCS"); } const auto &unit = cs->axisList()[0]->unit(); if (!unit._isEquivalentTo( common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT)) { // Could be enhanced to support other units... throw io::FormattingException( "Can apply Cartesian grid offsets only to " "EngineeringCRS with CartesianCS with metre unit"); } } else { throw io::FormattingException( "Can apply Cartesian grid offsets only to ProjectedCRS or " "EngineeringCRS"); } }; auto l_sourceCRS = sourceCRS(); auto sourceCRSProj = dynamic_cast(l_sourceCRS.get()); if (!sourceCRSProj) { checkIfCompatEngineeringCRS(l_sourceCRS); } auto l_targetCRS = targetCRS(); auto targetCRSProj = dynamic_cast(l_targetCRS.get()); if (!targetCRSProj) { checkIfCompatEngineeringCRS(l_targetCRS); } if (sourceCRSProj) { formatter->startInversion(); sourceCRSProj->addUnitConvertAndAxisSwap(formatter, false); formatter->stopInversion(); } if (eastingOffset != 0.0 || northingOffset != 0.0) { formatter->addStep("affine"); formatter->addParam("xoff", eastingOffset); formatter->addParam("yoff", northingOffset); } if (targetCRSProj) { targetCRSProj->addUnitConvertAndAxisSwap(formatter, false); } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { const crs::CRS *srcCRS = sourceCRS().get(); const crs::CRS *tgtCRS = targetCRS().get(); const auto sourceCRSCompound = dynamic_cast(srcCRS); const auto targetCRSCompound = dynamic_cast(tgtCRS); if (sourceCRSCompound && targetCRSCompound && sourceCRSCompound->componentReferenceSystems()[0]->_isEquivalentTo( targetCRSCompound->componentReferenceSystems()[0].get(), util::IComparable::Criterion::EQUIVALENT)) { srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get(); tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get(); } auto sourceCRSVert = dynamic_cast(srcCRS); if (!sourceCRSVert) { throw io::FormattingException( "Can apply Vertical offset only to VerticalCRS"); } auto targetCRSVert = dynamic_cast(tgtCRS); if (!targetCRSVert) { throw io::FormattingException( "Can apply Vertical offset only to VerticalCRS"); } auto offsetHeight = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); formatter->startInversion(); sourceCRSVert->addLinearUnitConvert(formatter); formatter->stopInversion(); if (offsetHeight != 0) { formatter->addStep("geogoffset"); formatter->addParam("dh", offsetHeight); } targetCRSVert->addLinearUnitConvert(formatter); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT || methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT) { const crs::CRS *tgtCRS = targetCRS().get(); if (const auto targetCRSCompound = dynamic_cast(tgtCRS)) { tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get(); } auto targetCRSVert = dynamic_cast(tgtCRS); if (!targetCRSVert) { throw io::FormattingException( "Can apply Geographic3D to GravityRelatedHeight only to " "VerticalCRS"); } auto geoidHeight = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT); if (geoidHeight != 0) { formatter->addStep("affine"); // In the forward direction (Geographic3D to GravityRelatedHeight) // we subtract the geoid height formatter->addParam("zoff", isMethodInverseOf ? geoidHeight : -geoidHeight); } targetCRSVert->addLinearUnitConvert(formatter); return true; } else if ( ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT) || ci_equal( l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT)) { const crs::CRS *srcCRS = sourceCRS().get(); if (const auto sourceCRSCompound = dynamic_cast(srcCRS)) { srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get(); } auto sourceCRSVert = dynamic_cast(srcCRS); if (!sourceCRSVert) { throw io::FormattingException( "Can apply Inverse of Geographic3D to GravityRelatedHeight " "only to VerticalCRS"); } auto geoidHeight = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT); formatter->startInversion(); sourceCRSVert->addLinearUnitConvert(formatter); formatter->stopInversion(); if (geoidHeight != 0) { formatter->addStep("affine"); // In the forward direction (Geographic3D to GravityRelatedHeight) // we subtract the geoid height formatter->addParam("zoff", geoidHeight); } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE) { const crs::CRS *srcCRS = sourceCRS().get(); const crs::CRS *tgtCRS = targetCRS().get(); const auto sourceCRSCompound = dynamic_cast(srcCRS); const auto targetCRSCompound = dynamic_cast(tgtCRS); if (sourceCRSCompound && targetCRSCompound && sourceCRSCompound->componentReferenceSystems()[0]->_isEquivalentTo( targetCRSCompound->componentReferenceSystems()[0].get(), util::IComparable::Criterion::EQUIVALENT)) { srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get(); tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get(); } auto sourceCRSVert = dynamic_cast(srcCRS); if (!sourceCRSVert) { throw io::FormattingException( "Can apply Vertical offset and slope only to VerticalCRS"); } auto targetCRSVert = dynamic_cast(tgtCRS); if (!targetCRSVert) { throw io::FormattingException( "Can apply Vertical offset and slope only to VerticalCRS"); } const auto latitudeEvaluationPoint = parameterValueNumeric(EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, common::UnitOfMeasure::DEGREE); const auto longitudeEvaluationPoint = parameterValueNumeric(EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, common::UnitOfMeasure::DEGREE); const auto offsetHeight = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); const auto inclinationLatitude = parameterValueNumeric(EPSG_CODE_PARAMETER_INCLINATION_IN_LATITUDE, common::UnitOfMeasure::ARC_SECOND); const auto inclinationLongitude = parameterValueNumeric(EPSG_CODE_PARAMETER_INCLINATION_IN_LONGITUDE, common::UnitOfMeasure::ARC_SECOND); formatter->startInversion(); sourceCRSVert->addLinearUnitConvert(formatter); formatter->stopInversion(); formatter->addStep("vertoffset"); formatter->addParam("lat_0", latitudeEvaluationPoint); formatter->addParam("lon_0", longitudeEvaluationPoint); formatter->addParam("dh", offsetHeight); formatter->addParam("slope_lat", inclinationLatitude); formatter->addParam("slope_lon", inclinationLongitude); targetCRSVert->addLinearUnitConvert(formatter); return true; } // Substitute grid names with PROJ friendly names. if (formatter->databaseContext()) { auto alternate = substitutePROJAlternativeGridNames( NN_NO_CHECK(formatter->databaseContext())); auto self = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); if (alternate != self) { alternate->_exportToPROJString(formatter); return true; } } const auto &NTv1Filename = _getNTv1Filename(this, true); const auto &NTv2Filename = _getNTv2Filename(this, true); const auto &CTABLE2Filename = _getCTABLE2Filename(this, true); const auto &HorizontalShiftGTIFFFilename = _getHorizontalShiftGTIFFFilename(this, true); const auto &hGridShiftFilename = !HorizontalShiftGTIFFFilename.empty() ? HorizontalShiftGTIFFFilename : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty() ? NTv2Filename : CTABLE2Filename; if (!hGridShiftFilename.empty()) { auto l_sourceCRS = sourceCRS(); auto sourceCRSGeog = l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_sourceCRS)) : nullptr; if (!sourceCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } auto l_targetCRS = targetCRS(); auto targetCRSGeog = l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_targetCRS)) : nullptr; if (!targetCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } if (!formatter->omitHorizontalConversionInVertTransformation()) { formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); } if (isMethodInverseOf) { formatter->startInversion(); } if (methodName.find(PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF) != std::string::npos) { formatter->addStep("gridshift"); if (sourceCRSGeog->coordinateSystem()->axisList().size() == 2 && parameterValue( PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE, 0) != nullptr) { formatter->addParam("no_z_transform"); } } else formatter->addStep("hgridshift"); formatter->addParam("grids", hGridShiftFilename); if (isMethodInverseOf) { formatter->stopInversion(); } if (!formatter->omitHorizontalConversionInVertTransformation()) { targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); } return true; } const auto &geocentricTranslationFilename = _getGeocentricTranslationFilename(this, true); if (!geocentricTranslationFilename.empty()) { auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } const auto &interpCRS = interpolationCRS(); if (!interpCRS) { throw io::FormattingException( "InterpolationCRS required " "for" " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN); } const bool interpIsSrc = interpCRS->_isEquivalentTo( sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS); const bool interpIsTarget = interpCRS->_isEquivalentTo( targetCRS().get(), util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS); if (!interpIsSrc && !interpIsTarget) { throw io::FormattingException( "For" " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN ", interpolation CRS should be the source or target CRS"); } formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); if (isMethodInverseOf) { formatter->startInversion(); } formatter->addStep("push"); formatter->addParam("v_3"); formatter->addStep("cart"); sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->addStep("xyzgridshift"); formatter->addParam("grids", geocentricTranslationFilename); formatter->addParam("grid_ref", interpIsTarget ? "output_crs" : "input_crs"); (interpIsTarget ? targetCRSGeog : sourceCRSGeog) ->ellipsoid() ->_exportToPROJString(formatter); formatter->startInversion(); formatter->addStep("cart"); targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->stopInversion(); formatter->addStep("pop"); formatter->addParam("v_3"); if (isMethodInverseOf) { formatter->stopInversion(); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } const auto &geographic3DOffsetByVelocityGridFilename = _getGeographic3DOffsetByVelocityGridFilename(this, true); if (!geographic3DOffsetByVelocityGridFilename.empty()) { auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } const auto &srcEpoch = sourceCRSGeog->datumNonNull(formatter->databaseContext()) ->anchorEpoch(); if (!srcEpoch.has_value()) { throw io::FormattingException( "For" " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL ", missing epoch for source CRS"); } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } const auto &dstEpoch = targetCRSGeog->datumNonNull(formatter->databaseContext()) ->anchorEpoch(); if (!dstEpoch.has_value()) { throw io::FormattingException( "For" " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL ", missing epoch for target CRS"); } const auto &interpCRS = interpolationCRS(); if (!interpCRS) { throw io::FormattingException( "InterpolationCRS required " "for" " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL); } const bool interpIsSrc = interpCRS->_isEquivalentTo( sourceCRS()->demoteTo2D(std::string(), nullptr).get(), util::IComparable::Criterion::EQUIVALENT); const bool interpIsTarget = interpCRS->_isEquivalentTo( targetCRS()->demoteTo2D(std::string(), nullptr).get(), util::IComparable::Criterion::EQUIVALENT); if (!interpIsSrc && !interpIsTarget) { throw io::FormattingException( "For" " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL ", interpolation CRS should be the source or target CRS"); } formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); if (isMethodInverseOf) { formatter->startInversion(); } const bool addPushPopV3 = ((sourceCRSGeog && sourceCRSGeog->coordinateSystem()->axisList().size() == 2) || (targetCRSGeog && targetCRSGeog->coordinateSystem()->axisList().size() == 2)); if (addPushPopV3) { formatter->addStep("push"); formatter->addParam("v_3"); } formatter->addStep("cart"); sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->addStep("deformation"); const double sourceYear = srcEpoch->convertToUnit(common::UnitOfMeasure::YEAR); const double targetYear = dstEpoch->convertToUnit(common::UnitOfMeasure::YEAR); formatter->addParam("dt", targetYear - sourceYear); formatter->addParam("grids", geographic3DOffsetByVelocityGridFilename); (interpIsTarget ? targetCRSGeog : sourceCRSGeog) ->ellipsoid() ->_exportToPROJString(formatter); formatter->startInversion(); formatter->addStep("cart"); targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->stopInversion(); if (addPushPopV3) { formatter->addStep("pop"); formatter->addParam("v_3"); } if (isMethodInverseOf) { formatter->stopInversion(); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } const auto &verticalOffsetByVelocityGridFilename = _getVerticalOffsetByVelocityGridFilename(this, true); if (!verticalOffsetByVelocityGridFilename.empty()) { const auto &interpCRS = interpolationCRS(); if (!interpCRS) { throw io::FormattingException( "InterpolationCRS required " "for" " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL); } auto interpCRSGeog = dynamic_cast(interpCRS.get()); if (!interpCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to a GeographicCRS interpolation CRS")); } const auto vertSrc = dynamic_cast(sourceCRS().get()); if (!vertSrc) { throw io::FormattingException(concat( "Can apply ", methodName, " only to a source VerticalCRS")); } const auto &srcEpoch = vertSrc->datumNonNull(formatter->databaseContext())->anchorEpoch(); if (!srcEpoch.has_value()) { throw io::FormattingException( "For" " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL ", missing epoch for source CRS"); } const auto vertDst = dynamic_cast(targetCRS().get()); if (!vertDst) { throw io::FormattingException(concat( "Can apply ", methodName, " only to a target VerticalCRS")); } const auto &dstEpoch = vertDst->datumNonNull(formatter->databaseContext())->anchorEpoch(); if (!dstEpoch.has_value()) { throw io::FormattingException( "For" " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL ", missing epoch for target CRS"); } const double sourceYear = srcEpoch->convertToUnit(common::UnitOfMeasure::YEAR); const double targetYear = dstEpoch->convertToUnit(common::UnitOfMeasure::YEAR); if (isMethodInverseOf) { formatter->startInversion(); } formatter->addStep("push"); formatter->addParam("v_1"); formatter->addParam("v_2"); formatter->addStep("cart"); interpCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->addStep("deformation"); formatter->addParam("dt", targetYear - sourceYear); formatter->addParam("grids", verticalOffsetByVelocityGridFilename); interpCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->startInversion(); formatter->addStep("cart"); interpCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->stopInversion(); formatter->addStep("pop"); formatter->addParam("v_1"); formatter->addParam("v_2"); if (isMethodInverseOf) { formatter->stopInversion(); } return true; } const auto &heightFilename = _getHeightToGeographic3DFilename(this, true); if (!heightFilename.empty()) { auto l_targetCRS = targetCRS(); auto targetCRSGeog = l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_targetCRS)) : nullptr; if (!targetCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } if (!formatter->omitHorizontalConversionInVertTransformation()) { formatter->startInversion(); formatter->pushOmitZUnitConversion(); targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->popOmitZUnitConversion(); formatter->stopInversion(); } if (isMethodInverseOf) { formatter->startInversion(); } formatter->addStep("vgridshift"); formatter->addParam("grids", heightFilename); formatter->addParam("multiplier", 1.0); if (isMethodInverseOf) { formatter->stopInversion(); } if (!formatter->omitHorizontalConversionInVertTransformation()) { formatter->pushOmitZUnitConversion(); targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->popOmitZUnitConversion(); } return true; } if (Transformation::isGeographic3DToGravityRelatedHeight(method(), true)) { auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { const auto &filename = fileParameter->valueFile(); auto l_sourceCRS = sourceCRS(); auto sourceCRSGeog = l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_sourceCRS)) : nullptr; if (!sourceCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } auto l_targetCRS = targetCRS(); auto targetVertCRS = l_targetCRS ? l_targetCRS->extractVerticalCRS() : nullptr; if (!targetVertCRS) { throw io::FormattingException( concat("Can apply ", methodName, " only to a target CRS that has a VerticalCRS")); } if (!formatter->omitHorizontalConversionInVertTransformation()) { formatter->startInversion(); formatter->pushOmitZUnitConversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->popOmitZUnitConversion(); formatter->stopInversion(); } bool doInversion = isMethodInverseOf; // The EPSG Geog3DToHeight is the reverse convention of PROJ ! doInversion = !doInversion; if (doInversion) { formatter->startInversion(); } // For Geographic3D to Depth methods, we rely on the vertical axis // direction instead of the name/code of the transformation method. if (targetVertCRS->coordinateSystem()->axisList()[0]->direction() == cs::AxisDirection::DOWN) { formatter->addStep("axisswap"); formatter->addParam("order", "1,2,-3"); } formatter->addStep("vgridshift"); formatter->addParam("grids", filename); formatter->addParam("multiplier", 1.0); if (doInversion) { formatter->stopInversion(); } if (!formatter->omitHorizontalConversionInVertTransformation()) { formatter->pushOmitZUnitConversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->popOmitZUnitConversion(); } return true; } } if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) { auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { formatter->addStep("vgridshift"); formatter->addParam("grids", fileParameter->valueFile()); if (fileParameter->valueFile().find(".tif") != std::string::npos) { formatter->addParam("multiplier", 1.0); } else { // The vertcon grids go from NGVD 29 to NAVD 88, with units // in millimeter (see // https://github.com/OSGeo/proj.4/issues/1071), for gtx files formatter->addParam("multiplier", 0.001); } return true; } } bool reverseOffsetSign = false; if (isRegularVerticalGridMethod(methodEPSGCode, reverseOffsetSign)) { int parameterCode = EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE; auto fileParameter = parameterValue( EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, parameterCode); if (!fileParameter) { parameterCode = EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE; fileParameter = parameterValue( EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE, parameterCode); } if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { formatter->addStep("vgridshift"); formatter->addParam("grids", fileParameter->valueFile()); formatter->addParam("multiplier", reverseOffsetSign ? -1.0 : 1.0); return true; } } if (isLongitudeRotation()) { double offsetDeg = parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, common::UnitOfMeasure::DEGREE); auto l_sourceCRS = sourceCRS(); auto sourceCRSGeog = l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_sourceCRS)) : nullptr; if (!sourceCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } auto l_targetCRS = targetCRS(); auto targetCRSGeog = l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( NN_NO_CHECK(l_targetCRS)) : nullptr; if (!targetCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName + " only to GeographicCRS")); } if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo( targetCRSGeog->ellipsoid().get(), util::IComparable::Criterion::EQUIVALENT)) { // This is arguable if we should check this... throw io::FormattingException("Can apply Longitude rotation " "only to SRS with same " "ellipsoid"); } formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); bool done = false; if (offsetDeg != 0.0) { // Optimization: as we are doing nominally a +step=inv, // if the negation of the offset value is a well-known name, // then use forward case with this name. auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName( common::Angle(-offsetDeg)); if (!projPMName.empty()) { done = true; formatter->addStep("longlat"); sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); formatter->addParam("pm", projPMName); } } if (!done) { // To actually add the offset, we must use the reverse longlat // operation. formatter->startInversion(); formatter->addStep("longlat"); sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); datum::PrimeMeridian::create(util::PropertyMap(), common::Angle(offsetDeg)) ->_exportToPROJString(formatter); formatter->stopInversion(); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } if (methodEPSGCode == EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL) { auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { throw io::FormattingException( concat("Can apply ", methodName, " only to GeographicCRS")); } auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { formatter->startInversion(); sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); double sourceYear = sourceCoordinateEpoch().has_value() ? getRoundedEpochInDecimalYear( sourceCoordinateEpoch() ->coordinateEpoch() .convertToUnit(common::UnitOfMeasure::YEAR)) : 0; double targetYear = targetCoordinateEpoch().has_value() ? getRoundedEpochInDecimalYear( targetCoordinateEpoch() ->coordinateEpoch() .convertToUnit(common::UnitOfMeasure::YEAR)) : 0; if (sourceYear > 0 && targetYear == 0) targetYear = sourceYear; else if (targetYear > 0 && sourceYear == 0) sourceYear = targetYear; if (sourceYear > 0) { formatter->addStep("set"); formatter->addParam("v_4", sourceYear); } if (isMethodInverseOf) { formatter->startInversion(); } // Operations are registered in EPSG with inverse order as // the +proj=defmodel implementation formatter->startInversion(); formatter->addStep("defmodel"); formatter->addParam("model", fileParameter->valueFile()); formatter->stopInversion(); if (isMethodInverseOf) { formatter->stopInversion(); } if (targetYear > 0) { formatter->addStep("set"); formatter->addParam("v_4", targetYear); } targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); return true; } } if (methodEPSGCode == EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON) { auto sourceCRSProj = dynamic_cast(sourceCRS().get()); if (!sourceCRSProj) { throw io::FormattingException( concat("Can apply ", methodName, " only to ProjectedCRS")); } auto targetCRSProj = dynamic_cast(targetCRS().get()); if (!targetCRSProj) { throw io::FormattingException( concat("Can apply ", methodName, " only to ProjectedCRS")); } auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { formatter->startInversion(); sourceCRSProj->addUnitConvertAndAxisSwap(formatter, false); formatter->stopInversion(); if (isMethodInverseOf) { formatter->startInversion(); } formatter->addStep("tinshift"); formatter->addParam("file", fileParameter->valueFile()); if (isMethodInverseOf) { formatter->stopInversion(); } targetCRSProj->addUnitConvertAndAxisSwap(formatter, false); return true; } } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON) { auto sourceCRSGeog = dynamic_cast(sourceCRS().get()); if (!sourceCRSGeog) { throw io::FormattingException(concat( "Can apply ", methodName, " only to source GeographicCRS")); } const auto hasDegreeUnit = [](const crs::GeographicCRS *crs) { const auto &axisList = crs->coordinateSystem()->axisList(); return axisList[0]->unit() == common::UnitOfMeasure::DEGREE; }; if (!hasDegreeUnit(sourceCRSGeog)) { throw io::FormattingException( concat("Can apply ", methodName, " only to source GeographicCRS with degree axis unit")); } auto targetCRSGeog = dynamic_cast(targetCRS().get()); if (!targetCRSGeog) { throw io::FormattingException(concat( "Can apply ", methodName, " only to target GeographicCRS")); } if (!hasDegreeUnit(targetCRSGeog)) { throw io::FormattingException( concat("Can apply ", methodName, " only to target GeographicCRS with degree axis unit")); } auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { formatter->startInversion(); sourceCRSGeog->addAxisSwap(formatter); formatter->stopInversion(); if (isMethodInverseOf) { formatter->startInversion(); } formatter->addStep("tinshift"); formatter->addParam("file", fileParameter->valueFile()); if (isMethodInverseOf) { formatter->stopInversion(); } targetCRSGeog->addAxisSwap(formatter); return true; } } if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON) { auto sourceCRSVert = dynamic_cast(sourceCRS().get()); if (!sourceCRSVert) { throw io::FormattingException( concat("Can apply ", methodName, " only to VerticalCRS")); } auto targetCRSVert = dynamic_cast(targetCRS().get()); if (!targetCRSVert) { throw io::FormattingException( concat("Can apply ", methodName, " only to VerticalCRS")); } auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { if (isMethodInverseOf) { formatter->startInversion(); } formatter->addStep("tinshift"); formatter->addParam("file", fileParameter->valueFile()); if (isMethodInverseOf) { formatter->stopInversion(); } return true; } } if (methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) { auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); auto sourceCRSGeod = dynamic_cast(l_sourceCRS.get()); auto targetCRSGeod = dynamic_cast(l_targetCRS.get()); if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) { throw io::FormattingException( concat("Can apply ", methodName, " only to a geocentric CRS")); } if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) { throw io::FormattingException( concat("Can apply ", methodName, " only to a geocentric CRS")); } auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (!(fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME)) { throw io::FormattingException( "Missing parameter Point motion velocity grid file"); } if (isMethodInverseOf) { formatter->startInversion(); } if (l_sourceCRS) { setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), false, "Helmert"); } formatter->addStep("helmert"); double x = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); double y = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); double z = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); formatter->addParam("x", x); formatter->addParam("y", y); formatter->addParam("z", z); double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, common::UnitOfMeasure::ARC_SECOND); double scaleDiff = parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, common::UnitOfMeasure::PARTS_PER_MILLION); formatter->addParam("rx", rx); formatter->addParam("ry", ry); formatter->addParam("rz", rz); formatter->addParam("s", scaleDiff); formatter->addParam("convention", "position_vector"); formatter->addStep("deformation"); const bool hasTargetEpoch = [this]() { const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH); return val && val->type() == ParameterValue::Type::MEASURE; }(); if (hasTargetEpoch) { const double targetYear = parameterValueNumeric( EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR); const bool hasSourceEpoch = [this]() { const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH); return val && val->type() == ParameterValue::Type::MEASURE; }(); if (hasSourceEpoch) { const double sourceYear = parameterValueNumeric(EPSG_CODE_PARAMETER_SOURCE_EPOCH, common::UnitOfMeasure::YEAR); formatter->addParam("dt", targetYear - sourceYear); } else { formatter->addParam("t_epoch", targetYear); } } else { throw io::FormattingException("Missing parameter target epoch"); } formatter->addParam("grids", fileParameter->valueFile()); sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter); if (l_targetCRS) { setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), false, "Helmert"); } if (isMethodInverseOf) { formatter->stopInversion(); } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG) { auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); auto sourceCRSGeod = dynamic_cast(l_sourceCRS.get()); auto targetCRSGeod = dynamic_cast(l_targetCRS.get()); if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) { throw io::FormattingException( concat("Can apply ", methodName, " only to a geocentric CRS")); } if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) { throw io::FormattingException( concat("Can apply ", methodName, " only to a geocentric CRS")); } auto fileParameter = parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (!(fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME)) { throw io::FormattingException( "Missing parameter Point motion velocity grid file"); } if (isMethodInverseOf) { formatter->startInversion(); } if (l_sourceCRS) { setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), false, "Helmert"); } const bool hasSourceEpoch = [this]() { const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH); return val && val->type() == ParameterValue::Type::MEASURE; }(); if (hasSourceEpoch) { throw io::FormattingException("Unsupported parameter source epoch"); } const bool hasTargetEpoch = [this]() { const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH); return val && val->type() == ParameterValue::Type::MEASURE; }(); if (!hasTargetEpoch) { throw io::FormattingException("Missing parameter target epoch"); } formatter->startInversion(); formatter->addStep("deformation"); const double targetYear = parameterValueNumeric( EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR); formatter->addParam("t_epoch", targetYear); formatter->addParam("grids", fileParameter->valueFile()); sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter); formatter->stopInversion(); if (l_targetCRS) { setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), false, "Helmert"); } if (isMethodInverseOf) { formatter->stopInversion(); } return true; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) { auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); auto sourceCRSGeod = dynamic_cast(l_sourceCRS.get()); auto targetCRSGeod = dynamic_cast(l_targetCRS.get()); if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) { throw io::FormattingException( concat("Can apply ", methodName, " only to a geocentric CRS")); } if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) { throw io::FormattingException( concat("Can apply ", methodName, " only to a geocentric CRS")); } auto fileParameterGTF = parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); if (!(fileParameterGTF && fileParameterGTF->type() == ParameterValue::Type::FILENAME)) { throw io::FormattingException( "Missing parameter Geocentric translation file"); } auto fileParameterPMV = parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); if (!(fileParameterPMV && fileParameterPMV->type() == ParameterValue::Type::FILENAME)) { throw io::FormattingException( "Missing parameter Point motion velocity grid file"); } if (isMethodInverseOf) { formatter->startInversion(); } if (l_sourceCRS) { setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), false, "Helmert"); } const bool hasSourceEpoch = [this]() { const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH); return val && val->type() == ParameterValue::Type::MEASURE; }(); if (!hasSourceEpoch) { throw io::FormattingException("Missing parameter source epoch"); } const bool hasTargetEpoch = [this]() { const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH); return val && val->type() == ParameterValue::Type::MEASURE; }(); if (!hasTargetEpoch) { throw io::FormattingException("Missing parameter target epoch"); } formatter->addStep("xyzgridshift"); formatter->addParam("grids", fileParameterGTF->valueFile()); formatter->addStep("deformation"); const double sourceYear = parameterValueNumeric( EPSG_CODE_PARAMETER_SOURCE_EPOCH, common::UnitOfMeasure::YEAR); const double targetYear = parameterValueNumeric( EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR); formatter->addParam("dt", targetYear - sourceYear); formatter->addParam("grids", fileParameterPMV->valueFile()); sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter); if (l_targetCRS) { setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), false, "Helmert"); } if (isMethodInverseOf) { formatter->stopInversion(); } return true; } const char *prefix = "PROJ-based operation method: "; if (starts_with(method()->nameStr(), prefix)) { auto projString = method()->nameStr().substr(strlen(prefix)); try { formatter->ingestPROJString(projString); return true; } catch (const io::ParsingException &e) { throw io::FormattingException( std::string("ingestPROJString() failed: ") + e.what()); } } return false; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress InverseCoordinateOperation::~InverseCoordinateOperation() = default; // --------------------------------------------------------------------------- InverseCoordinateOperation::InverseCoordinateOperation( const CoordinateOperationNNPtr &forwardOperationIn, bool wktSupportsInversion) : forwardOperation_(forwardOperationIn), wktSupportsInversion_(wktSupportsInversion) {} // --------------------------------------------------------------------------- void InverseCoordinateOperation::setPropertiesFromForward() { setProperties( createPropertiesForInverse(forwardOperation_.get(), false, false)); setAccuracies(forwardOperation_->coordinateOperationAccuracies()); if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) { setCRSs(forwardOperation_.get(), true); } setHasBallparkTransformation( forwardOperation_->hasBallparkTransformation()); setRequiresPerCoordinateInputTime( forwardOperation_->requiresPerCoordinateInputTime()); if (auto sourceEpoch = forwardOperation_->sourceCoordinateEpoch()) { setTargetCoordinateEpoch(sourceEpoch); } if (auto targetEpoch = forwardOperation_->targetCoordinateEpoch()) { setSourceCoordinateEpoch(targetEpoch); } } // --------------------------------------------------------------------------- CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const { return forwardOperation_; } // --------------------------------------------------------------------------- void InverseCoordinateOperation::_exportToPROJString( io::PROJStringFormatter *formatter) const { formatter->startInversion(); forwardOperation_->_exportToPROJString(formatter); formatter->stopInversion(); } // --------------------------------------------------------------------------- bool InverseCoordinateOperation::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherICO = dynamic_cast(other); if (otherICO == nullptr || !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { return false; } return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PointMotionOperation::~PointMotionOperation() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a point motion operation from a vector of * GeneralParameterValue. * * @param properties See \ref general_properties. At minimum the name should be * defined. * @param crsIn Source and target CRS. * @param methodIn Operation method. * @param values Vector of GeneralOperationParameterNNPtr. * @param accuracies Vector of positional accuracy (might be empty). * @return new PointMotionOperation. * @throws InvalidOperation if the object cannot be constructed. */ PointMotionOperationNNPtr PointMotionOperation::create( const util::PropertyMap &properties, const crs::CRSNNPtr &crsIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies) { if (methodIn->parameters().size() != values.size()) { throw InvalidOperation( "Inconsistent number of parameters and parameter values"); } auto pmo = PointMotionOperation::nn_make_shared( crsIn, methodIn, values, accuracies); pmo->assignSelf(pmo); pmo->setProperties(properties); const std::string l_name = pmo->nameStr(); auto pos = l_name.find(" from epoch "); if (pos != std::string::npos) { pos += strlen(" from epoch "); const auto pos2 = l_name.find(" to epoch ", pos); if (pos2 != std::string::npos) { const double sourceYear = std::stod(l_name.substr(pos, pos2 - pos)); const double targetYear = std::stod(l_name.substr(pos2 + strlen(" to epoch "))); pmo->setSourceCoordinateEpoch( util::optional(common::DataEpoch( common::Measure(sourceYear, common::UnitOfMeasure::YEAR)))); pmo->setTargetCoordinateEpoch( util::optional(common::DataEpoch( common::Measure(targetYear, common::UnitOfMeasure::YEAR)))); } } return pmo; } // --------------------------------------------------------------------------- /** \brief Instantiate a point motion operation and its OperationMethod. * * @param propertiesOperation The \ref general_properties of the * PointMotionOperation. * At minimum the name should be defined. * @param crsIn Source and target CRS. * @param propertiesOperationMethod The \ref general_properties of the * OperationMethod. * At minimum the name should be defined. * @param parameters Vector of parameters of the operation method. * @param values Vector of ParameterValueNNPtr. Constraint: * values.size() == parameters.size() * @param accuracies Vector of positional accuracy (might be empty). * @return new PointMotionOperation. * @throws InvalidOperation if the object cannot be constructed. */ PointMotionOperationNNPtr PointMotionOperation::create( const util::PropertyMap &propertiesOperation, const crs::CRSNNPtr &crsIn, const util::PropertyMap &propertiesOperationMethod, const std::vector ¶meters, const std::vector &values, const std::vector &accuracies) // throw InvalidOperation { OperationMethodNNPtr op( OperationMethod::create(propertiesOperationMethod, parameters)); if (parameters.size() != values.size()) { throw InvalidOperation( "Inconsistent number of parameters and parameter values"); } std::vector generalParameterValues; generalParameterValues.reserve(values.size()); for (size_t i = 0; i < values.size(); i++) { generalParameterValues.push_back( OperationParameterValue::create(parameters[i], values[i])); } return create(propertiesOperation, crsIn, op, generalParameterValues, accuracies); } // --------------------------------------------------------------------------- PointMotionOperation::PointMotionOperation( const crs::CRSNNPtr &crsIn, const OperationMethodNNPtr &methodIn, const std::vector &values, const std::vector &accuracies) : SingleOperation(methodIn) { setParameterValues(values); setCRSs(crsIn, crsIn, nullptr); setAccuracies(accuracies); } // --------------------------------------------------------------------------- PointMotionOperation::PointMotionOperation(const PointMotionOperation &other) : CoordinateOperation(other), SingleOperation(other) {} // --------------------------------------------------------------------------- CoordinateOperationNNPtr PointMotionOperation::inverse() const { auto inverse = shallowClone(); if (sourceCoordinateEpoch().has_value()) { // Switch source and target epochs inverse->setSourceCoordinateEpoch(targetCoordinateEpoch()); inverse->setTargetCoordinateEpoch(sourceCoordinateEpoch()); auto l_name = inverse->nameStr(); auto pos = l_name.find(" from epoch "); if (pos != std::string::npos) l_name.resize(pos); const double sourceYear = getRoundedEpochInDecimalYear( inverse->sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); const double targetYear = getRoundedEpochInDecimalYear( inverse->targetCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); l_name += " from epoch "; l_name += toString(sourceYear); l_name += " to epoch "; l_name += toString(targetYear); util::PropertyMap newProperties; newProperties.set(IdentifiedObject::NAME_KEY, l_name); inverse->setProperties(newProperties); } return inverse; } // --------------------------------------------------------------------------- /** \brief Return an equivalent transformation to the current one, but using * PROJ alternative grid names. */ PointMotionOperationNNPtr PointMotionOperation::substitutePROJAlternativeGridNames( io::DatabaseContextNNPtr databaseContext) const { auto self = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); const auto &l_method = method(); const int methodEPSGCode = l_method->getEPSGCode(); const char *const paramName = methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT ? EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE : EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; const int paramCode = methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT ? EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE : EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; std::string filename; if (methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL || methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL || methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT) { const auto &fileParameter = parameterValue(paramName, paramCode); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { filename = fileParameter->valueFile(); } } std::string projFilename; std::string projGridFormat; bool inverseDirection = false; if (!filename.empty() && databaseContext->lookForGridAlternative( filename, projFilename, projGridFormat, inverseDirection)) { if (filename == projFilename) { return self; } const VectorOfParameters parameters{ createOpParamNameEPSGCode(paramCode)}; const VectorOfValues values{ ParameterValue::createFilename(projFilename)}; return PointMotionOperation::create( createSimilarPropertiesOperation(self), sourceCRS(), createSimilarPropertiesMethod(method()), parameters, values, coordinateOperationAccuracies()); } return self; } // --------------------------------------------------------------------------- /** \brief Return the source crs::CRS of the operation. * * @return the source CRS. */ const crs::CRSNNPtr &PointMotionOperation::sourceCRS() PROJ_PURE_DEFN { return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PointMotionOperationNNPtr PointMotionOperation::shallowClone() const { auto pmo = PointMotionOperation::nn_make_shared(*this); pmo->assignSelf(pmo); pmo->setCRSs(this, false); return pmo; } CoordinateOperationNNPtr PointMotionOperation::_shallowClone() const { return util::nn_static_pointer_cast(shallowClone()); } // --------------------------------------------------------------------------- PointMotionOperationNNPtr PointMotionOperation::cloneWithEpochs( const common::DataEpoch &sourceEpoch, const common::DataEpoch &targetEpoch) const { auto pmo = PointMotionOperation::nn_make_shared(*this); pmo->assignSelf(pmo); pmo->setCRSs(this, false); pmo->setSourceCoordinateEpoch( util::optional(sourceEpoch)); pmo->setTargetCoordinateEpoch( util::optional(targetEpoch)); const double sourceYear = getRoundedEpochInDecimalYear( sourceEpoch.coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); const double targetYear = getRoundedEpochInDecimalYear( targetEpoch.coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); auto l_name = nameStr(); l_name += " from epoch "; l_name += toString(sourceYear); l_name += " to epoch "; l_name += toString(targetYear); util::PropertyMap newProperties; newProperties.set(IdentifiedObject::NAME_KEY, l_name); pmo->setProperties(newProperties); return pmo; } // --------------------------------------------------------------------------- void PointMotionOperation::_exportToWKT(io::WKTFormatter *formatter) const { if (formatter->version() != io::WKTFormatter::Version::WKT2 || !formatter->use2019Keywords()) { throw io::FormattingException( "Transformation can only be exported to WKT2:2019"); } formatter->startNode(io::WKTConstants::POINTMOTIONOPERATION, !identifiers().empty()); formatter->addQuotedString(nameStr()); const auto &version = operationVersion(); if (version.has_value()) { formatter->startNode(io::WKTConstants::VERSION, false); formatter->addQuotedString(*version); formatter->endNode(); } auto l_sourceCRS = sourceCRS(); const bool canExportCRSId = !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()); const bool hasDomains = !domains().empty(); if (hasDomains) { formatter->pushDisableUsage(); } formatter->startNode(io::WKTConstants::SOURCECRS, false); if (canExportCRSId && !l_sourceCRS->identifiers().empty()) { // fake that top node has no id, so that the sourceCRS id is // considered formatter->pushHasId(false); l_sourceCRS->_exportToWKT(formatter); formatter->popHasId(); } else { l_sourceCRS->_exportToWKT(formatter); } formatter->endNode(); if (hasDomains) { formatter->popDisableUsage(); } const auto &l_method = method(); l_method->_exportToWKT(formatter); for (const auto ¶mValue : parameterValues()) { paramValue->_exportToWKT(formatter, nullptr); } if (!coordinateOperationAccuracies().empty()) { formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); formatter->add(coordinateOperationAccuracies()[0]->value()); formatter->endNode(); } ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } // --------------------------------------------------------------------------- void PointMotionOperation::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(FormattingException) { if (formatter->convention() == io::PROJStringFormatter::Convention::PROJ_4) { throw io::FormattingException( "PointMotionOperation cannot be exported as a PROJ.4 string"); } const int methodEPSGCode = method()->getEPSGCode(); if (methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL || methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL || methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT) { if (!sourceCoordinateEpoch().has_value()) { throw io::FormattingException( "CoordinateOperationNNPtr::_exportToPROJString() unimplemented " "when source coordinate epoch is missing"); } if (!targetCoordinateEpoch().has_value()) { throw io::FormattingException( "CoordinateOperationNNPtr::_exportToPROJString() unimplemented " "when target coordinate epoch is missing"); } auto l_sourceCRS = dynamic_cast(sourceCRS().get()); if (!l_sourceCRS) { throw io::FormattingException("Can apply PointMotionOperation " "VelocityGrid only to GeodeticCRS"); } if (!l_sourceCRS->isGeocentric()) { formatter->startInversion(); l_sourceCRS->_exportToPROJString(formatter); formatter->stopInversion(); formatter->addStep("cart"); l_sourceCRS->ellipsoid()->_exportToPROJString(formatter); } else { formatter->startInversion(); l_sourceCRS->addGeocentricUnitConversionIntoPROJString(formatter); formatter->stopInversion(); } const double sourceYear = getRoundedEpochInDecimalYear( sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); const double targetYear = getRoundedEpochInDecimalYear( targetCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)); formatter->addStep("set"); formatter->addParam("v_4", sourceYear); formatter->addParam("omit_fwd"); formatter->addStep("deformation"); formatter->addParam("dt", targetYear - sourceYear); const char *const paramName = methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT ? EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE : EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; const int paramCode = methodEPSGCode == EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT ? EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE : EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; const auto &fileParameter = parameterValue(paramName, paramCode); if (fileParameter && fileParameter->type() == ParameterValue::Type::FILENAME) { formatter->addParam("grids", fileParameter->valueFile()); } else { throw io::FormattingException( "CoordinateOperationNNPtr::_exportToPROJString(): missing " "velocity grid file parameter"); } l_sourceCRS->ellipsoid()->_exportToPROJString(formatter); formatter->addStep("set"); formatter->addParam("v_4", targetYear); formatter->addParam("omit_inv"); if (!l_sourceCRS->isGeocentric()) { formatter->startInversion(); formatter->addStep("cart"); l_sourceCRS->ellipsoid()->_exportToPROJString(formatter); formatter->stopInversion(); l_sourceCRS->_exportToPROJString(formatter); } else { l_sourceCRS->addGeocentricUnitConversionIntoPROJString(formatter); } } else { throw io::FormattingException( "CoordinateOperationNNPtr::_exportToPROJString() unimplemented for " "this method"); } } // --------------------------------------------------------------------------- void PointMotionOperation::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext("PointMotionOperation", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("source_crs"); formatter->setAllowIDInImmediateChild(); sourceCRS()->_exportToJSON(formatter); writer->AddObjKey("method"); formatter->setOmitTypeInImmediateChild(); formatter->setAllowIDInImmediateChild(); method()->_exportToJSON(formatter); writer->AddObjKey("parameters"); { auto parametersContext(writer->MakeArrayContext(false)); for (const auto &genOpParamvalue : parameterValues()) { formatter->setAllowIDInImmediateChild(); formatter->setOmitTypeInImmediateChild(); genOpParamvalue->_exportToJSON(formatter); } } if (!coordinateOperationAccuracies().empty()) { writer->AddObjKey("accuracy"); writer->Add(coordinateOperationAccuracies()[0]->value()); } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/esriparammappings.hpp000664 001750 001750 00000006106 15166171715 022667 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef ESRIPARAMMAPPINGS_HPP #define ESRIPARAMMAPPINGS_HPP #include "proj/coordinateoperation.hpp" #include "proj/util.hpp" #include "esriparammappings.hpp" #include // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ESRIParamMapping { const char *esri_name; const char *wkt2_name; int epsg_code; const char *fixed_value; bool is_fixed_value; }; struct ESRIMethodMapping { const char *esri_name; const char *wkt2_name; int epsg_code; const ESRIParamMapping *const params; }; extern const ESRIParamMapping paramsESRI_Plate_Carree[]; extern const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[]; extern const ESRIParamMapping paramsESRI_Gauss_Kruger[]; extern const ESRIParamMapping paramsESRI_Transverse_Mercator[]; extern const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[]; extern const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[]; extern const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[]; extern const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[]; const ESRIMethodMapping *getEsriMappings(size_t &nElts); std::vector getMappingsFromESRI(const std::string &esri_name); //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END #endif // ESRIPARAMMAPPINGS_HPP proj-9.8.1/src/iso19111/operation/projbasedoperation.cpp000664 001750 001750 00000027157 15166171715 023043 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "coordinateoperation_internal.hpp" #include "oputils.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // M_PI // clang-format on #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- PROJBasedOperation::~PROJBasedOperation() = default; // --------------------------------------------------------------------------- PROJBasedOperation::PROJBasedOperation(const OperationMethodNNPtr &methodIn) : SingleOperation(methodIn) {} // --------------------------------------------------------------------------- PROJBasedOperationNNPtr PROJBasedOperation::create( const util::PropertyMap &properties, const std::string &PROJString, const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, const std::vector &accuracies) { auto method = OperationMethod::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, "PROJ-based operation method: " + PROJString), std::vector{}); auto op = PROJBasedOperation::nn_make_shared(method); op->assignSelf(op); op->projString_ = PROJString; if (sourceCRS && targetCRS) { op->setCRSs(NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), nullptr); } op->setProperties( addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); op->setAccuracies(accuracies); auto formatter = io::PROJStringFormatter::create(); try { formatter->ingestPROJString(PROJString); op->setRequiresPerCoordinateInputTime( formatter->requiresPerCoordinateInputTime()); } catch (const io::ParsingException &e) { throw util::UnsupportedOperationException( std::string("PROJBasedOperation::create() failed: ") + e.what()); } return op; } // --------------------------------------------------------------------------- PROJBasedOperationNNPtr PROJBasedOperation::create( const util::PropertyMap &properties, const io::IPROJStringExportableNNPtr &projExportable, bool inverse, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::CRSPtr &interpolationCRS, const std::vector &accuracies, bool hasBallparkTransformation) { auto formatter = io::PROJStringFormatter::create(); if (inverse) { formatter->startInversion(); } projExportable->_exportToPROJString(formatter.get()); if (inverse) { formatter->stopInversion(); } const auto &projString = formatter->toString(); auto method = OperationMethod::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, "PROJ-based operation method (approximate): " + projString), std::vector{}); auto op = PROJBasedOperation::nn_make_shared(method); op->assignSelf(op); op->projString_ = projString; op->setCRSs(sourceCRS, targetCRS, interpolationCRS); op->setProperties( addDefaultNameIfNeeded(properties, "PROJ-based coordinate operation")); op->setAccuracies(accuracies); op->projStringExportable_ = projExportable.as_nullable(); op->inverse_ = inverse; op->setHasBallparkTransformation(hasBallparkTransformation); op->setRequiresPerCoordinateInputTime( formatter->requiresPerCoordinateInputTime()); return op; } // --------------------------------------------------------------------------- CoordinateOperationNNPtr PROJBasedOperation::inverse() const { if (projStringExportable_ && sourceCRS() && targetCRS()) { return util::nn_static_pointer_cast( PROJBasedOperation::create( createPropertiesForInverse(this, false, false), NN_NO_CHECK(projStringExportable_), !inverse_, NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), interpolationCRS(), coordinateOperationAccuracies(), hasBallparkTransformation())); } auto formatter = io::PROJStringFormatter::create(); formatter->startInversion(); try { formatter->ingestPROJString(projString_); } catch (const io::ParsingException &e) { throw util::UnsupportedOperationException( std::string("PROJBasedOperation::inverse() failed: ") + e.what()); } formatter->stopInversion(); auto op = PROJBasedOperation::create( createPropertiesForInverse(this, false, false), formatter->toString(), targetCRS(), sourceCRS(), coordinateOperationAccuracies()); if (sourceCRS() && targetCRS()) { op->setCRSs(NN_NO_CHECK(targetCRS()), NN_NO_CHECK(sourceCRS()), interpolationCRS()); } op->setHasBallparkTransformation(hasBallparkTransformation()); op->setRequiresPerCoordinateInputTime( formatter->requiresPerCoordinateInputTime()); return util::nn_static_pointer_cast(op); } // --------------------------------------------------------------------------- void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const { if (sourceCRS() && targetCRS()) { exportTransformationToWKT(formatter); return; } const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { throw io::FormattingException( "PROJBasedOperation can only be exported to WKT2"); } formatter->startNode(io::WKTConstants::CONVERSION, false); formatter->addQuotedString(nameStr()); method()->_exportToWKT(formatter); for (const auto ¶mValue : parameterValues()) { paramValue->_exportToWKT(formatter); } formatter->endNode(); } // --------------------------------------------------------------------------- void PROJBasedOperation::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext( (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } if (sourceCRS() && targetCRS()) { writer->AddObjKey("source_crs"); formatter->setAllowIDInImmediateChild(); sourceCRS()->_exportToJSON(formatter); writer->AddObjKey("target_crs"); formatter->setAllowIDInImmediateChild(); targetCRS()->_exportToJSON(formatter); } writer->AddObjKey("method"); formatter->setOmitTypeInImmediateChild(); formatter->setAllowIDInImmediateChild(); method()->_exportToJSON(formatter); const auto &l_parameterValues = parameterValues(); writer->AddObjKey("parameters"); { auto parametersContext(writer->MakeArrayContext(false)); for (const auto &genOpParamvalue : l_parameterValues) { formatter->setAllowIDInImmediateChild(); formatter->setOmitTypeInImmediateChild(); genOpParamvalue->_exportToJSON(formatter); } } } // --------------------------------------------------------------------------- void PROJBasedOperation::_exportToPROJString( io::PROJStringFormatter *formatter) const { if (projStringExportable_) { if (inverse_) { formatter->startInversion(); } projStringExportable_->_exportToPROJString(formatter); if (inverse_) { formatter->stopInversion(); } return; } try { formatter->ingestPROJString(projString_); } catch (const io::ParsingException &e) { throw io::FormattingException( std::string("PROJBasedOperation::exportToPROJString() failed: ") + e.what()); } } // --------------------------------------------------------------------------- CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const { auto op = PROJBasedOperation::nn_make_shared(*this); op->assignSelf(op); op->setCRSs(this, false); return util::nn_static_pointer_cast(op); } // --------------------------------------------------------------------------- std::set PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const { std::set res; try { auto formatterOut = io::PROJStringFormatter::create(); auto formatter = io::PROJStringFormatter::create(); formatter->ingestPROJString(exportToPROJString(formatterOut.get())); const auto usedGridNames = formatter->getUsedGridNames(); for (const auto &shortName : usedGridNames) { GridDescription desc; desc.shortName = shortName; if (databaseContext) { databaseContext->lookForGridInfo( desc.shortName, considerKnownGridsAsAvailable, desc.fullName, desc.packageName, desc.url, desc.directDownload, desc.openLicense, desc.available); } res.insert(std::move(desc)); } } catch (const io::ParsingException &) { } return res; } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/parametervalue.cpp000664 001750 001750 00000027430 15166171715 022160 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ParameterValue::Private { ParameterValue::Type type_{ParameterValue::Type::STRING}; std::unique_ptr measure_{}; std::unique_ptr stringValue_{}; int integerValue_{}; bool booleanValue_{}; explicit Private(const common::Measure &valueIn) : type_(ParameterValue::Type::MEASURE), measure_(std::make_unique(valueIn)) {} Private(const std::string &stringValueIn, ParameterValue::Type typeIn) : type_(typeIn), stringValue_(std::make_unique(stringValueIn)) {} explicit Private(int integerValueIn) : type_(ParameterValue::Type::INTEGER), integerValue_(integerValueIn) {} explicit Private(bool booleanValueIn) : type_(ParameterValue::Type::BOOLEAN), booleanValue_(booleanValueIn) {} }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ParameterValue::~ParameterValue() = default; //! @endcond // --------------------------------------------------------------------------- ParameterValue::ParameterValue(const common::Measure &measureIn) : d(std::make_unique(measureIn)) {} // --------------------------------------------------------------------------- ParameterValue::ParameterValue(const std::string &stringValueIn, ParameterValue::Type typeIn) : d(std::make_unique(stringValueIn, typeIn)) {} // --------------------------------------------------------------------------- ParameterValue::ParameterValue(int integerValueIn) : d(std::make_unique(integerValueIn)) {} // --------------------------------------------------------------------------- ParameterValue::ParameterValue(bool booleanValueIn) : d(std::make_unique(booleanValueIn)) {} // --------------------------------------------------------------------------- /** \brief Instantiate a ParameterValue from a Measure (i.e. a value associated * with a * unit) * * @return a new ParameterValue. */ ParameterValueNNPtr ParameterValue::create(const common::Measure &measureIn) { return ParameterValue::nn_make_shared(measureIn); } // --------------------------------------------------------------------------- /** \brief Instantiate a ParameterValue from a string value. * * @return a new ParameterValue. */ ParameterValueNNPtr ParameterValue::create(const char *stringValueIn) { return ParameterValue::nn_make_shared( std::string(stringValueIn), ParameterValue::Type::STRING); } // --------------------------------------------------------------------------- /** \brief Instantiate a ParameterValue from a string value. * * @return a new ParameterValue. */ ParameterValueNNPtr ParameterValue::create(const std::string &stringValueIn) { return ParameterValue::nn_make_shared( stringValueIn, ParameterValue::Type::STRING); } // --------------------------------------------------------------------------- /** \brief Instantiate a ParameterValue from a filename. * * @return a new ParameterValue. */ ParameterValueNNPtr ParameterValue::createFilename(const std::string &stringValueIn) { return ParameterValue::nn_make_shared( stringValueIn, ParameterValue::Type::FILENAME); } // --------------------------------------------------------------------------- /** \brief Instantiate a ParameterValue from a integer value. * * @return a new ParameterValue. */ ParameterValueNNPtr ParameterValue::create(int integerValueIn) { return ParameterValue::nn_make_shared(integerValueIn); } // --------------------------------------------------------------------------- /** \brief Instantiate a ParameterValue from a boolean value. * * @return a new ParameterValue. */ ParameterValueNNPtr ParameterValue::create(bool booleanValueIn) { return ParameterValue::nn_make_shared(booleanValueIn); } // --------------------------------------------------------------------------- /** \brief Returns the type of a parameter value. * * @return the type. */ const ParameterValue::Type &ParameterValue::type() PROJ_PURE_DEFN { return d->type_; } // --------------------------------------------------------------------------- /** \brief Returns the value as a Measure (assumes type() == Type::MEASURE) * @return the value as a Measure. */ const common::Measure &ParameterValue::value() PROJ_PURE_DEFN { return *d->measure_; } // --------------------------------------------------------------------------- /** \brief Returns the value as a string (assumes type() == Type::STRING) * @return the value as a string. */ const std::string &ParameterValue::stringValue() PROJ_PURE_DEFN { return *d->stringValue_; } // --------------------------------------------------------------------------- /** \brief Returns the value as a filename (assumes type() == Type::FILENAME) * @return the value as a filename. */ const std::string &ParameterValue::valueFile() PROJ_PURE_DEFN { return *d->stringValue_; } // --------------------------------------------------------------------------- /** \brief Returns the value as a integer (assumes type() == Type::INTEGER) * @return the value as a integer. */ int ParameterValue::integerValue() PROJ_PURE_DEFN { return d->integerValue_; } // --------------------------------------------------------------------------- /** \brief Returns the value as a boolean (assumes type() == Type::BOOLEAN) * @return the value as a boolean. */ bool ParameterValue::booleanValue() PROJ_PURE_DEFN { return d->booleanValue_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ParameterValue::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const auto &l_type = type(); if (l_type == Type::MEASURE) { const auto &l_value = value(); if (formatter->abridgedTransformation()) { const auto &unit = l_value.unit(); const auto &unitType = unit.type(); if (unitType == common::UnitOfMeasure::Type::LINEAR) { formatter->add(l_value.getSIValue()); } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { formatter->add( l_value.convertToUnit(common::UnitOfMeasure::ARC_SECOND)); } else if (unit == common::UnitOfMeasure::PARTS_PER_MILLION) { formatter->add(1.0 + l_value.value() * 1e-6); } else { formatter->add(l_value.value()); } } else { const auto &unit = l_value.unit(); if (isWKT2) { formatter->add(l_value.value()); } else { // In WKT1, as we don't output the natural unit, output to the // registered linear / angular unit. const auto &unitType = unit.type(); if (unitType == common::UnitOfMeasure::Type::LINEAR) { const auto &targetUnit = *(formatter->axisLinearUnit()); if (targetUnit.conversionToSI() == 0.0) { throw io::FormattingException( "cannot convert value to target linear unit"); } formatter->add(l_value.convertToUnit(targetUnit)); } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { const auto &targetUnit = *(formatter->axisAngularUnit()); if (targetUnit.conversionToSI() == 0.0) { throw io::FormattingException( "cannot convert value to target angular unit"); } formatter->add(l_value.convertToUnit(targetUnit)); } else { formatter->add(l_value.getSIValue()); } } if (isWKT2 && unit != common::UnitOfMeasure::NONE) { if (!formatter ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() || (unit != common::UnitOfMeasure::SCALE_UNITY && unit != *(formatter->axisLinearUnit()) && unit != *(formatter->axisAngularUnit()))) { unit._exportToWKT(formatter); } } } } else if (l_type == Type::STRING || l_type == Type::FILENAME) { formatter->addQuotedString(stringValue()); } else if (l_type == Type::INTEGER) { formatter->add(integerValue()); } else { throw io::FormattingException("boolean parameter value not handled"); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool ParameterValue::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &) const { auto otherPV = dynamic_cast(other); if (otherPV == nullptr) { return false; } if (type() != otherPV->type()) { return false; } switch (type()) { case Type::MEASURE: { return value()._isEquivalentTo(otherPV->value(), criterion, 2e-10); } case Type::STRING: case Type::FILENAME: { return stringValue() == otherPV->stringValue(); } case Type::INTEGER: { return integerValue() == otherPV->integerValue(); } case Type::BOOLEAN: { return booleanValue() == otherPV->booleanValue(); } default: { assert(false); break; } } return true; } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/concatenatedoperation.cpp000664 001750 001750 00000122310 15166171715 023505 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/crs_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "coordinateoperation_internal.hpp" #include "oputils.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // M_PI // clang-format on #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ConcatenatedOperation::Private { std::vector operations_{}; bool computedName_ = false; explicit Private(const std::vector &operationsIn) : operations_(operationsIn) {} Private(const Private &) = default; }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ConcatenatedOperation::~ConcatenatedOperation() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ConcatenatedOperation::ConcatenatedOperation(const ConcatenatedOperation &other) : CoordinateOperation(other), d(std::make_unique(*(other.d))) {} //! @endcond // --------------------------------------------------------------------------- ConcatenatedOperation::ConcatenatedOperation( const std::vector &operationsIn) : CoordinateOperation(), d(std::make_unique(operationsIn)) { for (const auto &op : operationsIn) { if (op->requiresPerCoordinateInputTime()) { setRequiresPerCoordinateInputTime(true); break; } } } // --------------------------------------------------------------------------- /** \brief Return the operation steps of the concatenated operation. * * @return the operation steps. */ const std::vector & ConcatenatedOperation::operations() const { return d->operations_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool areCRSMoreOrLessEquivalent(const crs::CRS *a, const crs::CRS *b) { const auto &aIds = a->identifiers(); const auto &bIds = b->identifiers(); if (aIds.size() == 1 && bIds.size() == 1 && aIds[0]->code() == bIds[0]->code() && *aIds[0]->codeSpace() == *bIds[0]->codeSpace()) { return true; } if (a->_isEquivalentTo(b, util::IComparable::Criterion::EQUIVALENT)) { return true; } // This is for example for EPSG:10146 which is EPSG:9471 // (INAGeoid2020 v1 height) // to EPSG:20036 (INAGeoid2020 v2 height), but chains // EPSG:9629 (SRGI2013 to SRGI2013 + INAGeoid2020 v1 height (1)) // with EPSG:10145 (SRGI2013 to SRGI2013 + INAGeoid2020 v2 height (1)) const auto compoundA = dynamic_cast(a); const auto compoundB = dynamic_cast(b); if (compoundA && !compoundB) return areCRSMoreOrLessEquivalent( compoundA->componentReferenceSystems()[1].get(), b); else if (!compoundA && compoundB) return areCRSMoreOrLessEquivalent( a, compoundB->componentReferenceSystems()[1].get()); return false; } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a ConcatenatedOperation * * @param properties See \ref general_properties. At minimum the name should * be * defined. * @param operationsIn Vector of the CoordinateOperation steps. * @param accuracies Vector of positional accuracy (might be empty). * @return new Transformation. * @throws InvalidOperation if the object cannot be constructed. */ ConcatenatedOperationNNPtr ConcatenatedOperation::create( const util::PropertyMap &properties, const std::vector &operationsIn, const std::vector &accuracies) // throw InvalidOperation { if (operationsIn.size() < 2) { throw InvalidOperation( "ConcatenatedOperation must have at least 2 operations"); } crs::CRSPtr lastTargetCRS; crs::CRSPtr interpolationCRS; bool interpolationCRSValid = true; bool hasBallparkTransformation = false; for (size_t i = 0; i < operationsIn.size(); i++) { auto l_sourceCRS = operationsIn[i]->sourceCRS(); auto l_targetCRS = operationsIn[i]->targetCRS(); hasBallparkTransformation |= operationsIn[i]->hasBallparkTransformation(); if (interpolationCRSValid) { auto subOpInterpCRS = operationsIn[i]->interpolationCRS(); if (interpolationCRS == nullptr) interpolationCRS = std::move(subOpInterpCRS); else if (subOpInterpCRS == nullptr || !(subOpInterpCRS->isEquivalentTo( interpolationCRS.get(), util::IComparable::Criterion::EQUIVALENT))) { interpolationCRS = nullptr; interpolationCRSValid = false; } } if (l_sourceCRS == nullptr || l_targetCRS == nullptr) { throw InvalidOperation("At least one of the operation lacks a " "source and/or target CRS"); } if (i >= 1) { if (!areCRSMoreOrLessEquivalent(l_sourceCRS.get(), lastTargetCRS.get())) { #ifdef DEBUG_CONCATENATED_OPERATION std::cerr << "Step " << i - 1 << ": " << operationsIn[i - 1]->nameStr() << std::endl; std::cerr << "Step " << i << ": " << operationsIn[i]->nameStr() << std::endl; { auto f(io::WKTFormatter::create( io::WKTFormatter::Convention::WKT2_2019)); std::cerr << "Source CRS of step " << i << ":" << std::endl; std::cerr << l_sourceCRS->exportToWKT(f.get()) << std::endl; } { auto f(io::WKTFormatter::create( io::WKTFormatter::Convention::WKT2_2019)); std::cerr << "Target CRS of step " << i - 1 << ":" << std::endl; std::cerr << lastTargetCRS->exportToWKT(f.get()) << std::endl; } #endif throw InvalidOperation( "Inconsistent chaining of CRS in operations"); } } lastTargetCRS = std::move(l_targetCRS); } // When chaining VerticalCRS -> GeographicCRS -> VerticalCRS, use // GeographicCRS as the interpolationCRS const auto l_sourceCRS = NN_NO_CHECK(operationsIn[0]->sourceCRS()); const auto l_targetCRS = NN_NO_CHECK(operationsIn.back()->targetCRS()); if (operationsIn.size() == 2 && interpolationCRS == nullptr && dynamic_cast(l_sourceCRS.get()) != nullptr && dynamic_cast(l_targetCRS.get()) != nullptr) { const auto geog1 = dynamic_cast( operationsIn[0]->targetCRS().get()); const auto geog2 = dynamic_cast( operationsIn[1]->sourceCRS().get()); if (geog1 != nullptr && geog2 != nullptr && geog1->_isEquivalentTo(geog2, util::IComparable::Criterion::EQUIVALENT)) { interpolationCRS = operationsIn[0]->targetCRS(); } } auto op = ConcatenatedOperation::nn_make_shared( operationsIn); op->assignSelf(op); op->setProperties(properties); op->setHasBallparkTransformation(hasBallparkTransformation); op->setCRSs(l_sourceCRS, l_targetCRS, interpolationCRS); op->setAccuracies(accuracies); #ifdef DEBUG_CONCATENATED_OPERATION { auto f( io::WKTFormatter::create(io::WKTFormatter::Convention::WKT2_2019)); std::cerr << "ConcatenatedOperation::create()" << std::endl; std::cerr << op->exportToWKT(f.get()) << std::endl; } #endif return op; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- /* static */ void ConcatenatedOperation::setCRSsUpdateInverse(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { co->setCRSsUpdateInverse(sourceCRS, targetCRS, co->interpolationCRS()); } // --------------------------------------------------------------------------- void ConcatenatedOperation::fixSteps( const crs::CRSNNPtr &concatOpSourceCRS, const crs::CRSNNPtr &concatOpTargetCRS, std::vector &operationsInOut, const io::DatabaseContextPtr & /*dbContext*/, bool fixDirectionAllowed) { // Set of heuristics to assign CRS to steps, and possibly reverse them. const auto isGeographic = [](const crs::CRS *crs) -> bool { return dynamic_cast(crs) != nullptr; }; const auto isGeocentric = [](const crs::CRS *crs) -> bool { const auto geodCRS = dynamic_cast(crs); return (geodCRS && geodCRS->isGeocentric()); }; // Apply axis order reversal operation on first operation if needed // to set CRSs on it if (operationsInOut.size() >= 1) { auto &op = operationsInOut.front(); auto l_sourceCRS = op->sourceCRS(); auto l_targetCRS = op->targetCRS(); auto conv = dynamic_cast(op.get()); if (conv && !l_sourceCRS && !l_targetCRS && isAxisOrderReversal(conv->method()->getEPSGCode())) { auto reversedCRS = concatOpSourceCRS->applyAxisOrderReversal( NORMALIZED_AXIS_ORDER_SUFFIX_STR); setCRSsUpdateInverse(op.get(), concatOpSourceCRS, reversedCRS); } } // Apply axis order reversal operation on last operation if needed // to set CRSs on it if (operationsInOut.size() >= 2) { auto &op = operationsInOut.back(); auto l_sourceCRS = op->sourceCRS(); auto l_targetCRS = op->targetCRS(); auto conv = dynamic_cast(op.get()); if (conv && !l_sourceCRS && !l_targetCRS && isAxisOrderReversal(conv->method()->getEPSGCode())) { auto reversedCRS = concatOpTargetCRS->applyAxisOrderReversal( NORMALIZED_AXIS_ORDER_SUFFIX_STR); setCRSsUpdateInverse(op.get(), reversedCRS, concatOpTargetCRS); } } // If the first operation is a transformation whose target CRS matches the // source CRS of the concatenated operation, then reverse it. if (fixDirectionAllowed && operationsInOut.size() >= 2) { auto &op = operationsInOut.front(); auto l_sourceCRS = op->sourceCRS(); auto l_targetCRS = op->targetCRS(); if (l_sourceCRS && l_targetCRS && !areCRSMoreOrLessEquivalent(l_sourceCRS.get(), concatOpSourceCRS.get()) && areCRSMoreOrLessEquivalent(l_targetCRS.get(), concatOpSourceCRS.get())) { op = op->inverse(); } } // If the last operation is a transformation whose source CRS matches the // target CRS of the concatenated operation, then reverse it. if (fixDirectionAllowed && operationsInOut.size() >= 2) { auto &op = operationsInOut.back(); auto l_sourceCRS = op->sourceCRS(); auto l_targetCRS = op->targetCRS(); if (l_sourceCRS && l_targetCRS && !areCRSMoreOrLessEquivalent(l_targetCRS.get(), concatOpTargetCRS.get()) && areCRSMoreOrLessEquivalent(l_sourceCRS.get(), concatOpTargetCRS.get())) { op = op->inverse(); } } const auto extractDerivedCRS = [](const crs::CRS *crs) -> const crs::DerivedCRS * { auto derivedCRS = dynamic_cast(crs); if (derivedCRS) return derivedCRS; auto compoundCRS = dynamic_cast(crs); if (compoundCRS) { derivedCRS = dynamic_cast( compoundCRS->componentReferenceSystems().front().get()); if (derivedCRS) return derivedCRS; } return nullptr; }; for (size_t i = 0; i < operationsInOut.size(); ++i) { auto &op = operationsInOut[i]; auto l_sourceCRS = op->sourceCRS(); auto l_targetCRS = op->targetCRS(); auto conv = dynamic_cast(op.get()); if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) { if (auto derivedCRS = extractDerivedCRS(concatOpSourceCRS.get())) { if (i + 1 < operationsInOut.size()) { // use the sourceCRS of the next operation as our target CRS l_targetCRS = operationsInOut[i + 1]->sourceCRS(); // except if it looks like the next operation should // actually be reversed !!! if (l_targetCRS && !areCRSMoreOrLessEquivalent( l_targetCRS.get(), derivedCRS->baseCRS().get()) && operationsInOut[i + 1]->targetCRS() && areCRSMoreOrLessEquivalent( operationsInOut[i + 1]->targetCRS().get(), derivedCRS->baseCRS().get())) { l_targetCRS = operationsInOut[i + 1]->targetCRS(); } } if (!l_targetCRS) { l_targetCRS = derivedCRS->baseCRS().as_nullable(); } auto invConv = util::nn_dynamic_pointer_cast(op); auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); if (invConv) { setCRSsUpdateInverse(op.get(), concatOpSourceCRS, nn_targetCRS); } else if (fixDirectionAllowed) { op->setCRSs(nn_targetCRS, concatOpSourceCRS, nullptr); op = op->inverse(); } } else if (i + 1 < operationsInOut.size()) { /* coverity[copy_paste_error] */ l_targetCRS = operationsInOut[i + 1]->sourceCRS(); if (l_targetCRS) { setCRSsUpdateInverse(op.get(), concatOpSourceCRS, NN_NO_CHECK(l_targetCRS)); } } } else if (conv && i + 1 == operationsInOut.size() && !l_sourceCRS && !l_targetCRS) { auto derivedCRS = extractDerivedCRS(concatOpTargetCRS.get()); if (derivedCRS) { if (i >= 1) { // use the targetCRS of the previous operation as our source // CRS l_sourceCRS = operationsInOut[i - 1]->targetCRS(); // except if it looks like the previous operation should // actually be reversed !!! if (l_sourceCRS && !areCRSMoreOrLessEquivalent( l_sourceCRS.get(), derivedCRS->baseCRS().get()) && operationsInOut[i - 1]->sourceCRS() && areCRSMoreOrLessEquivalent( operationsInOut[i - 1]->sourceCRS().get(), derivedCRS->baseCRS().get())) { l_sourceCRS = operationsInOut[i - 1]->sourceCRS(); operationsInOut[i - 1] = operationsInOut[i - 1]->inverse(); } } if (!l_sourceCRS) { l_sourceCRS = derivedCRS->baseCRS().as_nullable(); } setCRSsUpdateInverse(op.get(), NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS); } else if (i >= 1) { l_sourceCRS = operationsInOut[i - 1]->targetCRS(); if (l_sourceCRS) { derivedCRS = extractDerivedCRS(l_sourceCRS.get()); if (fixDirectionAllowed && derivedCRS && conv->isEquivalentTo( derivedCRS->derivingConversion().get(), util::IComparable::Criterion::EQUIVALENT)) { op = op->inverse(); } setCRSsUpdateInverse(op.get(), NN_NO_CHECK(l_sourceCRS), concatOpTargetCRS); } } } else if (conv && i > 0 && i < operationsInOut.size() - 1) { l_sourceCRS = operationsInOut[i - 1]->targetCRS(); l_targetCRS = operationsInOut[i + 1]->sourceCRS(); // For an intermediate conversion, use the target CRS of the // previous step and the source CRS of the next step if (l_sourceCRS && l_targetCRS) { // If the sourceCRS is a projectedCRS and the target a // geographic one, then we must inverse the operation. See // https://github.com/OSGeo/PROJ/issues/2817 if (fixDirectionAllowed && dynamic_cast( l_sourceCRS.get()) && dynamic_cast( l_targetCRS.get())) { op = op->inverse(); setCRSsUpdateInverse(op.get(), NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS)); } else { setCRSsUpdateInverse(op.get(), NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS)); // Deal with special case of // https://github.com/OSGeo/PROJ/issues/4116 where EPSG:7989 // -- NAVD88 height to NAVD88 depth conversion is chained // with "NAD83(FBN)+LMSL to NAD83(FBN)+NAVD88 depth" The // latter must thus be inversed const auto nPosTo = conv->nameStr().find(" to "); const auto nPosToNextOp = operationsInOut[i + 1]->nameStr().find(" to "); if (fixDirectionAllowed && nPosTo != std::string::npos && nPosToNextOp != std::string::npos) { const std::string convTo = conv->nameStr().substr(nPosTo + strlen(" to ")); const std::string nextOpFrom = operationsInOut[i + 1]->nameStr().substr( 0, nPosToNextOp); const std::string nextOpTo = operationsInOut[i + 1]->nameStr().substr( nPosToNextOp + strlen(" to ")); if (nextOpTo.find(convTo) != std::string::npos && nextOpFrom.find(convTo) == std::string::npos && operationsInOut[i + 1]->sourceCRS()) { operationsInOut[i + 1] = operationsInOut[i + 1]->inverse(); setCRSsUpdateInverse( op.get(), NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK( operationsInOut[i + 1]->sourceCRS())); } } } } else if (l_sourceCRS && l_targetCRS == nullptr && conv->method()->getEPSGCode() == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { // Needed for EPSG:7987 e.g. auto vertCRS = dynamic_cast(l_sourceCRS.get()); if (vertCRS && ends_with(l_sourceCRS->nameStr(), " height") && &vertCRS->coordinateSystem()->axisList()[0]->direction() == &cs::AxisDirection::UP) { setCRSsUpdateInverse( op.get(), NN_NO_CHECK(l_sourceCRS), crs::VerticalCRS::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, l_sourceCRS->nameStr().substr( 0, l_sourceCRS->nameStr().size() - strlen(" height")) + " depth"), vertCRS->datum(), vertCRS->datumEnsemble(), cs::VerticalCS::create( util::PropertyMap(), cs::CoordinateSystemAxis::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "Gravity-related depth"), "D", cs::AxisDirection::DOWN, vertCRS->coordinateSystem() ->axisList()[0] ->unit())))); } } } else if (!conv && l_sourceCRS && l_targetCRS) { // Transformations might be mentioned in their forward directions, // whereas we should instead use the reverse path. auto prevOpTarget = (i == 0) ? concatOpSourceCRS.as_nullable() : operationsInOut[i - 1]->targetCRS(); if (prevOpTarget == nullptr) { throw InvalidOperation( "Cannot determine targetCRS of operation at step " + toString(static_cast(i))); } if (areCRSMoreOrLessEquivalent(l_sourceCRS.get(), prevOpTarget.get())) { // do nothing } else if (fixDirectionAllowed && areCRSMoreOrLessEquivalent(l_targetCRS.get(), prevOpTarget.get())) { op = op->inverse(); } // Below is needed for EPSG:9103 which chains NAD83(2011) geographic // 2D with NAD83(2011) geocentric else if (l_sourceCRS->nameStr() == prevOpTarget->nameStr() && ((isGeographic(l_sourceCRS.get()) && isGeocentric(prevOpTarget.get())) || (isGeocentric(l_sourceCRS.get()) && isGeographic(prevOpTarget.get())))) { auto newOp(Conversion::createGeographicGeocentric( NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_sourceCRS))); operationsInOut.insert(operationsInOut.begin() + i, newOp); } else if (l_targetCRS->nameStr() == prevOpTarget->nameStr() && ((isGeographic(l_targetCRS.get()) && isGeocentric(prevOpTarget.get())) || (isGeocentric(l_targetCRS.get()) && isGeographic(prevOpTarget.get())))) { auto newOp(Conversion::createGeographicGeocentric( NN_NO_CHECK(prevOpTarget), NN_NO_CHECK(l_targetCRS))); operationsInOut.insert(operationsInOut.begin() + i, newOp); // Particular case for https://github.com/OSGeo/PROJ/issues/3819 // where the antepenultimate transformation goes to A // (geographic 3D) // and the last transformation is a NADCON 3D // but between A (geographic 2D) to B (geographic 2D), and the // concatenated transformation target CRS is B (geographic 3D) // This is due to an oddity of the EPSG database that registers // the NADCON 3D transformation between the 2D geographic CRS // and not the 3D ones. } else if (i + 1 == operationsInOut.size() && l_sourceCRS->nameStr() == prevOpTarget->nameStr() && l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() && isGeographic(l_targetCRS.get()) && isGeographic(concatOpTargetCRS.get()) && isGeographic(l_sourceCRS.get()) && isGeographic(prevOpTarget.get()) && dynamic_cast( prevOpTarget.get()) ->coordinateSystem() ->axisList() .size() == 3 && dynamic_cast( l_sourceCRS.get()) ->coordinateSystem() ->axisList() .size() == 2 && dynamic_cast( l_targetCRS.get()) ->coordinateSystem() ->axisList() .size() == 2) { const auto transf = dynamic_cast(op.get()); if (transf && (transf->method()->getEPSGCode() == EPSG_CODE_METHOD_NADCON5_3D || transf->parameterValue( PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE, 0))) { setCRSsUpdateInverse(op.get(), NN_NO_CHECK(prevOpTarget), concatOpTargetCRS); } } } } if (!operationsInOut.empty()) { auto l_sourceCRS = operationsInOut.front()->sourceCRS(); if (l_sourceCRS && !areCRSMoreOrLessEquivalent( l_sourceCRS.get(), concatOpSourceCRS.get())) { throw InvalidOperation("The source CRS of the first step of " "concatenated operation is not the same " "as the source CRS of the concatenated " "operation itself"); } auto l_targetCRS = operationsInOut.back()->targetCRS(); if (l_targetCRS && !areCRSMoreOrLessEquivalent( l_targetCRS.get(), concatOpTargetCRS.get())) { if (l_targetCRS->nameStr() == concatOpTargetCRS->nameStr() && ((isGeographic(l_targetCRS.get()) && isGeocentric(concatOpTargetCRS.get())) || (isGeocentric(l_targetCRS.get()) && isGeographic(concatOpTargetCRS.get())))) { auto newOp(Conversion::createGeographicGeocentric( NN_NO_CHECK(l_targetCRS), concatOpTargetCRS)); operationsInOut.push_back(newOp); } else { throw InvalidOperation("The target CRS of the last step of " "concatenated operation is not the same " "as the target CRS of the concatenated " "operation itself"); } } } } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a ConcatenatedOperation, or return a single * coordinate * operation. * * This computes its accuracy from the sum of its member operations, its * extent * * @param operationsIn Vector of the CoordinateOperation steps. * @param checkExtent Whether we should check the non-emptiness of the * intersection * of the extents of the operations * @throws InvalidOperation if the object cannot be constructed. */ CoordinateOperationNNPtr ConcatenatedOperation::createComputeMetadata( const std::vector &operationsIn, bool checkExtent) // throw InvalidOperation { util::PropertyMap properties; if (operationsIn.size() == 1) { return operationsIn[0]; } std::vector flattenOps; bool hasBallparkTransformation = false; for (const auto &subOp : operationsIn) { hasBallparkTransformation |= subOp->hasBallparkTransformation(); auto subOpConcat = dynamic_cast(subOp.get()); if (subOpConcat) { auto subOps = subOpConcat->operations(); for (const auto &subSubOp : subOps) { flattenOps.emplace_back(subSubOp); } } else { flattenOps.emplace_back(subOp); } } // Remove consecutive inverse operations if (flattenOps.size() > 2) { std::vector indices; for (size_t i = 0; i < flattenOps.size(); ++i) indices.push_back(i); while (true) { bool bHasChanged = false; for (size_t i = 0; i + 1 < indices.size(); ++i) { if (flattenOps[indices[i]]->_isEquivalentTo( flattenOps[indices[i + 1]]->inverse().get(), util::IComparable::Criterion::EQUIVALENT) && flattenOps[indices[i]]->sourceCRS()->_isEquivalentTo( flattenOps[indices[i + 1]]->targetCRS().get(), util::IComparable::Criterion::EQUIVALENT)) { indices.erase(indices.begin() + i, indices.begin() + i + 2); bHasChanged = true; break; } } // We bail out if indices.size() == 2, because potentially // the last 2 remaining ones could auto-cancel, and we would have // to have a special case for that (and this happens in practice). if (!bHasChanged || indices.size() <= 2) break; } if (indices.size() < flattenOps.size()) { std::vector flattenOpsNew; for (size_t i = 0; i < indices.size(); ++i) { flattenOpsNew.emplace_back(flattenOps[indices[i]]); } flattenOps = std::move(flattenOpsNew); } } if (flattenOps.size() == 1) { return flattenOps[0]; } properties.set(common::IdentifiedObject::NAME_KEY, computeConcatenatedName(flattenOps)); bool emptyIntersection = false; auto extent = getExtent(flattenOps, false, emptyIntersection); if (checkExtent && emptyIntersection) { std::string msg( "empty intersection of area of validity of concatenated " "operations"); throw InvalidOperationEmptyIntersection(msg); } if (extent) { properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } std::vector accuracies; const double accuracy = getAccuracy(flattenOps); if (accuracy >= 0.0) { accuracies.emplace_back( metadata::PositionalAccuracy::create(toString(accuracy))); } auto op = create(properties, flattenOps, accuracies); op->setHasBallparkTransformation(hasBallparkTransformation); op->d->computedName_ = true; return op; } // --------------------------------------------------------------------------- CoordinateOperationNNPtr ConcatenatedOperation::inverse() const { std::vector inversedOperations; auto l_operations = operations(); inversedOperations.reserve(l_operations.size()); for (const auto &operation : l_operations) { inversedOperations.emplace_back(operation->inverse()); } std::reverse(inversedOperations.begin(), inversedOperations.end()); auto properties = createPropertiesForInverse(this, false, false); if (d->computedName_) { properties.set(common::IdentifiedObject::NAME_KEY, computeConcatenatedName(inversedOperations)); } auto op = create(properties, inversedOperations, coordinateOperationAccuracies()); op->d->computedName_ = d->computedName_; op->setHasBallparkTransformation(hasBallparkTransformation()); op->setSourceCoordinateEpoch(targetCoordinateEpoch()); op->setTargetCoordinateEpoch(sourceCoordinateEpoch()); return op; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2 || !formatter->use2019Keywords()) { throw io::FormattingException( "Transformation can only be exported to WKT2:2019"); } formatter->startNode(io::WKTConstants::CONCATENATEDOPERATION, !identifiers().empty()); formatter->addQuotedString(nameStr()); if (formatter->use2019Keywords()) { const auto &version = operationVersion(); if (version.has_value()) { formatter->startNode(io::WKTConstants::VERSION, false); formatter->addQuotedString(*version); formatter->endNode(); } } exportSourceCRSAndTargetCRSToWKT(this, formatter); const bool canExportOperationId = !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()); const bool hasDomains = !domains().empty(); if (hasDomains) { formatter->pushDisableUsage(); } for (const auto &operation : operations()) { formatter->startNode(io::WKTConstants::STEP, false); if (canExportOperationId && !operation->identifiers().empty()) { // fake that top node has no id, so that the operation id is // considered formatter->pushHasId(false); operation->_exportToWKT(formatter); formatter->popHasId(); } else { operation->_exportToWKT(formatter); } formatter->endNode(); } if (hasDomains) { formatter->popDisableUsage(); } if (!coordinateOperationAccuracies().empty()) { formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); formatter->add(coordinateOperationAccuracies()[0]->value()); formatter->endNode(); } ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ConcatenatedOperation::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("source_crs"); formatter->setAllowIDInImmediateChild(); sourceCRS()->_exportToJSON(formatter); writer->AddObjKey("target_crs"); formatter->setAllowIDInImmediateChild(); targetCRS()->_exportToJSON(formatter); writer->AddObjKey("steps"); { auto parametersContext(writer->MakeArrayContext(false)); for (const auto &operation : operations()) { formatter->setAllowIDInImmediateChild(); operation->_exportToJSON(formatter); } } if (!coordinateOperationAccuracies().empty()) { writer->AddObjKey("accuracy"); writer->Add(coordinateOperationAccuracies()[0]->value()); } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const { auto op = ConcatenatedOperation::nn_make_shared(*this); std::vector ops; for (const auto &subOp : d->operations_) { ops.emplace_back(subOp->shallowClone()); } op->d->operations_ = std::move(ops); op->assignSelf(op); op->setCRSs(this, false); return util::nn_static_pointer_cast(op); } //! @endcond // --------------------------------------------------------------------------- void ConcatenatedOperation::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(FormattingException) { double sourceYear = sourceCoordinateEpoch().has_value() ? getRoundedEpochInDecimalYear( sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)) : 0; double targetYear = targetCoordinateEpoch().has_value() ? getRoundedEpochInDecimalYear( targetCoordinateEpoch()->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR)) : 0; if (sourceYear > 0 && targetYear == 0) targetYear = sourceYear; else if (targetYear > 0 && sourceYear == 0) sourceYear = targetYear; if (sourceYear > 0) { formatter->addStep("set"); formatter->addParam("v_4", sourceYear); } for (const auto &operation : operations()) { operation->_exportToPROJString(formatter); } if (targetYear > 0) { formatter->addStep("set"); formatter->addParam("v_4", targetYear); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool ConcatenatedOperation::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherCO = dynamic_cast(other); if (otherCO == nullptr || (criterion == util::IComparable::Criterion::STRICT && !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto &steps = operations(); const auto &otherSteps = otherCO->operations(); if (steps.size() != otherSteps.size()) { return false; } for (size_t i = 0; i < steps.size(); i++) { if (!steps[i]->_isEquivalentTo(otherSteps[i].get(), criterion, dbContext)) { return false; } } return true; } //! @endcond // --------------------------------------------------------------------------- std::set ConcatenatedOperation::gridsNeeded( const io::DatabaseContextPtr &databaseContext, bool considerKnownGridsAsAvailable) const { std::set res; for (const auto &operation : operations()) { const auto l_gridsNeeded = operation->gridsNeeded( databaseContext, considerKnownGridsAsAvailable); for (const auto &gridDesc : l_gridsNeeded) { res.insert(gridDesc); } } return res; } // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/conversion.cpp000664 001750 001750 00000620353 15166171715 021333 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/tracing.hpp" #include "coordinateoperation_internal.hpp" #include "esriparammappings.hpp" #include "operationmethod_private.hpp" #include "oputils.hpp" #include "parammappings.hpp" #include "vectorofvaluesparams.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // M_PI // clang-format on #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { //! @cond Doxygen_Suppress constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0; constexpr double UTM_SCALE_FACTOR = 0.9996; constexpr double UTM_FALSE_EASTING = 500000.0; constexpr double UTM_NORTH_FALSE_NORTHING = 0.0; constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Conversion::Private {}; //! @endcond // --------------------------------------------------------------------------- Conversion::Conversion(const OperationMethodNNPtr &methodIn, const std::vector &values) : SingleOperation(methodIn), d(nullptr) { setParameterValues(values); } // --------------------------------------------------------------------------- Conversion::Conversion(const Conversion &other) : CoordinateOperation(other), SingleOperation(other), d(nullptr) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Conversion::~Conversion() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ConversionNNPtr Conversion::shallowClone() const { auto conv = Conversion::nn_make_shared(*this); conv->assignSelf(conv); conv->setCRSs(this, false); return conv; } CoordinateOperationNNPtr Conversion::_shallowClone() const { return util::nn_static_pointer_cast(shallowClone()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ConversionNNPtr Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit, bool convertToNewUnit) const { std::vector newValues; bool changesDone = false; for (const auto &genOpParamvalue : parameterValues()) { bool updated = false; auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶mValue = opParamvalue->parameterValue(); if (paramValue->type() == ParameterValue::Type::MEASURE) { const auto &measure = paramValue->value(); if (measure.unit().type() == common::UnitOfMeasure::Type::LINEAR) { if (!measure.unit()._isEquivalentTo( unit, util::IComparable::Criterion::EQUIVALENT)) { const double newValue = convertToNewUnit ? measure.convertToUnit(unit) : measure.value(); newValues.emplace_back(OperationParameterValue::create( opParamvalue->parameter(), ParameterValue::create( common::Measure(newValue, unit)))); updated = true; } } } } if (updated) { changesDone = true; } else { newValues.emplace_back(genOpParamvalue); } } if (changesDone) { auto conv = create(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "unknown"), method(), newValues); conv->setCRSs(this, false); return conv; } else { return NN_NO_CHECK( util::nn_dynamic_pointer_cast(shared_from_this())); } } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a Conversion from a vector of GeneralParameterValue. * * @param properties See \ref general_properties. At minimum the name should be * defined. * @param methodIn the operation method. * @param values the values. * @return a new Conversion. * @throws InvalidOperation if the object cannot be constructed. */ ConversionNNPtr Conversion::create(const util::PropertyMap &properties, const OperationMethodNNPtr &methodIn, const std::vector &values) // throw InvalidOperation { if (methodIn->parameters().size() != values.size()) { throw InvalidOperation( "Inconsistent number of parameters and parameter values"); } auto conv = Conversion::nn_make_shared(methodIn, values); conv->assignSelf(conv); conv->setProperties(properties); return conv; } // --------------------------------------------------------------------------- /** \brief Instantiate a Conversion and its OperationMethod * * @param propertiesConversion See \ref general_properties of the conversion. * At minimum the name should be defined. * @param propertiesOperationMethod See \ref general_properties of the operation * method. At minimum the name should be defined. * @param parameters the operation parameters. * @param values the operation values. Constraint: * values.size() == parameters.size() * @return a new Conversion. * @throws InvalidOperation if the object cannot be constructed. */ ConversionNNPtr Conversion::create( const util::PropertyMap &propertiesConversion, const util::PropertyMap &propertiesOperationMethod, const std::vector ¶meters, const std::vector &values) // throw InvalidOperation { OperationMethodNNPtr op( OperationMethod::create(propertiesOperationMethod, parameters)); if (parameters.size() != values.size()) { throw InvalidOperation( "Inconsistent number of parameters and parameter values"); } std::vector generalParameterValues; generalParameterValues.reserve(values.size()); for (size_t i = 0; i < values.size(); i++) { generalParameterValues.push_back( OperationParameterValue::create(parameters[i], values[i])); } return create(propertiesConversion, op, generalParameterValues); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- static util::PropertyMap getUTMConversionProperty(const util::PropertyMap &properties, int zone, bool north) { if (!properties.get(common::IdentifiedObject::NAME_KEY)) { std::string conversionName("UTM zone "); conversionName += toString(zone); conversionName += (north ? 'N' : 'S'); return createMapNameEPSGCode(conversionName, (north ? 16000 : 16100) + zone); } else { return properties; } } // --------------------------------------------------------------------------- static ConversionNNPtr createConversion(const util::PropertyMap &properties, const MethodMapping *mapping, const std::vector &values) { std::vector parameters; for (int i = 0; mapping->params != nullptr && mapping->params[i] != nullptr; i++) { const auto *param = mapping->params[i]; auto paramProperties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, param->wkt2_name); if (param->epsg_code != 0) { paramProperties .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, param->epsg_code); } parameters.push_back(OperationParameter::create(paramProperties)); } auto methodProperties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, mapping->wkt2_name); if (mapping->epsg_code != 0) { methodProperties .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, mapping->epsg_code); } return Conversion::create( addDefaultNameIfNeeded(properties, mapping->wkt2_name), methodProperties, parameters, values); } //! @endcond // --------------------------------------------------------------------------- ConversionNNPtr Conversion::create(const util::PropertyMap &properties, int method_epsg_code, const std::vector &values) { const MethodMapping *mapping = getMapping(method_epsg_code); assert(mapping); return createConversion(properties, mapping, values); } // --------------------------------------------------------------------------- ConversionNNPtr Conversion::create(const util::PropertyMap &properties, const char *method_wkt2_name, const std::vector &values) { const MethodMapping *mapping = getMapping(method_wkt2_name); assert(mapping); return createConversion(properties, mapping, values); } // --------------------------------------------------------------------------- /** \brief Instantiate a * * Universal Transverse Mercator conversion. * * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the * northern hemisphere, and 17001 to 17060 for the southern hemisphere, * based on the Transverse Mercator projection method. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param zone UTM zone number between 1 and 60. * @param north true for UTM northern hemisphere, false for UTM southern * hemisphere. * @return a new Conversion. */ ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties, int zone, bool north) { if (zone < 1 || zone > 60) { throw InvalidOperation("Invalid zone number"); } return create( getUTMConversionProperty(properties, zone, north), EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN), common::Angle(zone * 6.0 - 183.0), common::Scale(UTM_SCALE_FACTOR), common::Length(UTM_FALSE_EASTING), common::Length(north ? UTM_NORTH_FALSE_NORTHING : UTM_SOUTH_FALSE_NORTHING))); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Transverse Mercator projection method. * * This method is defined as * * EPSG:9807. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createTransverseMercator( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Gauss Schreiber Transverse Mercator projection method. * * This method is also known as Gauss-Laborde Reunion. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Transverse Mercator South Orientated projection method. * * This method is defined as * * EPSG:9808. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createTransverseMercatorSouthOriented( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Two Point Equidistant projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFirstPoint Latitude of first point. * @param longitudeFirstPoint Longitude of first point. * @param latitudeSecondPoint Latitude of second point. * @param longitudeSeconPoint Longitude of second point. * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createTwoPointEquidistant(const util::PropertyMap &properties, const common::Angle &latitudeFirstPoint, const common::Angle &longitudeFirstPoint, const common::Angle &latitudeSecondPoint, const common::Angle &longitudeSeconPoint, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, createParams(latitudeFirstPoint, longitudeFirstPoint, latitudeSecondPoint, longitudeSeconPoint, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Tunisia Mapping Grid projection * method. * * This method is defined as * * EPSG:9816. * * \note There is currently no implementation of the method formulas in PROJ. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. * @deprecated. Use createTunisiaMiningGrid() instead */ ConversionNNPtr Conversion::createTunisiaMappingGrid( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_TUNISIA_MINING_GRID, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Tunisia Mining Grid projection * method. * * This method is defined as * * EPSG:9816. * * \note There is currently no implementation of the method formulas in PROJ. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. * @since 9.2 */ ConversionNNPtr Conversion::createTunisiaMiningGrid( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_TUNISIA_MINING_GRID, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Albers Conic Equal Area projection method. * * This method is defined as * * EPSG:9822. * * @note the order of arguments is conformant with the corresponding EPSG * mode and different than OGRSpatialReference::setACEA() of GDAL <= 2.3 * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFalseOrigin See \ref latitude_false_origin * @param longitudeFalseOrigin See \ref longitude_false_origin * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param latitudeSecondParallel See \ref latitude_second_std_parallel * @param eastingFalseOrigin See \ref easting_false_origin * @param northingFalseOrigin See \ref northing_false_origin * @return a new Conversion. */ ConversionNNPtr Conversion::createAlbersEqualArea(const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin) { return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, createParams(latitudeFalseOrigin, longitudeFalseOrigin, latitudeFirstParallel, latitudeSecondParallel, eastingFalseOrigin, northingFalseOrigin)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Conic Conformal 1SP projection method. * * This method is defined as * * EPSG:9801. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertConicConformal_1SP( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Conic Conformal 1SP Variant B projection method. * * This method is defined as * * EPSG:1102. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeNatOrigin See \ref center_latitude * @param scale See \ref scale * @param latitudeFalseOrigin See \ref latitude_false_origin * @param longitudeFalseOrigin See \ref longitude_false_origin * @param eastingFalseOrigin See \ref easting_false_origin * @param northingFalseOrigin See \ref northing_false_origin * @return a new Conversion. * @since 9.2.1 */ ConversionNNPtr Conversion::createLambertConicConformal_1SP_VariantB( const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Scale &scale, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B, createParams(latitudeNatOrigin, scale, latitudeFalseOrigin, longitudeFalseOrigin, eastingFalseOrigin, northingFalseOrigin)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Conic Conformal 2SP projection method. * * This method is defined as * * EPSG:9802. * * @note the order of arguments is conformant with the corresponding EPSG * mode and different than OGRSpatialReference::setLCC() of GDAL <= 2.3 * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFalseOrigin See \ref latitude_false_origin * @param longitudeFalseOrigin See \ref longitude_false_origin * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param latitudeSecondParallel See \ref latitude_second_std_parallel * @param eastingFalseOrigin See \ref easting_false_origin * @param northingFalseOrigin See \ref northing_false_origin * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertConicConformal_2SP( const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, createParams(latitudeFalseOrigin, longitudeFalseOrigin, latitudeFirstParallel, latitudeSecondParallel, eastingFalseOrigin, northingFalseOrigin)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Conic Conformal (2SP Michigan) projection method. * * This method is defined as * * EPSG:1051. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFalseOrigin See \ref latitude_false_origin * @param longitudeFalseOrigin See \ref longitude_false_origin * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param latitudeSecondParallel See \ref latitude_second_std_parallel * @param eastingFalseOrigin See \ref easting_false_origin * @param northingFalseOrigin See \ref northing_false_origin * @param ellipsoidScalingFactor Ellipsoid scaling factor. * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan( const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin, const common::Scale &ellipsoidScalingFactor) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, createParams(latitudeFalseOrigin, longitudeFalseOrigin, latitudeFirstParallel, latitudeSecondParallel, eastingFalseOrigin, northingFalseOrigin, ellipsoidScalingFactor)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Conic Conformal (2SP Belgium) projection method. * * This method is defined as * * EPSG:9803. * * \warning The formulas used currently in PROJ are, incorrectly, the ones of * the regular LCC_2SP method. * * @note the order of arguments is conformant with the corresponding EPSG * mode and different than OGRSpatialReference::setLCCB() of GDAL <= 2.3 * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFalseOrigin See \ref latitude_false_origin * @param longitudeFalseOrigin See \ref longitude_false_origin * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param latitudeSecondParallel See \ref latitude_second_std_parallel * @param eastingFalseOrigin See \ref easting_false_origin * @param northingFalseOrigin See \ref northing_false_origin * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium( const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, createParams(latitudeFalseOrigin, longitudeFalseOrigin, latitudeFirstParallel, latitudeSecondParallel, eastingFalseOrigin, northingFalseOrigin)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Azimuthal Equidistant projection method. * * This method is defined as * * EPSG:1125. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeNatOrigin See \ref center_latitude * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createAzimuthalEquidistant( const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_AZIMUTHAL_EQUIDISTANT, createParams(latitudeNatOrigin, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Guam Projection method. * * This method is defined as * * EPSG:9831. * * @param properties See \ref general_properties of the conversion. If the name *is * not provided, it is automatically set. * @param latitudeNatOrigin See \ref center_latitude * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGuamProjection( const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION, createParams(latitudeNatOrigin, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Bonne projection method. * * This method is defined as * * EPSG:9827. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the * standard parallel 1. * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_BONNE, createParams(latitudeNatOrigin, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Cylindrical Equal Area (Spherical) projection method. * * This method is defined as * * EPSG:9834. * * \warning The PROJ cea computation code would select the ellipsoidal form if * a non-spherical ellipsoid is used for the base GeographicCRS. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFirstParallel See \ref latitude_first_std_parallel. * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, createParams(latitudeFirstParallel, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Cylindrical Equal Area (ellipsoidal form) projection method. * * This method is defined as * * EPSG:9835. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFirstParallel See \ref latitude_first_std_parallel. * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertCylindricalEqualArea( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, createParams(latitudeFirstParallel, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Cassini-Soldner projection method. * * This method is defined as * * EPSG:9806. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createCassiniSoldner( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_CASSINI_SOLDNER, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Equidistant Conic projection method. * * This method is defined as * * EPSG:1119. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFalseOrigin See \ref latitude_false_origin * @param longitudeFalseOrigin See \ref longitude_false_origin * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param latitudeSecondParallel See \ref latitude_second_std_parallel * @param eastingFalseOrigin See \ref easting_false_origin * @param northingFalseOrigin See \ref northing_false_origin * @return a new Conversion. */ ConversionNNPtr Conversion::createEquidistantConic(const util::PropertyMap &properties, const common::Angle &latitudeFalseOrigin, const common::Angle &longitudeFalseOrigin, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &eastingFalseOrigin, const common::Length &northingFalseOrigin) { return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CONIC, createParams(latitudeFalseOrigin, longitudeFalseOrigin, latitudeFirstParallel, latitudeSecondParallel, eastingFalseOrigin, northingFalseOrigin)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Eckert I projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Eckert II projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEckertII( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Eckert III projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEckertIII( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Eckert IV projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEckertIV( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Eckert V projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Eckert VI projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEckertVI( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Equidistant Cylindrical projection method. * * This is also known as the Equirectangular method, and in the particular case * where the latitude of first parallel is 0. * * This method is defined as * * EPSG:1028. * * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, * where the lat_0 / center_latitude parameter is forced to 0. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFirstParallel See \ref latitude_first_std_parallel. * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEquidistantCylindrical( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Equidistant Cylindrical (Spherical) projection method. * * This is also known as the Equirectangular method, and in the particular case * where the latitude of first parallel is 0. * * This method is defined as * * EPSG:1029. * * @note This is the equivalent OGRSpatialReference::SetEquirectangular2( * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL <= 2.3, * where the lat_0 / center_latitude parameter is forced to 0. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFirstParallel See \ref latitude_first_std_parallel. * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEquidistantCylindricalSpherical( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Gall (Stereographic) projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Goode Homolosine projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGoodeHomolosine( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Interrupted Goode Homolosine projection method. * * There is no equivalent in EPSG. * * @note OGRSpatialReference::SetIGH() of GDAL <= 2.3 assumes the 3 * projection * parameters to be zero and this is the nominal case. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createInterruptedGoodeHomolosine( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Geostationary Satellite View projection method, * with the sweep angle axis of the viewing instrument being x * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param height Height of the view point above the Earth. * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGeostationarySatelliteSweepX( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &height, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, createParams(centerLong, height, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Geostationary Satellite View projection method, * with the sweep angle axis of the viewing instrument being y. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param height Height of the view point above the Earth. * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGeostationarySatelliteSweepY( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &height, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, createParams(centerLong, height, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Gnomonic projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createGnomonic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, PROJ_WKT2_NAME_METHOD_GNOMONIC, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Hotine Oblique Mercator (Variant A) projection method. * * This is the variant with the no_uoff parameter, which corresponds to * GDAL >=2.3 Hotine_Oblique_Mercator projection. * In this variant, the false grid coordinates are * defined at the intersection of the initial line and the aposphere (the * equator on one of the intermediate surfaces inherent in the method), that is * at the natural origin of the coordinate system). * * This method is defined as * * EPSG:9812. * * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = * 90deg, this maps to the * * Swiss Oblique Mercator formulas. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeProjectionCentre See \ref latitude_projection_centre * @param longitudeProjectionCentre See \ref longitude_projection_centre * @param azimuthInitialLine See \ref azimuth_initial_line * @param angleFromRectifiedToSkrewGrid See * \ref angle_from_recitfied_to_skrew_grid * @param scale See \ref scale_factor_initial_line * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeProjectionCentre, const common::Angle &azimuthInitialLine, const common::Angle &angleFromRectifiedToSkrewGrid, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, createParams(latitudeProjectionCentre, longitudeProjectionCentre, azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Hotine Oblique Mercator (Variant B) projection method. * * This is the variant without the no_uoff parameter, which corresponds to * GDAL >=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection. * In this variant, the false grid coordinates are defined at the projection *centre. * * This method is defined as * * EPSG:9815. * * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid = * 90deg, this maps to the * * Swiss Oblique Mercator formulas. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeProjectionCentre See \ref latitude_projection_centre * @param longitudeProjectionCentre See \ref longitude_projection_centre * @param azimuthInitialLine See \ref azimuth_initial_line * @param angleFromRectifiedToSkrewGrid See * \ref angle_from_recitfied_to_skrew_grid * @param scale See \ref scale_factor_initial_line * @param eastingProjectionCentre See \ref easting_projection_centre * @param northingProjectionCentre See \ref northing_projection_centre * @return a new Conversion. */ ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeProjectionCentre, const common::Angle &azimuthInitialLine, const common::Angle &angleFromRectifiedToSkrewGrid, const common::Scale &scale, const common::Length &eastingProjectionCentre, const common::Length &northingProjectionCentre) { return create( properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, createParams(latitudeProjectionCentre, longitudeProjectionCentre, azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale, eastingProjectionCentre, northingProjectionCentre)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Hotine Oblique Mercator Two Point Natural Origin projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeProjectionCentre See \ref latitude_projection_centre * @param latitudePoint1 Latitude of point 1. * @param longitudePoint1 Latitude of point 1. * @param latitudePoint2 Latitude of point 2. * @param longitudePoint2 Longitude of point 2. * @param scale See \ref scale_factor_initial_line * @param eastingProjectionCentre See \ref easting_projection_centre * @param northingProjectionCentre See \ref northing_projection_centre * @return a new Conversion. */ ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &latitudePoint1, const common::Angle &longitudePoint1, const common::Angle &latitudePoint2, const common::Angle &longitudePoint2, const common::Scale &scale, const common::Length &eastingProjectionCentre, const common::Length &northingProjectionCentre) { return create( properties, PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, { ParameterValue::create(latitudeProjectionCentre), ParameterValue::create(latitudePoint1), ParameterValue::create(longitudePoint1), ParameterValue::create(latitudePoint2), ParameterValue::create(longitudePoint2), ParameterValue::create(scale), ParameterValue::create(eastingProjectionCentre), ParameterValue::create(northingProjectionCentre), }); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Laborde Oblique Mercator projection method. * * This method is defined as * * EPSG:9813. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeProjectionCentre See \ref latitude_projection_centre * @param longitudeProjectionCentre See \ref longitude_projection_centre * @param azimuthInitialLine See \ref azimuth_initial_line * @param scale See \ref scale_factor_initial_line * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createLabordeObliqueMercator( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeProjectionCentre, const common::Angle &azimuthInitialLine, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, createParams(latitudeProjectionCentre, longitudeProjectionCentre, azimuthInitialLine, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * International Map of the World Polyconic projection method. * * There is no equivalent in EPSG. * * @note the order of arguments is conformant with the corresponding EPSG * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL <= *2.3 * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param latitudeSecondParallel See \ref latitude_second_std_parallel * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createInternationalMapWorldPolyconic( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Angle &latitudeFirstParallel, const common::Angle &latitudeSecondParallel, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, createParams(centerLong, latitudeFirstParallel, latitudeSecondParallel, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Krovak (north oriented) projection method. * * This method is defined as * * EPSG:1041. * * The coordinates are returned in the "GIS friendly" order: easting, northing. * This method is similar to createKrovak(), except that the later returns * projected values as southing, westing, where * southing(Krovak) = -northing(Krovak_North) and * westing(Krovak) = -easting(Krovak_North). * * @note The current PROJ implementation of Krovak hard-codes * colatitudeConeAxis = 30deg17'17.30311" * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). * It also hard-codes the parameters of the Bessel ellipsoid typically used for * Krovak. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeProjectionCentre See \ref latitude_projection_centre * @param longitudeOfOrigin See \ref longitude_of_origin * @param colatitudeConeAxis See \ref colatitude_cone_axis * @param latitudePseudoStandardParallel See \ref *latitude_pseudo_standard_parallel * @param scaleFactorPseudoStandardParallel See \ref *scale_factor_pseudo_standard_parallel * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createKrovakNorthOriented( const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeOfOrigin, const common::Angle &colatitudeConeAxis, const common::Angle &latitudePseudoStandardParallel, const common::Scale &scaleFactorPseudoStandardParallel, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, createParams(latitudeProjectionCentre, longitudeOfOrigin, colatitudeConeAxis, latitudePseudoStandardParallel, scaleFactorPseudoStandardParallel, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Krovak projection method. * * This method is defined as * * EPSG:9819. * * The coordinates are returned in the historical order: southing, westing * This method is similar to createKrovakNorthOriented(), except that the later *returns * projected values as easting, northing, where * easting(Krovak_North) = -westing(Krovak) and * northing(Krovak_North) = -southing(Krovak). * * @note The current PROJ implementation of Krovak hard-codes * colatitudeConeAxis = 30deg17'17.30311" * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221). * It also hard-codes the parameters of the Bessel ellipsoid typically used for * Krovak. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeProjectionCentre See \ref latitude_projection_centre * @param longitudeOfOrigin See \ref longitude_of_origin * @param colatitudeConeAxis See \ref colatitude_cone_axis * @param latitudePseudoStandardParallel See \ref *latitude_pseudo_standard_parallel * @param scaleFactorPseudoStandardParallel See \ref *scale_factor_pseudo_standard_parallel * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createKrovak(const util::PropertyMap &properties, const common::Angle &latitudeProjectionCentre, const common::Angle &longitudeOfOrigin, const common::Angle &colatitudeConeAxis, const common::Angle &latitudePseudoStandardParallel, const common::Scale &scaleFactorPseudoStandardParallel, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_KROVAK, createParams(latitudeProjectionCentre, longitudeOfOrigin, colatitudeConeAxis, latitudePseudoStandardParallel, scaleFactorPseudoStandardParallel, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Lambert Azimuthal Equal Area projection method. * * This method is defined as * * EPSG:9820. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeNatOrigin See \ref center_latitude * @param longitudeNatOrigin See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createLambertAzimuthalEqualArea( const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin, const common::Angle &longitudeNatOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, createParams(latitudeNatOrigin, longitudeNatOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Miller Cylindrical projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createMillerCylindrical( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Mercator (variant A) projection method. * * This is the A variant, also known as Mercator (1SP), defined with the scale * factor. Note that latitude of natural origin (centerLat) is a parameter, * but unused in the transformation formulas. * * This method is defined as * * EPSG:9804. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude . Should be 0. * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createMercatorVariantA( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Mercator (variant B) projection method. * * This is the B variant, also known as Mercator (2SP), defined with the * latitude of the first standard parallel (the second standard parallel is * implicitly the opposite value). The latitude of natural origin is fixed to * zero. * * This method is defined as * * EPSG:9805. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeFirstParallel See \ref latitude_first_std_parallel * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createMercatorVariantB( const util::PropertyMap &properties, const common::Angle &latitudeFirstParallel, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, createParams(latitudeFirstParallel, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Popular Visualisation Pseudo Mercator projection method. * * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857 * (WGS 84 / Pseudo-Mercator) * * This method is defined as * * EPSG:1024. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude . Usually 0 * @param centerLong See \ref center_longitude . Usually 0 * @param falseEasting See \ref false_easting . Usually 0 * @param falseNorthing See \ref false_northing . Usually 0 * @return a new Conversion. */ ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- // clang-format off /** \brief Instantiate a conversion based on the * * Mercator projection method, using its spherical formulation * * When used with an ellipsoid, the radius used is the radius of the conformal * sphere at centerLat. * * This method is defined as * * EPSG:1026. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude . Usually 0 * @param centerLong See \ref center_longitude . Usually 0 * @param falseEasting See \ref false_easting . Usually 0 * @param falseNorthing See \ref false_northing . Usually 0 * @return a new Conversion. * @since 9.3 */ ConversionNNPtr Conversion::createMercatorSpherical( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_MERCATOR_SPHERICAL, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // clang-format on // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Mollweide projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createMollweide( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * New Zealand Map Grid projection method. * * This method is defined as * * EPSG:9811. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createNewZealandMappingGrid( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_NZMG, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Oblique Stereographic (alternative) projection method. * * This method is defined as * * EPSG:9809. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createObliqueStereographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Orthographic projection method. * * This method is defined as * * EPSG:9840. * * \note Before PROJ 7.2, only the spherical formulation was implemented. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createOrthographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_ORTHOGRAPHIC, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Orthographic projection method. * * This method is defined as * * EPSG:1130. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param azimuthInitialLine See \ref azimuth_initial_line * @param scale See \ref scale_factor_initial_line * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createLocalOrthographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Angle &azimuthInitialLine, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC, createParams(centerLat, centerLong, azimuthInitialLine, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * American Polyconic projection method. * * This method is defined as * * EPSG:9818. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createAmericanPolyconic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Polar Stereographic (Variant A) projection method. * * This method is defined as * * EPSG:9810. * * This is the variant of polar stereographic defined with a scale factor. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg. * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createPolarStereographicVariantA( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Polar Stereographic (Variant B) projection method. * * This method is defined as * * EPSG:9829. * * This is the variant of polar stereographic defined with a latitude of * standard parallel. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeStandardParallel See \ref latitude_std_parallel * @param longitudeOfOrigin See \ref longitude_of_origin * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createPolarStereographicVariantB( const util::PropertyMap &properties, const common::Angle &latitudeStandardParallel, const common::Angle &longitudeOfOrigin, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, createParams(latitudeStandardParallel, longitudeOfOrigin, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Robinson projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createRobinson( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Sinusoidal projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createSinusoidal( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Stereographic projection method. * * There is no equivalent in EPSG. This method implements the original "Oblique * Stereographic" method described in "Snyder's Map Projections - A Working *manual", * which is different from the "Oblique Stereographic (alternative)" method * implemented in createObliqueStereographic(). * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param scale See \ref scale * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createStereographic( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Scale &scale, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, createParams(centerLat, centerLong, scale, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Van der Grinten projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createVanDerGrinten( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner I projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner II projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerII( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner III projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param latitudeTrueScale Latitude of true scale. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerIII( const util::PropertyMap &properties, const common::Angle &latitudeTrueScale, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III, createParams(latitudeTrueScale, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner IV projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerIV( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner V projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner VI projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerVI( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Wagner VII projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createWagnerVII( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Quadrilateralized Spherical Cube projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLat See \ref center_latitude * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube( const util::PropertyMap &properties, const common::Angle ¢erLat, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create( properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, createParams(centerLat, centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Spherical Cross-Track Height projection method. * * There is no equivalent in EPSG. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param pegPointLat Peg point latitude. * @param pegPointLong Peg point longitude. * @param pegPointHeading Peg point heading. * @param pegPointHeight Peg point height. * @return a new Conversion. */ ConversionNNPtr Conversion::createSphericalCrossTrackHeight( const util::PropertyMap &properties, const common::Angle &pegPointLat, const common::Angle &pegPointLong, const common::Angle &pegPointHeading, const common::Length &pegPointHeight) { return create(properties, PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, createParams(pegPointLat, pegPointLong, pegPointHeading, pegPointHeight)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Equal Earth projection method. * * This method is defined as * * EPSG:1078. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param centerLong See \ref center_longitude * @param falseEasting See \ref false_easting * @param falseNorthing See \ref false_northing * @return a new Conversion. */ ConversionNNPtr Conversion::createEqualEarth( const util::PropertyMap &properties, const common::Angle ¢erLong, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH, createParams(centerLong, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the * * Vertical Perspective projection method. * * This method is defined as * * EPSG:9838. * * The PROJ implementation of the EPSG Vertical Perspective has the current * limitations with respect to the method described in EPSG: *
    *
  • it is a 2D-only method, ignoring the ellipsoidal height of the point to * project.
  • *
  • it has only a spherical development.
  • *
  • the height of the topocentric origin is ignored, and thus assumed to be * 0.
  • *
* * For completeness, PROJ adds the falseEasting and falseNorthing parameter, * which are not described in EPSG. They should usually be set to 0. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param topoOriginLat Latitude of topocentric origin * @param topoOriginLong Longitude of topocentric origin * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by * PROJ (that is assumed to be 0) * @param viewPointHeight Viewpoint height with respect to the * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is * the height above the ellipsoid surface at topoOriginLat, topoOriginLong. * @param falseEasting See \ref false_easting . (not in EPSG) * @param falseNorthing See \ref false_northing . (not in EPSG) * @return a new Conversion. * * @since 6.3 */ ConversionNNPtr Conversion::createVerticalPerspective( const util::PropertyMap &properties, const common::Angle &topoOriginLat, const common::Angle &topoOriginLong, const common::Length &topoOriginHeight, const common::Length &viewPointHeight, const common::Length &falseEasting, const common::Length &falseNorthing) { return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, createParams(topoOriginLat, topoOriginLong, topoOriginHeight, viewPointHeight, falseEasting, falseNorthing)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Pole Rotation method, using * the conventions of the GRIB 1 and GRIB 2 data formats. * * Those are mentioned in the Note 2 of * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml * * Several conventions for the pole rotation method exists. * The parameters provided in this method are remapped to the PROJ ob_tran * operation with: *
 * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
 *                               +o_lat_p=-southPoleLatInUnrotatedCRS
 *                               +lon_0=southPoleLongInUnrotatedCRS
 * 
* * Another implementation of that convention is also in the netcdf-java library: * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java * * The PROJ implementation of this method assumes a spherical ellipsoid. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated * CRS, expressed in the unrotated CRS, that will become the south pole of the * rotated CRS. * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated * CRS, expressed in the unrotated CRS, that will become the south pole of the * rotated CRS. * @param axisRotation The angle of rotation about the new polar * axis (measured clockwise when looking from the southern to the northern pole) * of the coordinate system, assuming the new axis to have been obtained by * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about * the geographic polar axis and then rotating through * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved * along the (previously rotated) Greenwich meridian. * @return a new Conversion. * * @since 7.0 */ ConversionNNPtr Conversion::createPoleRotationGRIBConvention( const util::PropertyMap &properties, const common::Angle &southPoleLatInUnrotatedCRS, const common::Angle &southPoleLongInUnrotatedCRS, const common::Angle &axisRotation) { return create(properties, PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, createParams(southPoleLatInUnrotatedCRS, southPoleLongInUnrotatedCRS, axisRotation)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Pole Rotation method, using * the conventions of the netCDF CF convention for the netCDF format. * * Those are mentioned in the Note 2 of * https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#_rotated_pole * * Several conventions for the pole rotation method exists. * The parameters provided in this method are remapped to the PROJ ob_tran * operation with: *
 * +proj=ob_tran +o_proj=longlat +o_lon_p=northPoleGridLongitude
 *                               +o_lat_p=gridNorthPoleLatitude
 *                               +lon_0=180+gridNorthPoleLongitude
 * 
* * Another implementation of that convention is also in the netcdf-java library: * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedPole.java * * The PROJ implementation of this method assumes a spherical ellipsoid. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param gridNorthPoleLatitude True latitude of the north pole of the rotated * grid * @param gridNorthPoleLongitude True longitude of the north pole of the rotated * grid. * @param northPoleGridLongitude Longitude of the true north pole in the rotated * grid. * @return a new Conversion. * * @since 8.2 */ ConversionNNPtr Conversion::createPoleRotationNetCDFCFConvention( const util::PropertyMap &properties, const common::Angle &gridNorthPoleLatitude, const common::Angle &gridNorthPoleLongitude, const common::Angle &northPoleGridLongitude) { return create(properties, PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION, createParams(gridNorthPoleLatitude, gridNorthPoleLongitude, northPoleGridLongitude)); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Change of Vertical Unit * method. * * This method is defined as * * EPSG:1069 [DEPRECATED]. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @param factor Conversion factor * @return a new Conversion. */ ConversionNNPtr Conversion::createChangeVerticalUnit(const util::PropertyMap &properties, const common::Scale &factor) { return create( properties, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT), VectorOfParameters{ createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR), }, VectorOfValues{ factor, }); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Change of Vertical Unit * method (without explicit conversion factor) * * This method is defined as * * EPSG:1104. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @return a new Conversion. */ ConversionNNPtr Conversion::createChangeVerticalUnit(const util::PropertyMap &properties) { return create(properties, createMethodMapNameEPSGCode( EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR), VectorOfParameters{}, VectorOfValues{}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Height Depth Reversal * method. * * This method is defined as * * EPSG:1068. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @return a new Conversion. * @since 6.3 */ ConversionNNPtr Conversion::createHeightDepthReversal(const util::PropertyMap &properties) { return create( properties, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL), {}, {}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Axis order reversal method * * This swaps the longitude, latitude axis. * * This method is defined as * * EPSG:9843 for 2D or * * EPSG:9844 for Geographic3D horizontal. * * @param is3D Whether this should apply on 3D geographicCRS * @return a new Conversion. */ ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) { if (is3D) { return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499), createMethodMapNameEPSGCode( EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D), {}, {}); } return create( createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498), createMethodMapNameEPSGCode(EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D), {}, {}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Geographic/Geocentric method. * * This method is defined as * * EPSG:9602. * * @param properties See \ref general_properties of the conversion. If the name * is not provided, it is automatically set. * @return a new Conversion. */ ConversionNNPtr Conversion::createGeographicGeocentric(const util::PropertyMap &properties) { return create( properties, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC), {}, {}); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ConversionNNPtr Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, buildOpName("Conversion", sourceCRS, targetCRS)); auto conv = createGeographicGeocentric(properties); conv->setCRSs(sourceCRS, targetCRS, nullptr); return conv; } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion between a GeographicCRS and a spherical * planetocentric GeodeticCRS * * This method performs conversion between geodetic latitude and geocentric * latitude * * @return a new Conversion. */ ConversionNNPtr Conversion::createGeographicGeocentricLatitude(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, buildOpName("Conversion", sourceCRS, targetCRS)); auto conv = create( properties, PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {}); conv->setCRSs(sourceCRS, targetCRS, nullptr); return conv; } // --------------------------------------------------------------------------- InverseConversion::InverseConversion(const ConversionNNPtr &forward) : Conversion( OperationMethod::create(createPropertiesForInverse(forward->method()), forward->method()->parameters()), forward->parameterValues()), InverseCoordinateOperation(forward, true) { setPropertiesFromForward(); } // --------------------------------------------------------------------------- InverseConversion::~InverseConversion() = default; // --------------------------------------------------------------------------- ConversionNNPtr InverseConversion::inverseAsConversion() const { return NN_NO_CHECK( util::nn_dynamic_pointer_cast(forwardOperation_)); } // --------------------------------------------------------------------------- CoordinateOperationNNPtr InverseConversion::create(const ConversionNNPtr &forward) { auto conv = util::nn_make_shared(forward); conv->assignSelf(conv); return conv; } // --------------------------------------------------------------------------- CoordinateOperationNNPtr InverseConversion::_shallowClone() const { auto op = InverseConversion::nn_make_shared( inverseAsConversion()->shallowClone()); op->assignSelf(op); op->setCRSs(this, false); return util::nn_static_pointer_cast(op); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool isAxisOrderReversal2D(int methodEPSGCode) { return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D; } static bool isAxisOrderReversal3D(int methodEPSGCode) { return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D; } bool isAxisOrderReversal(int methodEPSGCode) { return isAxisOrderReversal2D(methodEPSGCode) || isAxisOrderReversal3D(methodEPSGCode); } //! @endcond // --------------------------------------------------------------------------- CoordinateOperationNNPtr Conversion::inverse() const { const int methodEPSGCode = method()->getEPSGCode(); if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { const double convFactor = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); if (convFactor == 0) { throw InvalidOperation("Invalid conversion factor"); } // coverity[divide_by_zero] const double invConvFactor = 1.0 / convFactor; auto conv = createChangeVerticalUnit( createPropertiesForInverse(this, false, false), common::Scale(invConvFactor)); conv->setCRSs(this, true); return conv; } if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR) { auto conv = createChangeVerticalUnit( createPropertiesForInverse(this, false, false)); conv->setCRSs(this, true); return conv; } const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode); const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode); if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) { auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D); conv->setCRSs(this, true); return conv; } if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { auto conv = createGeographicGeocentric( createPropertiesForInverse(this, false, false)); conv->setCRSs(this, true); return conv; } if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { auto conv = createHeightDepthReversal( createPropertiesForInverse(this, false, false)); conv->setCRSs(this, true); return conv; } if (method()->nameStr() == PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) { auto conv = create(createPropertiesForInverse(this, false, false), PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {}); conv->setCRSs(this, true); return conv; } return InverseConversion::create(NN_NO_CHECK( util::nn_dynamic_pointer_cast(shared_from_this()))); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static double msfn(double phi, double e2) { const double sinphi = std::sin(phi); const double cosphi = std::cos(phi); return pj_msfn(sinphi, cosphi, e2); } // --------------------------------------------------------------------------- static double tsfn(double phi, double ec) { const double sinphi = std::sin(phi); return pj_tsfn(phi, sinphi, ec); } // --------------------------------------------------------------------------- // Function whose zeroes are the sin of the standard parallels of LCC_2SP static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) { const double x = sinphi; const double ecx = ec * x; return (1 - x * x) / (1 - ecx * ecx) - K * K * std::pow((1.0 - x) / (1.0 + x) * std::pow((1.0 + ecx) / (1.0 - ecx), ec), n); } // --------------------------------------------------------------------------- // Find the sin of the standard parallels of LCC_2SP static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K, double ec) { double a, b; double f_a; if (bNorth) { // Look for zero above phi0 a = sinphi0; b = 1.0; // sin(North pole) f_a = 1.0; // some positive value, but we only care about the sign } else { // Look for zero below phi0 a = -1.0; // sin(South pole) b = sinphi0; f_a = -1.0; // minus infinity in fact, but we only care about the sign } // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init, // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges for (int N = 0; N < 100; N++) { double c = (a + b) / 2; double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0); if (f_c == 0.0 || (b - a) < 1e-18) { return c; } if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) { a = c; f_a = f_c; } else { b = c; } } return (a + b) / 2; } static inline double DegToRad(double x) { return x / 180.0 * M_PI; } static inline double RadToDeg(double x) { return x / M_PI * 180.0; } //! @endcond // --------------------------------------------------------------------------- /** * \brief Return an equivalent projection. * * Currently implemented: *
    *
  • EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)
  • *
  • EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)
  • *
  • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP
  • *
  • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP
  • *
* * @param targetEPSGCode EPSG code of the target method. * @return new conversion, or nullptr */ ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const { const int current_epsg_code = method()->getEPSGCode(); if (current_epsg_code == targetEPSGCode) { return util::nn_dynamic_pointer_cast(shared_from_this()); } auto geogCRS = dynamic_cast(sourceCRS().get()); if (!geogCRS) { return nullptr; } const double e2 = geogCRS->ellipsoid()->squaredEccentricity(); if (e2 < 0) { return nullptr; } if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && parameterValueNumericAsSI( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { const double k0 = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) return nullptr; // coverity[divide_by_zero] const double dfStdP1Lat = (k0 >= 1.0) ? 0.0 : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2))); auto latitudeFirstParallel = common::Angle( common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN) .convertToUnit(common::UnitOfMeasure::DEGREE), common::UnitOfMeasure::DEGREE); auto conv = createMercatorVariantB( util::PropertyMap(), latitudeFirstParallel, common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); conv->setCRSs(this, false); return conv.as_nullable(); } if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { const double phi1 = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); if (!(std::fabs(phi1) < M_PI / 2)) return nullptr; const double k0 = msfn(phi1, e2); auto conv = createMercatorVariantA( util::PropertyMap(), common::Angle(0.0, common::UnitOfMeasure::DEGREE), common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY), common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); conv->setCRSs(this, false); return conv.as_nullable(); } if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance // "1.3.1.1 Lambert Conic Conformal (2SP)" and // "1.3.1.2 Lambert Conic Conformal (1SP)" and // or Snyder pages 106-109 auto latitudeOfOrigin = common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN)); const double phi0 = latitudeOfOrigin.getSIValue(); const double k0 = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); if (!(std::fabs(phi0) < M_PI / 2)) return nullptr; if (!(k0 > 0 && k0 <= 1.0 + 1e-10)) return nullptr; const double ec = std::sqrt(e2); const double m0 = msfn(phi0, e2); const double t0 = tsfn(phi0, ec); const double n = sin(phi0); if (std::fabs(n) < 1e-10) return nullptr; if (fabs(k0 - 1.0) <= 1e-10) { auto conv = createLambertConicConformal_2SP( util::PropertyMap(), latitudeOfOrigin, common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), latitudeOfOrigin, latitudeOfOrigin, common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING))); conv->setCRSs(this, false); return conv.as_nullable(); } else { const double K = k0 * m0 / std::pow(t0, n); const double phi1 = std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec)); const double phi2 = std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec)); double phi1Deg = RadToDeg(phi1); double phi2Deg = RadToDeg(phi2); // Try to round to hundreth of degree if very close to it if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) < 1e-8) phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000; if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) < 1e-8) phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000; // The following improvement is too turn the LCC1SP equivalent of // EPSG:2154 to the real LCC2SP // If the computed latitude of origin is close to .0 or .5 degrees // then check if rounding it to it will get a false northing // close to an integer const double FN = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); const double latitudeOfOriginDeg = latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE); if (std::fabs(latitudeOfOriginDeg * 2 - std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) { const double dfRoundedLatOfOrig = std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2; const double m1 = msfn(phi1, e2); const double t1 = tsfn(phi1, ec); const double F = m1 / (n * std::pow(t1, n)); const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); const double tRoundedLatOfOrig = tsfn(DegToRad(dfRoundedLatOfOrig), ec); const double FN_correction = a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n)); const double FN_corrected = FN - FN_correction; const double FN_corrected_rounded = std::floor(FN_corrected + 0.5); if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) { auto conv = createLambertConicConformal_2SP( util::PropertyMap(), common::Angle(dfRoundedLatOfOrig, common::UnitOfMeasure::DEGREE), common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), common::Length(parameterValueMeasure( EPSG_CODE_PARAMETER_FALSE_EASTING)), common::Length(FN_corrected_rounded)); conv->setCRSs(this, false); return conv.as_nullable(); } } auto conv = createLambertConicConformal_2SP( util::PropertyMap(), latitudeOfOrigin, common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)), common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE), common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE), common::Length( parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)), common::Length(FN)); conv->setCRSs(this, false); return conv.as_nullable(); } } if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) { // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance // "1.3.1.1 Lambert Conic Conformal (2SP)" and // "1.3.1.2 Lambert Conic Conformal (1SP)" and // or Snyder pages 106-109 const double phiF = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN) .getSIValue(); const double phi1 = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) .getSIValue(); const double phi2 = parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) .getSIValue(); if (!(std::fabs(phiF) < M_PI / 2)) return nullptr; if (!(std::fabs(phi1) < M_PI / 2)) return nullptr; if (!(std::fabs(phi2) < M_PI / 2)) return nullptr; const double ec = std::sqrt(e2); const double m1 = msfn(phi1, e2); const double m2 = msfn(phi2, e2); const double t1 = tsfn(phi1, ec); const double t2 = tsfn(phi2, ec); const double n_denom = std::log(t1) - std::log(t2); const double n = (std::fabs(n_denom) < 1e-10) ? std::sin(phi1) : (std::log(m1) - std::log(m2)) / n_denom; if (std::fabs(n) < 1e-10) return nullptr; const double F = m1 / (n * std::pow(t1, n)); const double phi0 = std::asin(n); const double m0 = msfn(phi0, e2); const double t0 = tsfn(phi0, ec); const double F0 = m0 / (n * std::pow(t0, n)); const double k0 = F / F0; const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); const double tF = tsfn(phiF, ec); const double FN_correction = a * F * (std::pow(tF, n) - std::pow(t0, n)); double phi0Deg = RadToDeg(phi0); // Try to round to thousandth of degree if very close to it if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8) phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000; auto conv = createLambertConicConformal_1SP( util::PropertyMap(), common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE), common::Angle(parameterValueMeasure( EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)), common::Scale(k0), common::Length(parameterValueMeasure( EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)), common::Length( parameterValueNumericAsSI( EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) + (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0))); conv->setCRSs(this, false); return conv.as_nullable(); } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name, int epsg_code) { size_t nEsriMappings = 0; const auto esriMappings = getEsriMappings(nEsriMappings); for (size_t i = 0; i < nEsriMappings; ++i) { const auto &mapping = esriMappings[i]; if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || ci_equal(wkt2_name, mapping.wkt2_name)) { return &mapping; } } return nullptr; } // --------------------------------------------------------------------------- static void getESRIMethodNameAndParams(const Conversion *conv, const std::string &methodName, int methodEPSGCode, const char *&esriMethodName, const ESRIParamMapping *&esriParams) { esriParams = nullptr; esriMethodName = nullptr; const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode); const auto l_targetCRS = conv->targetCRS(); if (esriMapping) { esriParams = esriMapping->params; esriMethodName = esriMapping->esri_name; if (esriMapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || esriMapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) { if (l_targetCRS && ci_find(l_targetCRS->nameStr(), "Plate Carree") != std::string::npos && conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) { esriParams = paramsESRI_Plate_Carree; esriMethodName = "Plate_Carree"; } else { esriParams = paramsESRI_Equidistant_Cylindrical; esriMethodName = "Equidistant_Cylindrical"; } } else if (esriMapping->epsg_code == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { if (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos || (l_targetCRS && (ci_find(l_targetCRS->nameStr(), "Gauss") != std::string::npos || ci_find(l_targetCRS->nameStr(), "GK_") != std::string::npos))) { esriParams = paramsESRI_Gauss_Kruger; esriMethodName = "Gauss_Kruger"; } else { esriParams = paramsESRI_Transverse_Mercator; esriMethodName = "Transverse_Mercator"; } } else if (esriMapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { if (std::abs( conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) - conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) < 1e-15) { esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin; esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Natural_Origin"; } else { esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin; esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin"; } } else if (esriMapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { if (std::abs( conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) - conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) < 1e-15) { esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center; esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center"; } else { esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center; esriMethodName = "Rectified_Skew_Orthomorphic_Center"; } } else if (esriMapping->epsg_code == EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A) { // Quite empiric, but looking at pe_list_projcs.csv, the only // CRS that use Polar_Stereographic_Variant_A are EPSG:5041 and 5042 if (l_targetCRS && // EPSG:5041 (l_targetCRS->nameStr() == "WGS 84 / UPS North (E,N)" || // EPSG:5042 l_targetCRS->nameStr() == "WGS 84 / UPS South (E,N)")) { esriMethodName = "Polar_Stereographic_Variant_A"; } else { esriMethodName = "Stereographic"; } } else if (esriMapping->epsg_code == EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { if (conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) { esriMethodName = "Stereographic_North_Pole"; } else { esriMethodName = "Stereographic_South_Pole"; } } else if (esriMapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) { if (std::abs(conv->parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, common::UnitOfMeasure::DEGREE) - 30.0) < 1e-10) { esriMethodName = "Behrmann"; } else { esriMethodName = "Cylindrical_Equal_Area"; } } } } // --------------------------------------------------------------------------- const char *Conversion::getESRIMethodName() const { const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const auto methodEPSGCode = l_method->getEPSGCode(); const ESRIParamMapping *esriParams = nullptr; const char *esriMethodName = nullptr; getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName, esriParams); return esriMethodName; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const char *Conversion::getWKT1GDALMethodName() const { const auto &l_method = method(); const auto methodEPSGCode = l_method->getEPSGCode(); if (methodEPSGCode == EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { return "Mercator_1SP"; } const MethodMapping *mapping = getMapping(l_method.get()); return mapping ? mapping->wkt1_name : nullptr; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Conversion::_exportToWKT(io::WKTFormatter *formatter) const { const auto &l_method = method(); std::string methodName = l_method->nameStr(); auto methodEPSGCode = l_method->getEPSGCode(); const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2 && formatter->useESRIDialect()) { if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { auto eqConv = convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B); if (eqConv) { eqConv->_exportToWKT(formatter); return; } } } if (isWKT2) { formatter->startNode(formatter->useDerivingConversion() ? io::WKTConstants::DERIVINGCONVERSION : io::WKTConstants::CONVERSION, !identifiers().empty()); formatter->addQuotedString(nameStr()); } else { formatter->enter(); formatter->pushOutputUnit(false); formatter->pushOutputId(false); } #ifdef DEBUG_CONVERSION_ID if (sourceCRS() && targetCRS()) { formatter->startNode("SOURCECRS_ID", false); sourceCRS()->formatID(formatter); formatter->endNode(); formatter->startNode("TARGETCRS_ID", false); targetCRS()->formatID(formatter); formatter->endNode(); } #endif bool bAlreadyWritten = false; bool methodWritten = false; const MethodMapping *mapping = !isWKT2 && !formatter->useESRIDialect() ? getMapping(l_method.get()) : nullptr; if (!isWKT2 && methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_SPHERICAL) { auto projCRS = dynamic_cast(targetCRS().get()); if (projCRS && projCRS->baseCRS()->ellipsoid()->isSphere()) { methodName = EPSG_NAME_METHOD_MERCATOR_VARIANT_A; methodEPSGCode = EPSG_CODE_METHOD_MERCATOR_VARIANT_A; if (!formatter->useESRIDialect()) { methodWritten = true; formatter->startNode(io::WKTConstants::PROJECTION, false); formatter->addQuotedString("Mercator_1SP"); formatter->endNode(); mapping = getMapping(methodEPSGCode); } } } if (!isWKT2 && formatter->useESRIDialect()) { const ESRIParamMapping *esriParams = nullptr; const char *esriMethodName = nullptr; getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName, esriParams); if (esriMethodName && esriParams) { formatter->startNode(io::WKTConstants::PROJECTION, false); formatter->addQuotedString(esriMethodName); formatter->endNode(); for (int i = 0; esriParams[i].esri_name != nullptr; i++) { const auto &esriParam = esriParams[i]; formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString(esriParam.esri_name); if (esriParam.wkt2_name) { const auto &pv = parameterValue(esriParam.wkt2_name, esriParam.epsg_code); if (pv && pv->type() == ParameterValue::Type::MEASURE) { const auto &v = pv->value(); // as we don't output the natural unit, output // to the registered linear / angular unit. const auto &unitType = v.unit().type(); if (unitType == common::UnitOfMeasure::Type::LINEAR) { formatter->add(v.convertToUnit( *(formatter->axisLinearUnit()))); } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { const auto &angUnit = *(formatter->axisAngularUnit()); double val = v.convertToUnit(angUnit); if (angUnit == common::UnitOfMeasure::DEGREE) { if (val > 180.0) { val -= 360.0; } else if (val < -180.0) { val += 360.0; } } formatter->add(val); } else { formatter->add(v.getSIValue()); } } else if (ci_find(esriParam.esri_name, "scale") != std::string::npos) { formatter->add(1.0); } else { formatter->add(0.0); } } else { formatter->add(esriParam.fixed_value); } formatter->endNode(); } bAlreadyWritten = true; } } else if (!isWKT2) { if (methodEPSGCode == EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { const double latitudeOrigin = parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE); if (latitudeOrigin != 0) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); } bAlreadyWritten = true; formatter->startNode(io::WKTConstants::PROJECTION, false); formatter->addQuotedString("Mercator_1SP"); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("central_meridian"); const double centralMeridian = parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE); formatter->add(centralMeridian); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("scale_factor"); formatter->add(1.0); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("false_easting"); const double falseEasting = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); formatter->add(falseEasting); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("false_northing"); const double falseNorthing = parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); formatter->add(falseNorthing); formatter->endNode(); } else if (starts_with(methodName, "PROJ ")) { bAlreadyWritten = true; formatter->startNode(io::WKTConstants::PROJECTION, false); formatter->addQuotedString("custom_proj4"); formatter->endNode(); } } if (!bAlreadyWritten) { if (!methodWritten) { l_method->_exportToWKT(formatter); } if (!isWKT2 && methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && parameterValueNumericAsSI( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) != 0.0) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); } bool hasInterpolationCRSParameter = false; for (const auto &genOpParamvalue : parameterValues()) { const auto opParamvalue = dynamic_cast( genOpParamvalue.get()); const int paramEPSGCode = opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0; // EPSG has normally no Latitude of natural origin for Equidistant // Cylindrical but PROJ can handle it, so output the parameter if // not zero if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { if (paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) { const auto ¶mValue = opParamvalue->parameterValue(); if (paramValue->type() == ParameterValue::Type::MEASURE) { const auto &measure = paramValue->value(); if (measure.getSIValue() == 0) { continue; } } } } // Same for false easting / false northing for Vertical Perspective else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) { if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING || paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) { const auto ¶mValue = opParamvalue->parameterValue(); if (paramValue->type() == ParameterValue::Type::MEASURE) { const auto &measure = paramValue->value(); if (measure.getSIValue() == 0) { continue; } } } } if (paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS || paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) { hasInterpolationCRSParameter = true; } genOpParamvalue->_exportToWKT(formatter, mapping); } // If we have an interpolation CRS that has a EPSG code, then // we can export it as a PARAMETER[] const auto l_interpolationCRS = interpolationCRS(); if (!hasInterpolationCRSParameter && l_interpolationCRS) { const auto code = l_interpolationCRS->getEPSGCode(); if (code != 0) { createOperationParameterValueFromInterpolationCRS( methodEPSGCode, code) ->_exportToWKT(formatter, mapping); } } } if (isWKT2) { if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } else { formatter->popOutputUnit(); formatter->popOutputId(); formatter->leave(); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Conversion::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("Conversion", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("method"); formatter->setOmitTypeInImmediateChild(); formatter->setAllowIDInImmediateChild(); const auto &l_method = method(); l_method->_exportToJSON(formatter); const auto &l_parameterValues = parameterValues(); const auto l_interpolationCRS = interpolationCRS(); if (!l_parameterValues.empty() || l_interpolationCRS) { writer->AddObjKey("parameters"); { bool hasInterpolationCRSParameter = false; auto parametersContext(writer->MakeArrayContext(false)); for (const auto &genOpParamvalue : l_parameterValues) { const auto opParamvalue = dynamic_cast( genOpParamvalue.get()); const int paramEPSGCode = opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0; if (paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS || paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) { hasInterpolationCRSParameter = true; } formatter->setAllowIDInImmediateChild(); formatter->setOmitTypeInImmediateChild(); genOpParamvalue->_exportToJSON(formatter); } // If we have an interpolation CRS that has a EPSG code, then // we can export it as a parameter if (!hasInterpolationCRSParameter && l_interpolationCRS) { const auto methodEPSGCode = l_method->getEPSGCode(); const auto code = l_interpolationCRS->getEPSGCode(); if (code != 0) { formatter->setAllowIDInImmediateChild(); formatter->setOmitTypeInImmediateChild(); createOperationParameterValueFromInterpolationCRS( methodEPSGCode, code) ->_exportToJSON(formatter); } } } } if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool createPROJ4WebMercator(const Conversion *conv, io::PROJStringFormatter *formatter) { const double centralMeridian = conv->parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE); const double falseEasting = conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING); const double falseNorthing = conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING); auto sourceCRS = conv->sourceCRS(); auto geogCRS = dynamic_cast(sourceCRS.get()); if (!geogCRS) { return false; } std::string units("m"); auto targetCRS = conv->targetCRS(); auto targetProjCRS = dynamic_cast(targetCRS.get()); if (targetProjCRS) { const auto &axisList = targetProjCRS->coordinateSystem()->axisList(); const auto &unit = axisList[0]->unit(); if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT)) { auto projUnit = unit.exportToPROJString(); if (!projUnit.empty()) { units = std::move(projUnit); } else { return false; } } } formatter->addStep("merc"); const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue(); formatter->addParam("a", a); formatter->addParam("b", a); formatter->addParam("lat_ts", 0.0); formatter->addParam("lon_0", centralMeridian); formatter->addParam("x_0", falseEasting); formatter->addParam("y_0", falseNorthing); formatter->addParam("k", 1.0); formatter->addParam("units", units); formatter->addParam("nadgrids", "@null"); if (targetProjCRS && targetProjCRS->hasOver()) { formatter->addParam("over"); } formatter->addParam("wktext"); formatter->addParam("no_defs"); return true; } // --------------------------------------------------------------------------- static bool createPROJExtensionFromCustomProj(const Conversion *conv, io::PROJStringFormatter *formatter, bool forExtensionNode) { const auto &methodName = conv->method()->nameStr(); assert(starts_with(methodName, "PROJ ")); auto tokens = split(methodName, ' '); formatter->addStep(tokens[1]); if (forExtensionNode) { auto sourceCRS = conv->sourceCRS(); auto geogCRS = dynamic_cast(sourceCRS.get()); if (!geogCRS) { return false; } geogCRS->addDatumInfoToPROJString(formatter); } for (size_t i = 2; i < tokens.size(); i++) { auto kv = split(tokens[i], '='); if (kv.size() == 2) { formatter->addParam(kv[0], kv[1]); } else { formatter->addParam(tokens[i]); } } for (const auto &genOpParamvalue : conv->parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto ¶mName = opParamvalue->parameter()->nameStr(); const auto ¶mValue = opParamvalue->parameterValue(); if (paramValue->type() == ParameterValue::Type::MEASURE) { const auto &measure = paramValue->value(); const auto unitType = measure.unit().type(); if (unitType == common::UnitOfMeasure::Type::LINEAR) { formatter->addParam(paramName, measure.getSIValue()); } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) { formatter->addParam( paramName, measure.convertToUnit(common::UnitOfMeasure::DEGREE)); } else { formatter->addParam(paramName, measure.value()); } } } } if (forExtensionNode) { formatter->addParam("wktext"); formatter->addParam("no_defs"); } return true; } //! @endcond // --------------------------------------------------------------------------- bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const int methodEPSGCode = l_method->getEPSGCode(); if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" || l_method->getPrivate()->projMethodOverride_ == "utm approx") { auto projFormatter = io::PROJStringFormatter::create(); projFormatter->setCRSExport(true); projFormatter->setUseApproxTMerc(true); formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4"); _exportToPROJString(projFormatter.get()); projFormatter->addParam("no_defs"); formatter->addQuotedString(projFormatter->toString()); formatter->endNode(); return true; } else if (methodEPSGCode == EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR || nameStr() == "Popular Visualisation Mercator") { auto projFormatter = io::PROJStringFormatter::create(); projFormatter->setCRSExport(true); if (createPROJ4WebMercator(this, projFormatter.get())) { formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4"); formatter->addQuotedString(projFormatter->toString()); formatter->endNode(); return true; } } else if (starts_with(methodName, "PROJ ")) { auto projFormatter = io::PROJStringFormatter::create(); projFormatter->setCRSExport(true); if (createPROJExtensionFromCustomProj(this, projFormatter.get(), true)) { formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4"); formatter->addQuotedString(projFormatter->toString()); formatter->endNode(); return true; } } else if (methodName == PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { auto projFormatter = io::PROJStringFormatter::create(); projFormatter->setCRSExport(true); formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4"); _exportToPROJString(projFormatter.get()); projFormatter->addParam("no_defs"); formatter->addQuotedString(projFormatter->toString()); formatter->endNode(); return true; } } return false; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Conversion::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(FormattingException) { const auto &l_method = method(); const auto &methodName = l_method->nameStr(); const int methodEPSGCode = l_method->getEPSGCode(); const bool isZUnitConversion = methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT || methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR; const bool isAffineParametric = methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION; const bool isSimilarity = methodEPSGCode == EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION; const bool isGeographicGeocentric = methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC; const bool isGeographicOffsets = methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS || methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS || methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS; const bool isHeightDepthReversal = methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL; const bool applySourceCRSModifiers = !isZUnitConversion && !isAffineParametric && !isSimilarity && !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric && !isGeographicOffsets && !isHeightDepthReversal; bool applyTargetCRSModifiers = applySourceCRSModifiers; if (formatter->getCRSExport()) { if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) { throw io::FormattingException("Transformation cannot be exported " "as a PROJ.4 string (but can be part " "of a PROJ pipeline)"); } } auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); if (methodName == PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) { const auto extractGeodeticCRSIfGeodeticCRSOrEquivalent = [](const crs::CRSPtr &crs) { auto geodCRS = std::dynamic_pointer_cast(crs); if (!geodCRS) { auto compoundCRS = std::dynamic_pointer_cast(crs); if (compoundCRS) { const auto &components = compoundCRS->componentReferenceSystems(); if (!components.empty()) { geodCRS = util::nn_dynamic_pointer_cast( components[0]); if (!geodCRS) { auto boundCRS = util::nn_dynamic_pointer_cast< crs::BoundCRS>(components[0]); if (boundCRS) { geodCRS = util::nn_dynamic_pointer_cast< crs::GeodeticCRS>(boundCRS->baseCRS()); } } } } else { auto boundCRS = std::dynamic_pointer_cast(crs); if (boundCRS) { geodCRS = util::nn_dynamic_pointer_cast( boundCRS->baseCRS()); } } } return geodCRS; }; auto sourceCRSGeod = dynamic_cast( extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_sourceCRS).get()); auto targetCRSGeod = dynamic_cast( extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_targetCRS).get()); if (sourceCRSGeod && targetCRSGeod) { auto sourceCRSGeog = dynamic_cast(sourceCRSGeod); auto targetCRSGeog = dynamic_cast(targetCRSGeod); bool isSrcGeocentricLat = sourceCRSGeod->isSphericalPlanetocentric(); bool isSrcGeographic = sourceCRSGeog != nullptr; bool isTargetGeocentricLat = targetCRSGeod->isSphericalPlanetocentric(); bool isTargetGeographic = targetCRSGeog != nullptr; if ((isSrcGeocentricLat && isTargetGeographic) || (isSrcGeographic && isTargetGeocentricLat)) { formatter->setOmitProjLongLatIfPossible(true); formatter->startInversion(); sourceCRSGeod->_exportToPROJString(formatter); formatter->stopInversion(); targetCRSGeod->_exportToPROJString(formatter); formatter->setOmitProjLongLatIfPossible(false); return; } } throw io::FormattingException("Invalid nature of source and/or " "targetCRS for Geographic latitude / " "Geocentric latitude" "conversion"); } crs::GeographicCRSPtr srcGeogCRS; if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) { crs::CRSPtr horiz = l_sourceCRS; const auto compound = dynamic_cast(l_sourceCRS.get()); if (compound) { const auto &components = compound->componentReferenceSystems(); if (!components.empty()) { horiz = components.front().as_nullable(); const auto boundCRS = dynamic_cast(horiz.get()); if (boundCRS) { horiz = boundCRS->baseCRS().as_nullable(); } } } auto srcGeodCRS = dynamic_cast(horiz.get()); if (srcGeodCRS) { srcGeogCRS = std::dynamic_pointer_cast(horiz); } if (srcGeodCRS && (srcGeogCRS || srcGeodCRS->isSphericalPlanetocentric())) { formatter->setOmitProjLongLatIfPossible(true); formatter->startInversion(); srcGeodCRS->_exportToPROJString(formatter); formatter->stopInversion(); formatter->setOmitProjLongLatIfPossible(false); } auto projCRS = dynamic_cast(horiz.get()); if (projCRS) { formatter->startInversion(); formatter->pushOmitZUnitConversion(); projCRS->addUnitConvertAndAxisSwap(formatter, false); formatter->popOmitZUnitConversion(); formatter->stopInversion(); } } const auto &convName = nameStr(); bool bConversionDone = false; bool bEllipsoidParametersDone = false; bool useApprox = false; bool insertAxisWSU = false; bool negateScaleFactor = false; if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { // Check for UTM int zone = 0; bool north = true; useApprox = formatter->getUseApproxTMerc() || l_method->getPrivate()->projMethodOverride_ == "tmerc approx" || l_method->getPrivate()->projMethodOverride_ == "utm approx"; if (isUTM(zone, north)) { bConversionDone = true; formatter->addStep("utm"); if (useApprox) { formatter->addParam("approx"); } formatter->addParam("zone", zone); if (!north) { formatter->addParam("south"); } } else if (l_targetCRS && parameterValueNumeric( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, common::UnitOfMeasure::SCALE_UNITY) < 0 && parameterValueNumeric(EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE) == 0 && parameterValueNumeric(EPSG_NAME_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE) == 0) { // Deal with ESRI:102470 that use Transverse Mercator with k=-1 // to indicate a westing-southing coordinate system, by inserting a // +axis=wsu and changing k to 1. auto projCRS = dynamic_cast(l_targetCRS.get()); if (projCRS) { const auto &axisList = projCRS->coordinateSystem()->axisList(); if (axisList[0]->direction() == cs::AxisDirection::EAST && axisList[1]->direction() == cs::AxisDirection::NORTH) { insertAxisWSU = true; negateScaleFactor = true; } } } } else if (methodEPSGCode == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) { const double azimuth = parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, common::UnitOfMeasure::DEGREE); const double angleRectifiedToSkewGrid = parameterValueNumeric( EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, common::UnitOfMeasure::DEGREE); // Map to Swiss Oblique Mercator / somerc if (std::fabs(azimuth - 90) < 1e-4 && std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { bConversionDone = true; formatter->addStep("somerc"); formatter->addParam( "lat_0", parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, common::UnitOfMeasure::DEGREE)); formatter->addParam( "lon_0", parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, common::UnitOfMeasure::DEGREE)); formatter->addParam( "k_0", parameterValueNumericAsSI( EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE)); formatter->addParam("x_0", parameterValueNumericAsSI( EPSG_CODE_PARAMETER_FALSE_EASTING)); formatter->addParam("y_0", parameterValueNumericAsSI( EPSG_CODE_PARAMETER_FALSE_NORTHING)); } } else if (methodEPSGCode == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) { const double azimuth = parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, common::UnitOfMeasure::DEGREE); const double angleRectifiedToSkewGrid = parameterValueNumeric( EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, common::UnitOfMeasure::DEGREE); // Map to Swiss Oblique Mercator / somerc if (std::fabs(azimuth - 90) < 1e-4 && std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) { bConversionDone = true; formatter->addStep("somerc"); formatter->addParam( "lat_0", parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, common::UnitOfMeasure::DEGREE)); formatter->addParam( "lon_0", parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, common::UnitOfMeasure::DEGREE)); formatter->addParam( "k_0", parameterValueNumericAsSI( EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE)); formatter->addParam( "x_0", parameterValueNumericAsSI( EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE)); formatter->addParam( "y_0", parameterValueNumericAsSI( EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE)); } } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED || methodEPSGCode == EPSG_CODE_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED) { double colatitude = parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, common::UnitOfMeasure::DEGREE); double latitudePseudoStandardParallel = parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, common::UnitOfMeasure::DEGREE); // 30deg 17' 17.30311'' = 30.28813975277777776 // 30deg 17' 17.303'' = 30.288139722222223 as used in GDAL WKT1 if (std::fabs(colatitude - 30.2881397) > 1e-7) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS); } if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL); } } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) { double latitudeOrigin = parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE); if (latitudeOrigin != 0) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); } } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) { const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0); if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN && std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) { throw io::FormattingException( "Unexpected presence of scale factor in Mercator (variant B)"); } double latitudeOrigin = parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE); if (latitudeOrigin != 0) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); } } else if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) { // We map TMSO to tmerc with axis=wsu. This only works if false easting // and northings are zero, which is the case in practice for South // African and Namibian EPSG CRS const auto falseEasting = parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE); if (falseEasting != 0) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_FALSE_EASTING); } const auto falseNorthing = parameterValueNumeric( EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE); if (falseNorthing != 0) { throw io::FormattingException( std::string("Unsupported value for ") + EPSG_NAME_PARAMETER_FALSE_NORTHING); } // PROJ.4 specific hack for webmercator } else if (formatter->getCRSExport() && methodEPSGCode == EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) { if (!createPROJ4WebMercator(this, formatter)) { throw io::FormattingException( std::string("Cannot export ") + EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR + " as PROJ.4 string outside of a ProjectedCRS context"); } bConversionDone = true; bEllipsoidParametersDone = true; applyTargetCRSModifiers = false; } else if (ci_equal(convName, "Popular Visualisation Mercator")) { if (formatter->getCRSExport()) { if (!createPROJ4WebMercator(this, formatter)) { throw io::FormattingException(concat( "Cannot export ", convName, " as PROJ.4 string outside of a ProjectedCRS context")); } applyTargetCRSModifiers = false; } else { formatter->addStep("webmerc"); if (l_sourceCRS) { datum::Ellipsoid::WGS84->_exportToPROJString(formatter); } } bConversionDone = true; bEllipsoidParametersDone = true; } else if (starts_with(methodName, "PROJ ")) { bConversionDone = true; createPROJExtensionFromCustomProj(this, formatter, false); } else if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) { double southPoleLat = parameterValueNumeric( PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, common::UnitOfMeasure::DEGREE); double southPoleLong = parameterValueNumeric( PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, common::UnitOfMeasure::DEGREE); double rotation = parameterValueNumeric( PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, common::UnitOfMeasure::DEGREE); formatter->addStep("ob_tran"); formatter->addParam("o_proj", "longlat"); formatter->addParam("o_lon_p", -rotation); formatter->addParam("o_lat_p", -southPoleLat); formatter->addParam("lon_0", southPoleLong); bConversionDone = true; } else if (ci_equal( methodName, PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) { double gridNorthPoleLatitude = parameterValueNumeric( PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION, common::UnitOfMeasure::DEGREE); double gridNorthPoleLongitude = parameterValueNumeric( PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION, common::UnitOfMeasure::DEGREE); double northPoleGridLongitude = parameterValueNumeric( PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION, common::UnitOfMeasure::DEGREE); formatter->addStep("ob_tran"); formatter->addParam("o_proj", "longlat"); formatter->addParam("o_lon_p", northPoleGridLongitude); formatter->addParam("o_lat_p", gridNorthPoleLatitude); formatter->addParam("lon_0", 180 + gridNorthPoleLongitude); bConversionDone = true; } else if (ci_equal(methodName, "Adams_Square_II")) { // Look for ESRI method and parameter names (to be opposed // to the OGC WKT2 names we use elsewhere, because there's no mapping // of those parameters to OGC WKT2) // We at least support ESRI:54098 WGS_1984_Adams_Square_II and // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square // More generally, we think our implementation of +proj=spilhaus // matches ESRI Adams_Square_II with just a sqrt(2) factor difference // for the scale factor, with a ~20 cm difference (difference in // ell_int_5() computation?) const double falseEasting = parameterValueNumeric( "False_Easting", common::UnitOfMeasure::METRE); const double falseNorthing = parameterValueNumeric( "False_Northing", common::UnitOfMeasure::METRE); const double scaleFactor = parameterValue("Scale_Factor", 0) ? parameterValueNumeric("Scale_Factor", common::UnitOfMeasure::SCALE_UNITY) : 1.0; const double azimuth = parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE); const double longitudeOfCenter = parameterValueNumeric( "Longitude_Of_Center", common::UnitOfMeasure::DEGREE); const double latitudeOfCenter = parameterValueNumeric( "Latitude_Of_Center", common::UnitOfMeasure::DEGREE); const double XYPlaneRotation = parameterValueNumeric( "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE); formatter->addStep("spilhaus"); formatter->addParam("lat_0", latitudeOfCenter); formatter->addParam("lon_0", longitudeOfCenter); formatter->addParam("azi", azimuth); formatter->addParam("k_0", M_SQRT2 * scaleFactor); formatter->addParam("rot", XYPlaneRotation); formatter->addParam("x_0", falseEasting); formatter->addParam("y_0", falseNorthing); bConversionDone = true; } else if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE) || ci_equal(methodName, PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND)) { const auto &scaleFactor = parameterValueMeasure( EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN); if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN && std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) { throw io::FormattingException( "Only scale factor = 1 handled for Peirce Quincuncial"); } const auto &latitudeOfOriginDeg = parameterValueMeasure( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); if (latitudeOfOriginDeg.unit().type() != common::UnitOfMeasure::Type::UNKNOWN && std::fabs(parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, common::UnitOfMeasure::DEGREE) - 90.0) > 1e-10) { throw io::FormattingException("Only latitude of natural origin = " "90 handled for Peirce Quincuncial"); } } else if (formatter->convention() == io::PROJStringFormatter::Convention::PROJ_5 && isZUnitConversion) { double convFactor; if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { convFactor = parameterValueNumericAsSI( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); } else { assert(methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR); const auto vertSrcCRS = dynamic_cast(l_sourceCRS.get()); const auto vertTgtCRS = dynamic_cast(l_targetCRS.get()); if (vertSrcCRS && vertTgtCRS) { const double convSrc = vertSrcCRS->coordinateSystem() ->axisList()[0] ->unit() .conversionToSI(); const double convDst = vertTgtCRS->coordinateSystem() ->axisList()[0] ->unit() .conversionToSI(); convFactor = convSrc / convDst; } else { throw io::FormattingException( "Export of " "EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR " "conversion to a PROJ string " "requires an input and output vertical CRS"); } } exportToPROJStringChangeVerticalUnit(formatter, convFactor); bConversionDone = true; bEllipsoidParametersDone = true; } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) { if (!srcGeogCRS) { throw io::FormattingException( "Export of Geographic/Topocentric conversion to a PROJ string " "requires an input geographic CRS"); } formatter->addStep("cart"); srcGeogCRS->ellipsoid()->_exportToPROJString(formatter); formatter->addStep("topocentric"); const auto latOrigin = parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, common::UnitOfMeasure::DEGREE); const auto longOrigin = parameterValueNumeric( EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, common::UnitOfMeasure::DEGREE); const auto heightOrigin = parameterValueNumeric( EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, common::UnitOfMeasure::METRE); formatter->addParam("lat_0", latOrigin); formatter->addParam("lon_0", longOrigin); formatter->addParam("h_0", heightOrigin); bConversionDone = true; } bool bAxisSpecFound = false; if (!bConversionDone) { const MethodMapping *mapping = getMapping(l_method.get()); if (mapping && mapping->proj_name_main) { formatter->addStep(mapping->proj_name_main); if (useApprox) { formatter->addParam("approx"); } if (mapping->proj_name_aux) { bool addAux = true; if (internal::starts_with(mapping->proj_name_aux, "axis=")) { if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK || mapping->epsg_code == EPSG_CODE_METHOD_KROVAK_MODIFIED) { auto projCRS = dynamic_cast( l_targetCRS.get()); if (projCRS) { const auto &axisList = projCRS->coordinateSystem()->axisList(); if (axisList[0]->direction() == cs::AxisDirection::WEST && axisList[1]->direction() == cs::AxisDirection::SOUTH) { formatter->addParam("czech"); addAux = false; } } } bAxisSpecFound = true; } // No need to add explicit f=0 or R_A if the ellipsoid is a // sphere if (strcmp(mapping->proj_name_aux, "f=0") == 0 || strcmp(mapping->proj_name_aux, "R_A") == 0) { crs::CRS *horiz = l_sourceCRS.get(); const auto compound = dynamic_cast(horiz); if (compound) { const auto &components = compound->componentReferenceSystems(); if (!components.empty()) { horiz = components.front().get(); const auto boundCRS = dynamic_cast(horiz); if (boundCRS) { horiz = boundCRS->baseCRS().get(); } } } auto geogCRS = dynamic_cast(horiz); if (geogCRS && geogCRS->ellipsoid()->isSphere()) { addAux = false; } } if (addAux) { auto kv = split(mapping->proj_name_aux, '='); if (kv.size() == 2) { formatter->addParam(kv[0], kv[1]); } else { formatter->addParam(mapping->proj_name_aux); } } } if (insertAxisWSU) { formatter->addParam("axis", "wsu"); } if (mapping->epsg_code == EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) { double latitudeStdParallel = parameterValueNumeric( EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, common::UnitOfMeasure::DEGREE); formatter->addParam("lat_0", (latitudeStdParallel >= 0) ? 90.0 : -90.0); } for (int i = 0; mapping->params[i] != nullptr; i++) { const auto *param = mapping->params[i]; if (!param->proj_name) { continue; } const auto &value = parameterValueMeasure(param->wkt2_name, param->epsg_code); double valueConverted = 0; if (value == nullMeasure) { // Deal with missing values. In an ideal world, this would // not happen if (param->epsg_code == EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) { valueConverted = 1.0; } if ((mapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A || mapping->epsg_code == EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) && param->epsg_code == EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) { // Do not use 0 as the default value for +gamma of // proj=omerc continue; } } else if (param->unit_type == common::UnitOfMeasure::Type::ANGULAR) { valueConverted = value.convertToUnit(common::UnitOfMeasure::DEGREE); } else { valueConverted = value.getSIValue(); } if (mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && strcmp(param->proj_name, "lat_1") == 0) { formatter->addParam(param->proj_name, valueConverted); formatter->addParam("lat_0", valueConverted); } else if ( negateScaleFactor && param->epsg_code == EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) { formatter->addParam(param->proj_name, -valueConverted); } else { formatter->addParam(param->proj_name, valueConverted); } } } else { if (!exportToPROJStringGeneric(formatter)) { throw io::FormattingException( concat("Unsupported conversion method: ", methodName)); } } } if (l_targetCRS && applyTargetCRSModifiers) { crs::CRS *horiz = l_targetCRS.get(); const auto compound = dynamic_cast(horiz); if (compound) { const auto &components = compound->componentReferenceSystems(); if (!components.empty()) { horiz = components.front().get(); } } auto derivedProjCRS = dynamic_cast(horiz); // horiz != nullptr: only to make clang static analyzer happy if (!bEllipsoidParametersDone && horiz != nullptr && derivedProjCRS == nullptr) { auto targetGeodCRS = horiz->extractGeodeticCRS(); auto targetGeogCRS = std::dynamic_pointer_cast(targetGeodCRS); if (targetGeogCRS) { if (formatter->getCRSExport()) { targetGeogCRS->addDatumInfoToPROJString(formatter); } else { targetGeogCRS->ellipsoid()->_exportToPROJString(formatter); targetGeogCRS->primeMeridian()->_exportToPROJString( formatter); } } else if (targetGeodCRS) { targetGeodCRS->ellipsoid()->_exportToPROJString(formatter); } } auto projCRS = dynamic_cast(horiz); if (projCRS == nullptr) { auto boundCRS = dynamic_cast(horiz); if (boundCRS) { projCRS = dynamic_cast( boundCRS->baseCRS().get()); } } if (projCRS) { formatter->pushOmitZUnitConversion(); projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound); formatter->popOmitZUnitConversion(); if (projCRS->hasOver()) { formatter->addParam("over"); } } else { if (derivedProjCRS) { formatter->pushOmitZUnitConversion(); derivedProjCRS->addUnitConvertAndAxisSwap(formatter); formatter->popOmitZUnitConversion(); } } auto derivedGeographicCRS = dynamic_cast(horiz); if (!formatter->getCRSExport() && derivedGeographicCRS) { formatter->setOmitProjLongLatIfPossible(true); derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter); formatter->setOmitProjLongLatIfPossible(false); } } } //! @endcond // --------------------------------------------------------------------------- /** \brief Return whether a conversion is a * * Universal Transverse Mercator conversion. * * @param[out] zone UTM zone number between 1 and 60. * @param[out] north true for UTM northern hemisphere, false for UTM southern * hemisphere. * @return true if it is a UTM conversion. */ bool Conversion::isUTM(int &zone, bool &north) const { zone = 0; north = true; if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { // Check for UTM bool bLatitudeNatOriginUTM = false; bool bScaleFactorUTM = false; bool bFalseEastingUTM = false; bool bFalseNorthingUTM = false; for (const auto &genOpParamvalue : parameterValues()) { auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (opParamvalue) { const auto epsg_code = opParamvalue->parameter()->getEPSGCode(); const auto &l_parameterValue = opParamvalue->parameterValue(); if (l_parameterValue->type() == ParameterValue::Type::MEASURE) { const auto &measure = l_parameterValue->value(); if (epsg_code == EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN && std::fabs(measure.value() - UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) { bLatitudeNatOriginUTM = true; } else if ( (epsg_code == EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN || epsg_code == EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) && measure.unit()._isEquivalentTo( common::UnitOfMeasure::DEGREE, util::IComparable::Criterion::EQUIVALENT)) { double dfZone = (measure.value() + 183.0) / 6.0; if (dfZone > 0.9 && dfZone < 60.1 && std::abs(dfZone - std::round(dfZone)) < 1e-10) { zone = static_cast(std::lround(dfZone)); } } else if ( epsg_code == EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN && measure.unit()._isEquivalentTo( common::UnitOfMeasure::SCALE_UNITY, util::IComparable::Criterion::EQUIVALENT) && std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) { bScaleFactorUTM = true; } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING && measure.value() == UTM_FALSE_EASTING && measure.unit()._isEquivalentTo( common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT)) { bFalseEastingUTM = true; } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_NORTHING && measure.unit()._isEquivalentTo( common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT)) { if (std::fabs(measure.value() - UTM_NORTH_FALSE_NORTHING) < 1e-10) { bFalseNorthingUTM = true; north = true; } else if (std::fabs(measure.value() - UTM_SOUTH_FALSE_NORTHING) < 1e-10) { bFalseNorthingUTM = true; north = false; } } } } } if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM && bFalseEastingUTM && bFalseNorthingUTM) { return true; } } return false; } // --------------------------------------------------------------------------- /** \brief Return a Conversion object where some parameters are better * identified. * * @return a new Conversion. */ ConversionNNPtr Conversion::identify() const { auto newConversion = Conversion::nn_make_shared(*this); newConversion->assignSelf(newConversion); if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) { // Check for UTM int zone = 0; bool north = true; if (isUTM(zone, north)) { newConversion->setProperties( getUTMConversionProperty(util::PropertyMap(), zone, north)); } } return newConversion; } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion with method Geographic 2D offsets * * This method is defined as * * EPSG:9619. * * @param properties See \ref general_properties of the conversion. * At minimum the name should be defined. * @param offsetLat Latitude offset to add. * @param offsetLong Longitude offset to add. * @return new conversion. */ ConversionNNPtr Conversion::createGeographic2DOffsets(const util::PropertyMap &properties, const common::Angle &offsetLat, const common::Angle &offsetLong) { return create( properties, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)}, VectorOfValues{offsetLat, offsetLong}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion with method Geographic 3D offsets * * This method is defined as * * EPSG:9660. * * @param properties See \ref general_properties of the Conversion. * At minimum the name should be defined. * @param offsetLat Latitude offset to add. * @param offsetLong Longitude offset to add. * @param offsetHeight Height offset to add. * @return new Conversion. */ ConversionNNPtr Conversion::createGeographic3DOffsets( const util::PropertyMap &properties, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight) { return create( properties, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, VectorOfValues{offsetLat, offsetLong, offsetHeight}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion with method Geographic 2D with * height * offsets * * This method is defined as * * EPSG:9618. * * @param properties See \ref general_properties of the Conversion. * At minimum the name should be defined. * @param offsetLat Latitude offset to add. * @param offsetLong Longitude offset to add. * @param offsetHeight Geoid undulation to add. * @return new Conversion. */ ConversionNNPtr Conversion::createGeographic2DWithHeightOffsets( const util::PropertyMap &properties, const common::Angle &offsetLat, const common::Angle &offsetLong, const common::Length &offsetHeight) { return create( properties, createMethodMapNameEPSGCode( EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_HEIGHT)}, VectorOfValues{offsetLat, offsetLong, offsetHeight}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion with method Vertical Offset. * * This method is defined as * * EPSG:9616. * * @param properties See \ref general_properties of the Conversion. * At minimum the name should be defined. * @param offsetHeight Geoid undulation to add. * @return new Conversion. */ ConversionNNPtr Conversion::createVerticalOffset(const util::PropertyMap &properties, const common::Length &offsetHeight) { return create(properties, createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET), VectorOfParameters{createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_VERTICAL_OFFSET)}, VectorOfValues{offsetHeight}); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion with method Affine Parametric * * This method is defined as * * EPSG:9624. * * @param properties See \ref general_properties of the Conversion. * At minimum the name should be defined. * @param A0 translation term for output first axis * @param A1 coefficient term for output first axis taking that is multiplied * with the value along the source first axis * @param A2 coefficient term for output first axis taking that is multiplied * with the value along the source second axis * @param B0 translation term for output second axis * @param B1 coefficient term for output second axis taking that is multiplied * with the value along the source first axis * @param B2 coefficient term for output second axis taking that is multiplied * with the value along the source second axis * @return new Conversion. * * @since 9.8 */ ConversionNNPtr Conversion::createAffineParametric( const util::PropertyMap &properties, const common::Measure &A0, const common::Scale &A1, const common::Scale &A2, const common::Measure &B0, const common::Scale &B1, const common::Scale &B2) { return create( properties, createMethodMapNameEPSGCode( EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION), VectorOfParameters{createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_A0), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_A1), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_A2), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_B0), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_B1), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_B2)}, VectorOfValues{A0, A1, A2, B0, B1, B2}); } // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/oputils.cpp000664 001750 001750 00000060434 15166171715 020643 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include #include "proj/coordinateoperation.hpp" #include "proj/crs.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "oputils.hpp" #include "parammappings.hpp" #include "proj_constants.h" #include // --------------------------------------------------------------------------- NS_PROJ_START using namespace internal; namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const char *BALLPARK_GEOCENTRIC_TRANSLATION = "Ballpark geocentric translation"; const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset"; const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation"; const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset"; const char *BALLPARK_VERTICAL_TRANSFORMATION = "ballpark vertical transformation"; const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT = "ballpark vertical transformation, without ellipsoid height to vertical " "height correction"; // --------------------------------------------------------------------------- OperationParameterNNPtr createOpParamNameEPSGCode(int code) { const char *name = OperationParameter::getNameForEPSGCode(code); assert(name); return OperationParameter::create(createMapNameEPSGCode(name, code)); } // --------------------------------------------------------------------------- util::PropertyMap createMethodMapNameEPSGCode(int code) { const char *name = nullptr; size_t nMethodNameCodes = 0; const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); for (size_t i = 0; i < nMethodNameCodes; ++i) { const auto &tuple = methodNameCodes[i]; if (tuple.epsg_code == code) { name = tuple.name; break; } } assert(name); return createMapNameEPSGCode(name, code); } // --------------------------------------------------------------------------- util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { return util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, code); } // --------------------------------------------------------------------------- util::PropertyMap createMapNameEPSGCode(const char *name, int code) { return util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, code); } // --------------------------------------------------------------------------- util::PropertyMap &addDomains(util::PropertyMap &map, const common::ObjectUsage *obj) { auto ar = util::ArrayOfBaseObject::create(); for (const auto &domain : obj->domains()) { ar->add(domain); } if (!ar->empty()) { map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); } return map; } // --------------------------------------------------------------------------- static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { auto geod = dynamic_cast(crs.get()); if (geod) { if (geod->isGeocentric()) { return " (geocentric)"; } auto geog = dynamic_cast(geod); if (geog) { if (geog->coordinateSystem()->axisList().size() == 2) { return " (geog2D)"; } else { return " (geog3D)"; } } } return ""; } // --------------------------------------------------------------------------- std::string buildOpName(const char *opType, const crs::CRSPtr &source, const crs::CRSPtr &target) { std::string res(opType); const auto &srcName = source->nameStr(); const auto &targetName = target->nameStr(); const char *srcQualifier = ""; const char *targetQualifier = ""; if (srcName == targetName) { srcQualifier = getCRSQualifierStr(source); targetQualifier = getCRSQualifierStr(target); if (strcmp(srcQualifier, targetQualifier) == 0) { srcQualifier = ""; targetQualifier = ""; } } res += " from "; res += srcName; res += srcQualifier; res += " to "; res += targetName; res += targetQualifier; return res; } // --------------------------------------------------------------------------- void addModifiedIdentifier(util::PropertyMap &map, const common::IdentifiedObject *obj, bool inverse, bool derivedFrom) { // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE // as identifier. auto ar = util::ArrayOfBaseObject::create(); for (const auto &idSrc : obj->identifiers()) { auto authName = *(idSrc->codeSpace()); const auto &srcCode = idSrc->code(); if (derivedFrom) { authName = concat("DERIVED_FROM(", authName, ")"); } if (inverse) { if (starts_with(authName, "INVERSE(") && authName.back() == ')') { authName = authName.substr(strlen("INVERSE(")); authName.resize(authName.size() - 1); } else { authName = concat("INVERSE(", authName, ")"); } } auto idsProp = util::PropertyMap().set( metadata::Identifier::CODESPACE_KEY, authName); ar->add(metadata::Identifier::create(srcCode, idsProp)); } if (!ar->empty()) { map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); } } // --------------------------------------------------------------------------- util::PropertyMap createPropertiesForInverse(const OperationMethodNNPtr &method) { util::PropertyMap map; const std::string &forwardName = method->nameStr(); if (!forwardName.empty()) { if (starts_with(forwardName, INVERSE_OF)) { map.set(common::IdentifiedObject::NAME_KEY, forwardName.substr(INVERSE_OF.size())); } else { map.set(common::IdentifiedObject::NAME_KEY, INVERSE_OF + forwardName); } } addModifiedIdentifier(map, method.get(), true, false); return map; } // --------------------------------------------------------------------------- util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, bool approximateInversion) { assert(op); util::PropertyMap map; // The domain(s) are unchanged by the inverse operation addDomains(map, op); const std::string &forwardName = op->nameStr(); // Forge a name for the inverse, either from the forward name, or // from the source and target CRS names const char *opType; if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) { opType = BALLPARK_GEOCENTRIC_TRANSLATION; } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) { opType = BALLPARK_GEOGRAPHIC_OFFSET; } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { opType = NULL_GEOGRAPHIC_OFFSET; } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { opType = NULL_GEOCENTRIC_TRANSLATION; } else if (dynamic_cast(op) || starts_with(forwardName, "Transformation from ")) { opType = "Transformation"; } else if (dynamic_cast(op)) { opType = "Conversion"; } else { opType = "Operation"; } auto sourceCRS = op->sourceCRS(); auto targetCRS = op->targetCRS(); std::string name; if (!forwardName.empty()) { if (dynamic_cast(op) == nullptr && dynamic_cast(op) == nullptr && (starts_with(forwardName, INVERSE_OF) || forwardName.find(" + ") != std::string::npos)) { std::vector tokens; std::string curToken; bool inString = false; for (size_t i = 0; i < forwardName.size(); ++i) { if (inString) { curToken += forwardName[i]; if (forwardName[i] == '\'') { inString = false; } } else if (i + 3 < forwardName.size() && memcmp(&forwardName[i], " + ", 3) == 0) { tokens.push_back(curToken); curToken.clear(); i += 2; } else if (forwardName[i] == '\'') { inString = true; curToken += forwardName[i]; } else { curToken += forwardName[i]; } } if (!curToken.empty()) { tokens.push_back(std::move(curToken)); } for (size_t i = tokens.size(); i > 0;) { i--; if (!name.empty()) { name += " + "; } if (starts_with(tokens[i], INVERSE_OF)) { name += tokens[i].substr(INVERSE_OF.size()); } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME || tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) { name += tokens[i]; } else { name += INVERSE_OF + tokens[i]; } } } else if (!sourceCRS || !targetCRS || forwardName != buildOpName(opType, sourceCRS, targetCRS)) { if (forwardName.find(" + ") != std::string::npos) { name = INVERSE_OF + '\'' + forwardName + '\''; } else { name = INVERSE_OF + forwardName; } } } if (name.empty() && sourceCRS && targetCRS) { name = buildOpName(opType, targetCRS, sourceCRS); } if (approximateInversion) { name += " (approx. inversion)"; } if (!name.empty()) { map.set(common::IdentifiedObject::NAME_KEY, name); } const std::string &remarks = op->remarks(); if (!remarks.empty()) { map.set(common::IdentifiedObject::REMARKS_KEY, remarks); } addModifiedIdentifier(map, op, true, derivedFrom); const auto so = dynamic_cast(op); if (so) { const int soMethodEPSGCode = so->method()->getEPSGCode(); if (soMethodEPSGCode > 0) { map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode); } } return map; } // --------------------------------------------------------------------------- util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, const std::string &defaultName) { if (!properties.get(common::IdentifiedObject::NAME_KEY)) { return util::PropertyMap(properties) .set(common::IdentifiedObject::NAME_KEY, defaultName); } else { return properties; } } // --------------------------------------------------------------------------- static std::string createEntryEqParam(const std::string &a, const std::string &b) { return a < b ? a + b : b + a; } static std::set buildSetEquivalentParameters() { std::set set; const char *const listOfEquivalentParameterNames[][7] = { {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr}, {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr}, {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr}, {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr}, {"satellite_height", "height", nullptr}, {EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr}, {EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr}, {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr}, {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER, EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel", nullptr}, {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER, EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr}, {EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, nullptr}, {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr}, }; for (const auto ¶mList : listOfEquivalentParameterNames) { for (size_t i = 0; paramList[i]; i++) { auto a = metadata::Identifier::canonicalizeName(paramList[i]); for (size_t j = i + 1; paramList[j]; j++) { auto b = metadata::Identifier::canonicalizeName(paramList[j]); set.insert(createEntryEqParam(a, b)); } } } return set; } bool areEquivalentParameters(const std::string &a, const std::string &b) { static const std::set setEquivalentParameters = buildSetEquivalentParameters(); auto a_can = metadata::Identifier::canonicalizeName(a); auto b_can = metadata::Identifier::canonicalizeName(b); return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != setEquivalentParameters.end(); } // --------------------------------------------------------------------------- bool isTimeDependent(const std::string &methodName) { return ci_find(methodName, "Time dependent") != std::string::npos || ci_find(methodName, "Time-dependent") != std::string::npos; } // --------------------------------------------------------------------------- std::string computeConcatenatedName( const std::vector &flattenOps) { std::string name; for (const auto &subOp : flattenOps) { if (!name.empty()) { name += " + "; } const auto &l_name = subOp->nameStr(); if (l_name.empty()) { name += "unnamed"; } else { name += l_name; } } return name; } // --------------------------------------------------------------------------- metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, bool conversionExtentIsWorld, bool &emptyIntersection) { auto conv = dynamic_cast(op.get()); if (conv) { emptyIntersection = false; return metadata::Extent::WORLD; } const auto &domains = op->domains(); if (!domains.empty()) { emptyIntersection = false; return domains[0]->domainOfValidity(); } auto concatenated = dynamic_cast(op.get()); if (!concatenated) { emptyIntersection = false; return nullptr; } return getExtent(concatenated->operations(), conversionExtentIsWorld, emptyIntersection); } // --------------------------------------------------------------------------- static const metadata::ExtentPtr nullExtent{}; const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { const auto &domains = crs->domains(); if (!domains.empty()) { return domains[0]->domainOfValidity(); } const auto *boundCRS = dynamic_cast(crs.get()); if (boundCRS) { return getExtent(boundCRS->baseCRS()); } return nullExtent; } const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, bool &approxOut) { const auto &rawExtent(getExtent(crs)); approxOut = false; if (rawExtent) return rawExtent; const auto compoundCRS = dynamic_cast(crs.get()); if (compoundCRS) { // For a compoundCRS, take the intersection of the extent of its // components. const auto &components = compoundCRS->componentReferenceSystems(); metadata::ExtentPtr extent; approxOut = true; for (const auto &component : components) { const auto &componentExtent(getExtent(component)); if (extent && componentExtent) extent = extent->intersection(NN_NO_CHECK(componentExtent)); else if (componentExtent) extent = componentExtent; } return extent; } return rawExtent; } // --------------------------------------------------------------------------- metadata::ExtentPtr getExtent(const std::vector &ops, bool conversionExtentIsWorld, bool &emptyIntersection) { metadata::ExtentPtr res = nullptr; for (const auto &subop : ops) { const auto &subExtent = getExtent(subop, conversionExtentIsWorld, emptyIntersection); if (!subExtent) { if (emptyIntersection) { return nullptr; } continue; } if (res == nullptr) { res = subExtent; } else { res = res->intersection(NN_NO_CHECK(subExtent)); if (!res) { emptyIntersection = true; return nullptr; } } } emptyIntersection = false; return res; } // --------------------------------------------------------------------------- // Returns the accuracy of an operation, or -1 if unknown double getAccuracy(const CoordinateOperationNNPtr &op) { if (dynamic_cast(op.get())) { // A conversion is perfectly accurate. return 0.0; } double accuracy = -1.0; const auto &accuracies = op->coordinateOperationAccuracies(); if (!accuracies.empty()) { try { accuracy = c_locale_stod(accuracies[0]->value()); } catch (const std::exception &) { } } else { auto concatenated = dynamic_cast(op.get()); if (concatenated) { accuracy = getAccuracy(concatenated->operations()); } } return accuracy; } // --------------------------------------------------------------------------- // Returns the accuracy of a set of concatenated operations, or -1 if unknown double getAccuracy(const std::vector &ops) { double accuracy = -1.0; double next_accuracy = -1.0; for (size_t i = 0; i < ops.size(); ++i) { const double subops_accuracy = next_accuracy >= 0 ? next_accuracy : getAccuracy(ops[i]); if (subops_accuracy < 0.0) { return -1.0; } if (accuracy < 0.0) { accuracy = 0.0; } next_accuracy = -1.0; if (subops_accuracy > 0 && i + 1 < ops.size()) { next_accuracy = getAccuracy(ops[i + 1]); if (next_accuracy > 0) { const auto crs1 = ops[i]->sourceCRS(); const auto crsMiddle = ops[i]->targetCRS(); const auto crs2 = ops[i + 1]->targetCRS(); // Special case when doing ETRS89-XXX -> ETRS89/ETRFzzzz -> // ETRS89-YYY and at least one of the 2 operations is a no-op constexpr double ACCURACY_ENSEMBLE_ETRS89 = 0.1; if (crs1 && crsMiddle && crs2 && std::max(subops_accuracy, next_accuracy) <= ACCURACY_ENSEMBLE_ETRS89 && (crsMiddle->nameStr() == "ETRS89" || starts_with(crsMiddle->nameStr(), "ETRF")) && starts_with(crs1->nameStr(), "ETRS89-") && starts_with(crs2->nameStr(), "ETRS89-")) { const auto IsNoOp = [](const CoordinateOperationNNPtr &op) { auto formatter = io::PROJStringFormatter::create(); try { return op->exportToPROJString(formatter.get()) == "+proj=noop"; } catch (const std::exception &) { } return false; }; if (IsNoOp(ops[i]) || IsNoOp(ops[i + 1])) { accuracy += std::max(subops_accuracy, next_accuracy); next_accuracy = -1.0; ++i; continue; } } } } accuracy += subops_accuracy; } return accuracy; } // --------------------------------------------------------------------------- void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, io::WKTFormatter *formatter) { auto l_sourceCRS = co->sourceCRS(); assert(l_sourceCRS); auto l_targetCRS = co->targetCRS(); assert(l_targetCRS); const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const bool canExportCRSId = (isWKT2 && formatter->use2019Keywords() && !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())); const bool hasDomains = !co->domains().empty(); if (hasDomains) { formatter->pushDisableUsage(); } formatter->startNode(io::WKTConstants::SOURCECRS, false); if (canExportCRSId && !l_sourceCRS->identifiers().empty()) { // fake that top node has no id, so that the sourceCRS id is // considered formatter->pushHasId(false); l_sourceCRS->_exportToWKT(formatter); formatter->popHasId(); } else { l_sourceCRS->_exportToWKT(formatter); } formatter->endNode(); formatter->startNode(io::WKTConstants::TARGETCRS, false); if (canExportCRSId && !l_targetCRS->identifiers().empty()) { // fake that top node has no id, so that the targetCRS id is // considered formatter->pushHasId(false); l_targetCRS->_exportToWKT(formatter); formatter->popHasId(); } else { l_targetCRS->_exportToWKT(formatter); } formatter->endNode(); if (hasDomains) { formatter->popDisableUsage(); } } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/operationmethod_private.hpp000664 001750 001750 00000004372 15166171715 024103 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef OPERATIONMETHOD_PRIVATE_HPP #define OPERATIONMETHOD_PRIVATE_HPP #include "proj/coordinateoperation.hpp" #include "proj/util.hpp" // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct OperationMethod::Private { util::optional formula_{}; util::optional formulaCitation_{}; std::vector parameters_{}; std::string projMethodOverride_{}; }; //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END #endif // OPERATIONMETHOD_PRIVATE_HPP proj-9.8.1/src/iso19111/operation/parammappings.hpp000664 001750 001750 00000007631 15166171715 022010 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PARAMMAPPINGS_HPP #define PARAMMAPPINGS_HPP #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/coordinateoperation.hpp" #include "proj/util.hpp" // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress extern const char *WKT1_LATITUDE_OF_ORIGIN; extern const char *WKT1_CENTRAL_MERIDIAN; extern const char *WKT1_SCALE_FACTOR; extern const char *WKT1_FALSE_EASTING; extern const char *WKT1_FALSE_NORTHING; extern const char *WKT1_STANDARD_PARALLEL_1; extern const char *WKT1_STANDARD_PARALLEL_2; extern const char *WKT1_LATITUDE_OF_CENTER; extern const char *WKT1_LONGITUDE_OF_CENTER; extern const char *WKT1_AZIMUTH; extern const char *WKT1_RECTIFIED_GRID_ANGLE; struct ParamMapping { const char *wkt2_name; const int epsg_code; const char *wkt1_name; const common::UnitOfMeasure::Type unit_type; const char *proj_name; }; struct MethodMapping { const char *wkt2_name; const int epsg_code; const char *wkt1_name; const char *proj_name_main; const char *proj_name_aux; const ParamMapping *const *params; }; extern const ParamMapping paramLatitudeNatOrigin; const MethodMapping *getProjectionMethodMappings(size_t &nElts); const MethodMapping *getOtherMethodMappings(size_t &nElts); struct MethodNameCode { const char *name; int epsg_code; }; const MethodNameCode *getMethodNameCodes(size_t &nElts); struct ParamNameCode { const char *name; int epsg_code; }; const ParamNameCode *getParamNameCodes(size_t &nElts); const MethodMapping *getMapping(int epsg_code) noexcept; const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept; const MethodMapping *getMapping(const char *wkt2_name) noexcept; const MethodMapping *getMapping(const OperationMethod *method) noexcept; std::vector getMappingsFromPROJName(const std::string &projName); const ParamMapping *getMapping(const MethodMapping *mapping, const OperationParameterNNPtr ¶m); const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, const std::string &wkt1_name); //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END #endif // PARAMMAPPINGS_HPP proj-9.8.1/src/iso19111/operation/oputils.hpp000664 001750 001750 00000011275 15166171715 020647 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef OPUTILS_HPP #define OPUTILS_HPP #include "proj/coordinateoperation.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress extern const common::Measure nullMeasure; extern const std::string INVERSE_OF; extern const char *BALLPARK_GEOCENTRIC_TRANSLATION; extern const char *NULL_GEOGRAPHIC_OFFSET; extern const char *NULL_GEOCENTRIC_TRANSLATION; extern const char *BALLPARK_GEOGRAPHIC_OFFSET; extern const char *BALLPARK_VERTICAL_TRANSFORMATION; extern const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT; extern const std::string AXIS_ORDER_CHANGE_2D_NAME; extern const std::string AXIS_ORDER_CHANGE_3D_NAME; OperationParameterNNPtr createOpParamNameEPSGCode(int code); util::PropertyMap createMethodMapNameEPSGCode(int code); util::PropertyMap createMapNameEPSGCode(const std::string &name, int code); util::PropertyMap createMapNameEPSGCode(const char *name, int code); util::PropertyMap &addDomains(util::PropertyMap &map, const common::ObjectUsage *obj); std::string buildOpName(const char *opType, const crs::CRSPtr &source, const crs::CRSPtr &target); void addModifiedIdentifier(util::PropertyMap &map, const common::IdentifiedObject *obj, bool inverse, bool derivedFrom); util::PropertyMap createPropertiesForInverse(const OperationMethodNNPtr &method); util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom, bool approximateInversion); util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, const std::string &defaultName); bool areEquivalentParameters(const std::string &a, const std::string &b); bool isTimeDependent(const std::string &methodName); std::string computeConcatenatedName( const std::vector &flattenOps); metadata::ExtentPtr getExtent(const std::vector &ops, bool conversionExtentIsWorld, bool &emptyIntersection); metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, bool conversionExtentIsWorld, bool &emptyIntersection); const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs); const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, bool &approxOut); double getAccuracy(const CoordinateOperationNNPtr &op); double getAccuracy(const std::vector &ops); void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, io::WKTFormatter *formatter); //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END #endif // OPUTILS_HPP proj-9.8.1/src/iso19111/operation/esriparammappings.cpp000664 001750 001750 00000202757 15166171715 022674 0ustar00eveneven000000 000000 // This file was generated by scripts/build_esri_projection_mapping.py. DO NOT // EDIT ! /****************************************************************************** * * Project: PROJ * Purpose: Mappings between ESRI projection and parameters names and WKT2 * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "esriparammappings.hpp" #include "proj_constants.h" #include "proj/internal/internal.hpp" NS_PROJ_START using namespace internal; namespace operation { //! @cond Doxygen_Suppress const ESRIParamMapping paramsESRI_Equidistant_Cylindrical[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Plate_Carree[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Miller_Cylindrical[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Mercator[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Gauss_Kruger[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Transverse_Mercator[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Transverse_Mercator_Complex[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Albers[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Sinusoidal[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Mollweide[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Eckert_I[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Eckert_II[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Eckert_III[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Eckert_IV[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Eckert_V[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Eckert_VI[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Gall_Stereographic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Winkel_I[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Winkel_II[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt1[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt2[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt3[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, {"Scale_Factor", nullptr, 0, "1.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Lambert_Conformal_Conic_alt4[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Polyconic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Quartic_Authalic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Loximuthal[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Central_Parallel", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Bonne[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Stereographic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Polar_Stereographic_Variant_A[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Equidistant_Conic[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {"Standard_Parallel_2", EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Cassini[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", nullptr, 0, "1.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Van_der_Grinten_I[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Robinson[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Two_Point_Equidistant[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Azimuthal_Equidistant[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Lambert_Azimuthal_Equal_Area[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Cylindrical_Equal_Area[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Behrmann[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", true}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "30.0", true}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_1st_Point", "Latitude of 1st point", 0, "0.0", false}, {"Latitude_Of_2nd_Point", "Latitude of 2nd point", 0, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_1st_Point", "Longitude of 1st point", 0, "0.0", false}, {"Longitude_Of_2nd_Point", "Longitude of 2nd point", 0, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Double_Stereographic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Krovak_alt1[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Pseudo_Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {"X_Scale", nullptr, 0, "1.0", false}, {"Y_Scale", nullptr, 0, "1.0", false}, {"XY_Plane_Rotation", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Krovak_alt2[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Pseudo_Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {"X_Scale", nullptr, 0, "-1.0", false}, {"Y_Scale", nullptr, 0, "1.0", false}, {"XY_Plane_Rotation", nullptr, 0, "90.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_New_Zealand_Map_Grid[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Origin", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Orthographic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Local[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Winkel_Tripel[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Aitoff[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Flat_Polar_Quartic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Craster_Parabolic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Gnomonic[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Times[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Vertical_Near_Side_Perspective[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, "0.0", false}, {"Height", EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Stereographic_North_Pole[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Stereographic_South_Pole[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin[] = {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; const ESRIParamMapping paramsESRI_Rectified_Skew_Orthomorphic_Center[] = { {"False_Easting", EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {"XY_Plane_Rotation", EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt1[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Option", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt2[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Option", nullptr, 0, "1.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Goode_Homolosine_alt3[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Option", nullptr, 0, "2.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Equidistant_Cylindrical_Ellipsoidal[] = {{"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Laborde_Oblique_Mercator[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, "0.0", false}, {"Azimuth", EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Gnomonic_Ellipsoidal[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Wagner_IV[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Wagner_V[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Wagner_VII[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Natural_Earth[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Natural_Earth_II[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Patterson[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Compact_Miller[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Geostationary_Satellite[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Height", "Satellite Height", 0, "0.0", false}, {"Option", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Mercator_Auxiliary_Sphere[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Auxiliary_Sphere_Type", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Mercator_Variant_A[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Mercator_Variant_C[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Standard_Parallel_1", EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, "0.0", false}, {"Latitude_Of_Origin", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Transverse_Cylindrical_Equal_Area[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_IGAC_Plano_Cartesiano[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Longitude_Of_Center", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Center", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Height", EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Equal_Earth[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Peirce_Quincuncial_alt1[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Option", nullptr, 0, "0.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIParamMapping paramsESRI_Peirce_Quincuncial_alt2[] = { {"False_Easting", EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, "0.0", false}, {"False_Northing", EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, "0.0", false}, {"Central_Meridian", EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Scale_Factor", EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, "0.0", false}, {"Latitude_Of_Origin", EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, "0.0", false}, {"Option", nullptr, 0, "1.0", false}, {nullptr, nullptr, 0, "0.0", false}}; static const ESRIMethodMapping esriMappings[] = { {"Equidistant_Cylindrical", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Equidistant_Cylindrical}, {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Plate_Carree}, {"Plate_Carree", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, paramsESRI_Plate_Carree}, {"Miller_Cylindrical", PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, paramsESRI_Miller_Cylindrical}, {"Mercator", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator}, {"Gauss_Kruger", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Gauss_Kruger}, {"Transverse_Mercator", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Transverse_Mercator}, {"Transverse_Mercator_Complex", EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, paramsESRI_Transverse_Mercator_Complex}, {"Albers", EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, paramsESRI_Albers}, {"Sinusoidal", PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, paramsESRI_Sinusoidal}, {"Mollweide", PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, paramsESRI_Mollweide}, {"Eckert_I", PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, paramsESRI_Eckert_I}, {"Eckert_II", PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, paramsESRI_Eckert_II}, {"Eckert_III", PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, paramsESRI_Eckert_III}, {"Eckert_IV", PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, paramsESRI_Eckert_IV}, {"Eckert_V", PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, paramsESRI_Eckert_V}, {"Eckert_VI", PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, paramsESRI_Eckert_VI}, {"Gall_Stereographic", PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, paramsESRI_Gall_Stereographic}, {"Winkel_I", "Winkel I", 0, paramsESRI_Winkel_I}, {"Winkel_II", "Winkel II", 0, paramsESRI_Winkel_II}, {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, paramsESRI_Lambert_Conformal_Conic_alt1}, {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, paramsESRI_Lambert_Conformal_Conic_alt2}, {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, paramsESRI_Lambert_Conformal_Conic_alt3}, {"Lambert_Conformal_Conic", EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, paramsESRI_Lambert_Conformal_Conic_alt4}, {"Polyconic", EPSG_NAME_METHOD_AMERICAN_POLYCONIC, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, paramsESRI_Polyconic}, {"Quartic_Authalic", "Quartic Authalic", 0, paramsESRI_Quartic_Authalic}, {"Loximuthal", "Loximuthal", 0, paramsESRI_Loximuthal}, {"Bonne", EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, paramsESRI_Bonne}, {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin", PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, paramsESRI_Hotine_Oblique_Mercator_Two_Point_Natural_Origin}, {"Stereographic", PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, paramsESRI_Stereographic}, {"Polar_Stereographic_Variant_A", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, paramsESRI_Polar_Stereographic_Variant_A}, {"Equidistant_Conic", EPSG_NAME_METHOD_EQUIDISTANT_CONIC, EPSG_CODE_METHOD_EQUIDISTANT_CONIC, paramsESRI_Equidistant_Conic}, {"Cassini", EPSG_NAME_METHOD_CASSINI_SOLDNER, EPSG_CODE_METHOD_CASSINI_SOLDNER, paramsESRI_Cassini}, {"Van_der_Grinten_I", PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, paramsESRI_Van_der_Grinten_I}, {"Robinson", PROJ_WKT2_NAME_METHOD_ROBINSON, 0, paramsESRI_Robinson}, {"Two_Point_Equidistant", PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, paramsESRI_Two_Point_Equidistant}, {"Azimuthal_Equidistant", EPSG_NAME_METHOD_AZIMUTHAL_EQUIDISTANT, EPSG_CODE_METHOD_AZIMUTHAL_EQUIDISTANT, paramsESRI_Azimuthal_Equidistant}, {"Lambert_Azimuthal_Equal_Area", EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, paramsESRI_Lambert_Azimuthal_Equal_Area}, {"Cylindrical_Equal_Area", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, paramsESRI_Cylindrical_Equal_Area}, {"Behrmann", EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, paramsESRI_Behrmann}, {"Hotine_Oblique_Mercator_Two_Point_Center", PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, paramsESRI_Hotine_Oblique_Mercator_Two_Point_Center}, {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin", EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin}, {"Hotine_Oblique_Mercator_Azimuth_Center", EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center}, {"Double_Stereographic", EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, paramsESRI_Double_Stereographic}, {"Krovak", EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, paramsESRI_Krovak_alt1}, {"Krovak", EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, paramsESRI_Krovak_alt2}, {"New_Zealand_Map_Grid", EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, paramsESRI_New_Zealand_Map_Grid}, {"Orthographic", PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, paramsESRI_Orthographic}, {"Local", EPSG_NAME_METHOD_LOCAL_ORTHOGRAPHIC, EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC, paramsESRI_Local}, {"Winkel_Tripel", "Winkel Tripel", 0, paramsESRI_Winkel_Tripel}, {"Aitoff", "Aitoff", 0, paramsESRI_Aitoff}, {"Flat_Polar_Quartic", PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, paramsESRI_Flat_Polar_Quartic}, {"Craster_Parabolic", "Craster Parabolic", 0, paramsESRI_Craster_Parabolic}, {"Gnomonic", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, paramsESRI_Gnomonic}, {"Times", PROJ_WKT2_NAME_METHOD_TIMES, 0, paramsESRI_Times}, {"Vertical_Near_Side_Perspective", EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, paramsESRI_Vertical_Near_Side_Perspective}, {"Stereographic_North_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, paramsESRI_Stereographic_North_Pole}, {"Stereographic_South_Pole", EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, paramsESRI_Stereographic_South_Pole}, {"Rectified_Skew_Orthomorphic_Natural_Origin", EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin}, {"Rectified_Skew_Orthomorphic_Center", EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, paramsESRI_Rectified_Skew_Orthomorphic_Center}, {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, 0, paramsESRI_Goode_Homolosine_alt1}, {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, paramsESRI_Goode_Homolosine_alt2}, {"Goode_Homolosine", PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, paramsESRI_Goode_Homolosine_alt3}, {"Equidistant_Cylindrical_Ellipsoidal", EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, paramsESRI_Equidistant_Cylindrical_Ellipsoidal}, {"Laborde_Oblique_Mercator", EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, paramsESRI_Laborde_Oblique_Mercator}, {"Gnomonic_Ellipsoidal", PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, paramsESRI_Gnomonic_Ellipsoidal}, {"Wagner_IV", PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, paramsESRI_Wagner_IV}, {"Wagner_V", PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, paramsESRI_Wagner_V}, {"Wagner_VII", PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, paramsESRI_Wagner_VII}, {"Natural_Earth", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, paramsESRI_Natural_Earth}, {"Natural_Earth_II", PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, paramsESRI_Natural_Earth_II}, {"Patterson", PROJ_WKT2_NAME_METHOD_PATTERSON, 0, paramsESRI_Patterson}, {"Compact_Miller", PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, paramsESRI_Compact_Miller}, {"Geostationary_Satellite", PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, paramsESRI_Geostationary_Satellite}, {"Mercator_Auxiliary_Sphere", EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, paramsESRI_Mercator_Auxiliary_Sphere}, {"Mercator_Variant_A", EPSG_NAME_METHOD_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, paramsESRI_Mercator_Variant_A}, {"Mercator_Variant_C", EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, paramsESRI_Mercator_Variant_C}, {"Transverse_Cylindrical_Equal_Area", "Transverse Cylindrical Equal Area", 0, paramsESRI_Transverse_Cylindrical_Equal_Area}, {"IGAC_Plano_Cartesiano", EPSG_NAME_METHOD_COLOMBIA_URBAN, EPSG_CODE_METHOD_COLOMBIA_URBAN, paramsESRI_IGAC_Plano_Cartesiano}, {"Equal_Earth", EPSG_NAME_METHOD_EQUAL_EARTH, EPSG_CODE_METHOD_EQUAL_EARTH, paramsESRI_Equal_Earth}, {"Peirce_Quincuncial", PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE, 0, paramsESRI_Peirce_Quincuncial_alt1}, {"Peirce_Quincuncial", PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND, 0, paramsESRI_Peirce_Quincuncial_alt2}, }; // --------------------------------------------------------------------------- const ESRIMethodMapping *getEsriMappings(size_t &nElts) { nElts = sizeof(esriMappings) / sizeof(esriMappings[0]); return esriMappings; } // --------------------------------------------------------------------------- std::vector getMappingsFromESRI(const std::string &esri_name) { std::vector res; for (const auto &mapping : esriMappings) { if (ci_equal(esri_name, mapping.esri_name)) { res.push_back(&mapping); } } return res; } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/operation/coordinateoperationfactory.cpp000664 001750 001750 00001267075 15166171715 024617 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/crs.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/datum_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/tracing.hpp" #include "coordinateoperation_internal.hpp" #include "coordinateoperation_private.hpp" #include "oputils.hpp" #include "vectorofvaluesparams.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // M_PI // clang-format on #include "proj_constants.h" #include #include #include #include #include #include #include #include // #define TRACE_CREATE_OPERATIONS // #define DEBUG_SORT // #define DEBUG_CONCATENATED_OPERATION #if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION) #include void dumpWKT(const NS_PROJ::crs::CRS *crs); void dumpWKT(const NS_PROJ::crs::CRS *crs) { auto f(NS_PROJ::io::WKTFormatter::create( NS_PROJ::io::WKTFormatter::Convention::WKT2_2019)); std::cerr << crs->exportToWKT(f.get()) << std::endl; } void dumpWKT(const NS_PROJ::crs::CRSPtr &crs); void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); } void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs); void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) { dumpWKT(crs.as_nullable().get()); } void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs); void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); } void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs); void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) { dumpWKT(crs.as_nullable().get()); } #endif using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- #ifdef TRACE_CREATE_OPERATIONS //! @cond Doxygen_Suppress static std::string objectAsStr(const common::IdentifiedObject *obj) { std::string ret(obj->nameStr()); const auto &ids = obj->identifiers(); if (const auto *geogCRS = dynamic_cast(obj)) { if (geogCRS->coordinateSystem()->axisList().size() == 3U) ret += " (geographic3D)"; else ret += " (geographic2D)"; } if (const auto *geodCRS = dynamic_cast(obj)) { if (geodCRS->isGeocentric()) { ret += " (geocentric)"; } } if (!ids.empty()) { ret += " ("; ret += (*ids[0]->codeSpace()) + ":" + ids[0]->code(); ret += ")"; } return ret; } //! @endcond #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static double getPseudoArea(const metadata::ExtentPtr &extent) { if (!extent) return 0.0; const auto &geographicElements = extent->geographicElements(); if (geographicElements.empty()) return 0.0; auto bbox = dynamic_cast( geographicElements[0].get()); if (!bbox) return 0; double w = bbox->westBoundLongitude(); double s = bbox->southBoundLatitude(); double e = bbox->eastBoundLongitude(); double n = bbox->northBoundLatitude(); if (w > e) { e += 360.0; } // Integrate cos(lat) between south_lat and north_lat return (e - w) * (std::sin(common::Angle(n).getSIValue()) - std::sin(common::Angle(s).getSIValue())); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateOperationContext::Private { io::AuthorityFactoryPtr authorityFactory_{}; metadata::ExtentPtr extent_{}; double accuracy_ = 0.0; SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse_ = CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST; SpatialCriterion spatialCriterion_ = CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT; bool usePROJNames_ = true; GridAvailabilityUse gridAvailabilityUse_ = GridAvailabilityUse::USE_FOR_SORTING; IntermediateCRSUse allowUseIntermediateCRS_ = CoordinateOperationContext:: IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION; std::vector> intermediateCRSAuthCodes_{}; bool discardSuperseded_ = true; bool allowBallpark_ = true; std::shared_ptr> sourceCoordinateEpoch_{ std::make_shared>()}; std::shared_ptr> targetCoordinateEpoch_{ std::make_shared>()}; Private() = default; Private(const Private &) = default; }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateOperationContext::~CoordinateOperationContext() = default; //! @endcond // --------------------------------------------------------------------------- CoordinateOperationContext::CoordinateOperationContext() : d(std::make_unique()) {} // --------------------------------------------------------------------------- CoordinateOperationContext::CoordinateOperationContext( const CoordinateOperationContext &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- /** \brief Return the authority factory, or null */ const io::AuthorityFactoryPtr & CoordinateOperationContext::getAuthorityFactory() const { return d->authorityFactory_; } // --------------------------------------------------------------------------- /** \brief Return the desired area of interest, or null */ const metadata::ExtentPtr & CoordinateOperationContext::getAreaOfInterest() const { return d->extent_; } // --------------------------------------------------------------------------- /** \brief Set the desired area of interest, or null */ void CoordinateOperationContext::setAreaOfInterest( const metadata::ExtentPtr &extent) { d->extent_ = extent; } // --------------------------------------------------------------------------- /** \brief Return the desired accuracy (in metre), or 0 */ double CoordinateOperationContext::getDesiredAccuracy() const { return d->accuracy_; } // --------------------------------------------------------------------------- /** \brief Set the desired accuracy (in metre), or 0 */ void CoordinateOperationContext::setDesiredAccuracy(double accuracy) { d->accuracy_ = accuracy; } // --------------------------------------------------------------------------- /** \brief Return whether ballpark transformations are allowed */ bool CoordinateOperationContext::getAllowBallparkTransformations() const { return d->allowBallpark_; } // --------------------------------------------------------------------------- /** \brief Set whether ballpark transformations are allowed */ void CoordinateOperationContext::setAllowBallparkTransformations(bool allow) { d->allowBallpark_ = allow; } // --------------------------------------------------------------------------- /** \brief Set how source and target CRS extent should be used * when considering if a transformation can be used (only takes effect if * no area of interest is explicitly defined). * * The default is * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. */ void CoordinateOperationContext::setSourceAndTargetCRSExtentUse( SourceTargetCRSExtentUse use) { d->sourceAndTargetCRSExtentUse_ = use; } // --------------------------------------------------------------------------- /** \brief Return how source and target CRS extent should be used * when considering if a transformation can be used (only takes effect if * no area of interest is explicitly defined). * * The default is * CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST. */ CoordinateOperationContext::SourceTargetCRSExtentUse CoordinateOperationContext::getSourceAndTargetCRSExtentUse() const { return d->sourceAndTargetCRSExtentUse_; } // --------------------------------------------------------------------------- /** \brief Set the spatial criterion to use when comparing the area of * validity * of coordinate operations with the area of interest / area of validity of * source and target CRS. * * The default is STRICT_CONTAINMENT. */ void CoordinateOperationContext::setSpatialCriterion( SpatialCriterion criterion) { d->spatialCriterion_ = criterion; } // --------------------------------------------------------------------------- /** \brief Return the spatial criterion to use when comparing the area of * validity * of coordinate operations with the area of interest / area of validity of * source and target CRS. * * The default is STRICT_CONTAINMENT. */ CoordinateOperationContext::SpatialCriterion CoordinateOperationContext::getSpatialCriterion() const { return d->spatialCriterion_; } // --------------------------------------------------------------------------- /** \brief Set whether PROJ alternative grid names should be substituted to * the official authority names. * * This only has effect is an authority factory with a non-null database context * has been attached to this context. * * If set to false, it is still possible to * obtain later the substitution by using io::PROJStringFormatter::create() * with a non-null database context. * * The default is true. */ void CoordinateOperationContext::setUsePROJAlternativeGridNames( bool usePROJNames) { d->usePROJNames_ = usePROJNames; } // --------------------------------------------------------------------------- /** \brief Return whether PROJ alternative grid names should be substituted to * the official authority names. * * The default is true. */ bool CoordinateOperationContext::getUsePROJAlternativeGridNames() const { return d->usePROJNames_; } // --------------------------------------------------------------------------- /** \brief Return whether transformations that are superseded (but not * deprecated) * should be discarded. * * The default is true. */ bool CoordinateOperationContext::getDiscardSuperseded() const { return d->discardSuperseded_; } // --------------------------------------------------------------------------- /** \brief Set whether transformations that are superseded (but not deprecated) * should be discarded. * * The default is true. */ void CoordinateOperationContext::setDiscardSuperseded(bool discard) { d->discardSuperseded_ = discard; } // --------------------------------------------------------------------------- /** \brief Set how grid availability is used. * * The default is USE_FOR_SORTING. */ void CoordinateOperationContext::setGridAvailabilityUse( GridAvailabilityUse use) { d->gridAvailabilityUse_ = use; } // --------------------------------------------------------------------------- /** \brief Return how grid availability is used. * * The default is USE_FOR_SORTING. */ CoordinateOperationContext::GridAvailabilityUse CoordinateOperationContext::getGridAvailabilityUse() const { return d->gridAvailabilityUse_; } // --------------------------------------------------------------------------- /** \brief Set whether an intermediate pivot CRS can be used for researching * coordinate operations between a source and target CRS. * * Concretely if in the database there is an operation from A to C * (or C to A), and another one from C to B (or B to C), but no direct * operation between A and B, setting this parameter to * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations. * * The current implementation is limited to researching one intermediate * step. * * By default, with the IF_NO_DIRECT_TRANSFORMATION strategy, all potential * C candidates will be used if there is no direct transformation. */ void CoordinateOperationContext::setAllowUseIntermediateCRS( IntermediateCRSUse use) { d->allowUseIntermediateCRS_ = use; } // --------------------------------------------------------------------------- /** \brief Return whether an intermediate pivot CRS can be used for researching * coordinate operations between a source and target CRS. * * Concretely if in the database there is an operation from A to C * (or C to A), and another one from C to B (or B to C), but no direct * operation between A and B, setting this parameter to * ALWAYS/IF_NO_DIRECT_TRANSFORMATION, allow chaining both operations. * * The default is IF_NO_DIRECT_TRANSFORMATION. */ CoordinateOperationContext::IntermediateCRSUse CoordinateOperationContext::getAllowUseIntermediateCRS() const { return d->allowUseIntermediateCRS_; } // --------------------------------------------------------------------------- /** \brief Restrict the potential pivot CRSs that can be used when trying to * build a coordinate operation between two CRS that have no direct operation. * * @param intermediateCRSAuthCodes a vector of (auth_name, code) that can be * used as potential pivot RS */ void CoordinateOperationContext::setIntermediateCRS( const std::vector> &intermediateCRSAuthCodes) { d->intermediateCRSAuthCodes_ = intermediateCRSAuthCodes; } // --------------------------------------------------------------------------- /** \brief Return the potential pivot CRSs that can be used when trying to * build a coordinate operation between two CRS that have no direct operation. * */ const std::vector> & CoordinateOperationContext::getIntermediateCRS() const { return d->intermediateCRSAuthCodes_; } // --------------------------------------------------------------------------- /** \brief Set the source coordinate epoch. */ void CoordinateOperationContext::setSourceCoordinateEpoch( const util::optional &epoch) { d->sourceCoordinateEpoch_ = std::make_shared>(epoch); } // --------------------------------------------------------------------------- /** \brief Return the source coordinate epoch. */ const util::optional & CoordinateOperationContext::getSourceCoordinateEpoch() const { return *(d->sourceCoordinateEpoch_); } // --------------------------------------------------------------------------- /** \brief Set the target coordinate epoch. */ void CoordinateOperationContext::setTargetCoordinateEpoch( const util::optional &epoch) { d->targetCoordinateEpoch_ = std::make_shared>(epoch); } // --------------------------------------------------------------------------- /** \brief Return the target coordinate epoch. */ const util::optional & CoordinateOperationContext::getTargetCoordinateEpoch() const { return *(d->targetCoordinateEpoch_); } // --------------------------------------------------------------------------- /** \brief Creates a context for a coordinate operation. * * If a non null authorityFactory is provided, the resulting context should * not be used simultaneously by more than one thread. * * If authorityFactory->getAuthority() is the empty string, then coordinate * operations from any authority will be searched, with the restrictions set * in the authority_to_authority_preference database table. * If authorityFactory->getAuthority() is set to "any", then coordinate * operations from any authority will be searched * If authorityFactory->getAuthority() is a non-empty string different of "any", * then coordinate operations will be searched only in that authority namespace. * * @param authorityFactory Authority factory, or null if no database lookup * is allowed. * Use io::authorityFactory::create(context, std::string()) to allow all * authorities to be used. * @param extent Area of interest, or null if none is known. * @param accuracy Maximum allowed accuracy in metre, as specified in or * 0 to get best accuracy. * @return a new context. */ CoordinateOperationContextNNPtr CoordinateOperationContext::create( const io::AuthorityFactoryPtr &authorityFactory, const metadata::ExtentPtr &extent, double accuracy) { auto ctxt = NN_NO_CHECK( CoordinateOperationContext::make_unique()); ctxt->d->authorityFactory_ = authorityFactory; ctxt->d->extent_ = extent; ctxt->d->accuracy_ = accuracy; return ctxt; } // --------------------------------------------------------------------------- /** \brief Clone a coordinate operation context. * * @return a new context. * @since 9.2 */ CoordinateOperationContextNNPtr CoordinateOperationContext::clone() const { return NN_NO_CHECK( CoordinateOperationContext::make_unique( *this)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateOperationFactory::Private { struct Context { // This is the extent of the source CRS and target CRS of the initial // CoordinateOperationFactory::createOperations() public call, not // necessarily the ones of intermediate // CoordinateOperationFactory::Private::createOperations() calls. // This is used to compare transformations area of use against the // area of use of the source & target CRS. const metadata::ExtentPtr &extent1; const metadata::ExtentPtr &extent2; const CoordinateOperationContextNNPtr &context; bool inCreateOperationsWithDatumPivotAntiRecursion = false; bool inCreateOperationsGeogToVertWithAlternativeGeog = false; bool inCreateOperationsGeogToVertWithIntermediateVert = false; bool inCreateOperationsVertToVertWithIntermediateVert = false; bool skipHorizontalTransformation = false; int nRecLevelCreateOperations = 0; std::map, std::list>> cacheNameToCRS{}; // Normally there should be at most one element in this stack // This is set when computing a CompoundCRS to GeogCRS operation to // relate the VerticalCRS of the CompoundCRS to the geographicCRS of // the horizontal component of the CompoundCRS. This is especially // used if the VerticalCRS is a DerivedVerticalCRS using a // (non-standard) "Ellipsoid" vertical datum std::vector geogCRSOfVertCRSStack{}; Context(const metadata::ExtentPtr &extent1In, const metadata::ExtentPtr &extent2In, const CoordinateOperationContextNNPtr &contextIn) : extent1(extent1In), extent2(extent2In), context(contextIn) {} // Returns whether empty extent intersection should be disallowed // when creating concatenated operations. When the user sets // SourceTargetCRSExtentUse::NONE, extent checking should be // completely disabled, including for internal operations. bool disallowEmptyIntersection() const { return context->getSourceAndTargetCRSExtentUse() != CoordinateOperationContext::SourceTargetCRSExtentUse::NONE; } }; static std::vector createOperations(const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Context &context); static void buildCRSIds(const crs::CRSNNPtr &crs, Private::Context &context, std::list> &ids); static std::vector findOpsInRegistryDirect( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, bool &resNonEmptyBeforeFiltering); static std::vector findOpsInRegistryDirectTo(const crs::CRSNNPtr &targetCRS, Private::Context &context); static std::vector findsOpsInRegistryWithIntermediate( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates); static bool hasTooSmallAreas(const std::vector &ops, const Private::Context &context); static void createOperationsFromProj4Ext( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, std::vector &res); static bool createOperationsFromDatabase( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector &res); static std::vector createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::VerticalCRS *vertDst, Context &context); static std::vector createOperationsGeogToVertWithIntermediateVert( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, const crs::VerticalCRS *vertDst, Context &context); static std::vector createOperationsGeogToVertWithAlternativeGeog( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Context &context); static std::vector createOperationsVertToVertWithIntermediateVert( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, Context &context); static void createOperationsFromDatabaseWithVertCRS( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector &res); static void createOperationsGeodToGeod( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, std::vector &res, bool forceBallpark); static void createOperationsFromSphericalPlanetocentric( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeodeticCRS *geodSrc, std::vector &res); static void createOperationsFromBoundOfSphericalPlanetocentric( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::GeodeticCRSNNPtr &geodSrcBase, std::vector &res); static void createOperationsDerivedTo( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::DerivedCRS *derivedSrc, std::vector &res); static void createOperationsBoundToGeog( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::GeographicCRS *geogDst, std::vector &res); static void createOperationsBoundToVert( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::VerticalCRS *vertDst, std::vector &res); static void createOperationsVertToVert( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector &res); static void createOperationsVertToGeog( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::GeographicCRS *geogDst, std::vector &res); static void createOperationsVertToGeogSynthetized( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::GeographicCRS *geogDst, std::vector &res); static void createOperationsBoundToBound( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, std::vector &res); static void createOperationsCompoundToGeog( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::CompoundCRS *compoundSrc, const crs::GeographicCRS *geogDst, std::vector &res); static void createOperationsToGeod(const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeodeticCRS *geodDst, std::vector &res); static void createOperationsCompoundToCompound( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::CompoundCRS *compoundSrc, const crs::CompoundCRS *compoundDst, std::vector &res); static void createOperationsBoundToCompound( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::CompoundCRS *compoundDst, std::vector &res); static std::vector createOperationsGeogToGeog( std::vector &res, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst, bool forceBallpark); static void createOperationsWithDatumPivot( std::vector &res, const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, Context &context); static bool hasPerfectAccuracyResult(const std::vector &res, const Context &context); static void setCRSs(CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS); }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateOperationFactory::~CoordinateOperationFactory() = default; //! @endcond // --------------------------------------------------------------------------- CoordinateOperationFactory::CoordinateOperationFactory() : d(nullptr) {} // --------------------------------------------------------------------------- /** \brief Find a CoordinateOperation from sourceCRS to targetCRS. * * This is a helper of createOperations(), using a coordinate operation * context * with no authority factory (so no catalog searching is done), no desired * accuracy and no area of interest. * This returns the first operation of the result set of createOperations(), * or null if none found. * * @param sourceCRS source CRS. * @param targetCRS source CRS. * @return a CoordinateOperation or nullptr. */ CoordinateOperationPtr CoordinateOperationFactory::createOperation( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) const { auto res = createOperations( sourceCRS, targetCRS, CoordinateOperationContext::create(nullptr, nullptr, 0.0)); if (!res.empty()) { return res[0]; } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- static bool isNullTransformation(const std::string &name) { if (name.find(" + ") != std::string::npos) return false; return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) || starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) || starts_with(name, NULL_GEOGRAPHIC_OFFSET) || starts_with(name, NULL_GEOCENTRIC_TRANSLATION); } // --------------------------------------------------------------------------- struct PrecomputedOpCharacteristics { double area_{}; double accuracy_{}; bool isPROJExportable_ = false; bool hasGrids_ = false; bool gridsAvailable_ = false; bool gridsKnown_ = false; size_t stepCount_ = 0; size_t projStepCount_ = 0; bool isApprox_ = false; bool hasBallparkVertical_ = false; bool isNullTransformation_ = false; // 3 below tests are for ETRS89-XXX to ETRS89-YYY following // recommandations of IOGP 373-07-7 to use ETRF2000 hub bool is_ETRS89_XXX_to_ETRS89_YYY_ = false; bool uses_ETRF2000_ = false; bool uses_ETRS89_to_ETRS89_XXX_generic_ = false; PrecomputedOpCharacteristics() = default; PrecomputedOpCharacteristics(const CoordinateOperationNNPtr &op, double area, bool isPROJExportable, bool hasGrids, bool gridsAvailable, bool gridsKnown, size_t stepCount, size_t projStepCount) : area_(area), accuracy_(getAccuracy(op)), isPROJExportable_(isPROJExportable), hasGrids_(hasGrids), gridsAvailable_(gridsAvailable), gridsKnown_(gridsKnown), stepCount_(stepCount), projStepCount_(projStepCount), isApprox_(op->hasBallparkTransformation()), hasBallparkVertical_( op->nameStr().find(BALLPARK_VERTICAL_TRANSFORMATION) != std::string::npos), isNullTransformation_(isNullTransformation(op->nameStr())), is_ETRS89_XXX_to_ETRS89_YYY_( starts_with(op->sourceCRS()->nameStr().c_str(), "ETRS89-") && starts_with(op->targetCRS()->nameStr().c_str(), "ETRS89-")), uses_ETRF2000_(op->nameStr().find("ETRF2000") != std::string::npos), uses_ETRS89_to_ETRS89_XXX_generic_( op->nameStr().find("ETRS89 to ETRS89-") != std::string::npos) {} }; // --------------------------------------------------------------------------- // We could have used a lambda instead of this old-school way, but // filterAndSort() is already huge. struct SortFunction { explicit SortFunction(const std::map &mapIn) : map(mapIn), BALLPARK_GEOGRAPHIC_OFFSET_FROM( std::string(BALLPARK_GEOGRAPHIC_OFFSET) + " from ") {} // Sorting function // Return true if a < b bool compare(const CoordinateOperationNNPtr &a, const CoordinateOperationNNPtr &b) const { auto iterA = map.find(a.get()); assert(iterA != map.end()); auto iterB = map.find(b.get()); assert(iterB != map.end()); // CAUTION: the order of the comparisons is extremely important // to get the intended result. if (iterA->second.isPROJExportable_ && !iterB->second.isPROJExportable_) { return true; } if (!iterA->second.isPROJExportable_ && iterB->second.isPROJExportable_) { return false; } if (!iterA->second.isApprox_ && iterB->second.isApprox_) { return true; } if (iterA->second.isApprox_ && !iterB->second.isApprox_) { return false; } if (!iterA->second.hasBallparkVertical_ && iterB->second.hasBallparkVertical_) { return true; } if (iterA->second.hasBallparkVertical_ && !iterB->second.hasBallparkVertical_) { return false; } if (!iterA->second.isNullTransformation_ && iterB->second.isNullTransformation_) { return true; } if (iterA->second.isNullTransformation_ && !iterB->second.isNullTransformation_) { return false; } // Operations where grids are all available go before other if (iterA->second.gridsAvailable_ && !iterB->second.gridsAvailable_) { return true; } if (iterB->second.gridsAvailable_ && !iterA->second.gridsAvailable_) { return false; } // Operations where grids are all known in our DB go before other if (iterA->second.gridsKnown_ && !iterB->second.gridsKnown_) { return true; } if (iterB->second.gridsKnown_ && !iterA->second.gridsKnown_) { return false; } // Operations with known accuracy go before those with unknown accuracy const double accuracyA = iterA->second.accuracy_; const double accuracyB = iterB->second.accuracy_; if (accuracyA >= 0 && accuracyB < 0) { return true; } if (accuracyB >= 0 && accuracyA < 0) { return false; } if (accuracyA < 0 && accuracyB < 0) { // unknown accuracy ? then prefer operations with grids, which // are likely to have best practical accuracy if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { return true; } if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { return false; } } // Operations with larger non-zero area of use go before those with // lower one const double areaA = iterA->second.area_; const double areaB = iterB->second.area_; if (areaA > 0) { if (areaA > areaB) { return true; } if (areaA < areaB) { return false; } } else if (areaB > 0) { return false; } // Operations with better accuracy go before those with worse one if (accuracyA >= 0 && accuracyA < accuracyB) { return true; } if (accuracyB >= 0 && accuracyB < accuracyA) { return false; } if (accuracyA >= 0 && accuracyA == accuracyB) { // same accuracy ? then prefer operations without grids if (!iterA->second.hasGrids_ && iterB->second.hasGrids_) { return true; } if (iterA->second.hasGrids_ && !iterB->second.hasGrids_) { return false; } } // Follow recommandations of IOGP 373-07-7 to use ETRF2000 hub if // no direct Transformation if (iterA->second.is_ETRS89_XXX_to_ETRS89_YYY_ && iterB->second.is_ETRS89_XXX_to_ETRS89_YYY_) { if (iterA->second.uses_ETRF2000_ && !iterA->second.uses_ETRS89_to_ETRS89_XXX_generic_ && !iterA->second.uses_ETRF2000_ && iterB->second.uses_ETRS89_to_ETRS89_XXX_generic_) return true; if (iterB->second.uses_ETRF2000_ && !iterB->second.uses_ETRS89_to_ETRS89_XXX_generic_ && !iterA->second.uses_ETRF2000_ && iterA->second.uses_ETRS89_to_ETRS89_XXX_generic_) return false; } // The less intermediate steps, the better if (iterA->second.stepCount_ < iterB->second.stepCount_) { return true; } if (iterB->second.stepCount_ < iterA->second.stepCount_) { return false; } // Compare number of steps in PROJ pipeline, and prefer the ones // with less operations. if (iterA->second.projStepCount_ != 0 && iterB->second.projStepCount_ != 0) { if (iterA->second.projStepCount_ < iterB->second.projStepCount_) { return true; } if (iterB->second.projStepCount_ < iterA->second.projStepCount_) { return false; } } const auto &a_name = a->nameStr(); const auto &b_name = b->nameStr(); // Make sure that // "Ballpark geographic offset from NAD83(CSRS)v6 to NAD83(CSRS)" // has more priority than // "Ballpark geographic offset from ITRF2008 to NAD83(CSRS)" const auto posA = a_name.find(BALLPARK_GEOGRAPHIC_OFFSET_FROM); const auto posB = b_name.find(BALLPARK_GEOGRAPHIC_OFFSET_FROM); if (posA != std::string::npos && posB != std::string::npos) { const auto pos2A = a_name.find(" to ", posA); const auto pos2B = b_name.find(" to ", posB); if (pos2A != std::string::npos && pos2B != std::string::npos) { const auto pos3A = a_name.find(" + ", pos2A); const auto pos3B = b_name.find(" + ", pos2B); const std::string fromA = a_name.substr( posA + BALLPARK_GEOGRAPHIC_OFFSET_FROM.size(), pos2A - (posA + BALLPARK_GEOGRAPHIC_OFFSET_FROM.size())); const std::string toA = a_name.substr(pos2A + strlen(" to "), pos3A == std::string::npos ? pos3A : pos3A - (pos2A + strlen(" to "))); const std::string fromB = b_name.substr( posB + BALLPARK_GEOGRAPHIC_OFFSET_FROM.size(), pos2B - (posB + BALLPARK_GEOGRAPHIC_OFFSET_FROM.size())); const std::string toB = b_name.substr(pos2B + strlen(" to "), pos3B == std::string::npos ? pos3B : pos3B - (pos2B + strlen(" to "))); const bool similarCRSInA = (fromA.find(toA) == 0 || toA.find(fromA) == 0); const bool similarCRSInB = (fromB.find(toB) == 0 || toB.find(fromB) == 0); if (similarCRSInA && !similarCRSInB) { return true; } if (!similarCRSInA && similarCRSInB) { return false; } } } // The shorter name, the better ? if (a_name.size() < b_name.size()) { return true; } if (b_name.size() < a_name.size()) { return false; } // Arbitrary final criterion. We actually return the greater element // first, so that "Amersfoort to WGS 84 (4)" is presented before // "Amersfoort to WGS 84 (3)", which is probably a better guess. // Except for French NTF (Paris) to NTF, where the (1) conversion // should be preferred because in the remarks of (2), it is mentioned // OGP prefers value from IGN Paris (code 1467)... if (a_name.find("NTF (Paris) to NTF (1)") != std::string::npos && b_name.find("NTF (Paris) to NTF (2)") != std::string::npos) { return true; } if (a_name.find("NTF (Paris) to NTF (2)") != std::string::npos && b_name.find("NTF (Paris) to NTF (1)") != std::string::npos) { return false; } if (a_name.find("NTF (Paris) to RGF93 v1 (1)") != std::string::npos && b_name.find("NTF (Paris) to RGF93 v1 (2)") != std::string::npos) { return true; } if (a_name.find("NTF (Paris) to RGF93 v1 (2)") != std::string::npos && b_name.find("NTF (Paris) to RGF93 v1 (1)") != std::string::npos) { return false; } return a_name > b_name; } bool operator()(const CoordinateOperationNNPtr &a, const CoordinateOperationNNPtr &b) const { const bool ret = compare(a, b); #if 0 std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl; #endif return ret; } SortFunction(const SortFunction &) = default; SortFunction &operator=(const SortFunction &) = delete; SortFunction(SortFunction &&) = default; SortFunction &operator=(SortFunction &&) = delete; private: const std::map ↦ const std::string BALLPARK_GEOGRAPHIC_OFFSET_FROM; }; // --------------------------------------------------------------------------- static size_t getStepCount(const CoordinateOperationNNPtr &op) { auto concat = dynamic_cast(op.get()); size_t stepCount = 1; if (concat) { stepCount = concat->operations().size(); } return stepCount; } // --------------------------------------------------------------------------- // Return number of steps that are transformations (and not conversions) static size_t getTransformationStepCount(const CoordinateOperationNNPtr &op) { auto concat = dynamic_cast(op.get()); size_t stepCount = 1; if (concat) { stepCount = 0; for (const auto &subOp : concat->operations()) { if (dynamic_cast(subOp.get()) == nullptr) { stepCount++; } } } return stepCount; } // --------------------------------------------------------------------------- struct FilterResults { FilterResults(const std::vector &sourceListIn, const CoordinateOperationContextNNPtr &contextIn, const metadata::ExtentPtr &extent1In, const metadata::ExtentPtr &extent2In, bool forceStrictContainmentTest) : sourceList(sourceListIn), context(contextIn), extent1(extent1In), extent2(extent2In), areaOfInterest(context->getAreaOfInterest()), areaOfInterestUserSpecified(areaOfInterest != nullptr), desiredAccuracy(context->getDesiredAccuracy()), sourceAndTargetCRSExtentUse( context->getSourceAndTargetCRSExtentUse()) { computeAreaOfInterest(); filterOut(forceStrictContainmentTest); } FilterResults &andSort() { sort(); // And now that we have a sorted list, we can remove uninteresting // results // ... removeSyntheticNullTransforms(); removeUninterestingOps(); removeDuplicateOps(); removeSyntheticNullTransforms(); return *this; } // ---------------------------------------------------------------------- // cppcheck-suppress functionStatic const std::vector &getRes() { return res; } // ---------------------------------------------------------------------- private: const std::vector &sourceList; const CoordinateOperationContextNNPtr &context; const metadata::ExtentPtr &extent1; const metadata::ExtentPtr &extent2; metadata::ExtentPtr areaOfInterest; const bool areaOfInterestUserSpecified; const double desiredAccuracy = context->getDesiredAccuracy(); const CoordinateOperationContext::SourceTargetCRSExtentUse sourceAndTargetCRSExtentUse; bool hasOpThatContainsAreaOfInterestAndNoGrid = false; std::vector res{}; // ---------------------------------------------------------------------- void computeAreaOfInterest() { // Compute an area of interest from the CRS extent if the user did // not specify one if (!areaOfInterest) { if (sourceAndTargetCRSExtentUse == CoordinateOperationContext::SourceTargetCRSExtentUse:: INTERSECTION) { if (extent1 && extent2) { areaOfInterest = extent1->intersection(NN_NO_CHECK(extent2)); } } else if (sourceAndTargetCRSExtentUse == CoordinateOperationContext::SourceTargetCRSExtentUse:: SMALLEST) { if (extent1 && extent2) { if (getPseudoArea(extent1) < getPseudoArea(extent2)) { areaOfInterest = extent1; } else { areaOfInterest = extent2; } } else if (extent1) { areaOfInterest = extent1; } else { areaOfInterest = extent2; } } } } // --------------------------------------------------------------------------- void filterOut(bool forceStrictContainmentTest) { // Filter out operations that do not match the expected accuracy // and area of use. const auto spatialCriterion = forceStrictContainmentTest ? CoordinateOperationContext::SpatialCriterion:: STRICT_CONTAINMENT : context->getSpatialCriterion(); bool hasOnlyBallpark = true; bool hasNonBallparkWithoutExtent = false; bool hasNonBallparkOpWithExtent = false; const bool allowBallpark = context->getAllowBallparkTransformations(); bool foundExtentWithExpectedDescription = false; if (areaOfInterestUserSpecified && areaOfInterest && areaOfInterest->description().has_value()) { for (const auto &op : sourceList) { bool emptyIntersection = false; auto extent = getExtent(op, true, emptyIntersection); if (extent && extent->description().has_value()) { if (*(areaOfInterest->description()) == *(extent->description())) { foundExtentWithExpectedDescription = true; break; } } } } for (const auto &op : sourceList) { if (desiredAccuracy != 0) { const double accuracy = getAccuracy(op); if (accuracy < 0 || accuracy > desiredAccuracy) { continue; } } if (!allowBallpark && op->hasBallparkTransformation()) { continue; } if (areaOfInterest) { bool emptyIntersection = false; auto extent = getExtent(op, true, emptyIntersection); if (!extent) { if (!op->hasBallparkTransformation()) { hasNonBallparkWithoutExtent = true; } continue; } if (foundExtentWithExpectedDescription && (!extent->description().has_value() || *(areaOfInterest->description()) != *(extent->description()))) { continue; } if (!op->hasBallparkTransformation()) { hasNonBallparkOpWithExtent = true; } bool extentContains = extent->contains(NN_NO_CHECK(areaOfInterest)); if (!hasOpThatContainsAreaOfInterestAndNoGrid && extentContains) { if (!op->hasBallparkTransformation() && op->gridsNeeded(nullptr, true).empty()) { hasOpThatContainsAreaOfInterestAndNoGrid = true; } } if (spatialCriterion == CoordinateOperationContext::SpatialCriterion:: STRICT_CONTAINMENT && !extentContains) { continue; } if (spatialCriterion == CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION && !extent->intersects(NN_NO_CHECK(areaOfInterest))) { continue; } } else if (sourceAndTargetCRSExtentUse == CoordinateOperationContext::SourceTargetCRSExtentUse:: BOTH) { bool emptyIntersection = false; auto extent = getExtent(op, true, emptyIntersection); if (!extent) { if (!op->hasBallparkTransformation()) { hasNonBallparkWithoutExtent = true; } continue; } if (!op->hasBallparkTransformation()) { hasNonBallparkOpWithExtent = true; } bool extentContainsExtent1 = !extent1 || extent->contains(NN_NO_CHECK(extent1)); bool extentContainsExtent2 = !extent2 || extent->contains(NN_NO_CHECK(extent2)); if (!hasOpThatContainsAreaOfInterestAndNoGrid && extentContainsExtent1 && extentContainsExtent2) { if (!op->hasBallparkTransformation() && op->gridsNeeded(nullptr, true).empty()) { hasOpThatContainsAreaOfInterestAndNoGrid = true; } } if (spatialCriterion == CoordinateOperationContext::SpatialCriterion:: STRICT_CONTAINMENT) { if (!extentContainsExtent1 || !extentContainsExtent2) { continue; } } else if (spatialCriterion == CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION) { bool extentIntersectsExtent1 = !extent1 || extent->intersects(NN_NO_CHECK(extent1)); bool extentIntersectsExtent2 = extent2 && extent->intersects(NN_NO_CHECK(extent2)); if (!extentIntersectsExtent1 || !extentIntersectsExtent2) { continue; } } } if (!op->hasBallparkTransformation()) { hasOnlyBallpark = false; } res.emplace_back(op); } // In case no operation has an extent and no result is found, // retain all initial operations that match accuracy criterion. if ((res.empty() && !hasNonBallparkOpWithExtent) || (hasOnlyBallpark && hasNonBallparkWithoutExtent)) { for (const auto &op : sourceList) { if (desiredAccuracy != 0) { const double accuracy = getAccuracy(op); if (accuracy < 0 || accuracy > desiredAccuracy) { continue; } } if (!allowBallpark && op->hasBallparkTransformation()) { continue; } res.emplace_back(op); } } } // ---------------------------------------------------------------------- void sort() { // Precompute a number of parameters for each operation that will be // useful for the sorting. std::map map; const auto gridAvailabilityUse = context->getGridAvailabilityUse(); for (const auto &op : res) { bool dummy = false; auto extentOp = getExtent(op, true, dummy); double area = 0.0; if (extentOp) { if (areaOfInterest) { area = getPseudoArea( extentOp->intersection(NN_NO_CHECK(areaOfInterest))); } else if (extent1 && extent2) { auto x = extentOp->intersection(NN_NO_CHECK(extent1)); auto y = extentOp->intersection(NN_NO_CHECK(extent2)); area = getPseudoArea(x) + getPseudoArea(y) - ((x && y) ? getPseudoArea(x->intersection(NN_NO_CHECK(y))) : 0.0); } else if (extent1) { area = getPseudoArea( extentOp->intersection(NN_NO_CHECK(extent1))); } else if (extent2) { area = getPseudoArea( extentOp->intersection(NN_NO_CHECK(extent2))); } else { area = getPseudoArea(extentOp); } } bool hasGrids = false; bool gridsAvailable = true; bool gridsKnown = true; if (context->getAuthorityFactory()) { const auto gridsNeeded = op->gridsNeeded( context->getAuthorityFactory()->databaseContext(), gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE); for (const auto &gridDesc : gridsNeeded) { hasGrids = true; if (gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: USE_FOR_SORTING && !gridDesc.available) { gridsAvailable = false; } if (gridDesc.packageName.empty() && !(!gridDesc.url.empty() && gridDesc.openLicense) && !gridDesc.available) { gridsKnown = false; } } } const auto stepCount = getStepCount(op); bool isPROJExportable = false; auto formatter = io::PROJStringFormatter::create(); size_t projStepCount = 0; try { const auto str = op->exportToPROJString(formatter.get()); // Grids might be missing, but at least this is something // PROJ could potentially process isPROJExportable = true; // We exclude pipelines with +proj=xyzgridshift as they // generate more steps, but are more precise. if (str.find("+proj=xyzgridshift") == std::string::npos) { auto formatter2 = io::PROJStringFormatter::create(); formatter2->ingestPROJString(str); projStepCount = formatter2->getStepCount(); } } catch (const std::exception &) { } #if 0 std::cerr << "name=" << op->nameStr() << " "; std::cerr << "area=" << area << " "; std::cerr << "accuracy=" << getAccuracy(op) << " "; std::cerr << "isPROJExportable=" << isPROJExportable << " "; std::cerr << "hasGrids=" << hasGrids << " "; std::cerr << "gridsAvailable=" << gridsAvailable << " "; std::cerr << "gridsKnown=" << gridsKnown << " "; std::cerr << "stepCount=" << stepCount << " "; std::cerr << "projStepCount=" << projStepCount << " "; std::cerr << "ballpark=" << op->hasBallparkTransformation() << " "; std::cerr << "vertBallpark=" << (op->nameStr().find( BALLPARK_VERTICAL_TRANSFORMATION) != std::string::npos) << " "; std::cerr << "isNull=" << isNullTransformation(op->nameStr()) << " "; std::cerr << std::endl; #endif map[op.get()] = PrecomputedOpCharacteristics( op, area, isPROJExportable, hasGrids, gridsAvailable, gridsKnown, stepCount, projStepCount); } // Sort ! std::sort(res.begin(), res.end(), SortFunction(map)); // Debug code to check consistency of the sort function #ifdef DEBUG_SORT constexpr bool debugSort = true; #elif !defined(NDEBUG) const bool debugSort = getenv("PROJ_DEBUG_SORT_FUNCT") != nullptr; #endif #if defined(DEBUG_SORT) || !defined(NDEBUG) if (debugSort) { SortFunction sortFunc(map); const bool assertIfIssue = !(getenv("PROJ_DEBUG_SORT_FUNCT_ASSERT") != nullptr); for (size_t i = 0; i < res.size(); ++i) { for (size_t j = i + 1; j < res.size(); ++j) { if (sortFunc(res[j], res[i])) { #ifdef DEBUG_SORT std::cerr << "Sorting issue with entry " << i << "(" << res[i]->nameStr() << ") and " << j << "(" << res[j]->nameStr() << ")" << std::endl; #endif if (assertIfIssue) { assert(false); } } } } } #endif } // ---------------------------------------------------------------------- void removeSyntheticNullTransforms() { // If we have more than one result, and than the last result is the // default "Ballpark geographic offset" or "Ballpark geocentric // translation" operations we have synthesized, and that at least one // operation has the desired area of interest and does not require the // use of grids, remove it as all previous results are necessarily // better if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) { const auto &opLast = res.back(); if (opLast->hasBallparkTransformation() || isNullTransformation(opLast->nameStr())) { std::vector resTemp; for (size_t i = 0; i < res.size() - 1; i++) { resTemp.emplace_back(res[i]); } res = std::move(resTemp); } } } // ---------------------------------------------------------------------- void removeUninterestingOps() { // Eliminate operations that bring nothing, ie for a given area of use, // do not keep operations that have similar or worse accuracy, but // involve more (non conversion) steps std::vector resTemp; metadata::ExtentPtr lastExtent; double lastAccuracy = -1; size_t lastStepCount = 0; CoordinateOperationPtr lastOp; bool first = true; for (const auto &op : res) { const auto curAccuracy = getAccuracy(op); bool dummy = false; auto curExtent = getExtent(op, true, dummy); // If a concatenated operation has an identifier, consider it as // a single step (to be opposed to synthesized concatenated // operations). Helps for example to get EPSG:8537, // "Egypt 1907 to WGS 84 (2)" const auto curStepCount = op->identifiers().empty() ? getTransformationStepCount(op) : 1; if (first) { resTemp.emplace_back(op); first = false; } else { if (lastOp->_isEquivalentTo(op.get())) { continue; } const bool sameExtent = ((!curExtent && !lastExtent) || (curExtent && lastExtent && curExtent->contains(NN_NO_CHECK(lastExtent)) && lastExtent->contains(NN_NO_CHECK(curExtent)))); if (((curAccuracy >= lastAccuracy && lastAccuracy >= 0) || (curAccuracy < 0 && lastAccuracy >= 0)) && sameExtent && curStepCount > lastStepCount) { continue; } resTemp.emplace_back(op); } lastOp = op.as_nullable(); lastStepCount = curStepCount; lastExtent = std::move(curExtent); lastAccuracy = curAccuracy; } res = std::move(resTemp); } // ---------------------------------------------------------------------- // cppcheck-suppress functionStatic void removeDuplicateOps() { if (res.size() <= 1) { return; } // When going from EPSG:4807 (NTF Paris) to EPSG:4171 (RGC93), we get // EPSG:7811, NTF (Paris) to RGF93 (2), 1 m // and unknown id, NTF (Paris) to NTF (1) + Inverse of RGF93 to NTF (2), // 1 m // both have same PROJ string and extent // Do not keep the later (that has more steps) as it adds no value. std::set setPROJPlusExtent; std::vector resTemp; for (const auto &op : res) { auto formatter = io::PROJStringFormatter::create(); try { std::string key(op->exportToPROJString(formatter.get())); bool dummy = false; auto extentOp = getExtent(op, true, dummy); if (extentOp) { const auto &geogElts = extentOp->geographicElements(); if (geogElts.size() == 1) { auto bbox = dynamic_cast< const metadata::GeographicBoundingBox *>( geogElts[0].get()); if (bbox) { double w = bbox->westBoundLongitude(); double s = bbox->southBoundLatitude(); double e = bbox->eastBoundLongitude(); double n = bbox->northBoundLatitude(); key += "-"; key += toString(w); key += "-"; key += toString(s); key += "-"; key += toString(e); key += "-"; key += toString(n); } } } if (setPROJPlusExtent.find(key) == setPROJPlusExtent.end()) { resTemp.emplace_back(op); setPROJPlusExtent.insert(std::move(key)); } } catch (const std::exception &) { resTemp.emplace_back(op); } } res = std::move(resTemp); } }; // --------------------------------------------------------------------------- /** \brief Filter operations and sort them given context. * * If a desired accuracy is specified, only keep operations whose accuracy * is at least the desired one. * If an area of interest is specified, only keep operations whose area of * use include the area of interest. * Then sort remaining operations by descending area of use, and increasing * accuracy. */ static std::vector filterAndSort(const std::vector &sourceList, const CoordinateOperationContextNNPtr &context, const metadata::ExtentPtr &extent1, const metadata::ExtentPtr &extent2) { #ifdef TRACE_CREATE_OPERATIONS ENTER_FUNCTION(); logTrace("number of results before filter and sort: " + toString(static_cast(sourceList.size()))); #endif FilterResults filterResults(sourceList, context, extent1, extent2, false); filterResults.andSort(); const auto &resFiltered = filterResults.getRes(); #ifdef TRACE_CREATE_OPERATIONS logTrace("number of results after filter and sort: " + toString(static_cast(resFiltered.size()))); #endif return resFiltered; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // Apply the inverse() method on all elements of the input list static std::vector applyInverse(const std::vector &list) { auto res = list; for (auto &op : res) { #ifdef DEBUG auto opNew = op->inverse(); assert(opNew->targetCRS()->isEquivalentTo(op->sourceCRS().get())); assert(opNew->sourceCRS()->isEquivalentTo(op->targetCRS().get())); op = opNew; #else op = op->inverse(); #endif } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateOperationFactory::Private::buildCRSIds( const crs::CRSNNPtr &crs, Private::Context &context, std::list> &ids) { const auto &authFactory = context.context->getAuthorityFactory(); assert(authFactory); for (const auto &id : crs->identifiers()) { const auto &authName = *(id->codeSpace()); const auto &code = id->code(); if (!authName.empty()) { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), authName); try { // Consistency check for the ID attached to the object. // See https://github.com/OSGeo/PROJ/issues/1982 where EPSG:4656 // is attached to a GeographicCRS whereas it is a ProjectedCRS if (tmpAuthFactory->createCoordinateReferenceSystem(code) ->_isEquivalentTo( crs.get(), util::IComparable::Criterion:: EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)) { ids.emplace_back(authName, code); } else { // TODO? log this inconsistency } } catch (const std::exception &) { // TODO? log this inconsistency } } } if (ids.empty() && !ci_equal(crs->nameStr(), "unknown")) { std::vector allowedObjects; auto geogCRS = dynamic_cast(crs.get()); if (geogCRS) { if (geogCRS->datumNonNull(authFactory->databaseContext()) ->nameStr() == "unknown") { return; } allowedObjects.push_back( geogCRS->coordinateSystem()->axisList().size() == 2 ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); } else if (dynamic_cast(crs.get())) { allowedObjects.push_back( io::AuthorityFactory::ObjectType::PROJECTED_CRS); } else if (dynamic_cast(crs.get())) { allowedObjects.push_back( io::AuthorityFactory::ObjectType::VERTICAL_CRS); } if (!allowedObjects.empty()) { const std::pair key( allowedObjects[0], crs->nameStr()); auto iter = context.cacheNameToCRS.find(key); if (iter != context.cacheNameToCRS.end()) { ids = iter->second; return; } const auto &authFactoryName = authFactory->getAuthority(); try { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), (authFactoryName.empty() || authFactoryName == "any") ? std::string() : authFactoryName); auto matches = tmpAuthFactory->createObjectsFromName( crs->nameStr(), allowedObjects, false, 2); if (matches.size() == 1 && crs->_isEquivalentTo( matches.front().get(), util::IComparable::Criterion::EQUIVALENT) && !matches.front()->identifiers().empty()) { const auto &tmpIds = matches.front()->identifiers(); ids.emplace_back(*(tmpIds[0]->codeSpace()), tmpIds[0]->code()); } } catch (const std::exception &) { } context.cacheNameToCRS[key] = ids; } } } // --------------------------------------------------------------------------- static std::vector getCandidateAuthorities(const io::AuthorityFactoryPtr &authFactory, const std::string &srcAuthName, const std::string &targetAuthName) { const auto &authFactoryName = authFactory->getAuthority(); std::vector authorities; if (authFactoryName.empty()) { for (const std::string &authName : authFactory->databaseContext()->getAllowedAuthorities( srcAuthName, targetAuthName)) { authorities.emplace_back(authName == "any" ? std::string() : authName); } if (authorities.empty()) { authorities.emplace_back(); } } else { authorities.emplace_back(authFactoryName == "any" ? std::string() : authFactoryName); } return authorities; } // --------------------------------------------------------------------------- // Look in the authority registry for operations from sourceCRS to targetCRS std::vector CoordinateOperationFactory::Private::findOpsInRegistryDirect( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, bool &resNonEmptyBeforeFiltering) { const auto &authFactory = context.context->getAuthorityFactory(); assert(authFactory); if ((sourceCRS->identifiers().empty() && ci_equal(sourceCRS->nameStr(), "unknown")) || (targetCRS->identifiers().empty() && ci_equal(targetCRS->nameStr(), "unknown"))) { return {}; } #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("findOpsInRegistryDirect(" + objectAsStr(sourceCRS.get()) + " --> " + objectAsStr(targetCRS.get()) + ")"); #endif resNonEmptyBeforeFiltering = false; std::list> sourceIds; std::list> targetIds; buildCRSIds(sourceCRS, context, sourceIds); buildCRSIds(targetCRS, context, targetIds); const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); for (const auto &idSrc : sourceIds) { const auto &srcAuthName = idSrc.first; const auto &srcCode = idSrc.second; for (const auto &idTarget : targetIds) { const auto &targetAuthName = idTarget.first; const auto &targetCode = idTarget.second; const auto authorities(getCandidateAuthorities( authFactory, srcAuthName, targetAuthName)); std::vector res; for (const auto &authName : authorities) { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), authName); auto resTmp = tmpAuthFactory->createFromCoordinateReferenceSystemCodes( srcAuthName, srcCode, targetAuthName, targetCode, context.context->getUsePROJAlternativeGridNames(), gridAvailabilityUse == CoordinateOperationContext:: GridAvailabilityUse:: DISCARD_OPERATION_IF_MISSING_GRID || gridAvailabilityUse == CoordinateOperationContext:: GridAvailabilityUse::KNOWN_AVAILABLE, gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE, context.context->getDiscardSuperseded(), true, false, context.extent1, context.extent2); res.insert(res.end(), resTmp.begin(), resTmp.end()); if (authName == "PROJ") { // Do not stop at the first transformations available in // the PROJ namespace, but allow the next authority to // continue continue; } if (!res.empty()) { resNonEmptyBeforeFiltering = true; auto resFiltered = FilterResults(res, context.context, context.extent1, context.extent2, false) .getRes(); #ifdef TRACE_CREATE_OPERATIONS logTrace("filtering reduced from " + toString(static_cast(res.size())) + " to " + toString(static_cast(resFiltered.size()))); #endif return resFiltered; } } } } return std::vector(); } // --------------------------------------------------------------------------- // Look in the authority registry for operations to targetCRS std::vector CoordinateOperationFactory::Private::findOpsInRegistryDirectTo( const crs::CRSNNPtr &targetCRS, Private::Context &context) { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("findOpsInRegistryDirectTo({any} -->" + objectAsStr(targetCRS.get()) + ")"); #endif const auto &authFactory = context.context->getAuthorityFactory(); assert(authFactory); std::list> ids; buildCRSIds(targetCRS, context, ids); const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); for (const auto &id : ids) { const auto &targetAuthName = id.first; const auto &targetCode = id.second; const auto authorities(getCandidateAuthorities( authFactory, targetAuthName, targetAuthName)); std::vector res; for (const auto &authName : authorities) { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), authName); auto resTmp = tmpAuthFactory->createFromCoordinateReferenceSystemCodes( std::string(), std::string(), targetAuthName, targetCode, context.context->getUsePROJAlternativeGridNames(), gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: DISCARD_OPERATION_IF_MISSING_GRID || gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE, gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE, context.context->getDiscardSuperseded(), true, true, context.extent1, context.extent2); res.insert(res.end(), resTmp.begin(), resTmp.end()); if (authName == "PROJ") { // Do not stop at the first transformations available in // the PROJ namespace, but allow the next authority to continue continue; } if (!res.empty()) { auto resFiltered = FilterResults(res, context.context, context.extent1, context.extent2, false) .getRes(); #ifdef TRACE_CREATE_OPERATIONS logTrace("filtering reduced from " + toString(static_cast(res.size())) + " to " + toString(static_cast(resFiltered.size()))); #endif return resFiltered; } } } return std::vector(); } //! @endcond // --------------------------------------------------------------------------- static std::list findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory, const crs::GeodeticCRS *crs, const datum::GeodeticReferenceFrameNNPtr &datum) { std::string preferredAuthName; const auto &crsIds = crs->identifiers(); if (crsIds.size() == 1) preferredAuthName = *(crsIds.front()->codeSpace()); return authFactory->createGeodeticCRSFromDatum(datum, preferredAuthName, std::string()); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool isSameGeodeticDatum(const datum::GeodeticReferenceFrameNNPtr &datum1, const datum::GeodeticReferenceFrameNNPtr &datum2, const io::DatabaseContextPtr &dbContext) { if (datum1->nameStr() == "unknown" && datum2->nameStr() != "unknown") return false; if (datum2->nameStr() == "unknown" && datum1->nameStr() != "unknown") return false; return datum1->_isEquivalentTo( datum2.get(), util::IComparable::Criterion::EQUIVALENT, dbContext); } // --------------------------------------------------------------------------- // Look in the authority registry for operations from sourceCRS to targetCRS // using an intermediate pivot std::vector CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, bool useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("findsOpsInRegistryWithIntermediate(" + objectAsStr(sourceCRS.get()) + " --> " + objectAsStr(targetCRS.get()) + ")"); #endif const auto &authFactory = context.context->getAuthorityFactory(); assert(authFactory); const auto dbContext = authFactory->databaseContext().as_nullable(); const auto geodSrc = dynamic_cast(sourceCRS.get()); const auto datumSrc = geodSrc ? geodSrc->datumNonNull(dbContext).as_nullable() : nullptr; // Optimization: check if the source/target CRS have no chance to match // a database entry if (datumSrc && datumSrc->identifiers().empty() && (ci_equal(datumSrc->nameStr(), "unknown") || ci_starts_with(datumSrc->nameStr(), UNKNOWN_BASED_ON))) return {}; if (!geodSrc && dynamic_cast(sourceCRS.get())) return {}; const auto geodDst = dynamic_cast(targetCRS.get()); const auto datumDst = geodDst ? geodDst->datumNonNull(dbContext).as_nullable() : nullptr; if (datumDst && datumDst->identifiers().empty() && (ci_equal(datumDst->nameStr(), "unknown") || ci_starts_with(datumDst->nameStr(), UNKNOWN_BASED_ON))) return {}; if (!geodDst && dynamic_cast(targetCRS.get())) return {}; std::list> sourceIds; buildCRSIds(sourceCRS, context, sourceIds); if (sourceIds.empty()) { if (geodSrc) { const std::string originatingAuthSrc = geodSrc->getOriginatingAuthName(); const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( authFactory, geodSrc, NN_NO_CHECK(datumSrc))); std::vector res; for (const auto &candidateSrcGeod : candidatesSrcGeod) { // Restrict to using only objects that have the same authority // as geodSrc. const auto &candidateIds = candidateSrcGeod->identifiers(); if ((originatingAuthSrc.empty() || (candidateIds.size() == 1 && *(candidateIds[0]->codeSpace()) == originatingAuthSrc) || candidateIds.size() > 1) && candidateSrcGeod->coordinateSystem()->axisList().size() == geodSrc->coordinateSystem()->axisList().size() && ((dynamic_cast(sourceCRS.get()) != nullptr) == (dynamic_cast( candidateSrcGeod.get()) != nullptr))) { if (geodDst) { const auto candidateSrcDatum = candidateSrcGeod->datumNonNull(dbContext); const bool sameGeodeticDatum = isSameGeodeticDatum( candidateSrcDatum, NN_NO_CHECK(datumDst), dbContext); if (sameGeodeticDatum) { continue; } } const auto opsWithIntermediate = findsOpsInRegistryWithIntermediate( candidateSrcGeod, targetCRS, context, useCreateBetweenGeodeticCRSWithDatumBasedIntermediates); if (!opsWithIntermediate.empty()) { const auto opsFirst = createOperations( sourceCRS, util::optional(), candidateSrcGeod, util::optional(), context); for (const auto &opFirst : opsFirst) { for (const auto &opSecond : opsWithIntermediate) { try { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {opFirst, opSecond}, context .disallowEmptyIntersection())); } catch ( const InvalidOperationEmptyIntersection &) { } } } if (!res.empty()) return res; } } } } return std::vector(); } std::list> targetIds; buildCRSIds(targetCRS, context, targetIds); if (targetIds.empty()) { return applyInverse(findsOpsInRegistryWithIntermediate( targetCRS, sourceCRS, context, useCreateBetweenGeodeticCRSWithDatumBasedIntermediates)); } const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); for (const auto &idSrc : sourceIds) { const auto &srcAuthName = idSrc.first; const auto &srcCode = idSrc.second; for (const auto &idTarget : targetIds) { const auto &targetAuthName = idTarget.first; const auto &targetCode = idTarget.second; const auto authorities(getCandidateAuthorities( authFactory, srcAuthName, targetAuthName)); assert(!authorities.empty()); const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), (authFactory->getAuthority() == "any" || authorities.size() > 1) ? std::string() : authorities.front()); std::vector res; if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) { res = tmpAuthFactory ->createBetweenGeodeticCRSWithDatumBasedIntermediates( sourceCRS, srcAuthName, srcCode, targetCRS, targetAuthName, targetCode, context.context->getUsePROJAlternativeGridNames(), gridAvailabilityUse == CoordinateOperationContext:: GridAvailabilityUse:: DISCARD_OPERATION_IF_MISSING_GRID || gridAvailabilityUse == CoordinateOperationContext:: GridAvailabilityUse::KNOWN_AVAILABLE, gridAvailabilityUse == CoordinateOperationContext:: GridAvailabilityUse::KNOWN_AVAILABLE, context.context->getDiscardSuperseded(), authFactory->getAuthority() != "any" && authorities.size() > 1 ? authorities : std::vector(), context.extent1, context.extent2); } else { io::AuthorityFactory::ObjectType intermediateObjectType = io::AuthorityFactory::ObjectType::CRS; // If doing GeogCRS --> GeogCRS, only use GeogCRS as // intermediate CRS // Avoid weird behavior when doing NAD83 -> NAD83(2011) // that would go through NAVD88 otherwise. if (context.context->getIntermediateCRS().empty() && dynamic_cast(sourceCRS.get()) && dynamic_cast(targetCRS.get())) { intermediateObjectType = io::AuthorityFactory::ObjectType::GEOGRAPHIC_CRS; } res = tmpAuthFactory->createFromCRSCodesWithIntermediates( srcAuthName, srcCode, targetAuthName, targetCode, context.context->getUsePROJAlternativeGridNames(), gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: DISCARD_OPERATION_IF_MISSING_GRID || gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE, gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE, context.context->getDiscardSuperseded(), context.context->getIntermediateCRS(), intermediateObjectType, authFactory->getAuthority() != "any" && authorities.size() > 1 ? authorities : std::vector(), context.extent1, context.extent2); } if (!res.empty()) { auto resFiltered = FilterResults(res, context.context, context.extent1, context.extent2, false) .getRes(); #ifdef TRACE_CREATE_OPERATIONS logTrace("filtering reduced from " + toString(static_cast(res.size())) + " to " + toString(static_cast(resFiltered.size()))); #endif return resFiltered; } } } return std::vector(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static TransformationNNPtr createBallparkGeographicOffset( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const io::DatabaseContextPtr &dbContext, bool forceBallpark) { const crs::GeographicCRS *geogSrc = dynamic_cast(sourceCRS.get()); const crs::GeographicCRS *geogDst = dynamic_cast(targetCRS.get()); const bool isSameDatum = !forceBallpark && geogSrc && geogDst && isSameGeodeticDatum(geogSrc->datumNonNull(dbContext), geogDst->datumNonNull(dbContext), dbContext); auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET : BALLPARK_GEOGRAPHIC_OFFSET, sourceCRS, targetCRS); const auto &sourceCRSExtent = getExtent(sourceCRS); const auto &targetCRSExtent = getExtent(targetCRS); const bool sameExtent = sourceCRSExtent && targetCRSExtent && sourceCRSExtent->_isEquivalentTo( targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); util::PropertyMap map; map.set(common::IdentifiedObject::NAME_KEY, name) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, sameExtent ? NN_NO_CHECK(sourceCRSExtent) : metadata::Extent::WORLD); const common::Angle angle0(0); std::vector accuracies; if (isSameDatum) { accuracies.emplace_back(metadata::PositionalAccuracy::create("0")); } const auto singleSourceCRS = dynamic_cast(sourceCRS.get()); const auto singleTargetCRS = dynamic_cast(targetCRS.get()); if ((singleSourceCRS && singleSourceCRS->coordinateSystem()->axisList().size() == 3) || (singleTargetCRS && singleTargetCRS->coordinateSystem()->axisList().size() == 3)) { return Transformation::createGeographic3DOffsets( map, sourceCRS, targetCRS, angle0, angle0, common::Length(0), accuracies); } else { return Transformation::createGeographic2DOffsets( map, sourceCRS, targetCRS, angle0, angle0, accuracies); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- struct MyPROJStringExportableGeodToGeod final : public io::IPROJStringExportable { crs::GeodeticCRSPtr geodSrc{}; crs::GeodeticCRSPtr geodDst{}; MyPROJStringExportableGeodToGeod(const crs::GeodeticCRSPtr &geodSrcIn, const crs::GeodeticCRSPtr &geodDstIn) : geodSrc(geodSrcIn), geodDst(geodDstIn) {} ~MyPROJStringExportableGeodToGeod() override; void // cppcheck-suppress functionStatic _exportToPROJString(io::PROJStringFormatter *formatter) const override { formatter->startInversion(); geodSrc->_exportToPROJString(formatter); formatter->stopInversion(); geodDst->_exportToPROJString(formatter); } }; MyPROJStringExportableGeodToGeod::~MyPROJStringExportableGeodToGeod() = default; // --------------------------------------------------------------------------- struct MyPROJStringExportableHorizVertical final : public io::IPROJStringExportable { CoordinateOperationPtr horizTransform{}; CoordinateOperationPtr verticalTransform{}; crs::GeographicCRSPtr geogDst{}; MyPROJStringExportableHorizVertical( const CoordinateOperationPtr &horizTransformIn, const CoordinateOperationPtr &verticalTransformIn, const crs::GeographicCRSPtr &geogDstIn) : horizTransform(horizTransformIn), verticalTransform(verticalTransformIn), geogDst(geogDstIn) {} ~MyPROJStringExportableHorizVertical() override; void // cppcheck-suppress functionStatic _exportToPROJString(io::PROJStringFormatter *formatter) const override { horizTransform->_exportToPROJString(formatter); formatter->startInversion(); geogDst->addAngularUnitConvertAndAxisSwap(formatter); formatter->stopInversion(); formatter->pushOmitHorizontalConversionInVertTransformation(); verticalTransform->_exportToPROJString(formatter); formatter->popOmitHorizontalConversionInVertTransformation(); formatter->pushOmitZUnitConversion(); geogDst->addAngularUnitConvertAndAxisSwap(formatter); formatter->popOmitZUnitConversion(); } }; MyPROJStringExportableHorizVertical::~MyPROJStringExportableHorizVertical() = default; // --------------------------------------------------------------------------- struct MyPROJStringExportableHorizVerticalHorizPROJBased final : public io::IPROJStringExportable { CoordinateOperationPtr opSrcCRSToGeogCRS{}; CoordinateOperationPtr verticalTransform{}; CoordinateOperationPtr opGeogCRStoDstCRS{}; crs::SingleCRSPtr interpolationCRS{}; MyPROJStringExportableHorizVerticalHorizPROJBased( const CoordinateOperationPtr &opSrcCRSToGeogCRSIn, const CoordinateOperationPtr &verticalTransformIn, const CoordinateOperationPtr &opGeogCRStoDstCRSIn, const crs::SingleCRSPtr &interpolationCRSIn) : opSrcCRSToGeogCRS(opSrcCRSToGeogCRSIn), verticalTransform(verticalTransformIn), opGeogCRStoDstCRS(opGeogCRStoDstCRSIn), interpolationCRS(interpolationCRSIn) {} ~MyPROJStringExportableHorizVerticalHorizPROJBased() override; void // cppcheck-suppress functionStatic _exportToPROJString(io::PROJStringFormatter *formatter) const override { bool saveHorizontalCoords = false; const auto transf = dynamic_cast(opSrcCRSToGeogCRS.get()); if (transf && opSrcCRSToGeogCRS->sourceCRS()->_isEquivalentTo( opGeogCRStoDstCRS->targetCRS() ->demoteTo2D(std::string(), nullptr) .get(), util::IComparable::Criterion::EQUIVALENT)) { int methodEPSGCode = transf->method()->getEPSGCode(); if (methodEPSGCode == 0) { // If the transformation is actually an inverse transformation, // we will not get the EPSG code. So get the forward // transformation. const auto invTrans = transf->inverse(); const auto invTransAsTrans = dynamic_cast(invTrans.get()); if (invTransAsTrans) methodEPSGCode = invTransAsTrans->method()->getEPSGCode(); } const bool bGeocentricTranslation = methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D; if ((bGeocentricTranslation && !(transf->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) == 0 && transf->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) == 0 && transf->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) == 0)) || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND || methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || methodEPSGCode == EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { saveHorizontalCoords = true; } } if (saveHorizontalCoords) { formatter->addStep("push"); formatter->addParam("v_1"); formatter->addParam("v_2"); } auto interpolationCRSGeog = dynamic_cast(interpolationCRS.get()); auto interpolationCRSProj = dynamic_cast(interpolationCRS.get()); formatter->pushOmitZUnitConversion(); opSrcCRSToGeogCRS->_exportToPROJString(formatter); formatter->startInversion(); if (interpolationCRSGeog) interpolationCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); else if (interpolationCRSProj) interpolationCRSProj->addUnitConvertAndAxisSwap(formatter, false); formatter->stopInversion(); formatter->popOmitZUnitConversion(); formatter->pushOmitHorizontalConversionInVertTransformation(); verticalTransform->_exportToPROJString(formatter); formatter->popOmitHorizontalConversionInVertTransformation(); formatter->pushOmitZUnitConversion(); if (interpolationCRSGeog) interpolationCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); else if (interpolationCRSProj) interpolationCRSProj->addUnitConvertAndAxisSwap(formatter, false); opGeogCRStoDstCRS->_exportToPROJString(formatter); formatter->popOmitZUnitConversion(); if (saveHorizontalCoords) { formatter->addStep("pop"); formatter->addParam("v_1"); formatter->addParam("v_2"); } } }; MyPROJStringExportableHorizVerticalHorizPROJBased:: ~MyPROJStringExportableHorizVerticalHorizPROJBased() = default; // --------------------------------------------------------------------------- struct MyPROJStringExportableHorizNullVertical final : public io::IPROJStringExportable { CoordinateOperationPtr horizTransform{}; explicit MyPROJStringExportableHorizNullVertical( const CoordinateOperationPtr &horizTransformIn) : horizTransform(horizTransformIn) {} ~MyPROJStringExportableHorizNullVertical() override; void // cppcheck-suppress functionStatic _exportToPROJString(io::PROJStringFormatter *formatter) const override { horizTransform->_exportToPROJString(formatter); } }; MyPROJStringExportableHorizNullVertical:: ~MyPROJStringExportableHorizNullVertical() = default; //! @endcond } // namespace operation NS_PROJ_END #if 0 namespace dropbox{ namespace oxygen { template<> nn>::~nn() = default; template<> nn>::~nn() = default; template<> nn>::~nn() = default; template<> nn>::~nn() = default; }} #endif NS_PROJ_START namespace operation { //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- static std::string buildTransfName(const std::string &srcName, const std::string &targetName) { std::string name("Transformation from "); name += srcName; name += " to "; name += targetName; return name; } // --------------------------------------------------------------------------- static std::string buildConvName(const std::string &srcName, const std::string &targetName) { std::string name("Conversion from "); name += srcName; name += " to "; name += targetName; return name; } // --------------------------------------------------------------------------- static SingleOperationNNPtr createPROJBased( const util::PropertyMap &properties, const io::IPROJStringExportableNNPtr &projExportable, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::CRSPtr &interpolationCRS, const std::vector &accuracies, bool hasBallparkTransformation) { return util::nn_static_pointer_cast( PROJBasedOperation::create(properties, projExportable, false, sourceCRS, targetCRS, interpolationCRS, accuracies, hasBallparkTransformation)); } // --------------------------------------------------------------------------- static CoordinateOperationNNPtr createGeodToGeodPROJBased(const crs::CRSNNPtr &geodSrc, const crs::CRSNNPtr &geodDst) { auto exportable = util::nn_make_shared( util::nn_dynamic_pointer_cast(geodSrc), util::nn_dynamic_pointer_cast(geodDst)); auto properties = util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, buildTransfName(geodSrc->nameStr(), geodDst->nameStr())) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD); return createPROJBased(properties, exportable, geodSrc, geodDst, nullptr, {}, false); } // --------------------------------------------------------------------------- static std::string getRemarks(const std::vector &ops) { std::string remarks; for (const auto &op : ops) { const auto &opRemarks = op->remarks(); if (!opRemarks.empty()) { if (!remarks.empty()) { remarks += '\n'; } std::string opName(op->nameStr()); if (starts_with(opName, INVERSE_OF)) { opName = opName.substr(INVERSE_OF.size()); } remarks += "For "; remarks += opName; const auto &ids = op->identifiers(); if (!ids.empty()) { std::string authority(*ids.front()->codeSpace()); if (starts_with(authority, "INVERSE(") && authority.back() == ')') { authority = authority.substr(strlen("INVERSE("), authority.size() - 1 - strlen("INVERSE(")); } if (starts_with(authority, "DERIVED_FROM(") && authority.back() == ')') { authority = authority.substr(strlen("DERIVED_FROM("), authority.size() - 1 - strlen("DERIVED_FROM(")); } remarks += " ("; remarks += authority; remarks += ':'; remarks += ids.front()->code(); remarks += ')'; } remarks += ": "; remarks += opRemarks; } } return remarks; } // --------------------------------------------------------------------------- static CoordinateOperationNNPtr createHorizVerticalPROJBased( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const operation::CoordinateOperationNNPtr &horizTransform, const operation::CoordinateOperationNNPtr &verticalTransform, bool checkExtent) { auto geogDst = util::nn_dynamic_pointer_cast(targetCRS); assert(geogDst); auto exportable = util::nn_make_shared( horizTransform, verticalTransform, geogDst); const bool horizTransformIsNoOp = starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) && horizTransform->nameStr().find(" + ") == std::string::npos; if (horizTransformIsNoOp) { auto properties = util::PropertyMap(); properties.set(common::IdentifiedObject::NAME_KEY, verticalTransform->nameStr()); bool dummy = false; auto extent = getExtent(verticalTransform, true, dummy); if (extent) { properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } const auto &remarks = verticalTransform->remarks(); if (!remarks.empty()) { properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); } auto accuracies = verticalTransform->coordinateOperationAccuracies(); if (accuracies.empty() && dynamic_cast(verticalTransform.get())) { accuracies.emplace_back(metadata::PositionalAccuracy::create("0")); } return createPROJBased(properties, exportable, sourceCRS, targetCRS, nullptr, accuracies, verticalTransform->hasBallparkTransformation()); } else { bool emptyIntersection = false; auto ops = std::vector{horizTransform, verticalTransform}; auto extent = getExtent(ops, true, emptyIntersection); if (checkExtent && emptyIntersection) { std::string msg( "empty intersection of area of validity of concatenated " "operations"); throw InvalidOperationEmptyIntersection(msg); } auto properties = util::PropertyMap(); properties.set(common::IdentifiedObject::NAME_KEY, computeConcatenatedName(ops)); if (extent) { properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } const auto remarks = getRemarks(ops); if (!remarks.empty()) { properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); } std::vector accuracies; const double accuracy = getAccuracy(ops); if (accuracy >= 0.0) { accuracies.emplace_back( metadata::PositionalAccuracy::create(toString(accuracy))); } return createPROJBased( properties, exportable, sourceCRS, targetCRS, nullptr, accuracies, horizTransform->hasBallparkTransformation() || verticalTransform->hasBallparkTransformation()); } } // --------------------------------------------------------------------------- static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const operation::CoordinateOperationNNPtr &opSrcCRSToGeogCRS, const operation::CoordinateOperationNNPtr &verticalTransform, const operation::CoordinateOperationNNPtr &opGeogCRStoDstCRS, const crs::SingleCRSPtr &interpolationCRS, bool checkExtent) { auto exportable = util::nn_make_shared( opSrcCRSToGeogCRS, verticalTransform, opGeogCRStoDstCRS, interpolationCRS); std::vector ops; if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) && opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) { ops.emplace_back(opSrcCRSToGeogCRS); } ops.emplace_back(verticalTransform); if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) && opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) { ops.emplace_back(opGeogCRStoDstCRS); } std::vector opsForRemarks; std::vector opsForAccuracy; std::string opName; if (ops.size() == 3 && opGeogCRStoDstCRS->inverse()->_isEquivalentTo( opSrcCRSToGeogCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { opsForRemarks.emplace_back(opSrcCRSToGeogCRS); opsForRemarks.emplace_back(verticalTransform); // Only taking into account the accuracy of the vertical transform when // opSrcCRSToGeogCRS and opGeogCRStoDstCRS are reversed and cancel // themselves would make sense. Unfortunately it causes // EPSG:4313+5710 (BD72 + Ostend height) to EPSG:9707 // (WGS 84 + EGM96 height) to use a non-ideal pipeline. // opsForAccuracy.emplace_back(verticalTransform); opsForAccuracy = ops; opName = verticalTransform->nameStr() + " using "; if (!starts_with(opSrcCRSToGeogCRS->nameStr(), "Inverse of")) opName += opSrcCRSToGeogCRS->nameStr(); else opName += opGeogCRStoDstCRS->nameStr(); } else { opsForRemarks = ops; opsForAccuracy = ops; opName = computeConcatenatedName(ops); } bool hasBallparkTransformation = false; for (const auto &op : ops) { hasBallparkTransformation |= op->hasBallparkTransformation(); } bool emptyIntersection = false; auto extent = getExtent(ops, false, emptyIntersection); if (checkExtent && emptyIntersection) { std::string msg( "empty intersection of area of validity of concatenated " "operations"); throw InvalidOperationEmptyIntersection(msg); } auto properties = util::PropertyMap(); properties.set(common::IdentifiedObject::NAME_KEY, opName); if (extent) { properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } const auto remarks = getRemarks(opsForRemarks); if (!remarks.empty()) { properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); } std::vector accuracies; const double accuracy = getAccuracy(opsForAccuracy); if (accuracy >= 0.0) { accuracies.emplace_back( metadata::PositionalAccuracy::create(toString(accuracy))); } return createPROJBased(properties, exportable, sourceCRS, targetCRS, nullptr, accuracies, hasBallparkTransformation); } // --------------------------------------------------------------------------- static CoordinateOperationNNPtr createHorizNullVerticalPROJBased( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const operation::CoordinateOperationNNPtr &horizTransform, const operation::CoordinateOperationNNPtr &verticalTransform) { auto exportable = util::nn_make_shared( horizTransform); std::vector ops = {horizTransform, verticalTransform}; const std::string opName = computeConcatenatedName(ops); auto properties = util::PropertyMap(); properties.set(common::IdentifiedObject::NAME_KEY, opName); bool emptyIntersection = false; auto extent = getExtent(ops, false, emptyIntersection); if (extent) { properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } const auto remarks = getRemarks(ops); if (!remarks.empty()) { properties.set(common::IdentifiedObject::REMARKS_KEY, remarks); } return createPROJBased(properties, exportable, sourceCRS, targetCRS, nullptr, {}, /*hasBallparkTransformation=*/true); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::vector CoordinateOperationFactory::Private::createOperationsGeogToGeog( std::vector &res, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst, bool forceBallpark) { assert(sourceCRS.get() == geogSrc); assert(targetCRS.get() == geogDst); const auto &src_pm = geogSrc->primeMeridian()->longitude(); const auto &dst_pm = geogDst->primeMeridian()->longitude(); const common::Angle offset_pm( (src_pm.unit() == dst_pm.unit()) ? common::Angle(src_pm.value() - dst_pm.value(), src_pm.unit()) : common::Angle( src_pm.convertToUnit(common::UnitOfMeasure::DEGREE) - dst_pm.convertToUnit(common::UnitOfMeasure::DEGREE), common::UnitOfMeasure::DEGREE)); double vconvSrc = 1.0; const auto &srcCS = geogSrc->coordinateSystem(); const auto &srcAxisList = srcCS->axisList(); if (srcAxisList.size() == 3) { vconvSrc = srcAxisList[2]->unit().conversionToSI(); } double vconvDst = 1.0; const auto &dstCS = geogDst->coordinateSystem(); const auto &dstAxisList = dstCS->axisList(); if (dstAxisList.size() == 3) { vconvDst = dstAxisList[2]->unit().conversionToSI(); } std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr())); const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const bool sameDatum = !forceBallpark && isSameGeodeticDatum(geogSrc->datumNonNull(dbContext), geogDst->datumNonNull(dbContext), dbContext); // Do the CRS differ by their axis order ? bool axisReversal2D = false; bool axisReversal3D = false; if (!srcCS->_isEquivalentTo(dstCS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto srcOrder = srcCS->axisOrder(); auto dstOrder = dstCS->axisOrder(); if (((srcOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST || srcOrder == cs::EllipsoidalCS::AxisOrder:: LAT_NORTH_LONG_EAST_HEIGHT_UP) && (dstOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || dstOrder == cs::EllipsoidalCS::AxisOrder:: LONG_EAST_LAT_NORTH_HEIGHT_UP)) || ((srcOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || srcOrder == cs::EllipsoidalCS::AxisOrder:: LONG_EAST_LAT_NORTH_HEIGHT_UP) && (dstOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST || dstOrder == cs::EllipsoidalCS::AxisOrder:: LAT_NORTH_LONG_EAST_HEIGHT_UP))) { if (srcAxisList.size() == 3 || dstAxisList.size() == 3) axisReversal3D = true; else axisReversal2D = true; } } // Do they differ by vertical units ? if (vconvSrc != vconvDst && geogSrc->ellipsoid()->_isEquivalentTo( geogDst->ellipsoid().get(), util::IComparable::Criterion::EQUIVALENT)) { if (offset_pm.value() == 0 && !axisReversal2D && !axisReversal3D) { // If only by vertical units, use a Change of Vertical // Unit transformation if (vconvDst == 0) throw InvalidOperation("Conversion factor of target unit is 0"); const double factor = vconvSrc / vconvDst; auto conv = Conversion::createChangeVerticalUnit( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name), common::Scale(factor)); conv->setCRSs(sourceCRS, targetCRS, nullptr); conv->setHasBallparkTransformation(!sameDatum); res.push_back(conv); return res; } else { auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS); op->setHasBallparkTransformation(!sameDatum); res.emplace_back(op); return res; } } // Do the CRS differ only by their axis order ? if (sameDatum && (axisReversal2D || axisReversal3D)) { auto conv = Conversion::createAxisOrderReversal(axisReversal3D); conv->setCRSs(sourceCRS, targetCRS, nullptr); res.emplace_back(conv); return res; } std::vector steps; // If both are geographic and only differ by their prime // meridian, // apply a longitude rotation transformation. if (geogSrc->ellipsoid()->_isEquivalentTo( geogDst->ellipsoid().get(), util::IComparable::Criterion::EQUIVALENT) && src_pm.getSIValue() != dst_pm.getSIValue()) { steps.emplace_back(Transformation::createLongitudeRotation( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), sourceCRS, targetCRS, offset_pm)); // If only the target has a non-zero prime meridian, chain a // null geographic offset and then the longitude rotation } else if (src_pm.getSIValue() == 0 && dst_pm.getSIValue() != 0) { auto datum = datum::GeodeticReferenceFrame::create( util::PropertyMap(), geogDst->ellipsoid(), util::optional(), geogSrc->primeMeridian()); std::string interm_crs_name(geogDst->nameStr()); interm_crs_name += " altered to use prime meridian of "; interm_crs_name += geogSrc->nameStr(); auto interm_crs = util::nn_static_pointer_cast(crs::GeographicCRS::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, interm_crs_name) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), datum, dstCS)); steps.emplace_back(createBallparkGeographicOffset( sourceCRS, interm_crs, dbContext, forceBallpark)); steps.emplace_back(Transformation::createLongitudeRotation( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, buildTransfName(geogSrc->nameStr(), interm_crs->nameStr())) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), interm_crs, targetCRS, offset_pm)); } else { // If the prime meridians are different, chain a longitude // rotation and the null geographic offset. if (src_pm.getSIValue() != dst_pm.getSIValue()) { auto datum = datum::GeodeticReferenceFrame::create( util::PropertyMap(), geogSrc->ellipsoid(), util::optional(), geogDst->primeMeridian()); std::string interm_crs_name(geogSrc->nameStr()); interm_crs_name += " altered to use prime meridian of "; interm_crs_name += geogDst->nameStr(); auto interm_crs = util::nn_static_pointer_cast( crs::GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, interm_crs_name), datum, srcCS)); steps.emplace_back(Transformation::createLongitudeRotation( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, buildTransfName(geogSrc->nameStr(), interm_crs->nameStr())) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), sourceCRS, interm_crs, offset_pm)); steps.emplace_back(createBallparkGeographicOffset( interm_crs, targetCRS, dbContext, forceBallpark)); } else { steps.emplace_back(createBallparkGeographicOffset( sourceCRS, targetCRS, dbContext, forceBallpark)); } } auto op = ConcatenatedOperation::createComputeMetadata( steps, context.disallowEmptyIntersection()); op->setHasBallparkTransformation(!sameDatum); res.emplace_back(op); return res; } // --------------------------------------------------------------------------- static bool hasIdentifiers(const CoordinateOperationNNPtr &op) { if (!op->identifiers().empty()) { return true; } auto concatenated = dynamic_cast(op.get()); if (concatenated) { for (const auto &subOp : concatenated->operations()) { if (hasIdentifiers(subOp)) { return true; } } } return false; } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::setCRSs( CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { co->setCRSsUpdateInverse(sourceCRS, targetCRS, co->interpolationCRS()); } // --------------------------------------------------------------------------- static bool hasResultSetOnlyResultsWithPROJStep( const std::vector &res) { for (const auto &op : res) { auto concat = dynamic_cast(op.get()); if (concat) { bool hasPROJStep = false; const auto &steps = concat->operations(); for (const auto &step : steps) { const auto &ids = step->identifiers(); if (!ids.empty()) { const auto &opAuthority = *(ids.front()->codeSpace()); if (opAuthority == "PROJ" || opAuthority == "INVERSE(PROJ)" || opAuthority == "DERIVED_FROM(PROJ)") { hasPROJStep = true; break; } } } if (!hasPROJStep) { return false; } } else { return false; } } return true; } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( std::vector &res, const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, Private::Context &context) { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("createOperationsWithDatumPivot(" + objectAsStr(sourceCRS.get()) + "," + objectAsStr(targetCRS.get()) + ")"); #endif struct CreateOperationsWithDatumPivotAntiRecursion { Context &context; explicit CreateOperationsWithDatumPivotAntiRecursion(Context &contextIn) : context(contextIn) { assert(!context.inCreateOperationsWithDatumPivotAntiRecursion); context.inCreateOperationsWithDatumPivotAntiRecursion = true; } ~CreateOperationsWithDatumPivotAntiRecursion() { context.inCreateOperationsWithDatumPivotAntiRecursion = false; } }; CreateOperationsWithDatumPivotAntiRecursion guard(context); const auto &authFactory = context.context->getAuthorityFactory(); const auto &dbContext = authFactory->databaseContext(); const auto srcDatum = geodSrc->datumNonNull(dbContext.as_nullable()); if (srcDatum->identifiers().empty() && (ci_equal(srcDatum->nameStr(), "unknown") || ci_starts_with(srcDatum->nameStr(), UNKNOWN_BASED_ON))) return; const auto dstDatum = geodDst->datumNonNull(dbContext.as_nullable()); if (dstDatum->identifiers().empty() && (ci_equal(dstDatum->nameStr(), "unknown") || ci_starts_with(dstDatum->nameStr(), UNKNOWN_BASED_ON))) return; const auto candidatesSrcGeod( findCandidateGeodCRSForDatum(authFactory, geodSrc, srcDatum)); const auto candidatesDstGeod( findCandidateGeodCRSForDatum(authFactory, geodDst, dstDatum)); const bool sourceAndTargetAre3D = geodSrc->coordinateSystem()->axisList().size() == 3 && geodDst->coordinateSystem()->axisList().size() == 3; auto createTransformations = [&](const crs::CRSNNPtr &candidateSrcGeod, const crs::CRSNNPtr &candidateDstGeod, const CoordinateOperationNNPtr &opFirst, bool isNullFirst, bool useOnlyDirectRegistryOp) { bool resNonEmptyBeforeFiltering; // Deal with potential epoch change std::vector opsEpochChangeSrc; std::vector opsEpochChangeDst; if (sourceEpoch.has_value() && targetEpoch.has_value() && !sourceEpoch->coordinateEpoch()._isEquivalentTo( targetEpoch->coordinateEpoch())) { const auto pmoSrc = context.context->getAuthorityFactory() ->getPointMotionOperationsFor( NN_NO_CHECK( util::nn_dynamic_pointer_cast( candidateSrcGeod)), true); if (!pmoSrc.empty()) { opsEpochChangeSrc = createOperations(candidateSrcGeod, sourceEpoch, candidateSrcGeod, targetEpoch, context); } else { const auto pmoDst = context.context->getAuthorityFactory() ->getPointMotionOperationsFor( NN_NO_CHECK( util::nn_dynamic_pointer_cast( candidateDstGeod)), true); if (!pmoDst.empty()) { opsEpochChangeDst = createOperations( candidateDstGeod, sourceEpoch, candidateDstGeod, targetEpoch, context); } } } const std::vector opsSecond( useOnlyDirectRegistryOp ? findOpsInRegistryDirect(candidateSrcGeod, candidateDstGeod, context, resNonEmptyBeforeFiltering) : createOperations(candidateSrcGeod, targetEpoch, candidateDstGeod, targetEpoch, context)); const auto opsThird = createOperations( sourceAndTargetAre3D ? candidateDstGeod->promoteTo3D(std::string(), dbContext) : candidateDstGeod, targetEpoch, targetCRS, targetEpoch, context); assert(!opsThird.empty()); const CoordinateOperationNNPtr &opThird(opsThird[0]); const auto nIters = std::max( 1, std::max(opsEpochChangeSrc.size(), opsEpochChangeDst.size())); for (size_t iEpochChange = 0; iEpochChange < nIters; ++iEpochChange) { for (auto &opSecond : opsSecond) { // Check that it is not a transformation synthesized by // ourselves if (!hasIdentifiers(opSecond)) { continue; } // And even if it is a referenced transformation, check that // it is not a trivial one auto so = dynamic_cast(opSecond.get()); if (so && isAxisOrderReversal(so->method()->getEPSGCode())) { continue; } std::vector subOps; const bool isNullThird = isNullTransformation(opThird->nameStr()); CoordinateOperationNNPtr opSecondCloned( (isNullFirst || isNullThird || sourceAndTargetAre3D) ? opSecond->shallowClone() : opSecond); if (isNullFirst || isNullThird) { if (opSecondCloned->identifiers().size() == 1 && (*opSecondCloned->identifiers()[0]->codeSpace()) .find("DERIVED_FROM") == std::string::npos) { { util::PropertyMap map; addModifiedIdentifier(map, opSecondCloned.get(), false, true); opSecondCloned->setProperties(map); } auto invCO = dynamic_cast( opSecondCloned.get()); if (invCO) { auto invCOForward = invCO->forwardOperation().get(); if (invCOForward->identifiers().size() == 1 && (*invCOForward->identifiers()[0]->codeSpace()) .find("DERIVED_FROM") == std::string::npos) { util::PropertyMap map; addModifiedIdentifier(map, invCOForward, false, true); invCOForward->setProperties(map); } } } } if (sourceAndTargetAre3D) { // Force Helmert operations to use the 3D domain, even if // the ones we found in EPSG are advertised for the 2D // domain. auto concat = dynamic_cast( opSecondCloned.get()); if (concat) { std::vector newSteps; for (const auto &step : concat->operations()) { auto newStep = step->shallowClone(); setCRSs(newStep.get(), newStep->sourceCRS()->promoteTo3D( std::string(), dbContext), newStep->targetCRS()->promoteTo3D( std::string(), dbContext)); newSteps.emplace_back(newStep); } opSecondCloned = ConcatenatedOperation::createComputeMetadata( newSteps, context.disallowEmptyIntersection()); } else { setCRSs(opSecondCloned.get(), opSecondCloned->sourceCRS()->promoteTo3D( std::string(), dbContext), opSecondCloned->targetCRS()->promoteTo3D( std::string(), dbContext)); } } if (!isNullFirst) { subOps.emplace_back(opFirst); } if (!opsEpochChangeSrc.empty()) { subOps.emplace_back(opsEpochChangeSrc[iEpochChange]); } subOps.emplace_back(opSecondCloned); if (!opsEpochChangeDst.empty()) { subOps.emplace_back(opsEpochChangeDst[iEpochChange]); } if (!isNullThird) { subOps.emplace_back(opThird); } subOps[0] = subOps[0]->shallowClone(); if (subOps[0]->targetCRS()) setCRSs(subOps[0].get(), sourceCRS, NN_NO_CHECK(subOps[0]->targetCRS())); subOps.back() = subOps.back()->shallowClone(); if (subOps[0]->sourceCRS()) setCRSs(subOps.back().get(), NN_NO_CHECK(subOps.back()->sourceCRS()), targetCRS); #ifdef TRACE_CREATE_OPERATIONS std::string debugStr; for (const auto &op : subOps) { if (!debugStr.empty()) { debugStr += " + "; } debugStr += objectAsStr(op.get()); debugStr += " ("; debugStr += objectAsStr(op->sourceCRS().get()); debugStr += "->"; debugStr += objectAsStr(op->targetCRS().get()); debugStr += ")"; } logTrace("transformation " + debugStr); #endif try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( subOps, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } }; // The below logic is thus quite fragile, and attempts at changing it // result in degraded results for other use cases... // // Start in priority with candidates that have exactly the same name as // the sourceCRS and targetCRS (Typically for the case of init=IGNF:XXXX); // and then attempt candidate geodetic CRS with different names // // Transformation from IGNF:NTFP to IGNF:RGF93G, // using // NTF geographiques Paris (gr) vers NTF GEOGRAPHIQUES GREENWICH (DMS) + // NOUVELLE TRIANGULATION DE LA FRANCE (NTF) vers RGF93 (ETRS89) // that is using ntf_r93.gsb, is horribly dependent // of IGNF:RGF93G being returned before IGNF:RGF93GEO in candidatesDstGeod. // If RGF93GEO is returned before then we go through WGS84 and use // instead a Helmert transformation. // // Actually, in the general case, we do the lookup in 3 passes with the 2 // above steps in each pass: // - one first pass where we only consider direct transformations (no // other intermediate CRS) // - a second pass where we allow transformation through another // intermediate CRS, but we make sure the candidate geodetic CRS are of // the same type // - a third where we allow transformation through another // intermediate CRS, where the candidate geodetic CRS are of different // type. // ... but when transforming between 2 IGNF CRS, we do just one single pass // by allowing directly all transformation. There is no strong reason for // that particular case, except that otherwise we'd get different results // for test/cli/test_cs2cs_ignf.yaml when transforming a point outside // the area of validity... Not totally sure the behavior we try to preserve // here with the particular case is fundamentally better than the general // case. The general case is needed typically for the RGNC91-93 -> RGNC15 // transformation where we we need to actually use a transformation between // RGNC91-93 (lon-lat) -> RGNC15 (lon-lat), and not chain 2 no-op // transformations RGNC91-93 -> WGS 84 and RGNC15 -> WGS84. const auto isIGNF = [](const crs::CRSNNPtr &crs) { const auto &ids = crs->identifiers(); return !ids.empty() && *(ids.front()->codeSpace()) == "IGNF"; }; const int nIters = (isIGNF(sourceCRS) && isIGNF(targetCRS)) ? 1 : 3; const auto getType = [](const crs::GeodeticCRSNNPtr &crs) { if (auto geogCRS = dynamic_cast(crs.get())) { if (geogCRS->coordinateSystem()->axisList().size() == 3) return 1; return 0; } return 2; }; for (int iter = 0; iter < nIters; ++iter) { const bool useOnlyDirectRegistryOp = (iter == 0 && nIters == 3); for (const auto &candidateSrcGeod : candidatesSrcGeod) { if (candidateSrcGeod->nameStr() == sourceCRS->nameStr()) { const auto typeSource = (iter >= 1) ? getType(candidateSrcGeod) : -1; auto sourceSrcGeodModified(sourceAndTargetAre3D ? candidateSrcGeod->promoteTo3D( std::string(), dbContext) : candidateSrcGeod); for (const auto &candidateDstGeod : candidatesDstGeod) { if (candidateDstGeod->nameStr() == targetCRS->nameStr()) { if (iter == 1) { if (typeSource != getType(candidateDstGeod)) { continue; } } else if (iter == 2) { if (typeSource == getType(candidateDstGeod)) { continue; } } #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("iter=" + toString(iter) + ", try " + objectAsStr(sourceCRS.get()) + "->" + objectAsStr(candidateSrcGeod.get()) + "->" + objectAsStr(candidateDstGeod.get()) + "->" + objectAsStr(targetCRS.get()) + ")"); #endif const auto opsFirst = createOperations( sourceCRS, sourceEpoch, sourceSrcGeodModified, sourceEpoch, context); assert(!opsFirst.empty()); const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr()); createTransformations( candidateSrcGeod, candidateDstGeod, opsFirst[0], isNullFirst, useOnlyDirectRegistryOp); if (!res.empty()) { if (hasResultSetOnlyResultsWithPROJStep(res)) { continue; } return; } } } } } for (const auto &candidateSrcGeod : candidatesSrcGeod) { const bool bSameSrcName = candidateSrcGeod->nameStr() == sourceCRS->nameStr(); #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK(""); #endif auto sourceSrcGeodModified( sourceAndTargetAre3D ? candidateSrcGeod->promoteTo3D(std::string(), dbContext) : candidateSrcGeod); const auto opsFirst = createOperations(sourceCRS, sourceEpoch, sourceSrcGeodModified, sourceEpoch, context); assert(!opsFirst.empty()); const bool isNullFirst = isNullTransformation(opsFirst[0]->nameStr()); for (const auto &candidateDstGeod : candidatesDstGeod) { if (bSameSrcName && candidateDstGeod->nameStr() == targetCRS->nameStr()) { continue; } #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("try " + objectAsStr(sourceCRS.get()) + "->" + objectAsStr(candidateSrcGeod.get()) + "->" + objectAsStr(candidateDstGeod.get()) + "->" + objectAsStr(targetCRS.get()) + ")"); #endif createTransformations(candidateSrcGeod, candidateDstGeod, opsFirst[0], isNullFirst, useOnlyDirectRegistryOp); if (!res.empty() && !hasResultSetOnlyResultsWithPROJStep(res)) { return; } } } } } // --------------------------------------------------------------------------- static CoordinateOperationNNPtr createBallparkGeocentricTranslation(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { std::string name(BALLPARK_GEOCENTRIC_TRANSLATION); name += " from "; name += sourceCRS->nameStr(); name += " to "; name += targetCRS->nameStr(); return util::nn_static_pointer_cast( Transformation::createGeocentricTranslations( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), sourceCRS, targetCRS, 0.0, 0.0, 0.0, {})); } // --------------------------------------------------------------------------- bool CoordinateOperationFactory::Private::hasPerfectAccuracyResult( const std::vector &res, const Context &context) { auto resTmp = FilterResults(res, context.context, context.extent1, context.extent2, true) .getRes(); for (const auto &op : resTmp) { const double acc = getAccuracy(op); if (acc == 0.0) { return true; } } return false; } // --------------------------------------------------------------------------- std::vector CoordinateOperationFactory::Private::createOperations( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context) { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("createOperations(" + objectAsStr(sourceCRS.get()) + " --> " + objectAsStr(targetCRS.get()) + ")"); #endif #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION // 10 is arbitrary and hopefully large enough for all transformations PROJ // can handle. // At time of writing 7 is the maximum known to be required by a few tests // like // operation.compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_WKT1_same_geoidgrids_context // We don't enable that check for fuzzing, to be able to detect // the root cause of recursions. if (context.nRecLevelCreateOperations == 10) { throw InvalidOperation("Too deep recursion in createOperations()"); } #endif struct RecLevelIncrementer { Private::Context &context_; explicit inline RecLevelIncrementer(Private::Context &contextIn) : context_(contextIn) { ++context_.nRecLevelCreateOperations; } inline ~RecLevelIncrementer() { --context_.nRecLevelCreateOperations; } }; RecLevelIncrementer recLevelIncrementer(context); std::vector res; auto boundSrc = dynamic_cast(sourceCRS.get()); auto boundDst = dynamic_cast(targetCRS.get()); const auto &sourceProj4Ext = boundSrc ? boundSrc->baseCRS()->getExtensionProj4() : sourceCRS->getExtensionProj4(); const auto &targetProj4Ext = boundDst ? boundDst->baseCRS()->getExtensionProj4() : targetCRS->getExtensionProj4(); if (!sourceProj4Ext.empty() || !targetProj4Ext.empty()) { createOperationsFromProj4Ext(sourceCRS, targetCRS, boundSrc, boundDst, res); return res; } auto geodSrc = dynamic_cast(sourceCRS.get()); auto geodDst = dynamic_cast(targetCRS.get()); auto geogSrc = dynamic_cast(sourceCRS.get()); auto geogDst = dynamic_cast(targetCRS.get()); auto vertSrc = dynamic_cast(sourceCRS.get()); auto vertDst = dynamic_cast(targetCRS.get()); // First look-up if the registry provide us with operations. auto derivedSrc = dynamic_cast(sourceCRS.get()); auto derivedDst = dynamic_cast(targetCRS.get()); const auto &authFactory = context.context->getAuthorityFactory(); if (authFactory && (derivedSrc == nullptr || !derivedSrc->baseCRS()->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) && (derivedDst == nullptr || !derivedDst->baseCRS()->_isEquivalentTo( sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT))) { if (createOperationsFromDatabase( sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, geodSrc, geodDst, geogSrc, geogDst, vertSrc, vertDst, res)) { return res; } } if (geodSrc && geodSrc->isSphericalPlanetocentric()) { createOperationsFromSphericalPlanetocentric(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, geodSrc, res); return res; } else if (geodDst && geodDst->isSphericalPlanetocentric()) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } // Special case if both CRS are geodetic if (geodSrc && geodDst && !derivedSrc && !derivedDst) { createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc, geodDst, res, /*forceBallpark=*/false); return res; } if (boundSrc) { auto geodSrcBase = util::nn_dynamic_pointer_cast( boundSrc->baseCRS()); if (geodSrcBase && geodSrcBase->isSphericalPlanetocentric()) { createOperationsFromBoundOfSphericalPlanetocentric( sourceCRS, targetCRS, context, boundSrc, NN_NO_CHECK(geodSrcBase), res); return res; } } else if (boundDst) { auto geodDstBase = util::nn_dynamic_pointer_cast( boundDst->baseCRS()); if (geodDstBase && geodDstBase->isSphericalPlanetocentric()) { return applyInverse(createOperations( targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } } // If the source is a derived CRS, then chain the inverse of its // deriving conversion, with transforms from its baseCRS to the // targetCRS if (derivedSrc) { createOperationsDerivedTo(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, derivedSrc, res); return res; } // reverse of previous case if (derivedDst) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } // Order of comparison between the geogDst vs geodDst is important if (boundSrc && geogDst) { createOperationsBoundToGeog(sourceCRS, targetCRS, context, boundSrc, geogDst, res); return res; } else if (boundSrc && geodDst) { createOperationsToGeod(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, geodDst, res); return res; } // reverse of previous case if (geodSrc && boundDst) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } // vertCRS (as boundCRS with transformation to target vertCRS) to // vertCRS if (boundSrc && vertDst) { createOperationsBoundToVert(sourceCRS, targetCRS, context, boundSrc, vertDst, res); return res; } // reverse of previous case if (boundDst && vertSrc) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } if (vertSrc && vertDst) { createOperationsVertToVert(sourceCRS, targetCRS, context, vertSrc, vertDst, res); return res; } // A bit odd case as we are comparing apples to oranges, but in case // the vertical unit differ, do something useful. if (vertSrc && geogDst) { createOperationsVertToGeog(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, vertSrc, geogDst, res); return res; } // reverse of previous case if (vertDst && geogSrc) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } // boundCRS to boundCRS if (boundSrc && boundDst) { createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc, boundDst, res); return res; } auto compoundSrc = dynamic_cast(sourceCRS.get()); // Order of comparison between the geogDst vs geodDst is important if (compoundSrc && geogDst) { createOperationsCompoundToGeog(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, compoundSrc, geogDst, res); return res; } else if (compoundSrc && geodDst) { createOperationsToGeod(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, geodDst, res); return res; } // reverse of previous cases auto compoundDst = dynamic_cast(targetCRS.get()); if (geodSrc && compoundDst) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } if (compoundSrc && compoundDst) { createOperationsCompoundToCompound(sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, compoundSrc, compoundDst, res); return res; } // '+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +type=crs' to // '+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@bar.gtx // +type=crs' if (boundSrc && compoundDst) { createOperationsBoundToCompound(sourceCRS, targetCRS, context, boundSrc, compoundDst, res); return res; } // reverse of previous case if (boundDst && compoundSrc) { return applyInverse(createOperations(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context)); } if (dynamic_cast(sourceCRS.get()) && sourceCRS->_isEquivalentTo(targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { std::string name("Identity transformation from "); name += sourceCRS->nameStr(); name += " to "; name += targetCRS->nameStr(); res.push_back(Transformation::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), sourceCRS, targetCRS, nullptr, createMethodMapNameEPSGCode( EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS), VectorOfParameters{ createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_EASTING_OFFSET), createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_NORTHING_OFFSET), }, VectorOfValues{ common::Length(0), common::Length(0), }, {metadata::PositionalAccuracy::create("0")})); } return res; } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsFromProj4Ext( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, std::vector &res) { ENTER_FUNCTION(); auto sourceProjExportable = dynamic_cast( boundSrc ? boundSrc : sourceCRS.get()); auto targetProjExportable = dynamic_cast( boundDst ? boundDst : targetCRS.get()); if (!sourceProjExportable) { throw InvalidOperation("Source CRS is not PROJ exportable"); } if (!targetProjExportable) { throw InvalidOperation("Target CRS is not PROJ exportable"); } auto projFormatter = io::PROJStringFormatter::create(); projFormatter->setCRSExport(true); projFormatter->setLegacyCRSToCRSContext(true); projFormatter->startInversion(); sourceProjExportable->_exportToPROJString(projFormatter.get()); auto geogSrc = dynamic_cast( boundSrc ? boundSrc->baseCRS().get() : sourceCRS.get()); if (geogSrc) { auto tmpFormatter = io::PROJStringFormatter::create(); geogSrc->addAngularUnitConvertAndAxisSwap(tmpFormatter.get()); projFormatter->ingestPROJString(tmpFormatter->toString()); } projFormatter->stopInversion(); targetProjExportable->_exportToPROJString(projFormatter.get()); auto geogDst = dynamic_cast( boundDst ? boundDst->baseCRS().get() : targetCRS.get()); if (geogDst) { auto tmpFormatter = io::PROJStringFormatter::create(); geogDst->addAngularUnitConvertAndAxisSwap(tmpFormatter.get()); projFormatter->ingestPROJString(tmpFormatter->toString()); } auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr())); res.emplace_back(SingleOperation::createPROJBased( properties, projFormatter->toString(), sourceCRS, targetCRS, {})); } // --------------------------------------------------------------------------- /** Browse through candidate operations and check if their area of use * is sufficiently large compared to the area of use of the * source/target CRS. If it is not, we might need to extend the lookup * to using intermediate CRSs. * But only do that if we have a relatively small number of solutions. * Otherwise we might just spend time appending more dubious solutions. */ /* static */ bool CoordinateOperationFactory::Private::hasTooSmallAreas( const std::vector &ops, const Private::Context &context) { bool tooSmallAreas = false; // 10: arbitrary threshold so that particular case triggers for // EPSG:9989 (ITRF2000) to EPSG:4937 (ETRS89), but not for // "WGS 84" to "NAD83(CSRS)v2", to avoid excessive computation time. if (!ops.empty() && ops.size() < 10) { const auto &areaOfInterest = context.context->getAreaOfInterest(); double targetArea = 0.0; if (areaOfInterest) { targetArea = getPseudoArea(NN_NO_CHECK(areaOfInterest)); } else if (context.extent1) { if (context.extent2) { auto intersection = context.extent1->intersection(NN_NO_CHECK(context.extent2)); if (intersection) targetArea = getPseudoArea(NN_NO_CHECK(intersection)); } else { targetArea = getPseudoArea(NN_NO_CHECK(context.extent1)); } } else if (context.extent2) { targetArea = getPseudoArea(NN_NO_CHECK(context.extent2)); } if (targetArea > 0) { tooSmallAreas = true; for (const auto &op : ops) { bool dummy = false; auto extentOp = getExtent(op, true, dummy); double area = 0.0; if (extentOp) { if (areaOfInterest) { area = getPseudoArea(extentOp->intersection( NN_NO_CHECK(areaOfInterest))); } else if (context.extent1 && context.extent2) { auto x = extentOp->intersection( NN_NO_CHECK(context.extent1)); auto y = extentOp->intersection( NN_NO_CHECK(context.extent2)); area = getPseudoArea(x) + getPseudoArea(y) - ((x && y) ? getPseudoArea( x->intersection(NN_NO_CHECK(y))) : 0.0); } else if (context.extent1) { area = getPseudoArea(extentOp->intersection( NN_NO_CHECK(context.extent1))); } else if (context.extent2) { area = getPseudoArea(extentOp->intersection( NN_NO_CHECK(context.extent2))); } else { area = getPseudoArea(extentOp); } } constexpr double HALF_RATIO = 0.5; if (area > HALF_RATIO * targetArea) { tooSmallAreas = false; break; } } } } return tooSmallAreas; } // --------------------------------------------------------------------------- static bool isDatumInEnsemble(const datum::DatumNNPtr &datum, const datum::DatumEnsembleNNPtr &ensemble) { for (const auto &member : ensemble->datums()) { if (datum->_isEquivalentTo(member.get(), util::IComparable::Criterion::EQUIVALENT)) return true; } return false; } // --------------------------------------------------------------------------- /** Returns true if all operations involve a sub-operation that is a null * transformation between a datum ensemble member and its datum. */ static bool useOnlyIntermediateCRSThroughDatumEnsembleNullOp( const std::vector &ops) { for (const auto &op : ops) { bool hasDatumToEnsembleOp = false; const auto concatOp = dynamic_cast(op.get()); if (concatOp) { for (const auto &subOp : concatOp->operations()) { const auto &ids = subOp->identifiers(); // Datum ensemble member to ensemble null transformation are // in the PROJ domain. This is a way of shortcircuiting // heavier checks. if (ids.size() == 1 && ids[0]->codeSpace()->find("PROJ") != std::string::npos) { const auto opSrcCRS = subOp->sourceCRS(); const auto opTgtCRS = subOp->targetCRS(); if (opSrcCRS && opTgtCRS) { const auto opSrcGeodCRS = opSrcCRS->extractGeodeticCRS(); const auto opTgtGeodCRS = opTgtCRS->extractGeodeticCRS(); if (opSrcGeodCRS && opTgtGeodCRS) { const auto srcDatum = opSrcGeodCRS->datum(); const auto srcDatumEns = opSrcGeodCRS->datumEnsemble(); const auto tgtDatum = opTgtGeodCRS->datum(); const auto tgtDatumEns = opTgtGeodCRS->datumEnsemble(); if (srcDatum && tgtDatumEns) { hasDatumToEnsembleOp = isDatumInEnsemble(NN_NO_CHECK(srcDatum), NN_NO_CHECK(tgtDatumEns)); } else if (srcDatumEns && tgtDatum) { hasDatumToEnsembleOp = isDatumInEnsemble(NN_NO_CHECK(tgtDatum), NN_NO_CHECK(srcDatumEns)); } } } } } } if (!hasDatumToEnsembleOp) return false; } return true; } // --------------------------------------------------------------------------- /** Returns true if all operations are replaced by another one. */ static bool useOnlyReplacedOperations(const std::vector &ops) { for (const auto &op : ops) { if (op->remarks().find("Replaced by") == std::string::npos) { return false; } } return true; } // --------------------------------------------------------------------------- bool CoordinateOperationFactory::Private::createOperationsFromDatabase( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector &res) { ENTER_FUNCTION(); if (geogSrc && vertDst) { createOperationsFromDatabase(targetCRS, targetEpoch, sourceCRS, sourceEpoch, context, geodDst, geodSrc, geogDst, geogSrc, vertDst, vertSrc, res); res = applyInverse(res); } else if (geogDst && vertSrc) { res = applyInverse(createOperationsGeogToVertFromGeoid( targetCRS, sourceCRS, vertSrc, context)); if (!res.empty()) { createOperationsVertToGeogSynthetized(sourceCRS, targetCRS, context, vertSrc, geogDst, res); } } if (!res.empty()) { return true; } // Use PointMotionOperations if appropriate and available if (geodSrc && geodDst && sourceEpoch.has_value() && targetEpoch.has_value() && !sourceEpoch->coordinateEpoch()._isEquivalentTo( targetEpoch->coordinateEpoch())) { const auto pmoSrc = context.context->getAuthorityFactory()->getPointMotionOperationsFor( NN_NO_CHECK( util::nn_dynamic_pointer_cast(sourceCRS)), true); if (!pmoSrc.empty()) { const auto pmoDst = context.context->getAuthorityFactory() ->getPointMotionOperationsFor( NN_NO_CHECK( util::nn_dynamic_pointer_cast( targetCRS)), true); if (pmoDst.size() == pmoSrc.size()) { bool ok = true; for (size_t i = 0; i < pmoSrc.size(); ++i) { if (pmoSrc[i]->_isEquivalentTo( pmoDst[i].get(), util::IComparable::Criterion::EQUIVALENT)) { auto pmo = pmoSrc[i]->cloneWithEpochs(*sourceEpoch, *targetEpoch); std::vector ops; if (!pmo->sourceCRS()->_isEquivalentTo( sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto tmp = createOperations(sourceCRS, sourceEpoch, pmo->sourceCRS(), sourceEpoch, context); assert(!tmp.empty()); ops.emplace_back(tmp.front()); } ops.emplace_back(pmo); // pmo->sourceCRS() == pmo->targetCRS() by definition if (!pmo->sourceCRS()->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto tmp = createOperations(pmo->sourceCRS(), targetEpoch, targetCRS, targetEpoch, context); assert(!tmp.empty()); ops.emplace_back(tmp.front()); } res.emplace_back( ConcatenatedOperation::createComputeMetadata( ops, context.disallowEmptyIntersection())); } else { ok = false; break; } } if (ok) { std::vector resTmp; createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc, geodDst, resTmp, /*forceBallpark=*/true); res.insert(res.end(), resTmp.begin(), resTmp.end()); return true; } } } } bool resFindDirectNonEmptyBeforeFiltering = false; res = findOpsInRegistryDirect(sourceCRS, targetCRS, context, resFindDirectNonEmptyBeforeFiltering); // If we get at least a result with perfect accuracy, do not // bother generating synthetic transforms. if (hasPerfectAccuracyResult(res, context)) { return true; } bool doFilterAndCheckPerfectOp = false; bool sameGeodeticDatum = false; if (vertSrc || vertDst) { if (res.empty()) { if (geogSrc && geogSrc->coordinateSystem()->axisList().size() == 2 && vertDst) { auto dbContext = context.context->getAuthorityFactory()->databaseContext(); auto resTmp = findOpsInRegistryDirect( sourceCRS->promoteTo3D(std::string(), dbContext), targetCRS, context, resFindDirectNonEmptyBeforeFiltering); for (auto &op : resTmp) { auto newOp = op->shallowClone(); setCRSs(newOp.get(), sourceCRS, targetCRS); res.emplace_back(newOp); } } else if (geogDst && geogDst->coordinateSystem()->axisList().size() == 2 && vertSrc) { auto dbContext = context.context->getAuthorityFactory()->databaseContext(); auto resTmp = findOpsInRegistryDirect( sourceCRS, targetCRS->promoteTo3D(std::string(), dbContext), context, resFindDirectNonEmptyBeforeFiltering); for (auto &op : resTmp) { auto newOp = op->shallowClone(); setCRSs(newOp.get(), sourceCRS, targetCRS); res.emplace_back(newOp); } } } if (res.empty()) { createOperationsFromDatabaseWithVertCRS( sourceCRS, sourceEpoch, targetCRS, targetEpoch, context, geogSrc, geogDst, vertSrc, vertDst, res); } } else if (geodSrc && geodDst) { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory->databaseContext().as_nullable(); const auto srcDatum = geodSrc->datumNonNull(dbContext); const auto dstDatum = geodDst->datumNonNull(dbContext); sameGeodeticDatum = isSameGeodeticDatum(srcDatum, dstDatum, dbContext); if (res.empty() && !sameGeodeticDatum && !context.inCreateOperationsWithDatumPivotAntiRecursion) { // If we still didn't find a transformation, and that the source // and target are GeodeticCRS, then go through their underlying // datum to find potential transformations between other // GeodeticCRSs // that are made of those datum // The typical example is if transforming between two // GeographicCRS, // but transformations are only available between their // corresponding geocentric CRS. createOperationsWithDatumPivot(res, sourceCRS, sourceEpoch, targetCRS, targetEpoch, geodSrc, geodDst, context); doFilterAndCheckPerfectOp = !res.empty(); } } bool foundInstantiableOp = false; // FIXME: the limitation to .size() == 1 is just for the // -s EPSG:4959+5759 -t "EPSG:4959+7839" case // finding EPSG:7860 'NZVD2016 height to Auckland 1946 // height (1)', which uses the EPSG:1071 'Vertical Offset by Grid // Interpolation (NZLVD)' method which is not currently implemented by PROJ // (cannot deal with .csv files) // Initially the test was written to iterate over for all operations of a // non-empty res, but this causes failures in the test suite when no grids // are installed at all. Ideally we should tweak the test suite to be // robust to that, or skip some tests. if (res.size() == 1) { try { res.front()->exportToPROJString( io::PROJStringFormatter::create().get()); foundInstantiableOp = true; } catch (const std::exception &) { } if (!foundInstantiableOp) { resFindDirectNonEmptyBeforeFiltering = false; } } else if (res.size() > 1) { foundInstantiableOp = true; } // NAD27 to NAD83 has tens of results already. No need to look // for a pivot if (!sameGeodeticDatum && (((res.empty() || !foundInstantiableOp) && !resFindDirectNonEmptyBeforeFiltering && context.context->getAllowUseIntermediateCRS() == CoordinateOperationContext::IntermediateCRSUse:: IF_NO_DIRECT_TRANSFORMATION) || context.context->getAllowUseIntermediateCRS() == CoordinateOperationContext::IntermediateCRSUse::ALWAYS || getenv("PROJ_FORCE_SEARCH_PIVOT"))) { auto resWithIntermediate = findsOpsInRegistryWithIntermediate( sourceCRS, targetCRS, context, false); res.insert(res.end(), resWithIntermediate.begin(), resWithIntermediate.end()); doFilterAndCheckPerfectOp = !res.empty(); } bool bUseOnlyReplacedOperations = false; if (!context.inCreateOperationsWithDatumPivotAntiRecursion && geodSrc && geodDst && !sameGeodeticDatum && context.context->getIntermediateCRS().empty() && context.context->getAllowUseIntermediateCRS() != CoordinateOperationContext::IntermediateCRSUse::NEVER && (!resFindDirectNonEmptyBeforeFiltering || (bUseOnlyReplacedOperations = useOnlyReplacedOperations(res)))) { const bool tooSmallAreas = hasTooSmallAreas(res, context); const auto allRequiresPerCoordinateInputTime = [&res]() { for (const auto &op : res) { if (!op->requiresPerCoordinateInputTime()) return false; } return true; }; if (res.empty() || bUseOnlyReplacedOperations || tooSmallAreas || // If all coordinate operations are time-dependent and none of the // source or target CRS are dynamic, try through an intermediate CRS // (we go here between ETRS89 and ETRS89-NO that would otherwise // only use NKG time-dependent transformations) (allRequiresPerCoordinateInputTime() && !geodSrc->isDynamic() && !geodDst->isDynamic()) // useOnlyIntermediateCRSThroughDatumEnsembleNullOp() triggered by // ETRF2000 to Amersfoort. || useOnlyIntermediateCRSThroughDatumEnsembleNullOp(res)) { // Currently triggered by "IG05/12 Intermediate CRS" to ITRF2014 std::set oSetNames; if (tooSmallAreas) { for (const auto &op : res) { oSetNames.insert(op->nameStr()); } } auto resWithIntermediate = findsOpsInRegistryWithIntermediate( sourceCRS, targetCRS, context, true); if (tooSmallAreas && !res.empty()) { // Only insert operations we didn't already find for (const auto &op : resWithIntermediate) { if (oSetNames.find(op->nameStr()) == oSetNames.end()) { res.emplace_back(op); } } } else { res.insert(res.end(), resWithIntermediate.begin(), resWithIntermediate.end()); } doFilterAndCheckPerfectOp = !res.empty(); } } if (doFilterAndCheckPerfectOp) { // If we get at least a result with perfect accuracy, do not bother // generating synthetic transforms. if (hasPerfectAccuracyResult(res, context)) { return true; } } return false; } // --------------------------------------------------------------------------- static std::vector findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory, const datum::VerticalReferenceFrame *datum) { std::vector candidates; assert(datum); const auto &ids = datum->identifiers(); const auto &datumName = datum->nameStr(); if (!ids.empty()) { for (const auto &id : ids) { const auto &authName = *(id->codeSpace()); const auto &code = id->code(); if (!authName.empty()) { auto l_candidates = authFactory->createVerticalCRSFromDatum(authName, code); for (const auto &candidate : l_candidates) { candidates.emplace_back(candidate); } } } } else if (datumName != "unknown" && datumName != "unnamed") { auto matches = authFactory->createObjectsFromName( datumName, {io::AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME}, false, 2); if (matches.size() == 1) { const auto &match = matches.front(); if (datum->_isEquivalentTo( match.get(), util::IComparable::Criterion::EQUIVALENT, authFactory->databaseContext().as_nullable()) && !match->identifiers().empty()) { return findCandidateVertCRSForDatum( authFactory, dynamic_cast( match.get())); } } } return candidates; } // --------------------------------------------------------------------------- std::vector CoordinateOperationFactory::Private::createOperationsGeogToVertFromGeoid( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::VerticalCRS *vertDst, Private::Context &context) { ENTER_FUNCTION(); const auto useTransf = [&sourceCRS, &targetCRS, &context, vertDst](const CoordinateOperationNNPtr &op) { // If the source geographic CRS has a non-metre vertical unit, we need // to create an intermediate and operation to do the vertical unit // conversion from that vertical unit to the one of the geographic CRS // of the source of the operation const auto geogCRS = dynamic_cast(sourceCRS.get()); assert(geogCRS); const auto &srcAxisList = geogCRS->coordinateSystem()->axisList(); CoordinateOperationPtr opPtr; const auto opSourceCRSGeog = dynamic_cast(op->sourceCRS().get()); // I assume opSourceCRSGeog should always be null in practice... if (opSourceCRSGeog && srcAxisList.size() == 3 && srcAxisList[2]->unit().conversionToSI() != 1) { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; auto tmpCRSWithSrcZ = opSourceCRSGeog->demoteTo2D(std::string(), dbContext) ->promoteTo3D(std::string(), dbContext, srcAxisList[2]); std::vector opsUnitConvert; createOperationsGeogToGeog( opsUnitConvert, tmpCRSWithSrcZ, NN_NO_CHECK(op->sourceCRS()), context, dynamic_cast(tmpCRSWithSrcZ.get()), opSourceCRSGeog, /*forceBallpark=*/false); assert(opsUnitConvert.size() == 1); opPtr = opsUnitConvert.front().as_nullable(); } std::vector ops; if (opPtr) ops.emplace_back(NN_NO_CHECK(opPtr)); ops.emplace_back(op); const auto targetOp = dynamic_cast(op->targetCRS().get()); assert(targetOp); if (targetOp->_isEquivalentTo( vertDst, util::IComparable::Criterion::EQUIVALENT)) { auto ret = ConcatenatedOperation::createComputeMetadata( ops, context.disallowEmptyIntersection()); return ret; } std::vector tmp; createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS, context, targetOp, vertDst, tmp); assert(!tmp.empty()); ops.emplace_back(tmp.front()); auto ret = ConcatenatedOperation::createComputeMetadata( ops, context.disallowEmptyIntersection()); return ret; }; const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst, &context](const CoordinateOperationNNPtr &model, const std::string &projFilename) { const auto getNameVertCRSMetre = [](const std::string &name) { if (name.empty()) return std::string("unnamed"); auto ret(name); bool haveOriginalUnit = false; if (name.back() == ')') { const auto pos = ret.rfind(" ("); if (pos != std::string::npos) { haveOriginalUnit = true; ret = ret.substr(0, pos); } } const auto pos = ret.rfind(" depth"); if (pos != std::string::npos) { ret = ret.substr(0, pos) + " height"; } if (!haveOriginalUnit) { ret += " (metre)"; } return ret; }; const auto &axis = vertDst->coordinateSystem()->axisList()[0]; const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto geogSrcCRS = dynamic_cast( model->interpolationCRS().get()) ? NN_NO_CHECK(model->interpolationCRS()) : sourceCRS->demoteTo2D(std::string(), dbContext) ->promoteTo3D(std::string(), dbContext); const auto vertCRSMetre = axis->unit() == common::UnitOfMeasure::METRE && axis->direction() == cs::AxisDirection::UP ? targetCRS : util::nn_static_pointer_cast( crs::VerticalCRS::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, getNameVertCRSMetre(targetCRS->nameStr())), vertDst->datum(), vertDst->datumEnsemble(), cs::VerticalCS::createGravityRelatedHeight( common::UnitOfMeasure::METRE))); auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, buildOpName("Transformation", vertCRSMetre, geogSrcCRS)); // Try to find a representative value for the accuracy of this grid // from the registered transformations. std::vector accuracies; const auto &modelAccuracies = model->coordinateOperationAccuracies(); std::vector transformationsForGrid; if (authFactory) { transformationsForGrid = io::DatabaseContext::getTransformationsForGridName( authFactory->databaseContext(), projFilename); } // Only select transformations whose datum of the target vertical // CRS match the one of the target vertical CRS of interest (when // there's such match) Helps for example if specifying GEOID // g2012bp0 whose has a record for Puerto Rico and another one for // Virgin Islands. { std::vector transformationsForGridMatchingDatum; for (const auto &op : transformationsForGrid) { const auto opTargetCRS = dynamic_cast( op->targetCRS().get()); if (opTargetCRS && opTargetCRS->datumNonNull(dbContext)->_isEquivalentTo( vertDst->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT)) { transformationsForGridMatchingDatum.push_back(op); } } if (!transformationsForGridMatchingDatum.empty()) { transformationsForGrid = std::move(transformationsForGridMatchingDatum); } } double accuracy = -1; size_t idx = static_cast(-1); if (modelAccuracies.empty()) { if (authFactory) { for (size_t i = 0; i < transformationsForGrid.size(); ++i) { const auto &transf = transformationsForGrid[i]; const double transfAcc = getAccuracy(transf); if (transfAcc - accuracy > 1e-10) { accuracy = transfAcc; idx = i; } } if (accuracy >= 0) { accuracies.emplace_back( metadata::PositionalAccuracy::create( toString(accuracy))); } } } // Set extent bool dummy = false; // Use in priority the one of the geoid model transformation auto extent = getExtent(model, true, dummy); // Otherwise fallback to the extent of a transformation using // the grid. if (extent == nullptr && authFactory != nullptr) { if (idx != static_cast(-1)) { const auto &transf = transformationsForGrid[idx]; extent = getExtent(transf, true, dummy); } else if (!transformationsForGrid.empty()) { const auto &transf = transformationsForGrid.front(); extent = getExtent(transf, true, dummy); } } if (extent) { properties.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, NN_NO_CHECK(extent)); } return Transformation::createGravityRelatedHeightToGeographic3D( properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename, !modelAccuracies.empty() ? modelAccuracies : accuracies); }; std::vector res; const auto &authFactory = context.context->getAuthorityFactory(); if (authFactory) { const auto &models = vertDst->geoidModel(); for (const auto &model : models) { const auto &modelName = model->nameStr(); const auto &modelIds = model->identifiers(); const std::vector transformations( !modelIds.empty() ? std::vector< CoordinateOperationNNPtr>{io::AuthorityFactory::create( authFactory ->databaseContext(), *(modelIds[0] ->codeSpace())) ->createCoordinateOperation( modelIds[0]->code(), true)} : starts_with(modelName, "PROJ ") ? std::vector< CoordinateOperationNNPtr>{getProjGeoidTransformation( model, modelName.substr(strlen("PROJ ")))} : authFactory->getTransformationsForGeoid( modelName, context.context->getUsePROJAlternativeGridNames())); for (const auto &transf : transformations) { if (dynamic_cast( transf->sourceCRS().get()) && dynamic_cast( transf->targetCRS().get())) { res.push_back(useTransf(transf)); } else if (dynamic_cast( transf->targetCRS().get()) && dynamic_cast( transf->sourceCRS().get())) { res.push_back(useTransf(transf->inverse())); } } } } return res; } // --------------------------------------------------------------------------- std::vector CoordinateOperationFactory::Private:: createOperationsGeogToVertWithIntermediateVert( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, const crs::VerticalCRS *vertDst, Private::Context &context) { ENTER_FUNCTION(); std::vector res; struct AntiRecursionGuard { Context &context; explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { assert(!context.inCreateOperationsGeogToVertWithIntermediateVert); context.inCreateOperationsGeogToVertWithIntermediateVert = true; } ~AntiRecursionGuard() { context.inCreateOperationsGeogToVertWithIntermediateVert = false; } }; AntiRecursionGuard guard(context); const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory->databaseContext().as_nullable(); auto candidatesVert = findCandidateVertCRSForDatum( authFactory, vertDst->datumNonNull(dbContext).get()); for (const auto &candidateVert : candidatesVert) { // Collect all registered source-to-candidate operations, which // may differ in accuracy or area of use, for later ranking. auto resTmp = createOperations(sourceCRS, sourceEpoch, candidateVert, sourceEpoch, context); if (!resTmp.empty()) { // The candidate-to-target conversion is expected to be a // trivial unit or axis change, so we only use the first // result (opsSecond.front()). const auto opsSecond = createOperations( candidateVert, sourceEpoch, targetCRS, targetEpoch, context); if (!opsSecond.empty()) { for (const auto &opFirst : resTmp) { if (hasIdentifiers(opFirst)) { if (candidateVert->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { res.emplace_back(opFirst); } else { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, opsSecond.front()}, context.disallowEmptyIntersection())); } } } if (!res.empty()) break; } } } return res; } // --------------------------------------------------------------------------- // When transforming between two vertical CRS with different datums, check // if there are registered operations whose target (or source) is a different // vertical CRS sharing the same datum as our target (or source). If so, // chain: source to intermediate + intermediate to target (the latter being a // simple unit/axis conversion handled by createOperationsVertToVert). // // Typical example: Baltic 1977 height (EPSG:5705) to Caspian depth (EPSG:5706). // EPSG has an operation 5705 to 5611 (Caspian height). 5611 and 5706 share the // same datum but differ only in axis direction. We compose: // 5705 ->(EPSG:5438)-> 5611 ->(height-to-depth)-> 5706 std::vector CoordinateOperationFactory::Private:: createOperationsVertToVertWithIntermediateVert( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, Private::Context &context) { ENTER_FUNCTION(); std::vector res; struct AntiRecursionGuard { Context &context; explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { assert(!context.inCreateOperationsVertToVertWithIntermediateVert); context.inCreateOperationsVertToVertWithIntermediateVert = true; } ~AntiRecursionGuard() { context.inCreateOperationsVertToVertWithIntermediateVert = false; } }; AntiRecursionGuard guard(context); const auto &authFactory = context.context->getAuthorityFactory(); if (!authFactory) return res; const auto dbContext = authFactory->databaseContext().as_nullable(); // Try both pivot strategies and collect all candidates. The caller's // filterAndSort will rank them (by accuracy, area of use, etc.) // Strategy 1: pivot through candidates sharing the TARGET's datum. // source -> candidate (registered CT) -> target (trivial axis/unit change) { auto candidatesDst = findCandidateVertCRSForDatum( authFactory, vertDst->datumNonNull(dbContext).get()); for (const auto &candidateVert : candidatesDst) { if (candidateVert->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { continue; } // Collect all registered source-to-candidate operations, which // may differ in accuracy or area of use, for later ranking. auto opsFirst = createOperations( sourceCRS, sourceEpoch, candidateVert, sourceEpoch, context); if (!opsFirst.empty()) { // The candidate-to-target conversion is expected to be a // trivial unit or axis change, so we only use the first // result (opsSecond.front()). const auto opsSecond = createOperations(candidateVert, sourceEpoch, targetCRS, targetEpoch, context); if (!opsSecond.empty()) { for (const auto &opFirst : opsFirst) { if (hasIdentifiers(opFirst)) { try { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {opFirst, opsSecond.front()}, context .disallowEmptyIntersection())); } catch ( const InvalidOperationEmptyIntersection &) { } } } } } } } // Strategy 2: pivot through candidates sharing the SOURCE's datum. // Find vertical CRSs on the same datum as the source, then look for // operations from each candidate to the target. if (res.empty()) { auto candidatesSrc = findCandidateVertCRSForDatum( authFactory, vertSrc->datumNonNull(dbContext).get()); for (const auto &candidateVert : candidatesSrc) { if (candidateVert->_isEquivalentTo( sourceCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { continue; } auto opsSecond = createOperations(candidateVert, sourceEpoch, targetCRS, targetEpoch, context); if (!opsSecond.empty()) { const auto opsFirst = createOperations(sourceCRS, sourceEpoch, candidateVert, sourceEpoch, context); if (!opsFirst.empty()) { for (const auto &opSecond : opsSecond) { if (hasIdentifiers(opSecond)) { try { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {opsFirst.front(), opSecond}, context .disallowEmptyIntersection())); } catch ( const InvalidOperationEmptyIntersection &) { } } } } } } } return res; } // --------------------------------------------------------------------------- std::vector CoordinateOperationFactory::Private:: createOperationsGeogToVertWithAlternativeGeog( const crs::CRSNNPtr &sourceCRS, // geographic CRS const crs::CRSNNPtr &targetCRS, // vertical CRS Private::Context &context) { ENTER_FUNCTION(); std::vector res; struct AntiRecursionGuard { Context &context; explicit AntiRecursionGuard(Context &contextIn) : context(contextIn) { assert(!context.inCreateOperationsGeogToVertWithAlternativeGeog); context.inCreateOperationsGeogToVertWithAlternativeGeog = true; } ~AntiRecursionGuard() { context.inCreateOperationsGeogToVertWithAlternativeGeog = false; } }; AntiRecursionGuard guard(context); // Generally EPSG has operations from GeogCrs to VertCRS auto ops = findOpsInRegistryDirectTo(targetCRS, context); const auto geogCRS = dynamic_cast(sourceCRS.get()); assert(geogCRS); const auto &srcAxisList = geogCRS->coordinateSystem()->axisList(); for (const auto &op : ops) { const auto tmpCRS = dynamic_cast(op->sourceCRS().get()); if (tmpCRS) { if (srcAxisList.size() == 3 && srcAxisList[2]->unit().conversionToSI() != 1) { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory->databaseContext().as_nullable(); auto tmpCRSWithSrcZ = tmpCRS->demoteTo2D(std::string(), dbContext) ->promoteTo3D(std::string(), dbContext, srcAxisList[2]); std::vector opsUnitConvert; createOperationsGeogToGeog( opsUnitConvert, tmpCRSWithSrcZ, NN_NO_CHECK(op->sourceCRS()), context, dynamic_cast( tmpCRSWithSrcZ.get()), tmpCRS, /*forceBallpark=*/false); assert(opsUnitConvert.size() == 1); auto concat = ConcatenatedOperation::createComputeMetadata( {opsUnitConvert.front(), op}, context.disallowEmptyIntersection()); res.emplace_back(concat); } else { res.emplace_back(op); } } } return res; } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private:: createOperationsFromDatabaseWithVertCRS( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector &res) { // Typically to transform from "NAVD88 height (ftUS)" to a geog CRS // by using transformations of "NAVD88 height" (metre) to that geog CRS if (res.empty() && !context.inCreateOperationsGeogToVertWithIntermediateVert && geogSrc && vertDst) { res = createOperationsGeogToVertWithIntermediateVert( sourceCRS, sourceEpoch, targetCRS, targetEpoch, vertDst, context); } else if (res.empty() && !context.inCreateOperationsGeogToVertWithIntermediateVert && geogDst && vertSrc) { res = applyInverse(createOperationsGeogToVertWithIntermediateVert( targetCRS, targetEpoch, sourceCRS, sourceEpoch, vertSrc, context)); } // NAD83 only exists in 2D version in EPSG, so if it has been // promoted to 3D, when researching a vertical to geog // transformation, try to down cast to 2D. const auto geog3DToVertTryThroughGeog2D = [&res, &context](const crs::GeographicCRS *geogSrcIn, const crs::VerticalCRS *vertDstIn, const crs::CRSNNPtr &targetCRSIn) { const auto &authFactory = context.context->getAuthorityFactory(); if (res.empty() && geogSrcIn && vertDstIn && authFactory && geogSrcIn->coordinateSystem()->axisList().size() == 3) { const auto &dbContext = authFactory->databaseContext(); const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( authFactory, geogSrcIn, geogSrcIn->datumNonNull(dbContext))); for (const auto &candidate : candidatesSrcGeod) { auto geogCandidate = util::nn_dynamic_pointer_cast( candidate); if (geogCandidate && geogCandidate->coordinateSystem()->axisList().size() == 2) { bool ignored; res = findOpsInRegistryDirect( NN_NO_CHECK(geogCandidate), targetCRSIn, context, ignored); break; } } return true; } return false; }; if (geog3DToVertTryThroughGeog2D(geogSrc, vertDst, targetCRS)) { // do nothing } else if (geog3DToVertTryThroughGeog2D(geogDst, vertSrc, sourceCRS)) { res = applyInverse(res); } // Typically to transform between two vertical CRS with different datums // (e.g. "Baltic 1977 height" to "Caspian depth") by pivoting through // an intermediate vertical CRS sharing the target's (or source's) datum. // This handles the case where a registered CT targets a height CRS but // the user requests the corresponding depth CRS (or vice versa). if (res.empty() && !context.inCreateOperationsVertToVertWithIntermediateVert && vertSrc && vertDst) { res = createOperationsVertToVertWithIntermediateVert( sourceCRS, sourceEpoch, targetCRS, targetEpoch, vertSrc, vertDst, context); } // There's no direct transformation from NAVD88 height to WGS84, // so try to research all transformations from NAVD88 to another // intermediate GeographicCRS. if (res.empty() && !context.inCreateOperationsGeogToVertWithAlternativeGeog && geogSrc && vertDst) { res = createOperationsGeogToVertWithAlternativeGeog(sourceCRS, targetCRS, context); } else if (res.empty() && !context.inCreateOperationsGeogToVertWithAlternativeGeog && geogDst && vertSrc) { res = applyInverse(createOperationsGeogToVertWithAlternativeGeog( targetCRS, sourceCRS, context)); } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsGeodToGeod( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::GeodeticCRS *geodSrc, const crs::GeodeticCRS *geodDst, std::vector &res, bool forceBallpark) { ENTER_FUNCTION(); const auto &srcEllps = geodSrc->ellipsoid(); const auto &dstEllps = geodDst->ellipsoid(); if (srcEllps->celestialBody() == dstEllps->celestialBody() && srcEllps->celestialBody() != NON_EARTH_BODY) { // Same celestial body, with a known name (that is not the generic // NON_EARTH_BODY used when we can't guess it) ==> compatible } else if ((srcEllps->celestialBody() != dstEllps->celestialBody() && srcEllps->celestialBody() != NON_EARTH_BODY && dstEllps->celestialBody() != NON_EARTH_BODY) || std::fabs(srcEllps->semiMajorAxis().getSIValue() - dstEllps->semiMajorAxis().getSIValue()) > REL_ERROR_FOR_SAME_CELESTIAL_BODY * dstEllps->semiMajorAxis().getSIValue()) { const char *envVarVal = getenv("PROJ_IGNORE_CELESTIAL_BODY"); if (envVarVal == nullptr || ci_equal(envVarVal, "NO") || ci_equal(envVarVal, "FALSE") || ci_equal(envVarVal, "OFF")) { std::string osMsg( "Source and target ellipsoid do not belong to the same " "celestial body ("); osMsg += srcEllps->celestialBody(); osMsg += " vs "; osMsg += dstEllps->celestialBody(); osMsg += ")."; if (envVarVal == nullptr) { osMsg += " You may override this check by setting the " "PROJ_IGNORE_CELESTIAL_BODY environment variable " "to YES."; } throw util::UnsupportedOperationException(osMsg.c_str()); } } auto geogSrc = dynamic_cast(geodSrc); auto geogDst = dynamic_cast(geodDst); if (geogSrc && geogDst) { createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc, geogDst, forceBallpark); return; } const bool isSrcGeocentric = geodSrc->isGeocentric(); const bool isSrcGeographic = geogSrc != nullptr; const bool isTargetGeocentric = geodDst->isGeocentric(); const bool isTargetGeographic = geogDst != nullptr; const auto IsSameDatum = [&context, &geodSrc, &geodDst]() { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; return geodSrc->datumNonNull(dbContext)->_isEquivalentTo( geodDst->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT); }; if (((isSrcGeocentric && isTargetGeographic) || (isSrcGeographic && isTargetGeocentric))) { // Same datum ? if (IsSameDatum()) { if (forceBallpark) { auto op = createGeodToGeodPROJBased(sourceCRS, targetCRS); op->setHasBallparkTransformation(true); res.emplace_back(op); } else { res.emplace_back(Conversion::createGeographicGeocentric( sourceCRS, targetCRS)); } } else if (isSrcGeocentric && geogDst) { #if 0 // The below logic was used between PROJ >= 6.0 and < 9.2 // It assumed that the geocentric origin of the 2 datums // matched. std::string interm_crs_name(geogDst->nameStr()); interm_crs_name += " (geocentric)"; auto interm_crs = util::nn_static_pointer_cast(crs::GeodeticCRS::create( addDomains(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, interm_crs_name), geogDst), geogDst->datum(), geogDst->datumEnsemble(), NN_CHECK_ASSERT( util::nn_dynamic_pointer_cast( geodSrc->coordinateSystem())))); auto opFirst = createBallparkGeocentricTranslation(sourceCRS, interm_crs); auto opSecond = Conversion::createGeographicGeocentric(interm_crs, targetCRS); #else // The below logic is used since PROJ >= 9.2. It emulates the // behavior of PROJ < 6 by converting from the source geocentric CRS // to its corresponding geographic CRS, and then doing a null // geographic offset between that CRS and the target geographic CRS std::string interm_crs_name(geodSrc->nameStr()); interm_crs_name += " (geographic)"; auto interm_crs = util::nn_static_pointer_cast( crs::GeographicCRS::create( addDomains(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, interm_crs_name), geodSrc), geodSrc->datum(), geodSrc->datumEnsemble(), cs::EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight( common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE))); auto opFirst = Conversion::createGeographicGeocentric(sourceCRS, interm_crs); auto opsSecond = createOperations( interm_crs, util::optional(), targetCRS, util::optional(), context); for (const auto &opSecond : opsSecond) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, opSecond}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } #endif } else { // Apply previous case in reverse way std::vector resTmp; createOperationsGeodToGeod(targetCRS, sourceCRS, context, geodDst, geodSrc, resTmp, forceBallpark); resTmp = applyInverse(resTmp); res.insert(res.end(), resTmp.begin(), resTmp.end()); } return; } if (isSrcGeocentric && isTargetGeocentric) { if (!forceBallpark && (sourceCRS->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) || IsSameDatum())) { std::string name(NULL_GEOCENTRIC_TRANSLATION); name += " from "; name += sourceCRS->nameStr(); name += " to "; name += targetCRS->nameStr(); res.emplace_back(Transformation::createGeocentricTranslations( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), sourceCRS, targetCRS, 0.0, 0.0, 0.0, {metadata::PositionalAccuracy::create("0")})); } else { res.emplace_back( createBallparkGeocentricTranslation(sourceCRS, targetCRS)); } return; } // Transformation between two geodetic systems of unknown type // This should normally not be triggered with "standard" CRS res.emplace_back(createGeodToGeodPROJBased(sourceCRS, targetCRS)); } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private:: createOperationsFromSphericalPlanetocentric( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeodeticCRS *geodSrc, std::vector &res) { ENTER_FUNCTION(); const auto IsSameDatum = [&context, &geodSrc](const crs::GeodeticCRS *geodDst) { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; return geodSrc->datumNonNull(dbContext)->_isEquivalentTo( geodDst->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT); }; auto geogDst = dynamic_cast(targetCRS.get()); if (geogDst && IsSameDatum(geogDst)) { res.emplace_back(Conversion::createGeographicGeocentricLatitude( sourceCRS, targetCRS)); return; } // Create an intermediate geographic CRS with the same datum as the // source spherical planetocentric one std::string interm_crs_name(geodSrc->nameStr()); interm_crs_name += " (geographic)"; auto interm_crs = util::nn_static_pointer_cast(crs::GeographicCRS::create( addDomains(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, interm_crs_name), geodSrc), geodSrc->datum(), geodSrc->datumEnsemble(), cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::DEGREE))); auto opFirst = Conversion::createGeographicGeocentricLatitude(sourceCRS, interm_crs); auto opsSecond = createOperations(interm_crs, sourceEpoch, targetCRS, targetEpoch, context); for (const auto &opSecond : opsSecond) { try { res.emplace_back(ConcatenatedOperation::createComputeMetadata( {opFirst, opSecond}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private:: createOperationsFromBoundOfSphericalPlanetocentric( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::GeodeticCRSNNPtr &geodSrcBase, std::vector &res) { ENTER_FUNCTION(); // Create an intermediate geographic CRS with the same datum as the // source spherical planetocentric one std::string interm_crs_name(geodSrcBase->nameStr()); interm_crs_name += " (geographic)"; auto intermGeog = util::nn_static_pointer_cast(crs::GeographicCRS::create( addDomains(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, interm_crs_name), geodSrcBase.get()), geodSrcBase->datum(), geodSrcBase->datumEnsemble(), cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::DEGREE))); // Create an intermediate boundCRS wrapping the above intermediate // geographic CRS auto transf = boundSrc->transformation()->shallowClone(); // keep a reference to the target before patching it with itself // (this is due to our abuse of passing shared_ptr by reference auto transfTarget = transf->targetCRS(); setCRSs(transf.get(), intermGeog, transfTarget); auto intermBoundCRS = crs::BoundCRS::create(intermGeog, boundSrc->hubCRS(), transf); auto opFirst = Conversion::createGeographicGeocentricLatitude(geodSrcBase, intermGeog); setCRSs(opFirst.get(), sourceCRS, intermBoundCRS); auto opsSecond = createOperations( intermBoundCRS, util::optional(), targetCRS, util::optional(), context); for (const auto &opSecond : opsSecond) { try { auto opSecondClone = opSecond->shallowClone(); // In theory, we should not need that setCRSs() forcing, but due // how BoundCRS transformations are implemented currently, we // need it in practice. setCRSs(opSecondClone.get(), intermBoundCRS, targetCRS); res.emplace_back(ConcatenatedOperation::createComputeMetadata( {opFirst, std::move(opSecondClone)}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsDerivedTo( const crs::CRSNNPtr & /*sourceCRS*/, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::DerivedCRS *derivedSrc, std::vector &res) { ENTER_FUNCTION(); auto opFirst = derivedSrc->derivingConversion()->inverse(); // Small optimization if the targetCRS is the baseCRS of the source // derivedCRS. if (derivedSrc->baseCRS()->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { res.emplace_back(opFirst); return; } auto opsSecond = createOperations(derivedSrc->baseCRS(), sourceEpoch, targetCRS, targetEpoch, context); // Optimization to remove a no-op "Conversion from WGS 84 to WGS 84" // when transforming from EPSG:4979 to a CompoundCRS whose vertical CRS // is a DerivedVerticalCRS of a datum with ellipsoid height. if (opsSecond.size() == 1 && !opsSecond.front()->hasBallparkTransformation() && dynamic_cast(derivedSrc->baseCRS().get()) && !dynamic_cast(derivedSrc->baseCRS().get()) && dynamic_cast(targetCRS.get()) && !dynamic_cast(targetCRS.get())) { auto conv = dynamic_cast(opsSecond.front().get()); if (conv && conv->nameStr() == buildConvName(targetCRS->nameStr(), targetCRS->nameStr()) && conv->method()->getEPSGCode() == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT && conv->parameterValueNumericAsSI( EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR) == 1.0) { res.emplace_back(opFirst); return; } } for (const auto &opSecond : opsSecond) { try { res.emplace_back(ConcatenatedOperation::createComputeMetadata( {opFirst, opSecond}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsBoundToGeog( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::GeographicCRS *geogDst, std::vector &res) { ENTER_FUNCTION(); const auto &hubSrc = boundSrc->hubCRS(); auto hubSrcGeog = dynamic_cast(hubSrc.get()); auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS(); { // If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base // instead (if it is a GeographicCRS) auto derivedGeogCRS = std::dynamic_pointer_cast( geogCRSOfBaseOfBoundSrc); if (derivedGeogCRS) { auto baseCRS = std::dynamic_pointer_cast( derivedGeogCRS->baseCRS().as_nullable()); if (baseCRS) { geogCRSOfBaseOfBoundSrc = std::move(baseCRS); } } } const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto geogDstDatum = geogDst->datumNonNull(dbContext); // If the underlying datum of the source is the same as the target, do // not consider the boundCRS at all, but just its base if (geogCRSOfBaseOfBoundSrc) { auto geogCRSOfBaseOfBoundSrcDatum = geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext); if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo( geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { res = createOperations( boundSrc->baseCRS(), util::optional(), targetCRS, util::optional(), context); return; } } bool triedBoundCrsToGeogCRSSameAsHubCRS = false; // Is it: boundCRS to a geogCRS that is the same as the hubCRS ? if (hubSrcGeog && geogCRSOfBaseOfBoundSrc && (hubSrcGeog->_isEquivalentTo( geogDst, util::IComparable::Criterion::EQUIVALENT) || hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) { triedBoundCrsToGeogCRSSameAsHubCRS = true; CoordinateOperationPtr opIntermediate; if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo( boundSrc->transformation()->sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT)) { auto opsIntermediate = createOperations(NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), util::optional(), boundSrc->transformation()->sourceCRS(), util::optional(), context); assert(!opsIntermediate.empty()); opIntermediate = opsIntermediate.front(); } if (boundSrc->baseCRS() == geogCRSOfBaseOfBoundSrc) { if (opIntermediate) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {NN_NO_CHECK(opIntermediate), boundSrc->transformation()}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } else { // Optimization to avoid creating a useless concatenated // operation res.emplace_back(boundSrc->transformation()); } return; } auto opsFirst = createOperations( boundSrc->baseCRS(), util::optional(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), util::optional(), context); if (!opsFirst.empty()) { for (const auto &opFirst : opsFirst) { try { std::vector subops; subops.emplace_back(opFirst); if (opIntermediate) { subops.emplace_back(NN_NO_CHECK(opIntermediate)); } subops.emplace_back(boundSrc->transformation()); res.emplace_back( ConcatenatedOperation::createComputeMetadata( subops, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } if (!res.empty()) { return; } } // If the datum are equivalent, this is also fine } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opsFirst = createOperations( boundSrc->baseCRS(), util::optional(), NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), util::optional(), context); auto opsLast = createOperations( hubSrc, util::optional(), targetCRS, util::optional(), context); if (!opsFirst.empty() && !opsLast.empty()) { CoordinateOperationPtr opIntermediate; if (!geogCRSOfBaseOfBoundSrc->_isEquivalentTo( boundSrc->transformation()->sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT)) { auto opsIntermediate = createOperations( NN_NO_CHECK(geogCRSOfBaseOfBoundSrc), util::optional(), boundSrc->transformation()->sourceCRS(), util::optional(), context); assert(!opsIntermediate.empty()); opIntermediate = opsIntermediate.front(); } for (const auto &opFirst : opsFirst) { for (const auto &opLast : opsLast) { try { std::vector subops; subops.emplace_back(opFirst); if (opIntermediate) { subops.emplace_back(NN_NO_CHECK(opIntermediate)); } subops.emplace_back(boundSrc->transformation()); subops.emplace_back(opLast); res.emplace_back( ConcatenatedOperation::createComputeMetadata( subops, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } if (!res.empty()) { return; } } // Consider WGS 84 and NAD83 as equivalent in that context if the // geogCRSOfBaseOfBoundSrc ellipsoid is Clarke66 (for NAD27) // Case of "+proj=latlong +ellps=clrk66 // +nadgrids=ntv1_can.dat,conus" // to "+proj=latlong +datum=NAD83" } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo( datum::Ellipsoid::CLARKE_1866.get(), util::IComparable::Criterion::EQUIVALENT, dbContext) && hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), util::IComparable::Criterion::EQUIVALENT, dbContext) && geogDstDatum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6269.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { auto nnGeogCRSOfBaseOfBoundSrc = NN_NO_CHECK(geogCRSOfBaseOfBoundSrc); if (boundSrc->baseCRS()->_isEquivalentTo( nnGeogCRSOfBaseOfBoundSrc.get(), util::IComparable::Criterion::EQUIVALENT)) { auto transf = boundSrc->transformation()->shallowClone(); transf->setProperties(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, buildTransfName(boundSrc->baseCRS()->nameStr(), targetCRS->nameStr()))); transf->setCRSs(boundSrc->baseCRS(), targetCRS, nullptr); res.emplace_back(transf); return; } else { auto opsFirst = createOperations( boundSrc->baseCRS(), util::optional(), nnGeogCRSOfBaseOfBoundSrc, util::optional(), context); auto transf = boundSrc->transformation()->shallowClone(); transf->setProperties(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, buildTransfName(nnGeogCRSOfBaseOfBoundSrc->nameStr(), targetCRS->nameStr()))); transf->setCRSs(nnGeogCRSOfBaseOfBoundSrc, targetCRS, nullptr); if (!opsFirst.empty()) { for (const auto &opFirst : opsFirst) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, transf}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } if (!res.empty()) { return; } } } } if (hubSrcGeog && hubSrcGeog->_isEquivalentTo(geogDst, util::IComparable::Criterion::EQUIVALENT) && dynamic_cast(boundSrc->baseCRS().get())) { auto transfSrc = boundSrc->transformation()->sourceCRS(); if (dynamic_cast(transfSrc.get()) && !boundSrc->baseCRS()->_isEquivalentTo( transfSrc.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opsFirst = createOperations( boundSrc->baseCRS(), util::optional(), transfSrc, util::optional(), context); for (const auto &opFirst : opsFirst) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, boundSrc->transformation()}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } return; } res.emplace_back(boundSrc->transformation()); return; } if (!triedBoundCrsToGeogCRSSameAsHubCRS && hubSrcGeog && geogCRSOfBaseOfBoundSrc) { // This one should go to the above 'Is it: boundCRS to a geogCRS // that is the same as the hubCRS ?' case auto opsFirst = createOperations( sourceCRS, util::optional(), hubSrc, util::optional(), context); auto opsLast = createOperations( hubSrc, util::optional(), targetCRS, util::optional(), context); if (!opsFirst.empty() && !opsLast.empty()) { for (const auto &opFirst : opsFirst) { for (const auto &opLast : opsLast) { // Exclude artificial transformations from the hub // to the target CRS, if it is the only one. if (opsLast.size() > 1 || !opLast->hasBallparkTransformation()) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, opLast}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } else { // std::cerr << "excluded " << opLast->nameStr() << // std::endl; } } } if (!res.empty()) { return; } } } auto vertCRSOfBaseOfBoundSrc = dynamic_cast(boundSrc->baseCRS().get()); // The test for hubSrcGeog not being a DerivedCRS is to avoid infinite // recursion in a scenario involving a // BoundCRS[SourceCRS[VertCRS],TargetCRS[DerivedGeographicCRS]] to a // GeographicCRS if (vertCRSOfBaseOfBoundSrc && hubSrcGeog && dynamic_cast(hubSrcGeog) == nullptr) { auto opsFirst = createOperations( sourceCRS, util::optional(), hubSrc, util::optional(), context); if (context.skipHorizontalTransformation) { if (!opsFirst.empty()) { const auto &hubAxisList = hubSrcGeog->coordinateSystem()->axisList(); const auto &targetAxisList = geogDst->coordinateSystem()->axisList(); if (hubAxisList.size() == 3 && targetAxisList.size() == 3 && !hubAxisList[2]->_isEquivalentTo( targetAxisList[2].get(), util::IComparable::Criterion::EQUIVALENT)) { const auto &srcAxis = hubAxisList[2]; const double convSrc = srcAxis->unit().conversionToSI(); const auto &dstAxis = targetAxisList[2]; const double convDst = dstAxis->unit().conversionToSI(); const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; const bool dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; const bool dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; const bool heightDepthReversal = ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); if (convDst == 0) throw InvalidOperation( "Conversion factor of target unit is 0"); const double factor = convSrc / convDst; auto conv = Conversion::createChangeVerticalUnit( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "Change of vertical unit"), common::Scale(heightDepthReversal ? -factor : factor)); conv->setCRSs( hubSrc, hubSrc->demoteTo2D(std::string(), dbContext) ->promoteTo3D(std::string(), dbContext, dstAxis), nullptr); for (const auto &op : opsFirst) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {op, conv}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } else { res = std::move(opsFirst); } } return; } else { auto opsSecond = createOperations( hubSrc, util::optional(), targetCRS, util::optional(), context); if (!opsFirst.empty() && !opsSecond.empty()) { for (const auto &opFirst : opsFirst) { for (const auto &opLast : opsSecond) { // Exclude artificial transformations from the hub // to the target CRS if (!opLast->hasBallparkTransformation()) { try { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {opFirst, opLast}, context .disallowEmptyIntersection())); } catch ( const InvalidOperationEmptyIntersection &) { } } else { // std::cerr << "excluded " << opLast->nameStr() << // std::endl; } } } if (!res.empty()) { return; } } } } res = createOperations(boundSrc->baseCRS(), util::optional(), targetCRS, util::optional(), context); } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsBoundToVert( const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::VerticalCRS *vertDst, std::vector &res) { ENTER_FUNCTION(); auto baseSrcVert = dynamic_cast(boundSrc->baseCRS().get()); const auto &hubSrc = boundSrc->hubCRS(); auto hubSrcVert = dynamic_cast(hubSrc.get()); if (baseSrcVert && hubSrcVert && vertDst->_isEquivalentTo(hubSrcVert, util::IComparable::Criterion::EQUIVALENT)) { res.emplace_back(boundSrc->transformation()); return; } res = createOperations(boundSrc->baseCRS(), util::optional(), targetCRS, util::optional(), context); } // --------------------------------------------------------------------------- static std::string getBallparkTransformationVertToVert(const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS) { auto name = buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()); name += " ("; name += BALLPARK_VERTICAL_TRANSFORMATION; name += ')'; return name; } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsVertToVert( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector &res) { ENTER_FUNCTION(); const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto srcDatum = vertSrc->datumNonNull(dbContext); const auto dstDatum = vertDst->datumNonNull(dbContext); const bool equivalentVDatum = srcDatum->_isEquivalentTo( dstDatum.get(), util::IComparable::Criterion::EQUIVALENT, dbContext); const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; const double convSrc = srcAxis->unit().conversionToSI(); const auto &dstAxis = vertDst->coordinateSystem()->axisList()[0]; const double convDst = dstAxis->unit().conversionToSI(); const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; const bool dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; const bool dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; const bool heightDepthReversal = ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); if (convDst == 0) throw InvalidOperation("Conversion factor of target unit is 0"); const double factor = convSrc / convDst; const auto &sourceCRSExtent = getExtent(sourceCRS); const auto &targetCRSExtent = getExtent(targetCRS); const bool sameExtent = sourceCRSExtent && targetCRSExtent && sourceCRSExtent->_isEquivalentTo( targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); util::PropertyMap map; map.set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, sameExtent ? NN_NO_CHECK(sourceCRSExtent) : metadata::Extent::WORLD); if (!equivalentVDatum) { const auto name = getBallparkTransformationVertToVert(sourceCRS, targetCRS); auto conv = Transformation::createChangeVerticalUnit( map.set(common::IdentifiedObject::NAME_KEY, name), sourceCRS, targetCRS, // In case of a height depth reversal, we should probably have // 2 steps instead of putting a negative factor... common::Scale(heightDepthReversal ? -factor : factor), {}); conv->setHasBallparkTransformation(true); res.push_back(conv); } else if (convSrc != convDst || !heightDepthReversal) { auto name = buildConvName(sourceCRS->nameStr(), targetCRS->nameStr()); auto conv = Conversion::createChangeVerticalUnit( map.set(common::IdentifiedObject::NAME_KEY, name), // In case of a height depth reversal, we should probably have // 2 steps instead of putting a negative factor... common::Scale(heightDepthReversal ? -factor : factor)); conv->setCRSs(sourceCRS, targetCRS, nullptr); res.push_back(conv); } else { auto name = buildConvName(sourceCRS->nameStr(), targetCRS->nameStr()); auto conv = Conversion::createHeightDepthReversal( map.set(common::IdentifiedObject::NAME_KEY, name)); conv->setCRSs(sourceCRS, targetCRS, nullptr); res.push_back(conv); } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsVertToGeog( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::GeographicCRS *geogDst, std::vector &res) { ENTER_FUNCTION(); if (vertSrc->identifiers().empty()) { const auto &vertSrcName = vertSrc->nameStr(); const auto &authFactory = context.context->getAuthorityFactory(); if (authFactory != nullptr && vertSrcName != "unnamed" && vertSrcName != "unknown") { auto matches = authFactory->createObjectsFromName( vertSrcName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, false, 2); if (matches.size() == 1) { const auto &match = matches.front(); if (vertSrc->_isEquivalentTo( match.get(), util::IComparable::Criterion::EQUIVALENT) && !match->identifiers().empty()) { auto resTmp = createOperations( NN_NO_CHECK( util::nn_dynamic_pointer_cast( match)), sourceEpoch, targetCRS, targetEpoch, context); res.insert(res.end(), resTmp.begin(), resTmp.end()); return; } } } } createOperationsVertToGeogSynthetized(sourceCRS, targetCRS, context, vertSrc, geogDst, res); } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsVertToGeogSynthetized( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::GeographicCRS *geogDst, std::vector &res) { ENTER_FUNCTION(); const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; const double convSrc = srcAxis->unit().conversionToSI(); double convDst = 1.0; const auto &geogAxis = geogDst->coordinateSystem()->axisList(); bool dstIsUp = true; bool dstIsDown = false; if (geogAxis.size() == 3) { const auto &dstAxis = geogAxis[2]; convDst = dstAxis->unit().conversionToSI(); dstIsUp = dstAxis->direction() == cs::AxisDirection::UP; dstIsDown = dstAxis->direction() == cs::AxisDirection::DOWN; } const bool srcIsUp = srcAxis->direction() == cs::AxisDirection::UP; const bool srcIsDown = srcAxis->direction() == cs::AxisDirection::DOWN; const bool heightDepthReversal = ((srcIsUp && dstIsDown) || (srcIsDown && dstIsUp)); if (convDst == 0) throw InvalidOperation("Conversion factor of target unit is 0"); const double factor = convSrc / convDst; const auto &sourceCRSExtent = getExtent(sourceCRS); const auto &targetCRSExtent = getExtent(targetCRS); const bool sameExtent = sourceCRSExtent && targetCRSExtent && sourceCRSExtent->_isEquivalentTo( targetCRSExtent.get(), util::IComparable::Criterion::EQUIVALENT); const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto vertDatum = vertSrc->datumNonNull(dbContext); const auto &vertDatumName = vertDatum->nameStr(); const auto geogDstDatum = geogDst->datumNonNull(dbContext); const auto &geogDstDatumName = geogDstDatum->nameStr(); // We accept a vertical CRS whose datum name is the same datum name as the // source geographic CRS, or whose datum name is "Ellipsoid" if it is part // of a CompoundCRS whose horizontal CRS has a geodetic datum of the same // datum name as the source geographic CRS, to mean an ellipsoidal height. // This is against OGC Topic 2, and an extension needed for use case of // https://github.com/OSGeo/PROJ/issues/4175 const bool bIsSameDatum = vertDatumName != "unknown" && (vertDatumName == geogDstDatumName || (vertDatumName == "Ellipsoid" && !context.geogCRSOfVertCRSStack.empty() && context.geogCRSOfVertCRSStack.back() ->datumNonNull(dbContext) ->nameStr() == geogDstDatumName)); std::string transfName; if (bIsSameDatum) { transfName = buildConvName(factor != 1.0 ? sourceCRS->nameStr() : targetCRS->nameStr(), targetCRS->nameStr()); } else { transfName = buildTransfName(sourceCRS->nameStr(), targetCRS->nameStr()); transfName += " ("; transfName += BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT; transfName += ')'; } util::PropertyMap map; map.set(common::IdentifiedObject::NAME_KEY, transfName) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, sameExtent ? NN_NO_CHECK(sourceCRSExtent) : metadata::Extent::WORLD); if (bIsSameDatum) { auto conv = Conversion::createChangeVerticalUnit( map, common::Scale(heightDepthReversal ? -factor : factor)); conv->setCRSs(sourceCRS, targetCRS, nullptr); res.push_back(conv); } else { auto transf = Transformation::createChangeVerticalUnit( map, sourceCRS, targetCRS, common::Scale(heightDepthReversal ? -factor : factor), {}); transf->setHasBallparkTransformation(true); res.push_back(transf); } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsBoundToBound( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::BoundCRS *boundDst, std::vector &res) { ENTER_FUNCTION(); // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub const auto &hubSrc = boundSrc->hubCRS(); auto hubSrcGeog = dynamic_cast(hubSrc.get()); const auto &hubDst = boundDst->hubCRS(); auto hubDstGeog = dynamic_cast(hubDst.get()); if (hubSrcGeog && hubDstGeog && hubSrcGeog->_isEquivalentTo(hubDstGeog, util::IComparable::Criterion::EQUIVALENT)) { auto opsFirst = createOperations( sourceCRS, util::optional(), hubSrc, util::optional(), context); auto opsLast = createOperations( hubSrc, util::optional(), targetCRS, util::optional(), context); for (const auto &opFirst : opsFirst) { for (const auto &opLast : opsLast) { try { std::vector ops; ops.push_back(opFirst); ops.push_back(opLast); res.emplace_back( ConcatenatedOperation::createComputeMetadata( ops, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } if (!res.empty()) { return; } } // BoundCRS to BoundCRS of vertical CRS using the same vertical datum // ==> ignore the bound transformation auto baseOfBoundSrcAsVertCRS = dynamic_cast(boundSrc->baseCRS().get()); auto baseOfBoundDstAsVertCRS = dynamic_cast(boundDst->baseCRS().get()); if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext); const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext); if (datumSrc->nameStr() == datumDst->nameStr() && (datumSrc->nameStr() != "unknown" || boundSrc->transformation()->_isEquivalentTo( boundDst->transformation().get(), util::IComparable::Criterion::EQUIVALENT))) { res = createOperations( boundSrc->baseCRS(), util::optional(), boundDst->baseCRS(), util::optional(), context); return; } } // BoundCRS to BoundCRS of vertical CRS auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS(); auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS(); if (hubSrcGeog && hubDstGeog && hubSrcGeog->_isEquivalentTo(hubDstGeog, util::IComparable::Criterion::EQUIVALENT) && vertCRSOfBaseOfBoundSrc && vertCRSOfBaseOfBoundDst) { auto opsFirst = createOperations( sourceCRS, util::optional(), hubSrc, util::optional(), context); auto opsLast = createOperations( hubSrc, util::optional(), targetCRS, util::optional(), context); if (!opsFirst.empty() && !opsLast.empty()) { for (const auto &opFirst : opsFirst) { for (const auto &opLast : opsLast) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, opLast}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } if (!res.empty()) { return; } } } res = createOperations( boundSrc->baseCRS(), util::optional(), boundDst->baseCRS(), util::optional(), context); } // --------------------------------------------------------------------------- static std::vector getOps(const CoordinateOperationNNPtr &op) { auto concatenated = dynamic_cast(op.get()); if (concatenated) return concatenated->operations(); return {op}; } // --------------------------------------------------------------------------- static std::string normalize2D3DInName(const std::string &s) { std::string out = s; const char *const patterns[] = { " (2D)", " (geographic3D horizontal)", " (geog2D)", " (geog3D)", }; for (const char *pattern : patterns) { out = replaceAll(out, pattern, ""); } return out; } // --------------------------------------------------------------------------- static bool useCompatibleTransformationsForSameSourceTarget( const CoordinateOperationNNPtr &opA, const CoordinateOperationNNPtr &opB) { const auto subOpsA = getOps(opA); const auto subOpsB = getOps(opB); for (const auto &subOpA : subOpsA) { if (!dynamic_cast(subOpA.get())) continue; const auto subOpAName = normalize2D3DInName(subOpA->nameStr()); const auto &subOpASourceCRSName = subOpA->sourceCRS()->nameStr(); const auto &subOpATargetCRSName = subOpA->targetCRS()->nameStr(); if (subOpASourceCRSName == "unknown" || subOpATargetCRSName == "unknown") continue; for (const auto &subOpB : subOpsB) { if (!dynamic_cast(subOpB.get())) continue; const auto &subOpBSourceCRSName = subOpB->sourceCRS()->nameStr(); const auto &subOpBTargetCRSName = subOpB->targetCRS()->nameStr(); if (subOpBSourceCRSName == "unknown" || subOpBTargetCRSName == "unknown") continue; if (subOpASourceCRSName == subOpBSourceCRSName && subOpATargetCRSName == subOpBTargetCRSName) { const auto &subOpBName = normalize2D3DInName(subOpB->nameStr()); if (starts_with(subOpAName, NULL_GEOGRAPHIC_OFFSET) && starts_with(subOpB->nameStr(), NULL_GEOGRAPHIC_OFFSET)) { continue; } if (subOpAName != subOpBName) { return false; } } else if (subOpASourceCRSName == subOpBTargetCRSName && subOpATargetCRSName == subOpBSourceCRSName) { const auto &subOpBName = subOpB->nameStr(); if (starts_with(subOpAName, NULL_GEOGRAPHIC_OFFSET) && starts_with(subOpBName, NULL_GEOGRAPHIC_OFFSET)) { continue; } if (subOpAName != normalize2D3DInName(subOpB->inverse()->nameStr())) { return false; } } } } return true; } // --------------------------------------------------------------------------- static crs::GeographicCRSPtr getInterpolationGeogCRS(const CoordinateOperationNNPtr &verticalTransform, const io::DatabaseContextPtr &dbContext) { crs::GeographicCRSPtr interpolationGeogCRS; auto transformationVerticalTransform = dynamic_cast(verticalTransform.get()); if (transformationVerticalTransform == nullptr) { const auto concat = dynamic_cast( verticalTransform.get()); if (concat) { const auto &steps = concat->operations(); // Is this change of unit and/or height depth reversal + // transformation ? for (const auto &step : steps) { const auto transf = dynamic_cast(step.get()); if (transf) { // Only support a single Transformation in the steps if (transformationVerticalTransform != nullptr) { transformationVerticalTransform = nullptr; break; } transformationVerticalTransform = transf; } } } } if (transformationVerticalTransform && !transformationVerticalTransform->hasBallparkTransformation()) { auto interpTransformCRS = transformationVerticalTransform->interpolationCRS(); if (interpTransformCRS) { interpolationGeogCRS = std::dynamic_pointer_cast( interpTransformCRS); } else { // If no explicit interpolation CRS, then // this will be the geographic CRS of the // vertical to geog transformation interpolationGeogCRS = std::dynamic_pointer_cast( transformationVerticalTransform->targetCRS().as_nullable()); } } if (interpolationGeogCRS) { if (interpolationGeogCRS->coordinateSystem()->axisList().size() == 3) { // We need to force the interpolation CRS, which // will // frequently be 3D, to 2D to avoid transformations // between source CRS and interpolation CRS to have // 3D terms. interpolationGeogCRS = interpolationGeogCRS->demoteTo2D(std::string(), dbContext) .as_nullable(); } } return interpolationGeogCRS; } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsCompoundToGeog( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::CompoundCRS *compoundSrc, const crs::GeographicCRS *geogDst, std::vector &res) { ENTER_FUNCTION(); const auto &authFactory = context.context->getAuthorityFactory(); const auto &componentsSrc = compoundSrc->componentReferenceSystems(); if (!componentsSrc.empty()) { const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; if (componentsSrc.size() == 2) { auto derivedHSrc = dynamic_cast(componentsSrc[0].get()); if (derivedHSrc) { std::vector intermComponents{ derivedHSrc->baseCRS(), componentsSrc[1]}; auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, intermComponents[0]->nameStr() + " + " + intermComponents[1]->nameStr()); auto intermCompound = crs::CompoundCRS::create(properties, intermComponents); auto opsFirst = createOperations(sourceCRS, sourceEpoch, intermCompound, sourceEpoch, context); assert(!opsFirst.empty()); auto opsLast = createOperations(intermCompound, sourceEpoch, targetCRS, targetEpoch, context); for (const auto &opLast : opsLast) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opsFirst.front(), opLast}, context.disallowEmptyIntersection())); } catch (const std::exception &) { } } return; } auto geogSrc = dynamic_cast( componentsSrc[0].get()); // Sorry for this hack... aimed at transforming // "NAD83(CSRS)v7 + CGVD2013a(1997) height @ 1997" to "NAD83(CSRS)v7 // @ 1997" to "NAD83(CSRS)v3 + CGVD2013a(1997) height" to // "NAD83(CSRS)v3" OR "NAD83(CSRS)v7 + CGVD2013a(2002) height @ // 2002" to "NAD83(CSRS)v7 @ 2002" to "NAD83(CSRS)v4 + // CGVD2013a(2002) height" to "NAD83(CSRS)v4" if (dbContext && geogSrc && geogSrc->nameStr() == "NAD83(CSRS)v7" && sourceEpoch.has_value() && geogDst->coordinateSystem()->axisList().size() == 3U && geogDst->nameStr() == geogSrc->nameStr() && targetEpoch.has_value() && sourceEpoch->coordinateEpoch()._isEquivalentTo( targetEpoch->coordinateEpoch())) { const bool is1997 = std::abs(sourceEpoch->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR) - 1997) < 1e-10; const bool is2002 = std::abs(sourceEpoch->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR) - 2002) < 1e-10; try { auto authFactoryEPSG = io::AuthorityFactory::create( authFactory->databaseContext(), "EPSG"); if (geogSrc->_isEquivalentTo( authFactoryEPSG ->createCoordinateReferenceSystem("8255") .get(), util::IComparable::Criterion:: EQUIVALENT) && // NAD83(CSRS)v7 // 2D geogDst->_isEquivalentTo( authFactoryEPSG ->createCoordinateReferenceSystem("8254") .get(), util::IComparable::Criterion:: EQUIVALENT) && // NAD83(CSRS)v7 // 3D ((is1997 && componentsSrc[1]->nameStr() == "CGVD2013a(1997) height") || (is2002 && componentsSrc[1]->nameStr() == "CGVD2013a(2002) height"))) { std::vector intermComponents{ authFactoryEPSG->createCoordinateReferenceSystem( is1997 ? "8240" : // NAD83(CSRS)v3 2D "8246" // NAD83(CSRS)v4 2D ), componentsSrc[1]}; auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, intermComponents[0]->nameStr() + " + " + intermComponents[1]->nameStr()); auto newCompound = crs::CompoundCRS::create( properties, intermComponents); auto ops = createOperations( newCompound, sourceEpoch, authFactoryEPSG->createCoordinateReferenceSystem( is1997 ? "8239" : // NAD83(CSRS)v3 3D "8244" // NAD83(CSRS)v4 3D ), sourceEpoch, context); for (const auto &op : ops) { auto opClone = op->shallowClone(); setCRSs(opClone.get(), sourceCRS, targetCRS); res.emplace_back(opClone); } return; } } catch (const std::exception &) { } } auto boundSrc = dynamic_cast(componentsSrc[0].get()); if (boundSrc) { derivedHSrc = dynamic_cast( boundSrc->baseCRS().get()); if (derivedHSrc) { std::vector intermComponents{ crs::BoundCRS::create(derivedHSrc->baseCRS(), boundSrc->hubCRS(), boundSrc->transformation()), componentsSrc[1]}; auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, intermComponents[0]->nameStr() + " + " + intermComponents[1]->nameStr()); auto intermCompound = crs::CompoundCRS::create(properties, intermComponents); auto opsFirst = createOperations(sourceCRS, sourceEpoch, intermCompound, sourceEpoch, context); assert(!opsFirst.empty()); auto opsLast = createOperations(intermCompound, sourceEpoch, targetCRS, targetEpoch, context); for (const auto &opLast : opsLast) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opsFirst.front(), opLast}, context.disallowEmptyIntersection())); } catch (const std::exception &) { } } return; } } } // Deal with "+proj=something +geoidgrids +nadgrids/+towgs84" to // another CRS whose datum is not the same as the horizontal datum // of the source if (componentsSrc.size() == 2) { auto comp0Bound = dynamic_cast(componentsSrc[0].get()); auto comp1Bound = dynamic_cast(componentsSrc[1].get()); auto comp0Geog = componentsSrc[0]->extractGeographicCRS(); auto dstGeog = targetCRS->extractGeographicCRS(); if (comp0Bound && comp1Bound && comp0Geog && comp1Bound->hubCRS() ->demoteTo2D(std::string(), dbContext) ->isEquivalentTo( comp0Geog.get(), util::IComparable::Criterion::EQUIVALENT) && dstGeog && !comp0Geog->datumNonNull(dbContext)->isEquivalentTo( dstGeog->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT)) { const auto &op1Dest = comp1Bound->hubCRS(); const auto ops1 = createOperations( crs::CompoundCRS::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, std::string()), {comp0Bound->baseCRS(), componentsSrc[1]}), util::optional(), op1Dest, util::optional(), context); const auto op2Dest = comp0Bound->hubCRS()->promoteTo3D(std::string(), dbContext); const auto ops2 = createOperations( crs::BoundCRS::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, std::string()), NN_NO_CHECK(comp0Geog), comp0Bound->hubCRS(), comp0Bound->transformation()) ->promoteTo3D(std::string(), dbContext), util::optional(), op2Dest, util::optional(), context); const auto ops3 = createOperations( op2Dest, util::optional(), targetCRS, util::optional(), context); for (const auto &op1 : ops1) { auto op1Clone = op1->shallowClone(); setCRSs(op1Clone.get(), sourceCRS, op1Dest); for (const auto &op2 : ops2) { auto op2Clone = op2->shallowClone(); setCRSs(op2Clone.get(), op1Dest, op2Dest); for (const auto &op3 : ops3) { try { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {op1Clone, op2Clone, op3}, context .disallowEmptyIntersection())); } catch (const std::exception &) { } } } } if (!res.empty()) { return; } } } // Only do a vertical transformation if the target CRS is 3D. if (geogDst->coordinateSystem()->axisList().size() <= 2) { auto tmp = createOperations(componentsSrc[0], sourceEpoch, targetCRS, targetEpoch, context); for (const auto &op : tmp) { auto opClone = op->shallowClone(); setCRSs(opClone.get(), sourceCRS, targetCRS); res.emplace_back(opClone); } return; } std::vector horizTransforms; auto srcGeogCRS = componentsSrc[0]->extractGeographicCRS(); if (srcGeogCRS) { horizTransforms = createOperations(componentsSrc[0], sourceEpoch, targetCRS, targetEpoch, context); } const auto hasAtLeastOneOpWithNonTrivialAndWithAllGrids = [&dbContext]( const std::vector &verticalTransforms, CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse, bool ignoreMissingGrids, bool *foundRegisteredTransform = nullptr) { if (foundRegisteredTransform) *foundRegisteredTransform = false; for (const auto &op : verticalTransforms) { if (hasIdentifiers(op) && dbContext) { bool missingGrid = false; if (!ignoreMissingGrids) { const auto gridsNeeded = op->gridsNeeded( dbContext, gridAvailabilityUse == CoordinateOperationContext:: GridAvailabilityUse::KNOWN_AVAILABLE); for (const auto &gridDesc : gridsNeeded) { if (!gridDesc.available) { missingGrid = true; break; } } } if (foundRegisteredTransform) *foundRegisteredTransform = true; if (!missingGrid) { return true; } } } return false; }; std::vector verticalTransforms; if (componentsSrc.size() >= 2 && componentsSrc[1]->extractVerticalCRS()) { struct SetSkipHorizontalTransform { Context &context; explicit SetSkipHorizontalTransform(Context &contextIn) : context(contextIn) { assert(!context.skipHorizontalTransformation); context.skipHorizontalTransformation = true; } ~SetSkipHorizontalTransform() { context.skipHorizontalTransformation = false; } }; struct SetGeogCRSOfVertCRS { Context &context; const bool hasPushed; explicit SetGeogCRSOfVertCRS( Context &contextIn, const crs::GeographicCRSPtr &geogCRS) : context(contextIn), hasPushed(geogCRS != nullptr) { if (geogCRS) context.geogCRSOfVertCRSStack.push_back( NN_NO_CHECK(geogCRS)); } ~SetGeogCRSOfVertCRS() { if (hasPushed) context.geogCRSOfVertCRSStack.pop_back(); } }; SetGeogCRSOfVertCRS setGeogCRSOfVertCRS(context, srcGeogCRS); { SetSkipHorizontalTransform setSkipHorizontalTransform(context); verticalTransforms = createOperations( componentsSrc[1], util::optional(), targetCRS, util::optional(), context); } const auto gridAvailabilityUse = context.context->getGridAvailabilityUse(); const bool ignoreMissingGrids = gridAvailabilityUse == CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY; bool foundRegisteredTransformWithAllGridsAvailable = hasAtLeastOneOpWithNonTrivialAndWithAllGrids( verticalTransforms, gridAvailabilityUse, ignoreMissingGrids); if (srcGeogCRS && !srcGeogCRS->_isEquivalentTo( geogDst, util::IComparable::Criterion::EQUIVALENT) && !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) { auto geogCRSTmp = NN_NO_CHECK(srcGeogCRS) ->demoteTo2D(std::string(), dbContext) ->promoteTo3D( std::string(), dbContext, geogDst->coordinateSystem()->axisList()[2]); std::vector verticalTransformsTmp; { SetSkipHorizontalTransform setSkipHorizontalTransform( context); verticalTransformsTmp = createOperations( componentsSrc[1], util::optional(), geogCRSTmp, util::optional(), context); } bool foundRegisteredTransform = false; const bool foundRegisteredTransformWithAllGridsAvailable2 = hasAtLeastOneOpWithNonTrivialAndWithAllGrids( verticalTransformsTmp, gridAvailabilityUse, ignoreMissingGrids, &foundRegisteredTransform); if (foundRegisteredTransformWithAllGridsAvailable2 && !foundRegisteredTransformWithAllGridsAvailable) { verticalTransforms = std::move(verticalTransformsTmp); } else if (foundRegisteredTransform) { verticalTransforms.insert(verticalTransforms.end(), verticalTransformsTmp.begin(), verticalTransformsTmp.end()); } } // Case for EPSG:5550+7651 ("PNG94 / PNGMG94 zone 54 + Kumul 34 // height") to EPSG:9754 ("WGS 84 (G2139)") // (https://github.com/OSGeo/PROJ/issues/4618) // where there is a vertical transformation between Kumul 34 height // and WGS84 going through EGM96 and the transformation between // PNG94 and WGS 84 (G2139) goes through WGS84 We can then get a // good result by doing EPSG:5550+7651 -> EPSG:4979 and EPSG:4979 -> // EPSG:9754 const auto hasNonBallparkLambda = [](const CoordinateOperationNNPtr &op) { return !op->hasBallparkTransformation(); }; const bool onlyVerticalBallparkTransformation = std::find_if(verticalTransforms.begin(), verticalTransforms.end(), hasNonBallparkLambda) == verticalTransforms.end(); if (onlyVerticalBallparkTransformation) { std::map candidateIntermGeogCRS; for (const auto &op : horizTransforms) { if (!op->hasBallparkTransformation()) { auto concatenatedOp = dynamic_cast( op.get()); if (concatenatedOp) { for (const auto &subOp : concatenatedOp->operations()) { auto subOpTargetCRS = subOp->targetCRS(); if (subOpTargetCRS) { auto tmpCRS = subOpTargetCRS->promoteTo3D( std::string(), dbContext); if (!tmpCRS->identifiers().empty() && !tmpCRS->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion:: EQUIVALENT) && candidateIntermGeogCRS.find( tmpCRS->nameStr()) == candidateIntermGeogCRS.end()) { candidateIntermGeogCRS.insert( std::make_pair(tmpCRS->nameStr(), std::move(tmpCRS))); } } } } } } for (const auto &[name, geogCRSTmp] : candidateIntermGeogCRS) { (void)name; const auto verticalTransformsTmp = createOperations( componentsSrc[1], util::optional(), geogCRSTmp, util::optional(), context); const bool onlyVerticalBallparkTransformationTmp = std::find_if(verticalTransformsTmp.begin(), verticalTransformsTmp.end(), hasNonBallparkLambda) == verticalTransformsTmp.end(); if (!onlyVerticalBallparkTransformationTmp) { const auto ops1 = createOperations( sourceCRS, util::optional(), geogCRSTmp, util::optional(), context); const auto ops2 = createOperations( geogCRSTmp, util::optional(), targetCRS, util::optional(), context); for (const auto &op1 : ops1) { if (!op1->hasBallparkTransformation()) { for (const auto &op2 : ops2) { if (!op2->hasBallparkTransformation()) { try { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {op1, op2}, context .disallowEmptyIntersection())); } catch (const std::exception &) { } } } } } } } } } if (horizTransforms.empty() || verticalTransforms.empty()) { res = std::move(horizTransforms); return; } typedef std::pair, std::vector> PairOfTransforms; std::map cacheHorizToInterpAndInterpToTarget; bool hasVerticalTransformWithInterpGeogCRS = false; for (const auto &verticalTransform : verticalTransforms) { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("Considering vertical transform " + objectAsStr(verticalTransform.get())); #endif crs::GeographicCRSPtr interpolationGeogCRS = getInterpolationGeogCRS(verticalTransform, dbContext); if (interpolationGeogCRS) { #ifdef TRACE_CREATE_OPERATIONS logTrace("Using " + objectAsStr(interpolationGeogCRS.get()) + " as interpolation CRS"); #endif std::vector srcToInterpOps; std::vector interpToTargetOps; std::string key; const auto &ids = interpolationGeogCRS->identifiers(); if (!ids.empty()) { key = (*ids.front()->codeSpace()) + ':' + ids.front()->code(); } const auto computeOpsToInterp = [&srcToInterpOps, &interpToTargetOps, &componentsSrc, &interpolationGeogCRS, &targetCRS, &geogDst, &dbContext, &context]() { // Do the sourceCRS to interpolation CRS in 2D only // to avoid altering the orthometric elevation srcToInterpOps = createOperations( componentsSrc[0], util::optional(), NN_NO_CHECK(interpolationGeogCRS), util::optional(), context); // e.g when doing COMPOUND_CRS[ // NAD83(CRS)+TOWGS84[0,0,0], // CGVD28 height + EXTENSION["PROJ4_GRIDS","HT2_0.gtx"] // to NAD83(CRS) 3D const auto boundSrc = dynamic_cast(componentsSrc[0].get()); if (boundSrc && boundSrc->baseCRS()->isEquivalentTo( targetCRS->demoteTo2D(std::string(), dbContext) .get(), util::IComparable::Criterion::EQUIVALENT) && boundSrc->hubCRS()->isEquivalentTo( interpolationGeogCRS ->demoteTo2D(std::string(), dbContext) .get(), util::IComparable::Criterion::EQUIVALENT)) { // Make sure to use the same horizontal transformation // (likely a null shift) interpToTargetOps = applyInverse(srcToInterpOps); return; } // But do the interpolation CRS to targetCRS in 3D // to have proper ellipsoid height transformation. // We need to force the vertical axis of this 3D'ified // interpolation CRS to be the same as the target CRS, // to avoid potential double vertical unit conversion, // as the vertical transformation already takes care of // that. auto interp3D = interpolationGeogCRS ->demoteTo2D(std::string(), dbContext) ->promoteTo3D( std::string(), dbContext, geogDst->coordinateSystem() ->axisList() .size() == 3 ? geogDst->coordinateSystem()->axisList()[2] : cs::VerticalCS:: createGravityRelatedHeight( common::UnitOfMeasure::METRE) ->axisList()[0]); auto interpToTargetOpsTmp = createOperations( interp3D, util::optional(), targetCRS, util::optional(), context); for (auto &op : interpToTargetOpsTmp) { // Skip operations that are clearly 2D only as they // would result in mis-interpreting the ellipsoidal // height of the interpolation CRS as the one of the // target CRS. const SingleOperation *so = dynamic_cast(op.get()); if (!so || so->method()->nameStr().find( PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) == std::string::npos) { interpToTargetOps.push_back(std::move(op)); } } }; if (!key.empty()) { auto iter = cacheHorizToInterpAndInterpToTarget.find(key); if (iter == cacheHorizToInterpAndInterpToTarget.end()) { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("looking for horizontal transformation " "from source to interpCRS and interpCRS to " "target"); #endif computeOpsToInterp(); cacheHorizToInterpAndInterpToTarget[key] = PairOfTransforms(srcToInterpOps, interpToTargetOps); } else { srcToInterpOps = iter->second.first; interpToTargetOps = iter->second.second; } } else { #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("looking for horizontal transformation " "from source to interpCRS and interpCRS to " "target"); #endif computeOpsToInterp(); } #ifdef TRACE_CREATE_OPERATIONS ENTER_BLOCK("creating HorizVerticalHorizPROJBased operations"); #endif const bool srcAndTargetGeogAreSame = componentsSrc[0]->isEquivalentTo( targetCRS->demoteTo2D(std::string(), dbContext).get(), util::IComparable::Criterion::EQUIVALENT); // Lambda to add to the set the name of geodetic datum of the // CRS const auto addDatumOfToSet = [&dbContext]( std::set &set, const crs::CRSNNPtr &crs) { auto geodCRS = crs->extractGeodeticCRS(); if (geodCRS) { set.insert(geodCRS->datumNonNull(dbContext)->nameStr()); } }; // Lambda to return the set of names of geodetic datums used // by the source and target CRS of a list of operations. const auto makeDatumSet = [&addDatumOfToSet]( const std::vector &ops) { std::set datumSetOps; for (const auto &subOp : ops) { if (!dynamic_cast( subOp.get())) continue; addDatumOfToSet(datumSetOps, NN_NO_CHECK(subOp->sourceCRS())); addDatumOfToSet(datumSetOps, NN_NO_CHECK(subOp->targetCRS())); } return datumSetOps; }; std::map> mapSetDatumsUsed; if (srcAndTargetGeogAreSame) { // When the geographic CRS of the source and target, we // want to make sure that the transformation from the // source to the interpolation CRS uses the same datums as // the one from the interpolation CRS to the target CRS. // A simplistic view would be that the srcToInterp and // interpToTarget should be the same, but they are // subtelties, like interpToTarget being done in 3D, so with // additional conversion steps, slightly different names in // operations between 2D and 3D. The initial filter on // checking that we use the same set of datum enable us // to be confident we reject upfront geodetically-dubious // operations. for (const auto &op : srcToInterpOps) { mapSetDatumsUsed[op.get()] = makeDatumSet(getOps(op)); } for (const auto &op : interpToTargetOps) { mapSetDatumsUsed[op.get()] = makeDatumSet(getOps(op)); } } const bool hasOnlyOneOp = srcToInterpOps.size() == 1 && interpToTargetOps.size() == 1; for (const auto &srcToInterp : srcToInterpOps) { for (const auto &interpToTarget : interpToTargetOps) { if (!hasOnlyOneOp && ((srcAndTargetGeogAreSame && mapSetDatumsUsed[srcToInterp.get()] != mapSetDatumsUsed[interpToTarget.get()]) || !useCompatibleTransformationsForSameSourceTarget( srcToInterp, interpToTarget))) { #ifdef TRACE_CREATE_OPERATIONS logTrace( "Considering that '" + srcToInterp->nameStr() + "' and '" + interpToTarget->nameStr() + "' do not use consistent operations in the pre " "and post-vertical transformation steps"); #endif continue; } try { auto op = createHorizVerticalHorizPROJBased( sourceCRS, targetCRS, srcToInterp, verticalTransform, interpToTarget, interpolationGeogCRS, true); res.emplace_back(op); hasVerticalTransformWithInterpGeogCRS = true; } catch (const std::exception &) { } } } } } for (const auto &verticalTransform : verticalTransforms) { crs::GeographicCRSPtr interpolationGeogCRS = getInterpolationGeogCRS(verticalTransform, dbContext); if (!interpolationGeogCRS && (!hasVerticalTransformWithInterpGeogCRS || verticalTransform->hasBallparkTransformation())) { // This case is probably only correct if // verticalTransform and horizTransform are independent // and in particular that verticalTransform does not // involve a grid, because of the rather arbitrary order // horizontal then vertical applied for (const auto &horizTransform : horizTransforms) { try { auto op = createHorizVerticalPROJBased( sourceCRS, targetCRS, horizTransform, verticalTransform, context.disallowEmptyIntersection()); res.emplace_back(op); } catch (const std::exception &) { } } } } } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsToGeod( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::GeodeticCRS *geodDst, std::vector &res) { auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); auto intermGeog3DCRS = util::nn_static_pointer_cast(crs::GeographicCRS::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr()) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), geodDst->datum(), geodDst->datumEnsemble(), cs)); auto sourceToGeog3DOps = createOperations( sourceCRS, sourceEpoch, intermGeog3DCRS, sourceEpoch, context); auto geog3DToTargetOps = createOperations(intermGeog3DCRS, targetEpoch, targetCRS, targetEpoch, context); if (!geog3DToTargetOps.empty()) { for (const auto &op : sourceToGeog3DOps) { auto newOp = op->shallowClone(); setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS); try { res.emplace_back(ConcatenatedOperation::createComputeMetadata( {std::move(newOp), geog3DToTargetOps.front()}, context.disallowEmptyIntersection())); } catch (const InvalidOperationEmptyIntersection &) { } } } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsCompoundToCompound( const crs::CRSNNPtr &sourceCRS, const util::optional &sourceEpoch, const crs::CRSNNPtr &targetCRS, const util::optional &targetEpoch, Private::Context &context, const crs::CompoundCRS *compoundSrc, const crs::CompoundCRS *compoundDst, std::vector &res) { const auto &componentsSrc = compoundSrc->componentReferenceSystems(); const auto &componentsDst = compoundDst->componentReferenceSystems(); if (componentsSrc.empty() || componentsSrc.size() != componentsDst.size()) { return; } const auto srcGeog = componentsSrc[0]->extractGeographicCRS(); const auto dstGeog = componentsDst[0]->extractGeographicCRS(); if (srcGeog == nullptr || dstGeog == nullptr) { return; } const bool srcGeogIsSameAsDstGeog = srcGeog->_isEquivalentTo( dstGeog.get(), util::IComparable::Criterion::EQUIVALENT); const auto &authFactory = context.context->getAuthorityFactory(); auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto intermGeogSrc = srcGeog->promoteTo3D(std::string(), dbContext); const auto intermGeogDst = srcGeogIsSameAsDstGeog ? intermGeogSrc : dstGeog->promoteTo3D(std::string(), dbContext); const auto opsGeogSrcToGeogDst = createOperations( intermGeogSrc, sourceEpoch, intermGeogDst, targetEpoch, context); // Use PointMotionOperations if appropriate and available if (authFactory && sourceEpoch.has_value() && targetEpoch.has_value() && !sourceEpoch->coordinateEpoch()._isEquivalentTo( targetEpoch->coordinateEpoch()) && srcGeogIsSameAsDstGeog) { const auto pmoSrc = authFactory->getPointMotionOperationsFor( NN_NO_CHECK(srcGeog), true); if (!pmoSrc.empty()) { auto geog3D = srcGeog->promoteTo3D( std::string(), authFactory->databaseContext().as_nullable()); auto opsFirst = createOperations(sourceCRS, sourceEpoch, geog3D, sourceEpoch, context); auto pmoOps = createOperations(geog3D, sourceEpoch, geog3D, targetEpoch, context); auto opsLast = createOperations(geog3D, targetEpoch, targetCRS, targetEpoch, context); for (const auto &opFirst : opsFirst) { if (!opFirst->hasBallparkTransformation()) { for (const auto &opMiddle : pmoOps) { if (!opMiddle->hasBallparkTransformation()) { for (const auto &opLast : opsLast) { if (!opLast->hasBallparkTransformation()) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {opFirst, opMiddle, opLast}, context .disallowEmptyIntersection())); } catch (const std::exception &) { } } } } } } } if (!res.empty()) { return; } } } // Deal with "+proj=something +geoidgrids +nadgrids/+towgs84" to // "+proj=something +geoidgrids +nadgrids/+towgs84", using WGS 84 as an // intermediate. if (componentsSrc.size() == 2 && componentsDst.size() == 2) { auto comp0SrcBound = dynamic_cast(componentsSrc[0].get()); auto comp1SrcBound = dynamic_cast(componentsSrc[1].get()); auto comp0DstBound = dynamic_cast(componentsDst[0].get()); auto comp1DstBound = dynamic_cast(componentsDst[1].get()); if (comp0SrcBound && comp1SrcBound && comp0DstBound && comp1DstBound && comp0SrcBound->hubCRS()->isEquivalentTo( comp0DstBound->hubCRS().get(), util::IComparable::Criterion::EQUIVALENT) && !comp1SrcBound->isEquivalentTo( comp1DstBound, util::IComparable::Criterion::EQUIVALENT)) { auto hub3D = comp0SrcBound->hubCRS()->promoteTo3D(std::string(), dbContext); const auto ops1 = createOperations(sourceCRS, sourceEpoch, hub3D, sourceEpoch, context); const auto ops2 = createOperations(hub3D, targetEpoch, targetCRS, targetEpoch, context); for (const auto &op1 : ops1) { for (const auto &op2 : ops2) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {op1, op2}, context.disallowEmptyIntersection())); } catch (const std::exception &) { } } } return; } } std::vector verticalTransforms; bool bHasTriedVerticalTransforms = false; bool bTryThroughIntermediateGeogCRS = false; if (componentsSrc.size() >= 2) { const auto vertSrc = componentsSrc[1]->extractVerticalCRS(); const auto vertDst = componentsDst[1]->extractVerticalCRS(); if (vertSrc && vertDst && !componentsSrc[1]->_isEquivalentTo( componentsDst[1].get(), util::IComparable::Criterion::EQUIVALENT)) { if ((!vertSrc->geoidModel().empty() || !vertDst->geoidModel().empty()) && // To be able to use "CGVD28 height to // CGVD2013a(1997|2002|2010)" single grid !(vertSrc->nameStr() == "CGVD28 height" && !vertSrc->geoidModel().empty() && vertSrc->geoidModel().front()->nameStr() == "HT2_1997" && vertDst->nameStr() == "CGVD2013a(1997) height" && vertDst->geoidModel().empty()) && !(vertSrc->nameStr() == "CGVD28 height" && !vertSrc->geoidModel().empty() && vertSrc->geoidModel().front()->nameStr() == "HT2_2002" && vertDst->nameStr() == "CGVD2013a(2002) height" && vertDst->geoidModel().empty()) && !(vertSrc->nameStr() == "CGVD28 height" && !vertSrc->geoidModel().empty() && vertSrc->geoidModel().front()->nameStr() == "HT2_2010" && vertDst->nameStr() == "CGVD2013a(2010) height" && vertDst->geoidModel().empty()) && !(vertDst->nameStr() == "CGVD28 height" && !vertDst->geoidModel().empty() && vertDst->geoidModel().front()->nameStr() == "HT2_1997" && vertSrc->nameStr() == "CGVD2013a(1997) height" && vertSrc->geoidModel().empty()) && !(vertDst->nameStr() == "CGVD28 height" && !vertDst->geoidModel().empty() && vertDst->geoidModel().front()->nameStr() == "HT2_2002" && vertSrc->nameStr() == "CGVD2013a(2002) height" && vertSrc->geoidModel().empty()) && !(vertDst->nameStr() == "CGVD28 height" && !vertDst->geoidModel().empty() && vertDst->geoidModel().front()->nameStr() == "HT2_2010" && vertSrc->nameStr() == "CGVD2013a(2010) height" && vertSrc->geoidModel().empty())) { // If we have a geoid model, force using through it bTryThroughIntermediateGeogCRS = true; } else { bHasTriedVerticalTransforms = true; verticalTransforms = createOperations(componentsSrc[1], sourceEpoch, componentsDst[1], targetEpoch, context); // If we didn't find a non-ballpark transformation between // the 2 vertical CRS, then try through intermediate geographic // CRS // Do that although when the geographic CRS of the source and // target CRS are not the same, but only if they have a 3D // known version, and there is a non-ballpark transformation // between them. // This helps for "GDA94 + AHD height" to "GDA2020 + AVWS // height" going through GDA94 3D and GDA2020 3D bTryThroughIntermediateGeogCRS = (verticalTransforms.size() == 1 && verticalTransforms.front()->hasBallparkTransformation()) || (!srcGeogIsSameAsDstGeog && !intermGeogSrc->identifiers().empty() && !intermGeogDst->identifiers().empty() && !opsGeogSrcToGeogDst.empty() && !opsGeogSrcToGeogDst.front()->hasBallparkTransformation()); } } } if (bTryThroughIntermediateGeogCRS) { const auto createWithIntermediateCRS = [&sourceCRS, &sourceEpoch, &targetCRS, &targetEpoch, &context, &res](const crs::CRSNNPtr &intermCRS) { const auto ops1 = createOperations( sourceCRS, sourceEpoch, intermCRS, sourceEpoch, context); const auto ops2 = createOperations( intermCRS, targetEpoch, targetCRS, targetEpoch, context); for (const auto &op1 : ops1) { for (const auto &op2 : ops2) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {op1, op2}, context.disallowEmptyIntersection())); } catch (const std::exception &) { } } } }; const auto boundSrcHorizontal = dynamic_cast(componentsSrc[0].get()); const auto boundDstHorizontal = dynamic_cast(componentsDst[0].get()); // CompoundCRS[something_with_TOWGS84, ...] to CompoundCRS[WGS84, ...] if (boundSrcHorizontal != nullptr && boundDstHorizontal == nullptr && boundSrcHorizontal->hubCRS()->_isEquivalentTo( componentsDst[0].get(), util::IComparable::Criterion::EQUIVALENT)) { createWithIntermediateCRS( componentsDst[0]->promoteTo3D(std::string(), dbContext)); if (!res.empty()) { return; } } // Inverse of above else if (boundDstHorizontal != nullptr && boundSrcHorizontal == nullptr && boundDstHorizontal->hubCRS()->_isEquivalentTo( componentsSrc[0].get(), util::IComparable::Criterion::EQUIVALENT)) { createWithIntermediateCRS( componentsSrc[0]->promoteTo3D(std::string(), dbContext)); if (!res.empty()) { return; } } // Deal with situation like // WGS 84 + EGM96 --> ETRS89 + Belfast height where // there is a geoid model for EGM96 referenced to WGS 84 // and a geoid model for Belfast height referenced to ETRS89 const auto opsSrcToGeog = createOperations( sourceCRS, sourceEpoch, intermGeogSrc, sourceEpoch, context); const auto opsGeogToTarget = createOperations( intermGeogDst, targetEpoch, targetCRS, targetEpoch, context); // We preferably accept using an intermediate if the operations // to it do not include ballpark operations, but if they do include // grids, using such operations result will be better than nothing. // This will help for example for "NAD83(CSRS) + CGVD28 height" to // "NAD83(CSRS) + CGVD2013(CGG2013) height" where the transformation // between "CGVD2013(CGG2013) height" and "NAD83(CSRS)" actually // involves going through NAD83(CSRS)v6 const auto hasKnownGrid = [&dbContext](const CoordinateOperationNNPtr &op) { const auto grids = op->gridsNeeded(dbContext, true); if (grids.empty()) { return false; } for (const auto &grid : grids) { if (grid.available) { return true; } } return false; }; const auto hasNonTrivialTransf = [&hasKnownGrid](const std::vector &ops) { return !ops.empty() && (!ops.front()->hasBallparkTransformation() || hasKnownGrid(ops.front())); }; const bool hasNonTrivialSrcTransf = hasNonTrivialTransf(opsSrcToGeog); const bool hasNonTrivialTargetTransf = hasNonTrivialTransf(opsGeogToTarget); double bestAccuracy = -1; size_t bestStepCount = 0; if (hasNonTrivialSrcTransf && hasNonTrivialTargetTransf) { // In first pass, exclude (horizontal) ballpark operations, but // accept them in second pass. for (int pass = 0; pass <= 1 && res.empty(); pass++) { for (const auto &op1 : opsSrcToGeog) { if (pass == 0 && op1->hasBallparkTransformation()) { // std::cerr << "excluded " << op1->nameStr() << // std::endl; continue; } if (op1->nameStr().find(BALLPARK_VERTICAL_TRANSFORMATION) != std::string::npos) { continue; } for (const auto &op2 : opsGeogSrcToGeogDst) { for (const auto &op3 : opsGeogToTarget) { if (pass == 0 && op3->hasBallparkTransformation()) { // std::cerr << "excluded " << op3->nameStr() << // std::endl; continue; } if (op3->nameStr().find( BALLPARK_VERTICAL_TRANSFORMATION) != std::string::npos) { continue; } try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( srcGeogIsSameAsDstGeog ? std::vector< CoordinateOperationNNPtr>{op1, op3} : std::vector< CoordinateOperationNNPtr>{op1, op2, op3}, context.disallowEmptyIntersection())); const double accuracy = getAccuracy(res.back()); const size_t stepCount = getStepCount(res.back()); if (accuracy >= 0 && (bestAccuracy < 0 || (accuracy < bestAccuracy || (accuracy == bestAccuracy && stepCount < bestStepCount)))) { bestAccuracy = accuracy; bestStepCount = stepCount; } } catch (const std::exception &) { } } } } } } const auto createOpsInTwoSteps = [&res, &bestAccuracy, &bestStepCount, &context](const std::vector &ops1, const std::vector &ops2) { std::vector res2; double bestAccuracy2 = -1; size_t bestStepCount2 = 0; // In first pass, exclude (horizontal) ballpark operations, but // accept them in second pass. for (int pass = 0; pass <= 1 && res2.empty(); pass++) { for (const auto &op1 : ops1) { if (pass == 0 && op1->hasBallparkTransformation()) { // std::cerr << "excluded " << op1->nameStr() << // std::endl; continue; } if (op1->nameStr().find( BALLPARK_VERTICAL_TRANSFORMATION) != std::string::npos) { continue; } for (const auto &op2 : ops2) { if (pass == 0 && op2->hasBallparkTransformation()) { // std::cerr << "excluded " << op2->nameStr() << // std::endl; continue; } if (op2->nameStr().find( BALLPARK_VERTICAL_TRANSFORMATION) != std::string::npos) { continue; } try { res2.emplace_back( ConcatenatedOperation:: createComputeMetadata( {op1, op2}, context .disallowEmptyIntersection())); const double accuracy = getAccuracy(res2.back()); const size_t stepCount = getStepCount(res2.back()); if (accuracy >= 0 && (bestAccuracy2 < 0 || (accuracy < bestAccuracy2 || (accuracy == bestAccuracy2 && stepCount < bestStepCount2)))) { bestAccuracy2 = accuracy; bestStepCount2 = stepCount; } } catch (const std::exception &) { } } } } // Use the results of this new attempt if they are better // than the previous ones if (bestAccuracy2 >= 0 && (bestAccuracy < 0 || (bestAccuracy2 < bestAccuracy || (bestAccuracy2 == bestAccuracy && bestStepCount2 < bestStepCount)))) { bestAccuracy = bestAccuracy2; bestStepCount = bestStepCount2; res = std::move(res2); } }; if (res.empty() || intermGeogSrc->identifiers().empty() || intermGeogDst->identifiers().empty()) { // If the promoted-to-3D source geographic CRS is not a known // object, transformations from it to another 3D one may not be // relevant, so try doing source -> geogDst 3D -> dest, if geogDst // 3D is a known object if (!srcGeog->identifiers().empty() && !intermGeogDst->identifiers().empty() && hasNonTrivialTargetTransf) { const auto opsSrcToIntermGeog = createOperations( sourceCRS, util::optional(), intermGeogDst, util::optional(), context); if (hasNonTrivialTransf(opsSrcToIntermGeog)) { createOpsInTwoSteps(opsSrcToIntermGeog, opsGeogToTarget); } } // Symmetrical situation with the promoted-to-3D target geographic // CRS if (!dstGeog->identifiers().empty() && !intermGeogSrc->identifiers().empty() && hasNonTrivialSrcTransf) { const auto opsIntermGeogToDst = createOperations( intermGeogSrc, util::optional(), targetCRS, util::optional(), context); if (hasNonTrivialTransf(opsIntermGeogToDst)) { createOpsInTwoSteps(opsSrcToGeog, opsIntermGeogToDst); } } } if (!res.empty()) { return; } if (!bHasTriedVerticalTransforms) { verticalTransforms = createOperations(componentsSrc[1], sourceEpoch, componentsDst[1], targetEpoch, context); } } for (const auto &verticalTransform : verticalTransforms) { auto interpolationCRS = NN_NO_CHECK(std::static_pointer_cast(srcGeog)); if (auto interpTransformCRS = verticalTransform->interpolationCRS()) { auto interpTransformSingleCRS = std::static_pointer_cast(interpTransformCRS); if (interpTransformSingleCRS) { interpolationCRS = NN_NO_CHECK(interpTransformSingleCRS); } } else { auto compSrc0BoundCrs = dynamic_cast(componentsSrc[0].get()); auto compDst0BoundCrs = dynamic_cast(componentsDst[0].get()); if (compSrc0BoundCrs && compDst0BoundCrs && dynamic_cast( compSrc0BoundCrs->hubCRS().get()) && compSrc0BoundCrs->hubCRS()->_isEquivalentTo( compDst0BoundCrs->hubCRS().get(), util::IComparable::Criterion::EQUIVALENT)) { interpolationCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast( compSrc0BoundCrs->hubCRS())); } else if (compSrc0BoundCrs && !compDst0BoundCrs && compSrc0BoundCrs->hubCRS()->_isEquivalentTo( dstGeog.get(), util::IComparable::Criterion::EQUIVALENT)) { interpolationCRS = NN_NO_CHECK( std::static_pointer_cast(dstGeog)); } } // Hack for // NAD83_CSRS_1997_xxxx_HT2_1997 to NAD83_CSRS_1997_yyyy_CGVD2013_1997 // NAD83_CSRS_2002_xxxx_HT2_2002 to NAD83_CSRS_2002_yyyy_CGVD2013_2002 if (dbContext && sourceEpoch.has_value() && targetEpoch.has_value() && sourceEpoch->coordinateEpoch()._isEquivalentTo( targetEpoch->coordinateEpoch()) && srcGeog->_isEquivalentTo( dstGeog.get(), util::IComparable::Criterion::EQUIVALENT) && srcGeog->nameStr() == "NAD83(CSRS)v7") { const bool is1997 = std::abs(sourceEpoch->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR) - 1997) < 1e-10; const bool is2002 = std::abs(sourceEpoch->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR) - 2002) < 1e-10; try { auto authFactoryEPSG = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "EPSG"); auto nad83CSRSv7 = authFactoryEPSG->createGeographicCRS("8255"); if (srcGeog->_isEquivalentTo(nad83CSRSv7.get(), util::IComparable::Criterion:: EQUIVALENT) && // NAD83(CSRS)v7 // 2D ((is1997 && verticalTransform->nameStr().find( "CGVD28 height to CGVD2013a(1997) height (1)") != std::string::npos) || (is2002 && verticalTransform->nameStr().find( "CGVD28 height to CGVD2013a(2002) height (1)") != std::string::npos))) { interpolationCRS = std::move(nad83CSRSv7); } } catch (const std::exception &) { } } const auto opSrcCRSToGeogCRS = createOperations( componentsSrc[0], util::optional(), interpolationCRS, util::optional(), context); const auto opGeogCRStoDstCRS = createOperations( interpolationCRS, util::optional(), componentsDst[0], util::optional(), context); for (const auto &opSrc : opSrcCRSToGeogCRS) { for (const auto &opDst : opGeogCRStoDstCRS) { try { auto op = createHorizVerticalHorizPROJBased( sourceCRS, targetCRS, opSrc, verticalTransform, opDst, interpolationCRS, true); res.emplace_back(op); } catch (const InvalidOperationEmptyIntersection &) { } catch (const io::FormattingException &) { } } } if (verticalTransforms.size() == 1U && verticalTransform->hasBallparkTransformation() && context.context->getAuthorityFactory() && dynamic_cast(componentsSrc[0].get()) && dynamic_cast(componentsDst[0].get()) && verticalTransform->nameStr() == getBallparkTransformationVertToVert(componentsSrc[1], componentsDst[1])) { // e.g EPSG:3912+EPSG:5779 to EPSG:3794+EPSG:8690 // "MGI 1901 / Slovene National Grid + SVS2000 height" to // "Slovenia 1996 / Slovene National Grid + SVS2010 height" // using the "D48/GK to D96/TM (xx)" family of horizontal // transformatoins between the projected CRS // Cf // https://github.com/OSGeo/PROJ/issues/3854#issuecomment-1689964773 // We restrict to a ballpark vertical transformation for now, // but ideally we should deal with a regular vertical transformation // but that would involve doing a transformation to its // interpolation CRS and we don't have such cases for now. std::vector opsHoriz; createOperationsFromDatabase( componentsSrc[0], util::optional(), componentsDst[0], util::optional(), context, srcGeog.get(), dstGeog.get(), srcGeog.get(), dstGeog.get(), /*vertSrc=*/nullptr, /*vertDst=*/nullptr, opsHoriz); for (const auto &opHoriz : opsHoriz) { res.emplace_back(createHorizNullVerticalPROJBased( sourceCRS, targetCRS, opHoriz, verticalTransform)); } } } if (verticalTransforms.empty()) { auto resTmp = createOperations( componentsSrc[0], util::optional(), componentsDst[0], util::optional(), context); for (const auto &op : resTmp) { auto opClone = op->shallowClone(); setCRSs(opClone.get(), sourceCRS, targetCRS); res.emplace_back(opClone); } } } // --------------------------------------------------------------------------- void CoordinateOperationFactory::Private::createOperationsBoundToCompound( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::BoundCRS *boundSrc, const crs::CompoundCRS *compoundDst, std::vector &res) { const auto &authFactory = context.context->getAuthorityFactory(); const auto dbContext = authFactory ? authFactory->databaseContext().as_nullable() : nullptr; const auto &componentsDst = compoundDst->componentReferenceSystems(); // Case of BOUND[NAD83_3D,TOWGS84] to // COMPOUND[BOUND[NAD83_2D,TOWGS84],BOUND[VERT[NAVD88],HUB=NAD83_3D,GRID]] // ==> We can ignore the TOWGS84 BOUND aspect and just ask for // NAD83_3D to COMPOUND[NAD83_2D,BOUND[VERT[NAVD88],HUB=NAD83_3D,GRID]] if (componentsDst.size() >= 2) { auto srcGeogCRS = boundSrc->baseCRS()->extractGeodeticCRS(); auto compDst0BoundCrs = util::nn_dynamic_pointer_cast(componentsDst[0]); auto compDst1BoundCrs = dynamic_cast(componentsDst[1].get()); if (srcGeogCRS && compDst0BoundCrs && compDst1BoundCrs) { auto compDst0Geog = compDst0BoundCrs->extractGeographicCRS(); auto compDst1Vert = dynamic_cast( compDst1BoundCrs->baseCRS().get()); auto compDst1BoundCrsHubCrsGeog = dynamic_cast( compDst1BoundCrs->hubCRS().get()); if (compDst1BoundCrsHubCrsGeog) { auto hubDst1Datum = compDst1BoundCrsHubCrsGeog->datumNonNull(dbContext); if (compDst0Geog && compDst1Vert && srcGeogCRS->datumNonNull(dbContext)->isEquivalentTo( hubDst1Datum.get(), util::IComparable::Criterion::EQUIVALENT) && compDst0Geog->datumNonNull(dbContext)->isEquivalentTo( hubDst1Datum.get(), util::IComparable::Criterion::EQUIVALENT)) { auto srcNew = boundSrc->baseCRS(); auto properties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, compDst0BoundCrs->nameStr() + " + " + componentsDst[1]->nameStr()); auto dstNew = crs::CompoundCRS::create( properties, {NN_NO_CHECK(compDst0BoundCrs), componentsDst[1]}); auto tmpRes = createOperations( srcNew, util::optional(), dstNew, util::optional(), context); for (const auto &op : tmpRes) { auto opClone = op->shallowClone(); setCRSs(opClone.get(), sourceCRS, targetCRS); res.emplace_back(opClone); } return; } } } } if (!componentsDst.empty()) { auto compDst0BoundCrs = dynamic_cast(componentsDst[0].get()); if (compDst0BoundCrs) { auto boundSrcHubAsGeogCRS = dynamic_cast(boundSrc->hubCRS().get()); auto compDst0BoundCrsHubAsGeogCRS = dynamic_cast( compDst0BoundCrs->hubCRS().get()); if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) { const auto boundSrcHubAsGeogCRSDatum = boundSrcHubAsGeogCRS->datumNonNull(dbContext); const auto compDst0BoundCrsHubAsGeogCRSDatum = compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext); if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo( compDst0BoundCrsHubAsGeogCRSDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { auto cs = cs::EllipsoidalCS:: createLatitudeLongitudeEllipsoidalHeight( common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); auto intermGeog3DCRS = util::nn_static_pointer_cast< crs::CRS>(crs::GeographicCRS::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, boundSrcHubAsGeogCRS->nameStr()) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), boundSrcHubAsGeogCRS->datum(), boundSrcHubAsGeogCRS->datumEnsemble(), cs)); auto sourceToGeog3DOps = createOperations( sourceCRS, util::optional(), intermGeog3DCRS, util::optional(), context); auto geog3DToTargetOps = createOperations( intermGeog3DCRS, util::optional(), targetCRS, util::optional(), context); for (const auto &opSrc : sourceToGeog3DOps) { for (const auto &opDst : geog3DToTargetOps) { if (opSrc->targetCRS() && opDst->sourceCRS() && !opSrc->targetCRS()->_isEquivalentTo( opDst->sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT)) { // Shouldn't happen normally, but typically // one of them can be 2D and the other 3D // due to above createOperations() not // exactly setting the expected source and // target CRS. // So create an adapter operation... auto intermOps = createOperations( NN_NO_CHECK(opSrc->targetCRS()), util::optional(), NN_NO_CHECK(opDst->sourceCRS()), util::optional(), context); if (!intermOps.empty()) { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {opSrc, intermOps.front(), opDst}, context .disallowEmptyIntersection())); } } else { res.emplace_back( ConcatenatedOperation:: createComputeMetadata( {opSrc, opDst}, context .disallowEmptyIntersection())); } } } return; } } } // e.g transforming from a Bound[something_not_WGS84_but_with_TOWGS84] // to a CompoundCRS[WGS84, ...] const auto srcBaseSingleCRS = dynamic_cast(boundSrc->baseCRS().get()); const auto srcGeogCRS = boundSrc->extractGeographicCRS(); const auto boundSrcHubAsGeogCRS = dynamic_cast(boundSrc->hubCRS().get()); const auto comp0Geog = componentsDst[0]->extractGeographicCRS(); if (srcBaseSingleCRS && srcBaseSingleCRS->coordinateSystem()->axisList().size() == 3 && srcGeogCRS && boundSrcHubAsGeogCRS && comp0Geog && boundSrcHubAsGeogCRS->coordinateSystem()->axisList().size() == 3 && !srcGeogCRS->datumNonNull(dbContext)->isEquivalentTo( comp0Geog->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT) && boundSrcHubAsGeogCRS && boundSrcHubAsGeogCRS->datumNonNull(dbContext)->isEquivalentTo( comp0Geog->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT)) { const auto ops1 = createOperations(sourceCRS, util::optional(), boundSrc->hubCRS(), util::optional(), context); const auto ops2 = createOperations( boundSrc->hubCRS(), util::optional(), targetCRS, util::optional(), context); for (const auto &op1 : ops1) { for (const auto &op2 : ops2) { try { res.emplace_back( ConcatenatedOperation::createComputeMetadata( {op1, op2}, context.disallowEmptyIntersection())); } catch (const std::exception &) { } } } if (!res.empty()) { return; } } } // There might be better things to do, but for now just ignore the // transformation of the bound CRS res = createOperations(boundSrc->baseCRS(), util::optional(), targetCRS, util::optional(), context); } //! @endcond // --------------------------------------------------------------------------- /** \brief Find a list of CoordinateOperation from sourceCRS to targetCRS. * * The operations are sorted with the most relevant ones first: by * descending * area (intersection of the transformation area with the area of interest, * or intersection of the transformation with the area of use of the CRS), * and * by increasing accuracy. Operations with unknown accuracy are sorted last, * whatever their area. * * When one of the source or target CRS has a vertical component but not the * other one, the one that has no vertical component is automatically promoted * to a 3D version, where its vertical axis is the ellipsoidal height in metres, * using the ellipsoid of the base geodetic CRS. * * @param sourceCRS source CRS. * @param targetCRS target CRS. * @param context Search context. * @return a list */ std::vector CoordinateOperationFactory::createOperations( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const CoordinateOperationContextNNPtr &context) const { #ifdef TRACE_CREATE_OPERATIONS ENTER_FUNCTION(); #endif // Look if we are called on CRS that have a link to a 'canonical' // BoundCRS // If so, use that one as input const auto &srcBoundCRS = sourceCRS->canonicalBoundCRS(); const auto &targetBoundCRS = targetCRS->canonicalBoundCRS(); auto l_sourceCRS = srcBoundCRS ? NN_NO_CHECK(srcBoundCRS) : sourceCRS; auto l_targetCRS = targetBoundCRS ? NN_NO_CHECK(targetBoundCRS) : targetCRS; const auto &authFactory = context->getAuthorityFactory(); metadata::ExtentPtr sourceCRSExtent; auto l_resolvedSourceCRS = crs::CRS::getResolvedCRS(l_sourceCRS, authFactory, sourceCRSExtent); metadata::ExtentPtr targetCRSExtent; auto l_resolvedTargetCRS = crs::CRS::getResolvedCRS(l_targetCRS, authFactory, targetCRSExtent); if (context->getSourceAndTargetCRSExtentUse() == CoordinateOperationContext::SourceTargetCRSExtentUse::NONE) { // Make sure *not* to use CRS extent if requested to ignore it sourceCRSExtent.reset(); targetCRSExtent.reset(); } Private::Context contextPrivate(sourceCRSExtent, targetCRSExtent, context); if (context->getSourceAndTargetCRSExtentUse() == CoordinateOperationContext::SourceTargetCRSExtentUse::INTERSECTION) { if (sourceCRSExtent && targetCRSExtent && !sourceCRSExtent->intersects(NN_NO_CHECK(targetCRSExtent))) { return std::vector(); } } auto resFiltered = filterAndSort( Private::createOperations( l_resolvedSourceCRS, context->getSourceCoordinateEpoch(), l_resolvedTargetCRS, context->getTargetCoordinateEpoch(), contextPrivate), context, sourceCRSExtent, targetCRSExtent); if (context->getSourceCoordinateEpoch().has_value() || context->getTargetCoordinateEpoch().has_value()) { std::vector res; res.reserve(resFiltered.size()); for (const auto &op : resFiltered) { auto opClone = op->shallowClone(); opClone->setSourceCoordinateEpoch( context->getSourceCoordinateEpoch()); opClone->setTargetCoordinateEpoch( context->getTargetCoordinateEpoch()); res.emplace_back(opClone); } return res; } return resFiltered; } // --------------------------------------------------------------------------- /** \brief Find a list of CoordinateOperation from a source coordinate metadata * to targetCRS. * @param sourceCoordinateMetadata source CoordinateMetadata. * @param targetCRS target CRS. * @param context Search context. * @return a list * @since 9.2 */ std::vector CoordinateOperationFactory::createOperations( const coordinates::CoordinateMetadataNNPtr &sourceCoordinateMetadata, const crs::CRSNNPtr &targetCRS, const CoordinateOperationContextNNPtr &context) const { auto newContext = context->clone(); newContext->setSourceCoordinateEpoch( sourceCoordinateMetadata->coordinateEpoch()); return createOperations(sourceCoordinateMetadata->crs(), targetCRS, newContext); } // --------------------------------------------------------------------------- /** \brief Find a list of CoordinateOperation from a source CRS to a target * coordinate metadata. * @param sourceCRS source CRS. * @param targetCoordinateMetadata target CoordinateMetadata. * @param context Search context. * @return a list * @since 9.2 */ std::vector CoordinateOperationFactory::createOperations( const crs::CRSNNPtr &sourceCRS, const coordinates::CoordinateMetadataNNPtr &targetCoordinateMetadata, const CoordinateOperationContextNNPtr &context) const { auto newContext = context->clone(); newContext->setTargetCoordinateEpoch( targetCoordinateMetadata->coordinateEpoch()); return createOperations(sourceCRS, targetCoordinateMetadata->crs(), newContext); } // --------------------------------------------------------------------------- /** \brief Find a list of CoordinateOperation from a source coordinate metadata * to a target coordinate metadata. * * Both source_crs and target_crs can be a CoordinateMetadata * with an associated coordinate epoch, to perform changes of coordinate epochs. * Note however than this is in practice limited to use of velocity grids inside * the same dynamic CRS. * * @param sourceCoordinateMetadata source CoordinateMetadata. * @param targetCoordinateMetadata target CoordinateMetadata. * @param context Search context. * @return a list * @since 9.4 */ std::vector CoordinateOperationFactory::createOperations( const coordinates::CoordinateMetadataNNPtr &sourceCoordinateMetadata, const coordinates::CoordinateMetadataNNPtr &targetCoordinateMetadata, const CoordinateOperationContextNNPtr &context) const { auto newContext = context->clone(); newContext->setSourceCoordinateEpoch( sourceCoordinateMetadata->coordinateEpoch()); newContext->setTargetCoordinateEpoch( targetCoordinateMetadata->coordinateEpoch()); return createOperations(sourceCoordinateMetadata->crs(), targetCoordinateMetadata->crs(), newContext); } // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateOperationFactory. */ CoordinateOperationFactoryNNPtr CoordinateOperationFactory::create() { return NN_NO_CHECK( CoordinateOperationFactory::make_unique()); } // --------------------------------------------------------------------------- } // namespace operation namespace crs { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress crs::CRSNNPtr CRS::getResolvedCRS(const crs::CRSNNPtr &crs, const io::AuthorityFactoryPtr &authFactory, metadata::ExtentPtr &extentOut) { if (crs->hasOver()) { return crs; } const auto &ids = crs->identifiers(); const auto &name = crs->nameStr(); bool approxExtent; extentOut = operation::getExtentPossiblySynthetized(crs, approxExtent); // We try to "identify" the provided CRS with the ones of the database, // but in a more restricted way that what identify() does. // If we get a match from id in priority, and from name as a fallback, and // that they are equivalent to the input CRS, then use the identified CRS. // Even if they aren't equivalent, we update extentOut with the one of the // identified CRS if our input one is absent/not reliable. const auto tryToIdentifyByName = [&crs, &name, &authFactory, approxExtent, &extentOut](io::AuthorityFactory::ObjectType objectType) { if (name != "unknown" && name != "unnamed") { auto matches = authFactory->createObjectsFromName( name, {objectType}, false, 2); if (matches.size() == 1) { auto match = util::nn_static_pointer_cast(matches.front()); if (approxExtent || !extentOut) { extentOut = operation::getExtent(match); } if (match->isEquivalentTo( crs.get(), util::IComparable::Criterion::EQUIVALENT)) { return match; } } } return crs; }; auto geogCRS = dynamic_cast(crs.get()); if (geogCRS && authFactory) { if (!ids.empty()) { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), *ids.front()->codeSpace()); try { auto resolvedCrs( tmpAuthFactory->createGeographicCRS(ids.front()->code())); if (approxExtent || !extentOut) { extentOut = operation::getExtent(resolvedCrs); } if (resolvedCrs->isEquivalentTo( crs.get(), util::IComparable::Criterion::EQUIVALENT)) { return util::nn_static_pointer_cast(resolvedCrs); } } catch (const std::exception &) { } } else { return tryToIdentifyByName( geogCRS->coordinateSystem()->axisList().size() == 2 ? io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS : io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); } } auto projectedCrs = dynamic_cast(crs.get()); if (projectedCrs && authFactory) { if (!ids.empty()) { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), *ids.front()->codeSpace()); try { auto resolvedCrs( tmpAuthFactory->createProjectedCRS(ids.front()->code())); if (approxExtent || !extentOut) { extentOut = operation::getExtent(resolvedCrs); } if (resolvedCrs->isEquivalentTo( crs.get(), util::IComparable::Criterion::EQUIVALENT)) { return util::nn_static_pointer_cast(resolvedCrs); } } catch (const std::exception &) { } } else { return tryToIdentifyByName( io::AuthorityFactory::ObjectType::PROJECTED_CRS); } } auto compoundCrs = dynamic_cast(crs.get()); if (compoundCrs && authFactory) { if (!ids.empty()) { const auto tmpAuthFactory = io::AuthorityFactory::create( authFactory->databaseContext(), *ids.front()->codeSpace()); try { auto resolvedCrs( tmpAuthFactory->createCompoundCRS(ids.front()->code())); if (approxExtent || !extentOut) { extentOut = operation::getExtent(resolvedCrs); } if (resolvedCrs->isEquivalentTo( crs.get(), util::IComparable::Criterion::EQUIVALENT)) { return util::nn_static_pointer_cast(resolvedCrs); } } catch (const std::exception &) { } } else { auto outCrs = tryToIdentifyByName( io::AuthorityFactory::ObjectType::COMPOUND_CRS); const auto &components = compoundCrs->componentReferenceSystems(); if (outCrs.get() != crs.get()) { bool hasGeoid = false; if (components.size() == 2) { auto vertCRS = dynamic_cast(components[1].get()); if (vertCRS && !vertCRS->geoidModel().empty()) { hasGeoid = true; } } if (!hasGeoid) { return outCrs; } } if (approxExtent || !extentOut) { // If we still did not get a reliable extent, then try to // resolve the components of the compoundCRS, and take the // intersection of their extent. extentOut = metadata::ExtentPtr(); for (const auto &component : components) { metadata::ExtentPtr componentExtent; getResolvedCRS(component, authFactory, componentExtent); if (extentOut && componentExtent) extentOut = extentOut->intersection( NN_NO_CHECK(componentExtent)); else if (componentExtent) extentOut = std::move(componentExtent); } } } } return crs; } //! @endcond } // namespace crs NS_PROJ_END proj-9.8.1/src/iso19111/operation/coordinateoperation_private.hpp000664 001750 001750 00000007643 15166171715 024756 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef COORDINATEROPERATION_PRIVATE_HPP #define COORDINATEROPERATION_PRIVATE_HPP #include "proj/coordinateoperation.hpp" #include "proj/util.hpp" // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateOperation::Private { util::optional operationVersion_{}; std::vector coordinateOperationAccuracies_{}; std::weak_ptr sourceCRSWeak_{}; std::weak_ptr targetCRSWeak_{}; crs::CRSPtr interpolationCRS_{}; std::shared_ptr> sourceCoordinateEpoch_{ std::make_shared>()}; std::shared_ptr> targetCoordinateEpoch_{ std::make_shared>()}; bool hasBallparkTransformation_ = false; bool requiresPerCoordinateInputTime_ = false; // do not set this for a ProjectedCRS.definingConversion struct CRSStrongRef { crs::CRSNNPtr sourceCRS_; crs::CRSNNPtr targetCRS_; CRSStrongRef(const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn) : sourceCRS_(sourceCRSIn), targetCRS_(targetCRSIn) {} }; std::unique_ptr strongRef_{}; Private() = default; Private(const Private &other) : operationVersion_(other.operationVersion_), coordinateOperationAccuracies_(other.coordinateOperationAccuracies_), sourceCRSWeak_(other.sourceCRSWeak_), targetCRSWeak_(other.targetCRSWeak_), interpolationCRS_(other.interpolationCRS_), sourceCoordinateEpoch_(other.sourceCoordinateEpoch_), targetCoordinateEpoch_(other.targetCoordinateEpoch_), hasBallparkTransformation_(other.hasBallparkTransformation_), requiresPerCoordinateInputTime_( other.requiresPerCoordinateInputTime_), strongRef_(other.strongRef_ ? std::make_unique(*(other.strongRef_)) : nullptr) {} Private &operator=(const Private &) = delete; }; //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END #endif // COORDINATEROPERATION_PRIVATE_HPP proj-9.8.1/src/iso19111/operation/vectorofvaluesparams.hpp000664 001750 001750 00000010025 15166171715 023413 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef VECTOROFVALUESPARAMS_HPP #define VECTOROFVALUESPARAMS_HPP #include "proj/coordinateoperation.hpp" #include "proj/util.hpp" // --------------------------------------------------------------------------- NS_PROJ_START namespace operation { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct VectorOfValues : public std::vector { VectorOfValues() : std::vector() {} explicit VectorOfValues(std::initializer_list list) : std::vector(list) {} explicit VectorOfValues(std::initializer_list list); VectorOfValues(const VectorOfValues &) = delete; VectorOfValues(VectorOfValues &&) = default; ~VectorOfValues(); }; VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3); VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4); VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4, const common::Measure &m5); VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4, const common::Measure &m5, const common::Measure &m6); VectorOfValues createParams(const common::Measure &m1, const common::Measure &m2, const common::Measure &m3, const common::Measure &m4, const common::Measure &m5, const common::Measure &m6, const common::Measure &m7); // --------------------------------------------------------------------------- struct VectorOfParameters : public std::vector { VectorOfParameters() : std::vector() {} explicit VectorOfParameters( std::initializer_list list) : std::vector(list) {} VectorOfParameters(const VectorOfParameters &) = delete; ~VectorOfParameters(); }; //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END #endif // VECTOROFVALUESPARAMS_HPP proj-9.8.1/src/iso19111/operation/parammappings.cpp000664 001750 001750 00000231240 15166171715 021776 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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 "parammappings.hpp" #include "oputils.hpp" #include "proj_constants.h" #include "proj/internal/internal.hpp" NS_PROJ_START using namespace internal; namespace operation { //! @cond Doxygen_Suppress const char *WKT1_LATITUDE_OF_ORIGIN = "latitude_of_origin"; const char *WKT1_CENTRAL_MERIDIAN = "central_meridian"; const char *WKT1_SCALE_FACTOR = "scale_factor"; const char *WKT1_FALSE_EASTING = "false_easting"; const char *WKT1_FALSE_NORTHING = "false_northing"; const char *WKT1_STANDARD_PARALLEL_1 = "standard_parallel_1"; const char *WKT1_STANDARD_PARALLEL_2 = "standard_parallel_2"; const char *WKT1_LATITUDE_OF_CENTER = "latitude_of_center"; const char *WKT1_LONGITUDE_OF_CENTER = "longitude_of_center"; const char *WKT1_AZIMUTH = "azimuth"; const char *WKT1_RECTIFIED_GRID_ANGLE = "rectified_grid_angle"; static const char *lat_0 = "lat_0"; static const char *lat_1 = "lat_1"; static const char *lat_2 = "lat_2"; static const char *lat_ts = "lat_ts"; static const char *lon_0 = "lon_0"; static const char *lon_1 = "lon_1"; static const char *lon_2 = "lon_2"; static const char *lonc = "lonc"; static const char *alpha = "alpha"; static const char *gamma = "gamma"; static const char *k_0 = "k_0"; static const char *k = "k"; static const char *x_0 = "x_0"; static const char *y_0 = "y_0"; static const char *h = "h"; // --------------------------------------------------------------------------- const ParamMapping paramLatitudeNatOrigin = { EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, lat_0}; static const ParamMapping paramLongitudeNatOrigin = { EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_CENTRAL_MERIDIAN, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping paramScaleFactor = { EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, k_0}; static const ParamMapping paramScaleFactorK = { EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, k}; static const ParamMapping paramFalseEasting = { EPSG_NAME_PARAMETER_FALSE_EASTING, EPSG_CODE_PARAMETER_FALSE_EASTING, WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0}; static const ParamMapping paramFalseNorthing = { EPSG_NAME_PARAMETER_FALSE_NORTHING, EPSG_CODE_PARAMETER_FALSE_NORTHING, WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0}; static const ParamMapping paramLatitudeFalseOrigin = { EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, lat_0}; static const ParamMapping paramLongitudeFalseOrigin = { EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_CENTRAL_MERIDIAN, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping paramEastingFalseOrigin = { EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0}; static const ParamMapping paramNorthingFalseOrigin = { EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0}; static const ParamMapping paramLatitude1stStdParallel = { EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, common::UnitOfMeasure::Type::ANGULAR, lat_1}; static const ParamMapping paramLatitude2ndStdParallel = { EPSG_NAME_PARAMETER_LATITUDE_2ND_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL, WKT1_STANDARD_PARALLEL_2, common::UnitOfMeasure::Type::ANGULAR, lat_2}; static const ParamMapping *const paramsNatOriginScale[] = { ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactor, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsNatOriginScaleK[] = { ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatFirstPoint = { "Latitude of 1st point", 0, "Latitude_Of_1st_Point", common::UnitOfMeasure::Type::ANGULAR, lat_1}; static const ParamMapping paramLongFirstPoint = { "Longitude of 1st point", 0, "Longitude_Of_1st_Point", common::UnitOfMeasure::Type::ANGULAR, lon_1}; static const ParamMapping paramLatSecondPoint = { "Latitude of 2nd point", 0, "Latitude_Of_2nd_Point", common::UnitOfMeasure::Type::ANGULAR, lat_2}; static const ParamMapping paramLongSecondPoint = { "Longitude of 2nd point", 0, "Longitude_Of_2nd_Point", common::UnitOfMeasure::Type::ANGULAR, lon_2}; static const ParamMapping *const paramsTPEQD[] = {¶mLatFirstPoint, ¶mLongFirstPoint, ¶mLatSecondPoint, ¶mLongSecondPoint, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsTMG[] = { ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, ¶mEastingFalseOrigin, ¶mNorthingFalseOrigin, nullptr}; static const ParamMapping paramLatFalseOriginLatOfCenter = { EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, WKT1_LATITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lat_0}; static const ParamMapping paramLongFalseOriginLongOfCenter = { EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, WKT1_LONGITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping *const paramsAEA_EQDC[] = { ¶mLatFalseOriginLatOfCenter, ¶mLongFalseOriginLongOfCenter, ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, ¶mEastingFalseOrigin, ¶mNorthingFalseOrigin, nullptr}; static const ParamMapping paramLatitudeNatOriginLCC1SP = { EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, lat_1}; static const ParamMapping *const paramsLCC1SP[] = { ¶mLatitudeNatOriginLCC1SP, ¶mLongitudeNatOrigin, ¶mScaleFactor, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsLCC1SPVariantB[] = { ¶mLatitudeNatOriginLCC1SP, ¶mScaleFactor, ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, ¶mEastingFalseOrigin, ¶mNorthingFalseOrigin, nullptr, }; static const ParamMapping *const paramsLCC2SP[] = { ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, ¶mEastingFalseOrigin, ¶mNorthingFalseOrigin, nullptr, }; static const ParamMapping paramEllipsoidScaleFactor = { EPSG_NAME_PARAMETER_ELLIPSOID_SCALE_FACTOR, EPSG_CODE_PARAMETER_ELLIPSOID_SCALE_FACTOR, nullptr, common::UnitOfMeasure::Type::SCALE, k_0}; static const ParamMapping *const paramsLCC2SPMichigan[] = { ¶mLatitudeFalseOrigin, ¶mLongitudeFalseOrigin, ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, ¶mEastingFalseOrigin, ¶mNorthingFalseOrigin, ¶mEllipsoidScaleFactor, nullptr, }; static const ParamMapping paramLatNatLatCenter = { EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lat_0}; static const ParamMapping paramLongNatLongCenter = { EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, WKT1_LONGITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping *const paramsAEQD[]{ ¶mLatNatLatCenter, ¶mLongNatLongCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsNatOrigin[] = { ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatNatOriginLat1 = { EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_STANDARD_PARALLEL_1, common::UnitOfMeasure::Type::ANGULAR, lat_1}; static const ParamMapping *const paramsBonne[] = { ¶mLatNatOriginLat1, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLat1stParallelLatTs = { EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, WKT1_STANDARD_PARALLEL_1, common::UnitOfMeasure::Type::ANGULAR, lat_ts}; static const ParamMapping *const paramsCEA[] = { ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsEQDC[] = {¶mLatNatLatCenter, ¶mLongNatLongCenter, ¶mLatitude1stStdParallel, ¶mLatitude2ndStdParallel, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsLongNatOrigin[] = { ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsEqc[] = { ¶mLat1stParallelLatTs, ¶mLatitudeNatOrigin, // extension of EPSG, but used by GDAL / PROJ ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramSatelliteHeight = { "Satellite Height", 0, "satellite_height", common::UnitOfMeasure::Type::LINEAR, h}; static const ParamMapping *const paramsGeos[] = { ¶mLongitudeNatOrigin, ¶mSatelliteHeight, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatCentreLatCenter = { EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE, WKT1_LATITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lat_0}; static const ParamMapping paramLonCentreLonCenterLonc = { EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lonc}; static const ParamMapping paramAzimuth = { EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, WKT1_AZIMUTH, common::UnitOfMeasure::Type::ANGULAR, alpha}; static const ParamMapping paramAngleToSkewGrid = { EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID, WKT1_RECTIFIED_GRID_ANGLE, common::UnitOfMeasure::Type::ANGULAR, gamma}; static const ParamMapping paramScaleFactorProjectionCentre = { EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, k}; static const ParamMapping *const paramsHomVariantA[] = { ¶mLatCentreLatCenter, ¶mLonCentreLonCenterLonc, ¶mAzimuth, ¶mAngleToSkewGrid, ¶mScaleFactorProjectionCentre, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramFalseEastingProjectionCentre = { EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE, WKT1_FALSE_EASTING, common::UnitOfMeasure::Type::LINEAR, x_0}; static const ParamMapping paramFalseNorthingProjectionCentre = { EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE, WKT1_FALSE_NORTHING, common::UnitOfMeasure::Type::LINEAR, y_0}; static const ParamMapping *const paramsHomVariantB[] = { ¶mLatCentreLatCenter, ¶mLonCentreLonCenterLonc, ¶mAzimuth, ¶mAngleToSkewGrid, ¶mScaleFactorProjectionCentre, ¶mFalseEastingProjectionCentre, ¶mFalseNorthingProjectionCentre, nullptr}; static const ParamMapping paramLatPoint1 = { "Latitude of 1st point", 0, "latitude_of_point_1", common::UnitOfMeasure::Type::ANGULAR, lat_1}; static const ParamMapping paramLongPoint1 = { "Longitude of 1st point", 0, "longitude_of_point_1", common::UnitOfMeasure::Type::ANGULAR, lon_1}; static const ParamMapping paramLatPoint2 = { "Latitude of 2nd point", 0, "latitude_of_point_2", common::UnitOfMeasure::Type::ANGULAR, lat_2}; static const ParamMapping paramLongPoint2 = { "Longitude of 2nd point", 0, "longitude_of_point_2", common::UnitOfMeasure::Type::ANGULAR, lon_2}; static const ParamMapping *const paramsHomTwoPoint[] = { ¶mLatCentreLatCenter, ¶mLatPoint1, ¶mLongPoint1, ¶mLatPoint2, ¶mLongPoint2, ¶mScaleFactorProjectionCentre, ¶mFalseEastingProjectionCentre, ¶mFalseNorthingProjectionCentre, nullptr}; static const ParamMapping *const paramsIMWP[] = { ¶mLongitudeNatOrigin, ¶mLatFirstPoint, ¶mLatSecondPoint, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLongCentre = { EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE, WKT1_LONGITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping *const paramsLocalOrthographic[] = { ¶mLatCentreLatCenter, ¶mLongCentre, ¶mAzimuth, ¶mScaleFactorProjectionCentre, ¶mFalseEastingProjectionCentre, ¶mFalseNorthingProjectionCentre, nullptr}; static const ParamMapping paramColatitudeConeAxis = { EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS, EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS, WKT1_AZIMUTH, common::UnitOfMeasure::Type::ANGULAR, "alpha"}; /* ignored by PROJ currently */ static const ParamMapping paramLatitudePseudoStdParallel = { EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL, "pseudo_standard_parallel_1", common::UnitOfMeasure::Type::ANGULAR, nullptr}; /* ignored by PROJ currently */ static const ParamMapping paramScaleFactorPseudoStdParallel = { EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, WKT1_SCALE_FACTOR, common::UnitOfMeasure::Type::SCALE, k}; /* ignored by PROJ currently */ static const ParamMapping paramLongCentreLongCenter = { EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_LONGITUDE_OF_CENTER, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping *const krovakParameters[] = { ¶mLatCentreLatCenter, ¶mLongCentreLongCenter, ¶mColatitudeConeAxis, ¶mLatitudePseudoStdParallel, ¶mScaleFactorPseudoStdParallel, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsLaea[] = { ¶mLatNatLatCenter, ¶mLongNatLongCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsMiller[] = { ¶mLongNatLongCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatMerc1SP = { EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, nullptr, // always set to zero, not to be exported in WKT1 common::UnitOfMeasure::Type::ANGULAR, nullptr}; // always set to zero, not to be exported in PROJ strings static const ParamMapping *const paramsMerc1SP[] = { ¶mLatMerc1SP, ¶mLongitudeNatOrigin, ¶mScaleFactorK, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsMerc2SP[] = { ¶mLat1stParallelLatTs, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsObliqueStereo[] = { ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mScaleFactorK, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatStdParallel = { EPSG_NAME_PARAMETER_LATITUDE_STD_PARALLEL, EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL, WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, lat_ts}; static const ParamMapping paramsLongOrigin = { EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN, WKT1_CENTRAL_MERIDIAN, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping *const paramsPolarStereo[] = { ¶mLatStdParallel, ¶msLongOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsLongNatOriginLongitudeCentre[] = { ¶mLongNatLongCenter, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatTrueScaleWag3 = { "Latitude of true scale", 0, WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, lat_ts}; static const ParamMapping *const paramsWag3[] = { ¶mLatTrueScaleWag3, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramPegLat = { "Peg point latitude", 0, "peg_point_latitude", common::UnitOfMeasure::Type::ANGULAR, "plat_0"}; static const ParamMapping paramPegLong = { "Peg point longitude", 0, "peg_point_longitude", common::UnitOfMeasure::Type::ANGULAR, "plon_0"}; static const ParamMapping paramPegHeading = { "Peg point heading", 0, "peg_point_heading", common::UnitOfMeasure::Type::ANGULAR, "phdg_0"}; static const ParamMapping paramPegHeight = { "Peg point height", 0, "peg_point_height", common::UnitOfMeasure::Type::LINEAR, "h_0"}; static const ParamMapping *const paramsSch[] = { ¶mPegLat, ¶mPegLong, ¶mPegHeading, ¶mPegHeight, nullptr}; static const ParamMapping *const paramsWink1[] = { ¶mLongitudeNatOrigin, ¶mLat1stParallelLatTs, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping *const paramsWink2[] = { ¶mLongitudeNatOrigin, ¶mLatitude1stStdParallel, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatLoxim = { EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, WKT1_LATITUDE_OF_ORIGIN, common::UnitOfMeasure::Type::ANGULAR, lat_1}; static const ParamMapping *const paramsLoxim[] = { ¶mLatLoxim, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLabordeObliqueMercatorAzimuth = { EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE, WKT1_AZIMUTH, common::UnitOfMeasure::Type::ANGULAR, "azi"}; static const ParamMapping *const paramsLabordeObliqueMercator[] = { ¶mLatCentreLatCenter, ¶mLongCentre, ¶mLabordeObliqueMercatorAzimuth, ¶mScaleFactorProjectionCentre, ¶mFalseEasting, ¶mFalseNorthing, nullptr}; static const ParamMapping paramLatTopoOrigin = { EPSG_NAME_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::ANGULAR, lat_0}; static const ParamMapping paramLongTopoOrigin = { EPSG_NAME_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::ANGULAR, lon_0}; static const ParamMapping paramHeightTopoOrigin = { EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; // unsupported by PROJ right now static const ParamMapping paramViewpointHeight = { EPSG_NAME_PARAMETER_VIEWPOINT_HEIGHT, EPSG_CODE_PARAMETER_VIEWPOINT_HEIGHT, nullptr, common::UnitOfMeasure::Type::LINEAR, "h"}; static const ParamMapping *const paramsVerticalPerspective[] = { ¶mLatTopoOrigin, ¶mLongTopoOrigin, ¶mHeightTopoOrigin, // unsupported by PROJ right now ¶mViewpointHeight, ¶mFalseEasting, // PROJ addition ¶mFalseNorthing, // PROJ addition nullptr}; static const ParamMapping paramProjectionPlaneOriginHeight = { EPSG_NAME_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, EPSG_CODE_PARAMETER_PROJECTION_PLANE_ORIGIN_HEIGHT, nullptr, common::UnitOfMeasure::Type::LINEAR, "h_0"}; static const ParamMapping *const paramsColombiaUrban[] = { ¶mLatitudeNatOrigin, ¶mLongitudeNatOrigin, ¶mFalseEasting, ¶mFalseNorthing, ¶mProjectionPlaneOriginHeight, nullptr}; static const ParamMapping paramGeocentricXTopocentricOrigin = { EPSG_NAME_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN, EPSG_CODE_PARAMETER_GEOCENTRIC_X_TOPOCENTRIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::LINEAR, "X_0"}; static const ParamMapping paramGeocentricYTopocentricOrigin = { EPSG_NAME_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN, EPSG_CODE_PARAMETER_GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::LINEAR, "Y_0"}; static const ParamMapping paramGeocentricZTopocentricOrigin = { EPSG_NAME_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN, EPSG_CODE_PARAMETER_GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::LINEAR, "Z_0"}; static const ParamMapping *const paramsGeocentricTopocentric[] = { ¶mGeocentricXTopocentricOrigin, ¶mGeocentricYTopocentricOrigin, ¶mGeocentricZTopocentricOrigin, nullptr}; static const ParamMapping paramHeightTopoOriginWithH0 = { EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN, nullptr, common::UnitOfMeasure::Type::LINEAR, "h_0"}; static const ParamMapping *const paramsGeographicTopocentric[] = { ¶mLatTopoOrigin, ¶mLongTopoOrigin, ¶mHeightTopoOriginWithH0, nullptr}; static const MethodMapping gProjectionMethodMappings[] = { {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR, "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK}, {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_3D, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_3D, "Transverse_Mercator", "tmerc", nullptr, paramsNatOriginScaleK}, {EPSG_NAME_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED, "Transverse_Mercator_South_Orientated", "tmerc", "axis=wsu", paramsNatOriginScaleK}, {PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT, 0, "Two_Point_Equidistant", "tpeqd", nullptr, paramsTPEQD}, {EPSG_NAME_METHOD_TUNISIA_MINING_GRID, EPSG_CODE_METHOD_TUNISIA_MINING_GRID, "Tunisia_Mining_Grid", nullptr, nullptr, // no proj equivalent paramsTMG}, // Deprecated. Use EPSG_NAME_METHOD_TUNISIA_MINING_GRID instead {EPSG_NAME_METHOD_TUNISIA_MAPPING_GRID, EPSG_CODE_METHOD_TUNISIA_MAPPING_GRID, "Tunisia_Mapping_Grid", nullptr, nullptr, // no proj equivalent paramsTMG}, {EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, "Albers_Conic_Equal_Area", "aea", nullptr, paramsAEA_EQDC}, // Variant used by Oracle WKT: // https://lists.osgeo.org/pipermail/qgis-user/2024-June/054599.html {EPSG_NAME_METHOD_ALBERS_EQUAL_AREA, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA, "Albers_Conical_Equal_Area", "aea", nullptr, paramsAEA_EQDC}, {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP, "Lambert_Conformal_Conic_1SP", "lcc", nullptr, paramsLCC1SP}, {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B, nullptr, // no mapping to WKT1_GDAL "lcc", nullptr, paramsLCC1SPVariantB}, {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP, "Lambert_Conformal_Conic_2SP", "lcc", nullptr, paramsLCC2SP}, {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN, nullptr, // no mapping to WKT1_GDAL "lcc", nullptr, paramsLCC2SPMichigan}, {EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM, "Lambert_Conformal_Conic_2SP_Belgium", "lcc", nullptr, // FIXME: this is what is done in GDAL, but the formula of // LCC 2SP // Belgium in the EPSG 7.2 guidance is difference from the regular // LCC 2SP paramsLCC2SP}, {EPSG_NAME_METHOD_AZIMUTHAL_EQUIDISTANT, EPSG_CODE_METHOD_AZIMUTHAL_EQUIDISTANT, "Azimuthal_Equidistant", "aeqd", nullptr, paramsAEQD}, // We don't actually implement the Modified variant of Azimuthal Equidistant // but the exact one. The difference between both is neglectable in a few // hundred of kilometers away from the center of projection {EPSG_NAME_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, EPSG_CODE_METHOD_MODIFIED_AZIMUTHAL_EQUIDISTANT, "Azimuthal_Equidistant", "aeqd", nullptr, paramsAEQD}, {EPSG_NAME_METHOD_GUAM_PROJECTION, EPSG_CODE_METHOD_GUAM_PROJECTION, nullptr, // no mapping to GDAL WKT1 "aeqd", "guam", paramsNatOrigin}, {EPSG_NAME_METHOD_BONNE, EPSG_CODE_METHOD_BONNE, "Bonne", "bonne", nullptr, paramsBonne}, {PROJ_WKT2_NAME_METHOD_COMPACT_MILLER, 0, "Compact_Miller", "comill", nullptr, paramsLongNatOrigin}, {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA, "Cylindrical_Equal_Area", "cea", nullptr, paramsCEA}, {EPSG_NAME_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL, "Cylindrical_Equal_Area", "cea", "R_A", paramsCEA}, {EPSG_NAME_METHOD_CASSINI_SOLDNER, EPSG_CODE_METHOD_CASSINI_SOLDNER, "Cassini_Soldner", "cass", nullptr, paramsNatOrigin}, {EPSG_NAME_METHOD_HYPERBOLIC_CASSINI_SOLDNER, EPSG_CODE_METHOD_HYPERBOLIC_CASSINI_SOLDNER, nullptr, "cass", "hyperbolic", paramsNatOrigin}, // Definition to be put before PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC {EPSG_NAME_METHOD_EQUIDISTANT_CONIC, EPSG_CODE_METHOD_EQUIDISTANT_CONIC, "Equidistant_Conic", "eqdc", nullptr, paramsAEA_EQDC}, // Definition before EPSG codified it. To be put after entry for // EPSG_NAME_METHOD_EQUIDISTANT_CONIC {PROJ_WKT2_NAME_METHOD_EQUIDISTANT_CONIC, 0, "Equidistant_Conic", "eqdc", nullptr, paramsEQDC}, {PROJ_WKT2_NAME_METHOD_ECKERT_I, 0, "Eckert_I", "eck1", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_ECKERT_II, 0, "Eckert_II", "eck2", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_ECKERT_III, 0, "Eckert_III", "eck3", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_ECKERT_IV, 0, "Eckert_IV", "eck4", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_ECKERT_V, 0, "Eckert_V", "eck5", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_ECKERT_VI, 0, "Eckert_VI", "eck6", nullptr, paramsLongNatOrigin}, {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL, "Equirectangular", "eqc", nullptr, paramsEqc}, {EPSG_NAME_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL, "Equirectangular", "eqc", nullptr, paramsEqc}, {PROJ_WKT2_NAME_METHOD_FLAT_POLAR_QUARTIC, 0, "Flat_Polar_Quartic", "mbtfpq", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC, 0, "Gall_Stereographic", "gall", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE, 0, "Goode_Homolosine", "goode", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE, 0, "Interrupted_Goode_Homolosine", "igh", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE_OCEAN, 0, nullptr, "igh_o", nullptr, paramsLongNatOrigin}, // No proper WKT1 representation fr sweep=x {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X, 0, nullptr, "geos", "sweep=x", paramsGeos}, {PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y, 0, "Geostationary_Satellite", "geos", nullptr, paramsGeos}, {PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR, 0, "Gauss_Schreiber_Transverse_Mercator", "gstmerc", nullptr, paramsNatOriginScale}, {PROJ_WKT2_NAME_METHOD_GNOMONIC, 0, "Gnomonic", "gnom", nullptr, paramsNatOrigin}, {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A, "Hotine_Oblique_Mercator", "omerc", "no_uoff", paramsHomVariantA}, {EPSG_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B, "Hotine_Oblique_Mercator_Azimuth_Center", "omerc", nullptr, paramsHomVariantB}, {PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, 0, "Hotine_Oblique_Mercator_Two_Point_Natural_Origin", "omerc", nullptr, paramsHomTwoPoint}, {PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC, 0, "International_Map_of_the_World_Polyconic", "imw_p", nullptr, paramsIMWP}, {EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED, "Krovak", "krovak", nullptr, krovakParameters}, {EPSG_NAME_METHOD_KROVAK, EPSG_CODE_METHOD_KROVAK, "Krovak", "krovak", "axis=swu", krovakParameters}, {EPSG_NAME_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED, EPSG_CODE_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED, nullptr, "mod_krovak", nullptr, krovakParameters}, {EPSG_NAME_METHOD_KROVAK_MODIFIED, EPSG_CODE_METHOD_KROVAK_MODIFIED, nullptr, "mod_krovak", "axis=swu", krovakParameters}, {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA, "Lambert_Azimuthal_Equal_Area", "laea", nullptr, paramsLaea}, {EPSG_NAME_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL, "Lambert_Azimuthal_Equal_Area", "laea", "R_A", paramsLaea}, {PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL, 0, "Miller_Cylindrical", "mill", "R_A", paramsMiller}, {EPSG_NAME_METHOD_MERCATOR_VARIANT_A, EPSG_CODE_METHOD_MERCATOR_VARIANT_A, "Mercator_1SP", "merc", nullptr, paramsMerc1SP}, {EPSG_NAME_METHOD_MERCATOR_VARIANT_B, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, "Mercator_2SP", "merc", nullptr, paramsMerc2SP}, {EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR, "Popular_Visualisation_Pseudo_Mercator", // particular case actually // handled manually "webmerc", nullptr, paramsNatOrigin}, {EPSG_NAME_METHOD_MERCATOR_SPHERICAL, EPSG_CODE_METHOD_MERCATOR_SPHERICAL, nullptr, "merc", "R_C", paramsNatOrigin}, {PROJ_WKT2_NAME_METHOD_MOLLWEIDE, 0, "Mollweide", "moll", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH, 0, "Natural_Earth", "natearth", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_NATURAL_EARTH_II, 0, "Natural_Earth_II", "natearth2", nullptr, paramsLongNatOrigin}, {EPSG_NAME_METHOD_NZMG, EPSG_CODE_METHOD_NZMG, "New_Zealand_Map_Grid", "nzmg", nullptr, paramsNatOrigin}, { EPSG_NAME_METHOD_OBLIQUE_STEREOGRAPHIC, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC, "Oblique_Stereographic", "sterea", nullptr, paramsObliqueStereo, }, {EPSG_NAME_METHOD_ORTHOGRAPHIC, EPSG_CODE_METHOD_ORTHOGRAPHIC, "Orthographic", "ortho", nullptr, paramsNatOrigin}, {EPSG_NAME_METHOD_LOCAL_ORTHOGRAPHIC, EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC, "Local Orthographic", "ortho", nullptr, paramsLocalOrthographic}, {PROJ_WKT2_NAME_ORTHOGRAPHIC_SPHERICAL, 0, "Orthographic", "ortho", "f=0", paramsNatOrigin}, {PROJ_WKT2_NAME_METHOD_PATTERSON, 0, "Patterson", "patterson", nullptr, paramsLongNatOrigin}, {EPSG_NAME_METHOD_AMERICAN_POLYCONIC, EPSG_CODE_METHOD_AMERICAN_POLYCONIC, "Polyconic", "poly", nullptr, paramsNatOrigin}, {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A, "Polar_Stereographic", "stere", nullptr, paramsObliqueStereo}, {EPSG_NAME_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B, "Polar_Stereographic", "stere", nullptr, paramsPolarStereo}, {PROJ_WKT2_NAME_METHOD_ROBINSON, 0, "Robinson", "robin", nullptr, paramsLongNatOriginLongitudeCentre}, {PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE, 0, nullptr, "peirce_q", "shape=square", paramsNatOriginScale}, {PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND, 0, nullptr, "peirce_q", "shape=diamond", paramsNatOriginScale}, {PROJ_WKT2_NAME_METHOD_SINUSOIDAL, 0, "Sinusoidal", "sinu", nullptr, paramsLongNatOriginLongitudeCentre}, {PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC, 0, "Stereographic", "stere", nullptr, paramsObliqueStereo}, {PROJ_WKT2_NAME_METHOD_TIMES, 0, "Times", "times", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN, 0, "VanDerGrinten", "vandg", "R_A", paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_WAGNER_I, 0, "Wagner_I", "wag1", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_WAGNER_II, 0, "Wagner_II", "wag2", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_WAGNER_III, 0, "Wagner_III", "wag3", nullptr, paramsWag3}, {PROJ_WKT2_NAME_METHOD_WAGNER_IV, 0, "Wagner_IV", "wag4", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_WAGNER_V, 0, "Wagner_V", "wag5", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_WAGNER_VI, 0, "Wagner_VI", "wag6", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_WAGNER_VII, 0, "Wagner_VII", "wag7", nullptr, paramsLongNatOrigin}, {PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE, 0, "Quadrilateralized_Spherical_Cube", "qsc", nullptr, paramsNatOrigin}, {PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT, 0, "Spherical_Cross_Track_Height", "sch", nullptr, paramsSch}, // The following methods have just the WKT <--> PROJ string mapping, but // no setter. Similarly to GDAL {"Aitoff", 0, "Aitoff", "aitoff", nullptr, paramsLongNatOrigin}, {"Winkel I", 0, "Winkel_I", "wink1", nullptr, paramsWink1}, {"Winkel II", 0, "Winkel_II", "wink2", nullptr, paramsWink2}, {"Winkel Tripel", 0, "Winkel_Tripel", "wintri", nullptr, paramsWink2}, {"Craster Parabolic", 0, "Craster_Parabolic", "crast", nullptr, paramsLongNatOrigin}, {"Loximuthal", 0, "Loximuthal", "loxim", nullptr, paramsLoxim}, {"Quartic Authalic", 0, "Quartic_Authalic", "qua_aut", nullptr, paramsLongNatOrigin}, {"Transverse Cylindrical Equal Area", 0, "Transverse_Cylindrical_Equal_Area", "tcea", nullptr, paramsObliqueStereo}, {EPSG_NAME_METHOD_EQUAL_EARTH, EPSG_CODE_METHOD_EQUAL_EARTH, nullptr, "eqearth", nullptr, paramsLongNatOrigin}, {EPSG_NAME_METHOD_LABORDE_OBLIQUE_MERCATOR, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR, "Laborde_Oblique_Mercator", "labrd", nullptr, paramsLabordeObliqueMercator}, {EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE, nullptr, "nsper", nullptr, paramsVerticalPerspective}, {EPSG_NAME_METHOD_COLOMBIA_URBAN, EPSG_CODE_METHOD_COLOMBIA_URBAN, nullptr, "col_urban", nullptr, paramsColombiaUrban}, {EPSG_NAME_METHOD_GEOCENTRIC_TOPOCENTRIC, EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC, nullptr, "topocentric", nullptr, paramsGeocentricTopocentric}, {EPSG_NAME_METHOD_GEOGRAPHIC_TOPOCENTRIC, EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC, nullptr, nullptr, nullptr, paramsGeographicTopocentric}, }; const MethodMapping *getProjectionMethodMappings(size_t &nElts) { nElts = sizeof(gProjectionMethodMappings) / sizeof(gProjectionMethodMappings[0]); return gProjectionMethodMappings; } #define METHOD_NAME_CODE(method) \ { EPSG_NAME_METHOD_##method, EPSG_CODE_METHOD_##method } const struct MethodNameCode methodNameCodesList[] = { // Projection methods METHOD_NAME_CODE(TRANSVERSE_MERCATOR), METHOD_NAME_CODE(TRANSVERSE_MERCATOR_SOUTH_ORIENTATED), METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_1SP), METHOD_NAME_CODE(NZMG), METHOD_NAME_CODE(TUNISIA_MINING_GRID), METHOD_NAME_CODE(ALBERS_EQUAL_AREA), METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP), METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM), METHOD_NAME_CODE(LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN), METHOD_NAME_CODE(MODIFIED_AZIMUTHAL_EQUIDISTANT), METHOD_NAME_CODE(GUAM_PROJECTION), METHOD_NAME_CODE(BONNE), METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL), METHOD_NAME_CODE(LAMBERT_CYLINDRICAL_EQUAL_AREA), METHOD_NAME_CODE(CASSINI_SOLDNER), METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL), METHOD_NAME_CODE(EQUIDISTANT_CYLINDRICAL_SPHERICAL), METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_A), METHOD_NAME_CODE(HOTINE_OBLIQUE_MERCATOR_VARIANT_B), METHOD_NAME_CODE(KROVAK_NORTH_ORIENTED), METHOD_NAME_CODE(KROVAK), METHOD_NAME_CODE(LAMBERT_AZIMUTHAL_EQUAL_AREA), METHOD_NAME_CODE(POPULAR_VISUALISATION_PSEUDO_MERCATOR), METHOD_NAME_CODE(MERCATOR_SPHERICAL), METHOD_NAME_CODE(MERCATOR_VARIANT_A), METHOD_NAME_CODE(MERCATOR_VARIANT_B), METHOD_NAME_CODE(OBLIQUE_STEREOGRAPHIC), METHOD_NAME_CODE(AMERICAN_POLYCONIC), METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_A), METHOD_NAME_CODE(POLAR_STEREOGRAPHIC_VARIANT_B), METHOD_NAME_CODE(EQUAL_EARTH), METHOD_NAME_CODE(LABORDE_OBLIQUE_MERCATOR), METHOD_NAME_CODE(VERTICAL_PERSPECTIVE), METHOD_NAME_CODE(COLOMBIA_URBAN), // Other conversions METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT), METHOD_NAME_CODE(CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR), METHOD_NAME_CODE(HEIGHT_DEPTH_REVERSAL), METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_2D), METHOD_NAME_CODE(AXIS_ORDER_REVERSAL_3D), METHOD_NAME_CODE(GEOGRAPHIC_GEOCENTRIC), METHOD_NAME_CODE(GEOCENTRIC_TOPOCENTRIC), METHOD_NAME_CODE(GEOGRAPHIC_TOPOCENTRIC), // Transformations METHOD_NAME_CODE(LONGITUDE_ROTATION), METHOD_NAME_CODE(AFFINE_PARAMETRIC_TRANSFORMATION), METHOD_NAME_CODE(SIMILARITY_TRANSFORMATION), METHOD_NAME_CODE(COORDINATE_FRAME_GEOCENTRIC), METHOD_NAME_CODE(COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC), METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_2D), METHOD_NAME_CODE(COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D), METHOD_NAME_CODE(COORDINATE_FRAME_GEOGRAPHIC_3D), METHOD_NAME_CODE(COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D), METHOD_NAME_CODE(COORDINATE_FRAME_GEOG3D_TO_COMPOUND), METHOD_NAME_CODE(POSITION_VECTOR_GEOCENTRIC), METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_2D), METHOD_NAME_CODE(POSITION_VECTOR_GEOGRAPHIC_3D), METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOCENTRIC), METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D), METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D), METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC), METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D), METHOD_NAME_CODE(TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D), METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC), METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D), METHOD_NAME_CODE(TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D), METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOCENTRIC), METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D), METHOD_NAME_CODE(MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D), METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOCENTRIC), METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D), METHOD_NAME_CODE(MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D), METHOD_NAME_CODE(MOLODENSKY), METHOD_NAME_CODE(ABRIDGED_MOLODENSKY), METHOD_NAME_CODE(GEOGRAPHIC2D_OFFSETS), METHOD_NAME_CODE(GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS), METHOD_NAME_CODE(GEOGRAPHIC3D_OFFSETS), METHOD_NAME_CODE(CARTESIAN_GRID_OFFSETS), METHOD_NAME_CODE(VERTICAL_OFFSET), METHOD_NAME_CODE(VERTICAL_OFFSET_AND_SLOPE), METHOD_NAME_CODE(NTV2), METHOD_NAME_CODE(NTV1), METHOD_NAME_CODE(VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON), METHOD_NAME_CODE(CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON), METHOD_NAME_CODE(GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON), METHOD_NAME_CODE(NADCON), METHOD_NAME_CODE(NADCON5_2D), METHOD_NAME_CODE(NADCON5_3D), METHOD_NAME_CODE(VERTCON), // Since EPSG 12.019 METHOD_NAME_CODE(GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN), // Before EPSG 12.019 METHOD_NAME_CODE(GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN), METHOD_NAME_CODE(VERTICALGRID_NZLVD), METHOD_NAME_CODE(VERTICALGRID_BEV_AT), METHOD_NAME_CODE(VERTICALGRID_GTX), METHOD_NAME_CODE(VERTICALGRID_ASC), METHOD_NAME_CODE(VERTICALGRID_GTG), METHOD_NAME_CODE(VERTICALGRID_PL_TXT), // Since EPSG 12.019 METHOD_NAME_CODE(VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL), // Before EPSG 12.019 METHOD_NAME_CODE(VERTICAL_OFFSET_BY_VELOCITY_GRID_NRCAN), // PointMotionOperation METHOD_NAME_CODE(POINT_MOTION_BY_GRID_CANADA_NTV2_VEL), // Since EPSG 12.019 METHOD_NAME_CODE(POINT_MOTION_BY_GRID_CANADA_NEU_DOMAIN_NTV2_VEL), // Before EPSG 12.019 METHOD_NAME_CODE( POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL), METHOD_NAME_CODE( POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT), METHOD_NAME_CODE( POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG), METHOD_NAME_CODE( GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG), METHOD_NAME_CODE(GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG), }; const MethodNameCode *getMethodNameCodes(size_t &nElts) { nElts = sizeof(methodNameCodesList) / sizeof(methodNameCodesList[0]); return methodNameCodesList; } #define PARAM_NAME_CODE(method) \ { EPSG_NAME_PARAMETER_##method, EPSG_CODE_PARAMETER_##method } const struct ParamNameCode gParamNameCodes[] = { // Parameters of projection methods PARAM_NAME_CODE(COLATITUDE_CONE_AXIS), PARAM_NAME_CODE(LATITUDE_OF_NATURAL_ORIGIN), PARAM_NAME_CODE(LONGITUDE_OF_NATURAL_ORIGIN), PARAM_NAME_CODE(SCALE_FACTOR_AT_NATURAL_ORIGIN), PARAM_NAME_CODE(FALSE_EASTING), PARAM_NAME_CODE(FALSE_NORTHING), PARAM_NAME_CODE(LATITUDE_PROJECTION_CENTRE), PARAM_NAME_CODE(LONGITUDE_PROJECTION_CENTRE), PARAM_NAME_CODE(AZIMUTH_PROJECTION_CENTRE), PARAM_NAME_CODE(ANGLE_RECTIFIED_TO_SKEW_GRID), PARAM_NAME_CODE(SCALE_FACTOR_PROJECTION_CENTRE), PARAM_NAME_CODE(EASTING_PROJECTION_CENTRE), PARAM_NAME_CODE(NORTHING_PROJECTION_CENTRE), PARAM_NAME_CODE(LATITUDE_PSEUDO_STANDARD_PARALLEL), PARAM_NAME_CODE(SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL), PARAM_NAME_CODE(LATITUDE_FALSE_ORIGIN), PARAM_NAME_CODE(LONGITUDE_FALSE_ORIGIN), PARAM_NAME_CODE(LATITUDE_1ST_STD_PARALLEL), PARAM_NAME_CODE(LATITUDE_2ND_STD_PARALLEL), PARAM_NAME_CODE(EASTING_FALSE_ORIGIN), PARAM_NAME_CODE(NORTHING_FALSE_ORIGIN), PARAM_NAME_CODE(LATITUDE_STD_PARALLEL), PARAM_NAME_CODE(LONGITUDE_OF_ORIGIN), PARAM_NAME_CODE(ELLIPSOID_SCALE_FACTOR), PARAM_NAME_CODE(PROJECTION_PLANE_ORIGIN_HEIGHT), PARAM_NAME_CODE(GEOCENTRIC_X_TOPOCENTRIC_ORIGIN), PARAM_NAME_CODE(GEOCENTRIC_Y_TOPOCENTRIC_ORIGIN), PARAM_NAME_CODE(GEOCENTRIC_Z_TOPOCENTRIC_ORIGIN), // Parameters of transformations PARAM_NAME_CODE(SEMI_MAJOR_AXIS_DIFFERENCE), PARAM_NAME_CODE(FLATTENING_DIFFERENCE), PARAM_NAME_CODE(LATITUDE_LONGITUDE_DIFFERENCE_FILE), PARAM_NAME_CODE(GEOID_CORRECTION_FILENAME), PARAM_NAME_CODE(VERTICAL_OFFSET_FILE), PARAM_NAME_CODE(GEOID_MODEL_DIFFERENCE_FILE), PARAM_NAME_CODE(LATITUDE_DIFFERENCE_FILE), PARAM_NAME_CODE(LONGITUDE_DIFFERENCE_FILE), PARAM_NAME_CODE(UNIT_CONVERSION_SCALAR), PARAM_NAME_CODE(LATITUDE_OFFSET), PARAM_NAME_CODE(LONGITUDE_OFFSET), PARAM_NAME_CODE(VERTICAL_OFFSET), PARAM_NAME_CODE(EASTING_OFFSET), PARAM_NAME_CODE(NORTHING_OFFSET), PARAM_NAME_CODE(GEOID_HEIGHT), PARAM_NAME_CODE(GEOID_UNDULATION), // deprecated PARAM_NAME_CODE(A0), PARAM_NAME_CODE(A1), PARAM_NAME_CODE(A2), PARAM_NAME_CODE(B0), PARAM_NAME_CODE(B1), PARAM_NAME_CODE(B2), PARAM_NAME_CODE(X_AXIS_TRANSLATION), PARAM_NAME_CODE(Y_AXIS_TRANSLATION), PARAM_NAME_CODE(Z_AXIS_TRANSLATION), PARAM_NAME_CODE(X_AXIS_ROTATION), PARAM_NAME_CODE(Y_AXIS_ROTATION), PARAM_NAME_CODE(Z_AXIS_ROTATION), PARAM_NAME_CODE(SCALE_DIFFERENCE), PARAM_NAME_CODE(RATE_X_AXIS_TRANSLATION), PARAM_NAME_CODE(RATE_Y_AXIS_TRANSLATION), PARAM_NAME_CODE(RATE_Z_AXIS_TRANSLATION), PARAM_NAME_CODE(RATE_X_AXIS_ROTATION), PARAM_NAME_CODE(RATE_Y_AXIS_ROTATION), PARAM_NAME_CODE(RATE_Z_AXIS_ROTATION), PARAM_NAME_CODE(RATE_SCALE_DIFFERENCE), PARAM_NAME_CODE(REFERENCE_EPOCH), PARAM_NAME_CODE(TRANSFORMATION_REFERENCE_EPOCH), PARAM_NAME_CODE(ORDINATE_1_EVAL_POINT), PARAM_NAME_CODE(ORDINATE_2_EVAL_POINT), PARAM_NAME_CODE(ORDINATE_3_EVAL_POINT), PARAM_NAME_CODE(GEOCENTRIC_TRANSLATION_FILE), PARAM_NAME_CODE(INCLINATION_IN_LATITUDE), PARAM_NAME_CODE(INCLINATION_IN_LONGITUDE), PARAM_NAME_CODE(EPSG_CODE_FOR_HORIZONTAL_CRS), PARAM_NAME_CODE(EPSG_CODE_FOR_INTERPOLATION_CRS), // Parameters of point motion operations PARAM_NAME_CODE(POINT_MOTION_VELOCITY_GRID_FILE), PARAM_NAME_CODE(POINT_MOTION_VELOCITY_NORTH_GRID_FILE), PARAM_NAME_CODE(SOURCE_EPOCH), PARAM_NAME_CODE(TARGET_EPOCH), }; const ParamNameCode *getParamNameCodes(size_t &nElts) { nElts = sizeof(gParamNameCodes) / sizeof(gParamNameCodes[0]); return gParamNameCodes; } static const ParamMapping paramUnitConversionScalar = { EPSG_NAME_PARAMETER_UNIT_CONVERSION_SCALAR, EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR, nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; static const ParamMapping *const paramsChangeVerticalUnit[] = { ¶mUnitConversionScalar, nullptr}; static const ParamMapping paramLongitudeOffset = { EPSG_NAME_PARAMETER_LONGITUDE_OFFSET, EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping *const paramsLongitudeRotation[] = { ¶mLongitudeOffset, nullptr}; static const ParamMapping paramA0 = { EPSG_NAME_PARAMETER_A0, EPSG_CODE_PARAMETER_A0, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramA1 = { EPSG_NAME_PARAMETER_A1, EPSG_CODE_PARAMETER_A1, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramA2 = { EPSG_NAME_PARAMETER_A2, EPSG_CODE_PARAMETER_A2, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramB0 = { EPSG_NAME_PARAMETER_B0, EPSG_CODE_PARAMETER_B0, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramB1 = { EPSG_NAME_PARAMETER_B1, EPSG_CODE_PARAMETER_B1, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramB2 = { EPSG_NAME_PARAMETER_B2, EPSG_CODE_PARAMETER_B2, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping *const paramsAffineParametricTransformation[] = { ¶mA0, ¶mA1, ¶mA2, ¶mB0, ¶mB1, ¶mB2, nullptr}; static const ParamMapping paramOrdinate1EvalPointTargetCRS = { EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS, EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramOrdinate2EvalPointTargetCRS = { EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS, EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS, nullptr, common::UnitOfMeasure::Type::UNKNOWN, nullptr}; static const ParamMapping paramScaleFactorForSourceCRSAxes = { EPSG_NAME_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES, EPSG_CODE_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES, nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; static const ParamMapping paramRotationAngleOfSourceCRSAxes = { EPSG_NAME_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES, EPSG_CODE_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping *const paramsSimilarityTransformation[] = { ¶mOrdinate1EvalPointTargetCRS, ¶mOrdinate2EvalPointTargetCRS, ¶mScaleFactorForSourceCRSAxes, ¶mRotationAngleOfSourceCRSAxes, nullptr}; static const ParamMapping paramXTranslation = { EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION, EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramYTranslation = { EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION, EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramZTranslation = { EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION, EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramXRotation = { EPSG_NAME_PARAMETER_X_AXIS_ROTATION, EPSG_CODE_PARAMETER_X_AXIS_ROTATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramYRotation = { EPSG_NAME_PARAMETER_Y_AXIS_ROTATION, EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramZRotation = { EPSG_NAME_PARAMETER_Z_AXIS_ROTATION, EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramScaleDifference = { EPSG_NAME_PARAMETER_SCALE_DIFFERENCE, EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; static const ParamMapping *const paramsHelmert3[] = { ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, nullptr}; static const ParamMapping *const paramsHelmert7[] = { ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, ¶mXRotation, ¶mYRotation, ¶mZRotation, ¶mScaleDifference, nullptr}; static const ParamMapping paramRateXTranslation = { EPSG_NAME_PARAMETER_RATE_X_AXIS_TRANSLATION, EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramRateYTranslation = { EPSG_NAME_PARAMETER_RATE_Y_AXIS_TRANSLATION, EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramRateZTranslation = { EPSG_NAME_PARAMETER_RATE_Z_AXIS_TRANSLATION, EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramRateXRotation = { EPSG_NAME_PARAMETER_RATE_X_AXIS_ROTATION, EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramRateYRotation = { EPSG_NAME_PARAMETER_RATE_Y_AXIS_ROTATION, EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramRateZRotation = { EPSG_NAME_PARAMETER_RATE_Z_AXIS_ROTATION, EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramRateScaleDifference = { EPSG_NAME_PARAMETER_RATE_SCALE_DIFFERENCE, EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, nullptr, common::UnitOfMeasure::Type::SCALE, nullptr}; static const ParamMapping paramReferenceEpoch = { EPSG_NAME_PARAMETER_REFERENCE_EPOCH, EPSG_CODE_PARAMETER_REFERENCE_EPOCH, nullptr, common::UnitOfMeasure::Type::TIME, nullptr}; static const ParamMapping *const paramsHelmert15[] = { ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, ¶mXRotation, ¶mYRotation, ¶mZRotation, ¶mScaleDifference, ¶mRateXTranslation, ¶mRateYTranslation, ¶mRateZTranslation, ¶mRateXRotation, ¶mRateYRotation, ¶mRateZRotation, ¶mRateScaleDifference, ¶mReferenceEpoch, nullptr}; static const ParamMapping paramOrdinate1EvalPoint = { EPSG_NAME_PARAMETER_ORDINATE_1_EVAL_POINT, EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramOrdinate2EvalPoint = { EPSG_NAME_PARAMETER_ORDINATE_2_EVAL_POINT, EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramOrdinate3EvalPoint = { EPSG_NAME_PARAMETER_ORDINATE_3_EVAL_POINT, EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping *const paramsMolodenskyBadekas[] = { ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, ¶mXRotation, ¶mYRotation, ¶mZRotation, ¶mScaleDifference, ¶mOrdinate1EvalPoint, ¶mOrdinate2EvalPoint, ¶mOrdinate3EvalPoint, nullptr}; static const ParamMapping paramSemiMajorAxisDifference = { EPSG_NAME_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramFlatteningDifference = { EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE, EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsMolodensky[] = { ¶mXTranslation, ¶mYTranslation, ¶mZTranslation, ¶mSemiMajorAxisDifference, ¶mFlatteningDifference, nullptr}; static const ParamMapping paramLatitudeOffset = { EPSG_NAME_PARAMETER_LATITUDE_OFFSET, EPSG_CODE_PARAMETER_LATITUDE_OFFSET, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping *const paramsGeographic2DOffsets[] = { ¶mLatitudeOffset, ¶mLongitudeOffset, nullptr}; static const ParamMapping paramGeoidHeight = { EPSG_NAME_PARAMETER_GEOID_HEIGHT, EPSG_CODE_PARAMETER_GEOID_HEIGHT, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping *const paramsGeographic2DWithHeightOffsets[] = { ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mGeoidHeight, nullptr}; static const ParamMapping paramVerticalOffset = { EPSG_NAME_PARAMETER_VERTICAL_OFFSET, EPSG_CODE_PARAMETER_VERTICAL_OFFSET, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping *const paramsGeographic3DOffsets[] = { ¶mLatitudeOffset, ¶mLongitudeOffset, ¶mVerticalOffset, nullptr}; static const ParamMapping paramEastingOffset = { EPSG_NAME_PARAMETER_EASTING_OFFSET, EPSG_CODE_PARAMETER_EASTING_OFFSET, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping paramNorthingOffset = { EPSG_NAME_PARAMETER_NORTHING_OFFSET, EPSG_CODE_PARAMETER_NORTHING_OFFSET, nullptr, common::UnitOfMeasure::Type::LINEAR, nullptr}; static const ParamMapping *const paramsCartesianGridOffsets[] = { ¶mEastingOffset, ¶mNorthingOffset, nullptr}; static const ParamMapping *const paramsVerticalOffsets[] = { ¶mVerticalOffset, nullptr}; static const ParamMapping *const paramsGeographic3DToGravityRelatedHeight[] = { ¶mGeoidHeight, nullptr}; static const ParamMapping paramInclinationInLatitude = { EPSG_NAME_PARAMETER_INCLINATION_IN_LATITUDE, EPSG_CODE_PARAMETER_INCLINATION_IN_LATITUDE, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping paramInclinationInLongitude = { EPSG_NAME_PARAMETER_INCLINATION_IN_LONGITUDE, EPSG_CODE_PARAMETER_INCLINATION_IN_LONGITUDE, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping *const paramsVerticalOffsetAndSlope[] = { ¶mOrdinate1EvalPoint, ¶mOrdinate2EvalPoint, ¶mVerticalOffset, ¶mInclinationInLatitude, ¶mInclinationInLongitude, nullptr}; static const ParamMapping paramLatitudeLongitudeDifferenceFile = { EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsNTV2[] = { ¶mLatitudeLongitudeDifferenceFile, nullptr}; static const ParamMapping paramGeocentricTranslationFile = { EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsGeocentricTranslationGridInterpolationIGN[] = { ¶mGeocentricTranslationFile, nullptr}; static const ParamMapping paramLatitudeDifferenceFile = { EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping paramLongitudeDifferenceFile = { EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsNADCON[] = { ¶mLatitudeDifferenceFile, ¶mLongitudeDifferenceFile, nullptr}; static const ParamMapping *const paramsNADCON5_2D[] = { ¶mLatitudeDifferenceFile, ¶mLongitudeDifferenceFile, nullptr}; static const ParamMapping paramEllipsoidalHeightDifference = { EPSG_NAME_PARAMETER_ELLIPSOIDAL_HEIGHT_DIFFERENCE_FILE, EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_DIFFERENCE_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsNADCON5_3D[] = { ¶mLatitudeDifferenceFile, ¶mLongitudeDifferenceFile, ¶mEllipsoidalHeightDifference, nullptr}; static const ParamMapping paramVerticalOffsetFile = { EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsVERTCON[] = {¶mVerticalOffsetFile, nullptr}; static const ParamMapping paramPointMotiionVelocityGridFile = { EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsPointMotionOperationByVelocityGrid[] = { ¶mPointMotiionVelocityGridFile, nullptr}; static const ParamMapping paramSouthPoleLatGRIB = { PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, 0, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping paramSouthPoleLongGRIB = { PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, 0, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping paramAxisRotationGRIB = { PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, 0, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping *const paramsPoleRotationGRIBConvention[] = { ¶mSouthPoleLatGRIB, ¶mSouthPoleLongGRIB, ¶mAxisRotationGRIB, nullptr}; static const ParamMapping paramGridNorthPoleLatitudeNetCDF = { PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION, 0, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping paramGridNorthPoleLongitudeNetCDF = { PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION, 0, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping paramNorthPoleGridLongitudeNetCDF = { PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION, 0, nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; static const ParamMapping *const paramsPoleRotationNetCDFCFConvention[] = { ¶mGridNorthPoleLatitudeNetCDF, ¶mGridNorthPoleLongitudeNetCDF, ¶mNorthPoleGridLongitudeNetCDF, nullptr}; static const ParamMapping paramTINOffsetFile = { EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, nullptr, common::UnitOfMeasure::Type::NONE, nullptr}; static const ParamMapping *const paramsTINOffsetFile[] = {¶mTINOffsetFile, nullptr}; static const MethodMapping gOtherMethodMappings[] = { {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT, EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr, paramsChangeVerticalUnit}, {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR, EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR, nullptr, nullptr, nullptr, nullptr}, {EPSG_NAME_METHOD_HEIGHT_DEPTH_REVERSAL, EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL, nullptr, nullptr, nullptr, nullptr}, {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_2D, EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D, nullptr, nullptr, nullptr, nullptr}, {EPSG_NAME_METHOD_AXIS_ORDER_REVERSAL_3D, EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D, nullptr, nullptr, nullptr, nullptr}, {EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC, EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC, nullptr, nullptr, nullptr, nullptr}, {PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, 0, nullptr, nullptr, nullptr, nullptr}, {EPSG_NAME_METHOD_LONGITUDE_ROTATION, EPSG_CODE_METHOD_LONGITUDE_ROTATION, nullptr, nullptr, nullptr, paramsLongitudeRotation}, {EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, nullptr, nullptr, nullptr, paramsAffineParametricTransformation}, {EPSG_NAME_METHOD_SIMILARITY_TRANSFORMATION, EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION, nullptr, nullptr, nullptr, paramsSimilarityTransformation}, {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr, nullptr, paramsPoleRotationGRIBConvention}, {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION, 0, nullptr, nullptr, nullptr, paramsPoleRotationNetCDFCFConvention}, {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert3}, {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert3}, {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert3}, {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOCENTRIC, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC, EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D, EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D, EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND, EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_POSITION_VECTOR_GEOCENTRIC, EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert7}, {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert15}, {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert15}, {EPSG_NAME_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert15}, {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert15}, {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsHelmert15}, {EPSG_NAME_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsHelmert15}, {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC, nullptr, nullptr, nullptr, paramsMolodenskyBadekas}, {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsMolodenskyBadekas}, {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsMolodenskyBadekas}, {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC, nullptr, nullptr, nullptr, paramsMolodenskyBadekas}, {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D, nullptr, nullptr, nullptr, paramsMolodenskyBadekas}, {EPSG_NAME_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D, nullptr, nullptr, nullptr, paramsMolodenskyBadekas}, {EPSG_NAME_METHOD_MOLODENSKY, EPSG_CODE_METHOD_MOLODENSKY, nullptr, nullptr, nullptr, paramsMolodensky}, {EPSG_NAME_METHOD_ABRIDGED_MOLODENSKY, EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY, nullptr, nullptr, nullptr, paramsMolodensky}, {EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS, EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS, nullptr, nullptr, nullptr, paramsGeographic2DOffsets}, {EPSG_NAME_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS, nullptr, nullptr, nullptr, paramsGeographic2DWithHeightOffsets}, {EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSETS, EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS, nullptr, nullptr, nullptr, paramsGeographic3DOffsets}, {EPSG_NAME_METHOD_CARTESIAN_GRID_OFFSETS, EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS, nullptr, nullptr, nullptr, paramsCartesianGridOffsets}, {EPSG_NAME_METHOD_VERTICAL_OFFSET, EPSG_CODE_METHOD_VERTICAL_OFFSET, nullptr, nullptr, nullptr, paramsVerticalOffsets}, {EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT, EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT, nullptr, nullptr, nullptr, paramsGeographic3DToGravityRelatedHeight}, {EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT, EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT, nullptr, nullptr, nullptr, paramsGeographic3DToGravityRelatedHeight}, {EPSG_NAME_METHOD_VERTICAL_OFFSET_AND_SLOPE, EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE, nullptr, nullptr, nullptr, paramsVerticalOffsetAndSlope}, {EPSG_NAME_METHOD_NTV2, EPSG_CODE_METHOD_NTV2, nullptr, nullptr, nullptr, paramsNTV2}, {EPSG_NAME_METHOD_NTV1, EPSG_CODE_METHOD_NTV1, nullptr, nullptr, nullptr, paramsNTV2}, {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN, EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN, nullptr, nullptr, nullptr, paramsGeocentricTranslationGridInterpolationIGN}, {EPSG_NAME_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON, EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON, nullptr, nullptr, nullptr, paramsTINOffsetFile}, {EPSG_NAME_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON, EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON, nullptr, nullptr, nullptr, paramsTINOffsetFile}, {EPSG_NAME_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON, EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON, nullptr, nullptr, nullptr, paramsTINOffsetFile}, {EPSG_NAME_METHOD_NADCON, EPSG_CODE_METHOD_NADCON, nullptr, nullptr, nullptr, paramsNADCON}, {EPSG_NAME_METHOD_NADCON5_2D, EPSG_CODE_METHOD_NADCON5_2D, nullptr, nullptr, nullptr, paramsNADCON5_2D}, {EPSG_NAME_METHOD_NADCON5_3D, EPSG_CODE_METHOD_NADCON5_3D, nullptr, nullptr, nullptr, paramsNADCON5_3D}, {EPSG_NAME_METHOD_VERTCON, EPSG_CODE_METHOD_VERTCON, nullptr, nullptr, nullptr, paramsVERTCON}, {EPSG_NAME_METHOD_VERTCON_OLDNAME, EPSG_CODE_METHOD_VERTCON, nullptr, nullptr, nullptr, paramsVERTCON}, {EPSG_NAME_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL, EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL, nullptr, nullptr, nullptr, paramsPointMotionOperationByVelocityGrid}, {EPSG_NAME_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL, EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL, nullptr, nullptr, nullptr, paramsPointMotionOperationByVelocityGrid}, }; const MethodMapping *getOtherMethodMappings(size_t &nElts) { nElts = sizeof(gOtherMethodMappings) / sizeof(gOtherMethodMappings[0]); return gOtherMethodMappings; } // --------------------------------------------------------------------------- PROJ_NO_INLINE const MethodMapping *getMapping(int epsg_code) noexcept { for (const auto &mapping : gProjectionMethodMappings) { if (mapping.epsg_code == epsg_code) { return &mapping; } } return nullptr; } // --------------------------------------------------------------------------- const MethodMapping *getMapping(const OperationMethod *method) noexcept { const std::string &name(method->nameStr()); const int epsg_code = method->getEPSGCode(); for (const auto &mapping : gProjectionMethodMappings) { if ((epsg_code != 0 && mapping.epsg_code == epsg_code) || metadata::Identifier::isEquivalentName(mapping.wkt2_name, name.c_str())) { return &mapping; } } return nullptr; } // --------------------------------------------------------------------------- const MethodMapping *getMappingFromWKT1(const std::string &wkt1_name) noexcept { // Unusual for a WKT1 projection name, but mentioned in OGC 12-063r5 C.4.2 if (ci_starts_with(wkt1_name, "UTM zone")) { return getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR); } for (const auto &mapping : gProjectionMethodMappings) { if (mapping.wkt1_name && metadata::Identifier::isEquivalentName( mapping.wkt1_name, wkt1_name.c_str())) { return &mapping; } } return nullptr; } // --------------------------------------------------------------------------- const MethodMapping *getMapping(const char *wkt2_name) noexcept { for (const auto &mapping : gProjectionMethodMappings) { if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, wkt2_name)) { return &mapping; } } for (const auto &mapping : gOtherMethodMappings) { if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, wkt2_name)) { return &mapping; } } return nullptr; } // --------------------------------------------------------------------------- std::vector getMappingsFromPROJName(const std::string &projName) { std::vector res; for (const auto &mapping : gProjectionMethodMappings) { if (mapping.proj_name_main && projName == mapping.proj_name_main) { res.push_back(&mapping); } } return res; } // --------------------------------------------------------------------------- const ParamMapping *getMapping(const MethodMapping *mapping, const OperationParameterNNPtr ¶m) { if (mapping->params == nullptr) { return nullptr; } // First try with id const int epsg_code = param->getEPSGCode(); if (epsg_code) { for (int i = 0; mapping->params[i] != nullptr; ++i) { const auto *paramMapping = mapping->params[i]; if (paramMapping->epsg_code == epsg_code) { return paramMapping; } } } // then equivalent name const std::string &name = param->nameStr(); for (int i = 0; mapping->params[i] != nullptr; ++i) { const auto *paramMapping = mapping->params[i]; if (metadata::Identifier::isEquivalentName(paramMapping->wkt2_name, name.c_str())) { return paramMapping; } } // and finally different name, but equivalent parameter for (int i = 0; mapping->params[i] != nullptr; ++i) { const auto *paramMapping = mapping->params[i]; if (areEquivalentParameters(paramMapping->wkt2_name, name)) { return paramMapping; } } return nullptr; } // --------------------------------------------------------------------------- const ParamMapping *getMappingFromWKT1(const MethodMapping *mapping, const std::string &wkt1_name) { for (int i = 0; mapping->params[i] != nullptr; ++i) { const auto *paramMapping = mapping->params[i]; if (paramMapping->wkt1_name && (metadata::Identifier::isEquivalentName(paramMapping->wkt1_name, wkt1_name.c_str()) || areEquivalentParameters(paramMapping->wkt1_name, wkt1_name))) { return paramMapping; } } return nullptr; } //! @endcond // --------------------------------------------------------------------------- } // namespace operation NS_PROJ_END proj-9.8.1/src/iso19111/static.cpp000664 001750 001750 00000065244 15166171715 016437 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "operation/oputils.hpp" #include "proj/internal/coordinatesystem_internal.hpp" #include "proj/internal/io_internal.hpp" #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // We put all static definitions in the same compilation unit, and in // increasing order of dependency, to avoid the "static initialization fiasco" // See https://isocpp.org/wiki/faq/ctors#static-init-order using namespace NS_PROJ::crs; using namespace NS_PROJ::datum; using namespace NS_PROJ::io; using namespace NS_PROJ::metadata; using namespace NS_PROJ::operation; using namespace NS_PROJ::util; NS_PROJ_START // --------------------------------------------------------------------------- const NameSpaceNNPtr NameSpace::GLOBAL(NameSpace::createGLOBAL()); // --------------------------------------------------------------------------- /** \brief Key to set the authority citation of a metadata::Identifier. * * The value is to be provided as a string or a metadata::Citation. */ const std::string Identifier::AUTHORITY_KEY("authority"); /** \brief Key to set the code of a metadata::Identifier. * * The value is to be provided as a integer or a string. */ const std::string Identifier::CODE_KEY("code"); /** \brief Key to set the organization responsible for definition and * maintenance of the code of a metadata::Identifier. * * The value is to be provided as a string. */ const std::string Identifier::CODESPACE_KEY("codespace"); /** \brief Key to set the version identifier for the namespace of a * metadata::Identifier. * * The value is to be provided as a string. */ const std::string Identifier::VERSION_KEY("version"); /** \brief Key to set the natural language description of the meaning of the * code value of a metadata::Identifier. * * The value is to be provided as a string. */ const std::string Identifier::DESCRIPTION_KEY("description"); /** \brief Key to set the URI of a metadata::Identifier. * * The value is to be provided as a string. */ const std::string Identifier::URI_KEY("uri"); /** \brief EPSG codespace. */ const std::string Identifier::EPSG("EPSG"); /** \brief OGC codespace. */ const std::string Identifier::OGC("OGC"); // --------------------------------------------------------------------------- /** \brief Key to set the name of a common::IdentifiedObject * * The value is to be provided as a string or metadata::IdentifierNNPtr. */ const std::string common::IdentifiedObject::NAME_KEY("name"); /** \brief Key to set the identifier(s) of a common::IdentifiedObject * * The value is to be provided as a common::IdentifierNNPtr or a * util::ArrayOfBaseObjectNNPtr * of common::IdentifierNNPtr. */ const std::string common::IdentifiedObject::IDENTIFIERS_KEY("identifiers"); /** \brief Key to set the alias(es) of a common::IdentifiedObject * * The value is to be provided as string, a util::GenericNameNNPtr or a * util::ArrayOfBaseObjectNNPtr * of util::GenericNameNNPtr. */ const std::string common::IdentifiedObject::ALIAS_KEY("alias"); /** \brief Key to set the remarks of a common::IdentifiedObject * * The value is to be provided as a string. */ const std::string common::IdentifiedObject::REMARKS_KEY("remarks"); /** \brief Key to set the deprecation flag of a common::IdentifiedObject * * The value is to be provided as a boolean. */ const std::string common::IdentifiedObject::DEPRECATED_KEY("deprecated"); // --------------------------------------------------------------------------- /** \brief Key to set the scope of a common::ObjectUsage * * The value is to be provided as a string. */ const std::string common::ObjectUsage::SCOPE_KEY("scope"); /** \brief Key to set the domain of validity of a common::ObjectUsage * * The value is to be provided as a common::ExtentNNPtr. */ const std::string common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY("domainOfValidity"); /** \brief Key to set the object domain(s) of a common::ObjectUsage * * The value is to be provided as a common::ObjectDomainNNPtr or a * util::ArrayOfBaseObjectNNPtr * of common::ObjectDomainNNPtr. */ const std::string common::ObjectUsage::OBJECT_DOMAIN_KEY("objectDomain"); // --------------------------------------------------------------------------- /** \brief World extent. */ const ExtentNNPtr Extent::WORLD(Extent::createFromBBOX(-180, -90, 180, 90, util::optional("World"))); // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::vector WKTConstants::constants_; const char *WKTConstants::createAndAddToConstantList(const char *text) { WKTConstants::constants_.push_back(text); return text; } #define DEFINE_WKT_CONSTANT(x) \ const std::string WKTConstants::x(createAndAddToConstantList(#x)) DEFINE_WKT_CONSTANT(GEOCCS); DEFINE_WKT_CONSTANT(GEOGCS); DEFINE_WKT_CONSTANT(DATUM); DEFINE_WKT_CONSTANT(UNIT); DEFINE_WKT_CONSTANT(SPHEROID); DEFINE_WKT_CONSTANT(AXIS); DEFINE_WKT_CONSTANT(PRIMEM); DEFINE_WKT_CONSTANT(AUTHORITY); DEFINE_WKT_CONSTANT(PROJCS); DEFINE_WKT_CONSTANT(PROJECTION); DEFINE_WKT_CONSTANT(PARAMETER); DEFINE_WKT_CONSTANT(VERT_CS); DEFINE_WKT_CONSTANT(VERTCS); DEFINE_WKT_CONSTANT(VERT_DATUM); DEFINE_WKT_CONSTANT(COMPD_CS); DEFINE_WKT_CONSTANT(TOWGS84); DEFINE_WKT_CONSTANT(EXTENSION); DEFINE_WKT_CONSTANT(LOCAL_CS); DEFINE_WKT_CONSTANT(LOCAL_DATUM); DEFINE_WKT_CONSTANT(LINUNIT); DEFINE_WKT_CONSTANT(GEODCRS); DEFINE_WKT_CONSTANT(LENGTHUNIT); DEFINE_WKT_CONSTANT(ANGLEUNIT); DEFINE_WKT_CONSTANT(SCALEUNIT); DEFINE_WKT_CONSTANT(TIMEUNIT); DEFINE_WKT_CONSTANT(ELLIPSOID); const std::string WKTConstants::CS_(createAndAddToConstantList("CS")); DEFINE_WKT_CONSTANT(ID); DEFINE_WKT_CONSTANT(PROJCRS); DEFINE_WKT_CONSTANT(BASEGEODCRS); DEFINE_WKT_CONSTANT(MERIDIAN); DEFINE_WKT_CONSTANT(ORDER); DEFINE_WKT_CONSTANT(ANCHOR); DEFINE_WKT_CONSTANT(ANCHOREPOCH); DEFINE_WKT_CONSTANT(CONVERSION); DEFINE_WKT_CONSTANT(METHOD); DEFINE_WKT_CONSTANT(REMARK); DEFINE_WKT_CONSTANT(GEOGCRS); DEFINE_WKT_CONSTANT(BASEGEOGCRS); DEFINE_WKT_CONSTANT(SCOPE); DEFINE_WKT_CONSTANT(AREA); DEFINE_WKT_CONSTANT(BBOX); DEFINE_WKT_CONSTANT(CITATION); DEFINE_WKT_CONSTANT(URI); DEFINE_WKT_CONSTANT(VERTCRS); DEFINE_WKT_CONSTANT(VDATUM); DEFINE_WKT_CONSTANT(COMPOUNDCRS); DEFINE_WKT_CONSTANT(PARAMETERFILE); DEFINE_WKT_CONSTANT(COORDINATEOPERATION); DEFINE_WKT_CONSTANT(SOURCECRS); DEFINE_WKT_CONSTANT(TARGETCRS); DEFINE_WKT_CONSTANT(INTERPOLATIONCRS); DEFINE_WKT_CONSTANT(OPERATIONACCURACY); DEFINE_WKT_CONSTANT(CONCATENATEDOPERATION); DEFINE_WKT_CONSTANT(STEP); DEFINE_WKT_CONSTANT(BOUNDCRS); DEFINE_WKT_CONSTANT(ABRIDGEDTRANSFORMATION); DEFINE_WKT_CONSTANT(DERIVINGCONVERSION); DEFINE_WKT_CONSTANT(TDATUM); DEFINE_WKT_CONSTANT(CALENDAR); DEFINE_WKT_CONSTANT(TIMEORIGIN); DEFINE_WKT_CONSTANT(TIMECRS); DEFINE_WKT_CONSTANT(VERTICALEXTENT); DEFINE_WKT_CONSTANT(TIMEEXTENT); DEFINE_WKT_CONSTANT(USAGE); DEFINE_WKT_CONSTANT(DYNAMIC); DEFINE_WKT_CONSTANT(FRAMEEPOCH); DEFINE_WKT_CONSTANT(MODEL); DEFINE_WKT_CONSTANT(VELOCITYGRID); DEFINE_WKT_CONSTANT(ENSEMBLE); DEFINE_WKT_CONSTANT(MEMBER); DEFINE_WKT_CONSTANT(ENSEMBLEACCURACY); DEFINE_WKT_CONSTANT(DERIVEDPROJCRS); DEFINE_WKT_CONSTANT(BASEPROJCRS); DEFINE_WKT_CONSTANT(EDATUM); DEFINE_WKT_CONSTANT(ENGCRS); DEFINE_WKT_CONSTANT(PDATUM); DEFINE_WKT_CONSTANT(PARAMETRICCRS); DEFINE_WKT_CONSTANT(PARAMETRICUNIT); DEFINE_WKT_CONSTANT(BASEVERTCRS); DEFINE_WKT_CONSTANT(BASEENGCRS); DEFINE_WKT_CONSTANT(BASEPARAMCRS); DEFINE_WKT_CONSTANT(BASETIMECRS); DEFINE_WKT_CONSTANT(VERSION); DEFINE_WKT_CONSTANT(GEOIDMODEL); DEFINE_WKT_CONSTANT(COORDINATEMETADATA); DEFINE_WKT_CONSTANT(EPOCH); DEFINE_WKT_CONSTANT(AXISMINVALUE); DEFINE_WKT_CONSTANT(AXISMAXVALUE); DEFINE_WKT_CONSTANT(RANGEMEANING); DEFINE_WKT_CONSTANT(POINTMOTIONOPERATION); DEFINE_WKT_CONSTANT(GEODETICCRS); DEFINE_WKT_CONSTANT(GEODETICDATUM); DEFINE_WKT_CONSTANT(PROJECTEDCRS); DEFINE_WKT_CONSTANT(PRIMEMERIDIAN); DEFINE_WKT_CONSTANT(GEOGRAPHICCRS); DEFINE_WKT_CONSTANT(TRF); DEFINE_WKT_CONSTANT(VERTICALCRS); DEFINE_WKT_CONSTANT(VERTICALDATUM); DEFINE_WKT_CONSTANT(VRF); DEFINE_WKT_CONSTANT(TIMEDATUM); DEFINE_WKT_CONSTANT(TEMPORALQUANTITY); DEFINE_WKT_CONSTANT(ENGINEERINGDATUM); DEFINE_WKT_CONSTANT(ENGINEERINGCRS); DEFINE_WKT_CONSTANT(PARAMETRICDATUM); //! @endcond // --------------------------------------------------------------------------- namespace common { /** \brief "Empty"/"None", unit of measure of type NONE. */ const UnitOfMeasure UnitOfMeasure::NONE("", 1.0, UnitOfMeasure::Type::NONE); /** \brief Scale unity, unit of measure of type SCALE. */ const UnitOfMeasure UnitOfMeasure::SCALE_UNITY("unity", 1.0, UnitOfMeasure::Type::SCALE, Identifier::EPSG, "9201"); /** \brief Parts-per-million, unit of measure of type SCALE. */ const UnitOfMeasure UnitOfMeasure::PARTS_PER_MILLION("parts per million", 1e-6, UnitOfMeasure::Type::SCALE, Identifier::EPSG, "9202"); /** \brief Metre, unit of measure of type LINEAR (SI unit). */ const UnitOfMeasure UnitOfMeasure::METRE("metre", 1.0, UnitOfMeasure::Type::LINEAR, Identifier::EPSG, "9001"); /** \brief Foot, unit of measure of type LINEAR. */ const UnitOfMeasure UnitOfMeasure::FOOT("foot", 0.3048, UnitOfMeasure::Type::LINEAR, Identifier::EPSG, "9002"); /** \brief US survey foot, unit of measure of type LINEAR. */ const UnitOfMeasure UnitOfMeasure::US_FOOT("US survey foot", 0.304800609601219241184, UnitOfMeasure::Type::LINEAR, Identifier::EPSG, "9003"); /** \brief Degree, unit of measure of type ANGULAR. */ const UnitOfMeasure UnitOfMeasure::DEGREE("degree", M_PI / 180., UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "9122"); /** \brief Arc-second, unit of measure of type ANGULAR. */ const UnitOfMeasure UnitOfMeasure::ARC_SECOND("arc-second", M_PI / 180. / 3600., UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "9104"); /** \brief Grad, unit of measure of type ANGULAR. */ const UnitOfMeasure UnitOfMeasure::GRAD("grad", M_PI / 200., UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "9105"); /** \brief Radian, unit of measure of type ANGULAR (SI unit). */ const UnitOfMeasure UnitOfMeasure::RADIAN("radian", 1.0, UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "9101"); /** \brief Microradian, unit of measure of type ANGULAR. */ const UnitOfMeasure UnitOfMeasure::MICRORADIAN("microradian", 1e-6, UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "9109"); /** \brief Second, unit of measure of type TIME (SI unit). */ const UnitOfMeasure UnitOfMeasure::SECOND("second", 1.0, UnitOfMeasure::Type::TIME, Identifier::EPSG, "1040"); /** \brief Year, unit of measure of type TIME */ const UnitOfMeasure UnitOfMeasure::YEAR("year", 31556925.445, UnitOfMeasure::Type::TIME, Identifier::EPSG, "1029"); /** \brief Metre per year, unit of measure of type LINEAR. */ const UnitOfMeasure UnitOfMeasure::METRE_PER_YEAR("metres per year", 1.0 / 31556925.445, UnitOfMeasure::Type::LINEAR, Identifier::EPSG, "1042"); /** \brief Arc-second per year, unit of measure of type ANGULAR. */ const UnitOfMeasure UnitOfMeasure::ARC_SECOND_PER_YEAR( "arc-seconds per year", M_PI / 180. / 3600. / 31556925.445, UnitOfMeasure::Type::ANGULAR, Identifier::EPSG, "1043"); /** \brief Parts-per-million per year, unit of measure of type SCALE. */ const UnitOfMeasure UnitOfMeasure::PPM_PER_YEAR("parts per million per year", 1e-6 / 31556925.445, UnitOfMeasure::Type::SCALE, Identifier::EPSG, "1036"); } // namespace common // --------------------------------------------------------------------------- namespace cs { std::map AxisDirection::registry; /** Axis positive direction is north. In a geodetic or projected CRS, north is * defined through the geodetic reference frame. In an engineering CRS, north * may be defined with respect to an engineering object rather than a * geographical direction. */ const AxisDirection AxisDirection::NORTH("north"); /** Axis positive direction is approximately north-north-east. */ const AxisDirection AxisDirection::NORTH_NORTH_EAST("northNorthEast"); /** Axis positive direction is approximately north-east. */ const AxisDirection AxisDirection::NORTH_EAST("northEast"); /** Axis positive direction is approximately east-north-east. */ const AxisDirection AxisDirection::EAST_NORTH_EAST("eastNorthEast"); /** Axis positive direction is 90deg clockwise from north. */ const AxisDirection AxisDirection::EAST("east"); /** Axis positive direction is approximately east-south-east. */ const AxisDirection AxisDirection::EAST_SOUTH_EAST("eastSouthEast"); /** Axis positive direction is approximately south-east. */ const AxisDirection AxisDirection::SOUTH_EAST("southEast"); /** Axis positive direction is approximately south-south-east. */ const AxisDirection AxisDirection::SOUTH_SOUTH_EAST("southSouthEast"); /** Axis positive direction is 180deg clockwise from north. */ const AxisDirection AxisDirection::SOUTH("south"); /** Axis positive direction is approximately south-south-west. */ const AxisDirection AxisDirection::SOUTH_SOUTH_WEST("southSouthWest"); /** Axis positive direction is approximately south-west. */ const AxisDirection AxisDirection::SOUTH_WEST("southWest"); /** Axis positive direction is approximately west-south-west. */ const AxisDirection AxisDirection::WEST_SOUTH_WEST("westSouthWest"); /** Axis positive direction is 270deg clockwise from north. */ const AxisDirection AxisDirection::WEST("west"); /** Axis positive direction is approximately west-north-west. */ const AxisDirection AxisDirection::WEST_NORTH_WEST("westNorthWest"); /** Axis positive direction is approximately north-west. */ const AxisDirection AxisDirection::NORTH_WEST("northWest"); /** Axis positive direction is approximately north-north-west. */ const AxisDirection AxisDirection::NORTH_NORTH_WEST("northNorthWest"); /** Axis positive direction is up relative to gravity. */ const AxisDirection AxisDirection::UP("up"); /** Axis positive direction is down relative to gravity. */ const AxisDirection AxisDirection::DOWN("down"); /** Axis positive direction is in the equatorial plane from the centre of the * modelled Earth towards the intersection of the equator with the prime * meridian. */ const AxisDirection AxisDirection::GEOCENTRIC_X("geocentricX"); /** Axis positive direction is in the equatorial plane from the centre of the * modelled Earth towards the intersection of the equator and the meridian 90deg * eastwards from the prime meridian. */ const AxisDirection AxisDirection::GEOCENTRIC_Y("geocentricY"); /** Axis positive direction is from the centre of the modelled Earth parallel to * its rotation axis and towards its north pole. */ const AxisDirection AxisDirection::GEOCENTRIC_Z("geocentricZ"); /** Axis positive direction is towards higher pixel column. */ const AxisDirection AxisDirection::COLUMN_POSITIVE("columnPositive"); /** Axis positive direction is towards lower pixel column. */ const AxisDirection AxisDirection::COLUMN_NEGATIVE("columnNegative"); /** Axis positive direction is towards higher pixel row. */ const AxisDirection AxisDirection::ROW_POSITIVE("rowPositive"); /** Axis positive direction is towards lower pixel row. */ const AxisDirection AxisDirection::ROW_NEGATIVE("rowNegative"); /** Axis positive direction is right in display. */ const AxisDirection AxisDirection::DISPLAY_RIGHT("displayRight"); /** Axis positive direction is left in display. */ const AxisDirection AxisDirection::DISPLAY_LEFT("displayLeft"); /** Axis positive direction is towards top of approximately vertical display * surface. */ const AxisDirection AxisDirection::DISPLAY_UP("displayUp"); /** Axis positive direction is towards bottom of approximately vertical display * surface. */ const AxisDirection AxisDirection::DISPLAY_DOWN("displayDown"); /** Axis positive direction is forward; for an observer at the centre of the * object this is will be towards its front, bow or nose. */ const AxisDirection AxisDirection::FORWARD("forward"); /** Axis positive direction is aft; for an observer at the centre of the object * this will be towards its back, stern or tail. */ const AxisDirection AxisDirection::AFT("aft"); /** Axis positive direction is port; for an observer at the centre of the object * this will be towards its left. */ const AxisDirection AxisDirection::PORT("port"); /** Axis positive direction is starboard; for an observer at the centre of the * object this will be towards its right. */ const AxisDirection AxisDirection::STARBOARD("starboard"); /** Axis positive direction is clockwise from a specified direction. */ const AxisDirection AxisDirection::CLOCKWISE("clockwise"); /** Axis positive direction is counter clockwise from a specified direction. */ const AxisDirection AxisDirection::COUNTER_CLOCKWISE("counterClockwise"); /** Axis positive direction is towards the object. */ const AxisDirection AxisDirection::TOWARDS("towards"); /** Axis positive direction is away from the object. */ const AxisDirection AxisDirection::AWAY_FROM("awayFrom"); /** Temporal axis positive direction is towards the future. */ const AxisDirection AxisDirection::FUTURE("future"); /** Temporal axis positive direction is towards the past. */ const AxisDirection AxisDirection::PAST("past"); /** Axis positive direction is unspecified. */ const AxisDirection AxisDirection::UNSPECIFIED("unspecified"); // --------------------------------------------------------------------------- std::map RangeMeaning::registry; /** any value between and including minimumValue and maximumValue is valid. */ const RangeMeaning RangeMeaning::EXACT("exact"); /** Axis is continuous with values wrapping around at the minimumValue and * maximumValue */ const RangeMeaning RangeMeaning::WRAPAROUND("wraparound"); // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::map AxisDirectionWKT1::registry; const AxisDirectionWKT1 AxisDirectionWKT1::NORTH("NORTH"); const AxisDirectionWKT1 AxisDirectionWKT1::EAST("EAST"); const AxisDirectionWKT1 AxisDirectionWKT1::SOUTH("SOUTH"); const AxisDirectionWKT1 AxisDirectionWKT1::WEST("WEST"); const AxisDirectionWKT1 AxisDirectionWKT1::UP("UP"); const AxisDirectionWKT1 AxisDirectionWKT1::DOWN("DOWN"); const AxisDirectionWKT1 AxisDirectionWKT1::OTHER("OTHER"); //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const std::string AxisName::Longitude("Longitude"); const std::string AxisName::Latitude("Latitude"); const std::string AxisName::Easting("Easting"); const std::string AxisName::Northing("Northing"); const std::string AxisName::Westing("Westing"); const std::string AxisName::Southing("Southing"); const std::string AxisName::Ellipsoidal_height("Ellipsoidal height"); const std::string AxisName::Geocentric_X("Geocentric X"); const std::string AxisName::Geocentric_Y("Geocentric Y"); const std::string AxisName::Geocentric_Z("Geocentric Z"); //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const std::string AxisAbbreviation::lon("lon"); const std::string AxisAbbreviation::lat("lat"); const std::string AxisAbbreviation::E("E"); const std::string AxisAbbreviation::N("N"); const std::string AxisAbbreviation::h("h"); const std::string AxisAbbreviation::X("X"); const std::string AxisAbbreviation::Y("Y"); const std::string AxisAbbreviation::Z("Z"); //! @endcond } // namespace cs // --------------------------------------------------------------------------- /** \brief The realization is by adjustment of a levelling network fixed to one * or more tide gauges. */ const RealizationMethod RealizationMethod::LEVELLING("levelling"); /** \brief The realization is through a geoid height model or a height * correction model. This is applied to a specified geodetic CRS. */ const RealizationMethod RealizationMethod::GEOID("geoid"); /** \brief The realization is through a tidal model or by tidal predictions. */ const RealizationMethod RealizationMethod::TIDAL("tidal"); // --------------------------------------------------------------------------- /** \brief The Greenwich PrimeMeridian */ const PrimeMeridianNNPtr PrimeMeridian::GREENWICH(PrimeMeridian::createGREENWICH()); /** \brief The "Reference Meridian" PrimeMeridian. * * This is a meridian of longitude 0 to be used with non-Earth bodies. */ const PrimeMeridianNNPtr PrimeMeridian::REFERENCE_MERIDIAN( PrimeMeridian::createREFERENCE_MERIDIAN()); /** \brief The Paris PrimeMeridian */ const PrimeMeridianNNPtr PrimeMeridian::PARIS(PrimeMeridian::createPARIS()); // --------------------------------------------------------------------------- /** \brief Earth celestial body */ const std::string Ellipsoid::EARTH("Earth"); /** \brief The EPSG:7008 / "Clarke 1866" Ellipsoid */ const EllipsoidNNPtr Ellipsoid::CLARKE_1866(Ellipsoid::createCLARKE_1866()); /** \brief The EPSG:7030 / "WGS 84" Ellipsoid */ const EllipsoidNNPtr Ellipsoid::WGS84(Ellipsoid::createWGS84()); /** \brief The EPSG:7019 / "GRS 1980" Ellipsoid */ const EllipsoidNNPtr Ellipsoid::GRS1980(Ellipsoid::createGRS1980()); // --------------------------------------------------------------------------- /** \brief The EPSG:6267 / "North_American_Datum_1927" GeodeticReferenceFrame */ const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6267( GeodeticReferenceFrame::createEPSG_6267()); /** \brief The EPSG:6269 / "North_American_Datum_1983" GeodeticReferenceFrame */ const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6269( GeodeticReferenceFrame::createEPSG_6269()); /** \brief The EPSG:6326 / "WGS_1984" GeodeticReferenceFrame */ const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::EPSG_6326( GeodeticReferenceFrame::createEPSG_6326()); // --------------------------------------------------------------------------- /** \brief The proleptic Gregorian calendar. */ const std::string TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN("proleptic Gregorian"); // --------------------------------------------------------------------------- /** \brief EPSG:4978 / "WGS 84" Geocentric */ const GeodeticCRSNNPtr GeodeticCRS::EPSG_4978(GeodeticCRS::createEPSG_4978()); // --------------------------------------------------------------------------- /** \brief EPSG:4267 / "NAD27" 2D GeographicCRS */ const GeographicCRSNNPtr GeographicCRS::EPSG_4267(GeographicCRS::createEPSG_4267()); /** \brief EPSG:4269 / "NAD83" 2D GeographicCRS */ const GeographicCRSNNPtr GeographicCRS::EPSG_4269(GeographicCRS::createEPSG_4269()); /** \brief EPSG:4326 / "WGS 84" 2D GeographicCRS */ const GeographicCRSNNPtr GeographicCRS::EPSG_4326(GeographicCRS::createEPSG_4326()); /** \brief OGC:CRS84 / "CRS 84" 2D GeographicCRS (long, lat)*/ const GeographicCRSNNPtr GeographicCRS::OGC_CRS84(GeographicCRS::createOGC_CRS84()); /** \brief EPSG:4807 / "NTF (Paris)" 2D GeographicCRS */ const GeographicCRSNNPtr GeographicCRS::EPSG_4807(GeographicCRS::createEPSG_4807()); /** \brief EPSG:4979 / "WGS 84" 3D GeographicCRS */ const GeographicCRSNNPtr GeographicCRS::EPSG_4979(GeographicCRS::createEPSG_4979()); // --------------------------------------------------------------------------- /** \brief Key to set the operation version of a operation::CoordinateOperation * * The value is to be provided as a string. */ const std::string operation::CoordinateOperation::OPERATION_VERSION_KEY("operationVersion"); //! @cond Doxygen_Suppress const common::Measure operation::nullMeasure{}; const std::string operation::INVERSE_OF = "Inverse of "; const std::string operation::AXIS_ORDER_CHANGE_2D_NAME = "axis order change (2D)"; const std::string operation::AXIS_ORDER_CHANGE_3D_NAME = "axis order change (geographic3D horizontal)"; //! @endcond // --------------------------------------------------------------------------- NS_PROJ_END proj-9.8.1/src/iso19111/metadata.cpp000664 001750 001750 00000147021 15166171715 016722 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/metadata.hpp" #include "proj/common.hpp" #include "proj/io.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include using namespace NS_PROJ::internal; using namespace NS_PROJ::io; using namespace NS_PROJ::util; #if 0 namespace dropbox{ namespace oxygen { template<> nn>::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; }} #endif NS_PROJ_START namespace metadata { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Citation::Private { optional title{}; }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Citation::Citation() : d(std::make_unique()) {} //! @endcond // --------------------------------------------------------------------------- /** \brief Constructs a citation by its title. */ Citation::Citation(const std::string &titleIn) : d(std::make_unique()) { d->title = titleIn; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Citation::Citation(const Citation &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- Citation::~Citation() = default; // --------------------------------------------------------------------------- Citation &Citation::operator=(const Citation &other) { if (this != &other) { *d = *other.d; } return *this; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns the name by which the cited resource is known. */ const optional &Citation::title() PROJ_PURE_DEFN { return d->title; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeographicExtent::Private {}; //! @endcond // --------------------------------------------------------------------------- GeographicExtent::GeographicExtent() : d(std::make_unique()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeographicExtent::~GeographicExtent() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeographicBoundingBox::Private { double west_{}; double south_{}; double east_{}; double north_{}; Private(double west, double south, double east, double north) : west_(west), south_(south), east_(east), north_(north) {} bool intersects(const Private &other) const; std::unique_ptr intersection(const Private &other) const; }; //! @endcond // --------------------------------------------------------------------------- GeographicBoundingBox::GeographicBoundingBox(double west, double south, double east, double north) : GeographicExtent(), d(std::make_unique(west, south, east, north)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeographicBoundingBox::~GeographicBoundingBox() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Returns the western-most coordinate of the limit of the dataset * extent. * * The unit is degrees. * * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses * the anti-meridian. */ double GeographicBoundingBox::westBoundLongitude() PROJ_PURE_DEFN { return d->west_; } // --------------------------------------------------------------------------- /** \brief Returns the southern-most coordinate of the limit of the dataset * extent. * * The unit is degrees. */ double GeographicBoundingBox::southBoundLatitude() PROJ_PURE_DEFN { return d->south_; } // --------------------------------------------------------------------------- /** \brief Returns the eastern-most coordinate of the limit of the dataset * extent. * * The unit is degrees. * * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses * the anti-meridian. */ double GeographicBoundingBox::eastBoundLongitude() PROJ_PURE_DEFN { return d->east_; } // --------------------------------------------------------------------------- /** \brief Returns the northern-most coordinate of the limit of the dataset * extent. * * The unit is degrees. */ double GeographicBoundingBox::northBoundLatitude() PROJ_PURE_DEFN { return d->north_; } // --------------------------------------------------------------------------- /** \brief Instantiate a GeographicBoundingBox. * * If east < west, then the bounding box crosses the anti-meridian. * * @param west Western-most coordinate of the limit of the dataset extent (in * degrees). * @param south Southern-most coordinate of the limit of the dataset extent (in * degrees). * @param east Eastern-most coordinate of the limit of the dataset extent (in * degrees). * @param north Northern-most coordinate of the limit of the dataset extent (in * degrees). * @return a new GeographicBoundingBox. */ GeographicBoundingBoxNNPtr GeographicBoundingBox::create(double west, double south, double east, double north) { if (std::isnan(west) || std::isnan(south) || std::isnan(east) || std::isnan(north)) { throw InvalidValueTypeException( "GeographicBoundingBox::create() does not accept NaN values"); } if (south > north) { throw InvalidValueTypeException( "GeographicBoundingBox::create() does not accept south > north"); } // Avoid creating a degenerate bounding box if reduced to a point or a line if (west == east) { if (west > -180) west = std::nextafter(west, -std::numeric_limits::infinity()); if (east < 180) east = std::nextafter(east, std::numeric_limits::infinity()); } if (south == north) { if (south > -90) south = std::nextafter(south, -std::numeric_limits::infinity()); if (north < 90) north = std::nextafter(north, std::numeric_limits::infinity()); } return GeographicBoundingBox::nn_make_shared( west, south, east, north); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool GeographicBoundingBox::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion, const io::DatabaseContextPtr &) const { auto otherExtent = dynamic_cast(other); if (!otherExtent) return false; return d->west_ == otherExtent->d->west_ && d->south_ == otherExtent->d->south_ && d->east_ == otherExtent->d->east_ && d->north_ == otherExtent->d->north_; } //! @endcond // --------------------------------------------------------------------------- bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const { auto otherExtent = dynamic_cast(other.get()); if (!otherExtent) { return false; } const double W = d->west_; const double E = d->east_; const double N = d->north_; const double S = d->south_; const double oW = otherExtent->d->west_; const double oE = otherExtent->d->east_; const double oN = otherExtent->d->north_; const double oS = otherExtent->d->south_; if (!(S <= oS && N >= oN)) { return false; } if (W == -180.0 && E == 180.0) { return oW != oE; } if (oW == -180.0 && oE == 180.0) { return false; } // Normal bounding box ? if (W < E) { if (oW < oE) { return W <= oW && E >= oE; } else { return false; } // No: crossing antimerian } else { if (oW < oE) { if (oW >= W) { return true; } else if (oE <= E) { return true; } else { return false; } } else { return W <= oW && E >= oE; } } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool GeographicBoundingBox::Private::intersects(const Private &other) const { const double W = west_; const double E = east_; const double N = north_; const double S = south_; const double oW = other.west_; const double oE = other.east_; const double oN = other.north_; const double oS = other.south_; // Check intersection along the latitude axis if (N < oS || S > oN) { return false; } // Check world coverage of this bbox, and other bbox overlapping // antimeridian (e.g. oW=175 and oE=-175) // Check oW > oE written for symmetry with the intersection() method. if (W == -180.0 && E == 180.0 && oW > oE) { return true; } // Check world coverage of other bbox, and this bbox overlapping // antimeridian (e.g. W=175 and E=-175) // Check W > E written for symmetry with the intersection() method. if (oW == -180.0 && oE == 180.0 && W > E) { return true; } // Normal bounding box ? if (W <= E) { if (oW <= oE) { if (std::max(W, oW) < std::min(E, oE)) { return true; } return false; } // Bail out on longitudes not in [-180,180]. We could probably make // some sense of them, but this check at least avoid potential infinite // recursion. if (oW > 180 || oE < -180) { return false; } return intersects(Private(oW, oS, 180.0, oN)) || intersects(Private(-180.0, oS, oE, oN)); // No: crossing antimeridian } else { if (oW <= oE) { return other.intersects(*this); } return true; } } //! @endcond bool GeographicBoundingBox::intersects( const GeographicExtentNNPtr &other) const { auto otherExtent = dynamic_cast(other.get()); if (!otherExtent) { return false; } return d->intersects(*(otherExtent->d)); } // --------------------------------------------------------------------------- GeographicExtentPtr GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const { auto otherExtent = dynamic_cast(other.get()); if (!otherExtent) { return nullptr; } auto ret = d->intersection(*(otherExtent->d)); if (ret) { auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_, ret->east_, ret->north_); return bbox.as_nullable(); } return nullptr; } //! @cond Doxygen_Suppress std::unique_ptr GeographicBoundingBox::Private::intersection(const Private &otherExtent) const { const double W = west_; const double E = east_; const double N = north_; const double S = south_; const double oW = otherExtent.west_; const double oE = otherExtent.east_; const double oN = otherExtent.north_; const double oS = otherExtent.south_; // Check intersection along the latitude axis if (N < oS || S > oN) { return nullptr; } // Check world coverage of this bbox, and other bbox overlapping // antimeridian (e.g. oW=175 and oE=-175) if (W == -180.0 && E == 180.0 && oW > oE) { return std::make_unique(oW, std::max(S, oS), oE, std::min(N, oN)); } // Check world coverage of other bbox, and this bbox overlapping // antimeridian (e.g. W=175 and E=-175) if (oW == -180.0 && oE == 180.0 && W > E) { return std::make_unique(W, std::max(S, oS), E, std::min(N, oN)); } // Normal bounding box ? if (W <= E) { if (oW <= oE) { const double resW = std::max(W, oW); const double resE = std::min(E, oE); if (resW < resE) { return std::make_unique(resW, std::max(S, oS), resE, std::min(N, oN)); } return nullptr; } // Bail out on longitudes not in [-180,180]. We could probably make // some sense of them, but this check at least avoid potential infinite // recursion. if (oW > 180 || oE < -180) { return nullptr; } // Return larger of two parts of the multipolygon auto inter1 = intersection(Private(oW, oS, 180.0, oN)); auto inter2 = intersection(Private(-180.0, oS, oE, oN)); if (!inter1) { return inter2; } if (!inter2) { return inter1; } if (inter1->east_ - inter1->west_ > inter2->east_ - inter2->west_) { return inter1; } return inter2; // No: crossing antimeridian } else { if (oW <= oE) { return otherExtent.intersection(*this); } return std::make_unique(std::max(W, oW), std::max(S, oS), std::min(E, oE), std::min(N, oN)); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct VerticalExtent::Private { double minimum_{}; double maximum_{}; common::UnitOfMeasureNNPtr unit_; Private(double minimum, double maximum, const common::UnitOfMeasureNNPtr &unit) : minimum_(minimum), maximum_(maximum), unit_(unit) {} }; //! @endcond // --------------------------------------------------------------------------- VerticalExtent::VerticalExtent(double minimumIn, double maximumIn, const common::UnitOfMeasureNNPtr &unitIn) : d(std::make_unique(minimumIn, maximumIn, unitIn)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress VerticalExtent::~VerticalExtent() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Returns the minimum of the vertical extent. */ double VerticalExtent::minimumValue() PROJ_PURE_DEFN { return d->minimum_; } // --------------------------------------------------------------------------- /** \brief Returns the maximum of the vertical extent. */ double VerticalExtent::maximumValue() PROJ_PURE_DEFN { return d->maximum_; } // --------------------------------------------------------------------------- /** \brief Returns the unit of the vertical extent. */ common::UnitOfMeasureNNPtr &VerticalExtent::unit() PROJ_PURE_DEFN { return d->unit_; } // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalExtent. * * @param minimumIn minimum. * @param maximumIn maximum. * @param unitIn unit. * @return a new VerticalExtent. */ VerticalExtentNNPtr VerticalExtent::create(double minimumIn, double maximumIn, const common::UnitOfMeasureNNPtr &unitIn) { return VerticalExtent::nn_make_shared(minimumIn, maximumIn, unitIn); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool VerticalExtent::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion, const io::DatabaseContextPtr &) const { auto otherExtent = dynamic_cast(other); if (!otherExtent) return false; return d->minimum_ == otherExtent->d->minimum_ && d->maximum_ == otherExtent->d->maximum_ && d->unit_ == otherExtent->d->unit_; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns whether this extent contains the other one. */ bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const { const double thisUnitToSI = d->unit_->conversionToSI(); const double otherUnitToSI = other->d->unit_->conversionToSI(); return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI && d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI; } // --------------------------------------------------------------------------- /** \brief Returns whether this extent intersects the other one. */ bool VerticalExtent::intersects(const VerticalExtentNNPtr &other) const { const double thisUnitToSI = d->unit_->conversionToSI(); const double otherUnitToSI = other->d->unit_->conversionToSI(); return d->minimum_ * thisUnitToSI <= other->d->maximum_ * otherUnitToSI && d->maximum_ * thisUnitToSI >= other->d->minimum_ * otherUnitToSI; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct TemporalExtent::Private { std::string start_{}; std::string stop_{}; Private(const std::string &start, const std::string &stop) : start_(start), stop_(stop) {} }; //! @endcond // --------------------------------------------------------------------------- TemporalExtent::TemporalExtent(const std::string &startIn, const std::string &stopIn) : d(std::make_unique(startIn, stopIn)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalExtent::~TemporalExtent() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Returns the start of the temporal extent. */ const std::string &TemporalExtent::start() PROJ_PURE_DEFN { return d->start_; } // --------------------------------------------------------------------------- /** \brief Returns the end of the temporal extent. */ const std::string &TemporalExtent::stop() PROJ_PURE_DEFN { return d->stop_; } // --------------------------------------------------------------------------- /** \brief Instantiate a TemporalExtent. * * @param start start. * @param stop stop. * @return a new TemporalExtent. */ TemporalExtentNNPtr TemporalExtent::create(const std::string &start, const std::string &stop) { return TemporalExtent::nn_make_shared(start, stop); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool TemporalExtent::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion, const io::DatabaseContextPtr &) const { auto otherExtent = dynamic_cast(other); if (!otherExtent) return false; return start() == otherExtent->start() && stop() == otherExtent->stop(); } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns whether this extent contains the other one. */ bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const { return start() <= other->start() && stop() >= other->stop(); } // --------------------------------------------------------------------------- /** \brief Returns whether this extent intersects the other one. */ bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const { return start() <= other->stop() && stop() >= other->start(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Extent::Private { optional description_{}; std::vector geographicElements_{}; std::vector verticalElements_{}; std::vector temporalElements_{}; }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Extent::Extent() : d(std::make_unique()) {} // --------------------------------------------------------------------------- Extent::Extent(const Extent &other) : d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- Extent::~Extent() = default; //! @endcond // --------------------------------------------------------------------------- /** Return a textual description of the extent. * * @return the description, or empty. */ const optional &Extent::description() PROJ_PURE_DEFN { return d->description_; } // --------------------------------------------------------------------------- /** Return the geographic element(s) of the extent * * @return the geographic element(s), or empty. */ const std::vector & Extent::geographicElements() PROJ_PURE_DEFN { return d->geographicElements_; } // --------------------------------------------------------------------------- /** Return the vertical element(s) of the extent * * @return the vertical element(s), or empty. */ const std::vector & Extent::verticalElements() PROJ_PURE_DEFN { return d->verticalElements_; } // --------------------------------------------------------------------------- /** Return the temporal element(s) of the extent * * @return the temporal element(s), or empty. */ const std::vector & Extent::temporalElements() PROJ_PURE_DEFN { return d->temporalElements_; } // --------------------------------------------------------------------------- /** \brief Instantiate a Extent. * * @param descriptionIn Textual description, or empty. * @param geographicElementsIn Geographic element(s), or empty. * @param verticalElementsIn Vertical element(s), or empty. * @param temporalElementsIn Temporal element(s), or empty. * @return a new Extent. */ ExtentNNPtr Extent::create(const optional &descriptionIn, const std::vector &geographicElementsIn, const std::vector &verticalElementsIn, const std::vector &temporalElementsIn) { auto extent = Extent::nn_make_shared(); extent->assignSelf(extent); extent->d->description_ = descriptionIn; extent->d->geographicElements_ = geographicElementsIn; extent->d->verticalElements_ = verticalElementsIn; extent->d->temporalElements_ = temporalElementsIn; return extent; } // --------------------------------------------------------------------------- /** \brief Instantiate a Extent from a bounding box * * @param west Western-most coordinate of the limit of the dataset extent (in * degrees). * @param south Southern-most coordinate of the limit of the dataset extent (in * degrees). * @param east Eastern-most coordinate of the limit of the dataset extent (in * degrees). * @param north Northern-most coordinate of the limit of the dataset extent (in * degrees). * @param descriptionIn Textual description, or empty. * @return a new Extent. */ ExtentNNPtr Extent::createFromBBOX(double west, double south, double east, double north, const util::optional &descriptionIn) { return create( descriptionIn, std::vector{ nn_static_pointer_cast( GeographicBoundingBox::create(west, south, east, north))}, std::vector(), std::vector()); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool Extent::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherExtent = dynamic_cast(other); bool ret = (otherExtent && description().has_value() == otherExtent->description().has_value() && *description() == *otherExtent->description() && d->geographicElements_.size() == otherExtent->d->geographicElements_.size() && d->verticalElements_.size() == otherExtent->d->verticalElements_.size() && d->temporalElements_.size() == otherExtent->d->temporalElements_.size()); if (ret) { for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) { ret = d->geographicElements_[i]->_isEquivalentTo( otherExtent->d->geographicElements_[i].get(), criterion, dbContext); } for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) { ret = d->verticalElements_[i]->_isEquivalentTo( otherExtent->d->verticalElements_[i].get(), criterion, dbContext); } for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) { ret = d->temporalElements_[i]->_isEquivalentTo( otherExtent->d->temporalElements_[i].get(), criterion, dbContext); } } return ret; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns whether this extent contains the other one. * * Behavior only well specified if each sub-extent category as at most * one element. */ bool Extent::contains(const ExtentNNPtr &other) const { bool res = true; if (d->geographicElements_.size() == 1 && other->d->geographicElements_.size() == 1) { res = d->geographicElements_[0]->contains( other->d->geographicElements_[0]); } if (res && d->verticalElements_.size() == 1 && other->d->verticalElements_.size() == 1) { res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]); } if (res && d->temporalElements_.size() == 1 && other->d->temporalElements_.size() == 1) { res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]); } return res; } // --------------------------------------------------------------------------- /** \brief Returns whether this extent intersects the other one. * * Behavior only well specified if each sub-extent category as at most * one element. */ bool Extent::intersects(const ExtentNNPtr &other) const { bool res = true; if (d->geographicElements_.size() == 1 && other->d->geographicElements_.size() == 1) { res = d->geographicElements_[0]->intersects( other->d->geographicElements_[0]); } if (res && d->verticalElements_.size() == 1 && other->d->verticalElements_.size() == 1) { res = d->verticalElements_[0]->intersects(other->d->verticalElements_[0]); } if (res && d->temporalElements_.size() == 1 && other->d->temporalElements_.size() == 1) { res = d->temporalElements_[0]->intersects(other->d->temporalElements_[0]); } return res; } // --------------------------------------------------------------------------- /** \brief Returns the intersection of this extent with another one. * * Behavior only well specified if there is one single GeographicExtent * in each object. * Returns nullptr otherwise. */ ExtentPtr Extent::intersection(const ExtentNNPtr &other) const { if (d->geographicElements_.size() == 1 && other->d->geographicElements_.size() == 1) { if (contains(other)) { return other.as_nullable(); } auto self = util::nn_static_pointer_cast(shared_from_this()); if (other->contains(self)) { return self.as_nullable(); } auto geogIntersection = d->geographicElements_[0]->intersection( other->d->geographicElements_[0]); if (geogIntersection) { return create(util::optional(), std::vector{ NN_NO_CHECK(geogIntersection)}, std::vector{}, std::vector{}); } } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Identifier::Private { optional authority_{}; std::string code_{}; optional codeSpace_{}; optional version_{}; optional description_{}; optional uri_{}; Private() = default; Private(const std::string &codeIn, const PropertyMap &properties) : code_(codeIn) { setProperties(properties); } private: // cppcheck-suppress functionStatic void setProperties(const PropertyMap &properties); }; // --------------------------------------------------------------------------- void Identifier::Private::setProperties( const PropertyMap &properties) // throw(InvalidValueTypeException) { { const auto pVal = properties.get(AUTHORITY_KEY); if (pVal) { if (auto genVal = dynamic_cast(pVal->get())) { if (genVal->type() == BoxedValue::Type::STRING) { authority_ = Citation(genVal->stringValue()); } else { throw InvalidValueTypeException("Invalid value type for " + AUTHORITY_KEY); } } else { auto citation = dynamic_cast(pVal->get()); if (citation) { authority_ = *citation; } else { throw InvalidValueTypeException("Invalid value type for " + AUTHORITY_KEY); } } } } { const auto pVal = properties.get(CODE_KEY); if (pVal) { if (auto genVal = dynamic_cast(pVal->get())) { if (genVal->type() == BoxedValue::Type::INTEGER) { code_ = toString(genVal->integerValue()); } else if (genVal->type() == BoxedValue::Type::STRING) { code_ = genVal->stringValue(); } else { throw InvalidValueTypeException("Invalid value type for " + CODE_KEY); } } else { throw InvalidValueTypeException("Invalid value type for " + CODE_KEY); } } } properties.getStringValue(CODESPACE_KEY, codeSpace_); properties.getStringValue(VERSION_KEY, version_); properties.getStringValue(DESCRIPTION_KEY, description_); properties.getStringValue(URI_KEY, uri_); } //! @endcond // --------------------------------------------------------------------------- Identifier::Identifier(const std::string &codeIn, const util::PropertyMap &properties) : d(std::make_unique(codeIn, properties)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- Identifier::Identifier() : d(std::make_unique()) {} // --------------------------------------------------------------------------- Identifier::Identifier(const Identifier &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- Identifier::~Identifier() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a Identifier. * * @param codeIn Alphanumeric value identifying an instance in the codespace * @param properties See \ref general_properties. * Generally, the Identifier::CODESPACE_KEY should be set. * @return a new Identifier. */ IdentifierNNPtr Identifier::create(const std::string &codeIn, const PropertyMap &properties) { return Identifier::nn_make_shared(codeIn, properties); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress IdentifierNNPtr Identifier::createFromDescription(const std::string &descriptionIn) { auto id = Identifier::nn_make_shared(); id->d->description_ = descriptionIn; return id; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a citation for the organization responsible for definition and * maintenance of the code. * * @return the citation for the authority, or empty. */ const optional &Identifier::authority() PROJ_PURE_DEFN { return d->authority_; } // --------------------------------------------------------------------------- /** \brief Return the alphanumeric value identifying an instance in the * codespace. * * e.g. "4326" (for EPSG:4326 WGS 84 GeographicCRS) * * @return the code. */ const std::string &Identifier::code() PROJ_PURE_DEFN { return d->code_; } // --------------------------------------------------------------------------- /** \brief Return the organization responsible for definition and maintenance of * the code. * * e.g "EPSG" * * @return the authority codespace, or empty. */ const optional &Identifier::codeSpace() PROJ_PURE_DEFN { return d->codeSpace_; } // --------------------------------------------------------------------------- /** \brief Return the version identifier for the namespace. * * When appropriate, the edition is identified by the effective date, coded * using ISO 8601 date format. * * @return the version or empty. */ const optional &Identifier::version() PROJ_PURE_DEFN { return d->version_; } // --------------------------------------------------------------------------- /** \brief Return the natural language description of the meaning of the code * value. * * @return the description or empty. */ const optional &Identifier::description() PROJ_PURE_DEFN { return d->description_; } // --------------------------------------------------------------------------- /** \brief Return the URI of the identifier. * * @return the URI or empty. */ const optional &Identifier::uri() PROJ_PURE_DEFN { return d->uri_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Identifier::_exportToWKT(WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; const std::string &l_code = code(); std::string l_codeSpace = *codeSpace(); std::string l_version = *version(); const auto &dbContext = formatter->databaseContext(); if (dbContext) { dbContext->getAuthorityAndVersion(*codeSpace(), l_codeSpace, l_version); } if (!l_codeSpace.empty() && !l_code.empty()) { if (isWKT2) { formatter->startNode(WKTConstants::ID, false); formatter->addQuotedString(l_codeSpace); try { (void)std::stoi(l_code); formatter->add(l_code); } catch (const std::exception &) { formatter->addQuotedString(l_code); } if (!l_version.empty()) { bool isDouble = false; (void)c_locale_stod(l_version, isDouble); if (isDouble) { formatter->add(l_version); } else { formatter->addQuotedString(l_version); } } if (authority().has_value() && *(authority()->title()) != *codeSpace()) { formatter->startNode(WKTConstants::CITATION, false); formatter->addQuotedString(*(authority()->title())); formatter->endNode(); } if (uri().has_value()) { formatter->startNode(WKTConstants::URI, false); formatter->addQuotedString(*(uri())); formatter->endNode(); } formatter->endNode(); } else { formatter->startNode(WKTConstants::AUTHORITY, false); formatter->addQuotedString(l_codeSpace); formatter->addQuotedString(l_code); formatter->endNode(); } } } // --------------------------------------------------------------------------- void Identifier::_exportToJSON(JSONFormatter *formatter) const { const std::string &l_code = code(); std::string l_codeSpace = *codeSpace(); std::string l_version = *version(); const auto &dbContext = formatter->databaseContext(); if (dbContext) { dbContext->getAuthorityAndVersion(*codeSpace(), l_codeSpace, l_version); } if (!l_codeSpace.empty() && !l_code.empty()) { auto writer = formatter->writer(); auto objContext(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("authority"); writer->Add(l_codeSpace); writer->AddObjKey("code"); try { writer->Add(std::stoi(l_code)); } catch (const std::exception &) { writer->Add(l_code); } if (!l_version.empty()) { writer->AddObjKey("version"); bool isDouble = false; (void)c_locale_stod(l_version, isDouble); if (isDouble) { writer->AddUnquoted(l_version.c_str()); } else { writer->Add(l_version); } } if (authority().has_value() && *(authority()->title()) != *codeSpace()) { writer->AddObjKey("authority_citation"); writer->Add(*(authority()->title())); } if (uri().has_value()) { writer->AddObjKey("uri"); writer->Add(*(uri())); } } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool isIgnoredChar(char ch) { return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' || ch == ')' || ch == '.' || ch == '&' || ch == ','; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static char lower(char ch) { return ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const struct utf8_to_lower { const char *utf8; char ascii; } map_utf8_to_lower[] = { {"\xc3\xa1", 'a'}, // a acute {"\xc3\xa4", 'a'}, // a tremma {"\xc4\x9b", 'e'}, // e reverse circumflex {"\xc3\xa8", 'e'}, // e grave {"\xc3\xa9", 'e'}, // e acute {"\xc3\xab", 'e'}, // e tremma {"\xc3\xad", 'i'}, // i grave {"\xc3\xb4", 'o'}, // o circumflex {"\xc3\xb6", 'o'}, // o tremma {"\xc3\xa7", 'c'}, // c cedilla }; static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) { for (const auto &pair : map_utf8_to_lower) { if (*c_str == pair.utf8[0] && strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) { return &pair; } } return nullptr; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** Checks if needle is a substring of c_str. * * e.g matchesLowerCase("JavaScript", "java") returns true */ static bool matchesLowerCase(const char *c_str, const char *needle) { size_t i = 0; for (; c_str[i] && needle[i]; ++i) { if (lower(c_str[i]) != lower(needle[i])) { return false; } } return needle[i] == 0; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static inline bool isdigit(char ch) { return ch >= '0' && ch <= '9'; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::string Identifier::canonicalizeName(const std::string &str, bool biggerDifferencesAllowed) { std::string res; const char *c_str = str.c_str(); for (size_t i = 0; c_str[i] != 0; ++i) { const auto ch = lower(c_str[i]); if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') { i += 2; continue; } // Canonicalize "19dd" (where d is a digit) as "dd" if (ch == '1' && !res.empty() && !isdigit(res.back()) && c_str[i + 1] == '9' && isdigit(c_str[i + 2]) && isdigit(c_str[i + 3])) { ++i; continue; } if (biggerDifferencesAllowed) { const auto skipSubstring = [](char l_ch, const char *l_str, size_t &idx, const char *substr) { if (l_ch == substr[0] && idx > 0 && isIgnoredChar(l_str[idx - 1]) && matchesLowerCase(l_str + idx, substr)) { idx += strlen(substr) - 1; return true; } return false; }; // Skip "zone" or "height" if preceding character is a space if (skipSubstring(ch, c_str, i, "zone") || skipSubstring(ch, c_str, i, "height")) { continue; } // Replace a substring by its first character if preceding character // is a space or a digit const auto replaceByFirstChar = [](char l_ch, const char *l_str, size_t &idx, const char *substr, std::string &l_res) { if (l_ch == substr[0] && idx > 0 && (isIgnoredChar(l_str[idx - 1]) || isdigit(l_str[idx - 1])) && matchesLowerCase(l_str + idx, substr)) { l_res.push_back(l_ch); idx += strlen(substr) - 1; return true; } return false; }; // Replace "north" or "south" by its first character if preceding // character is a space or a digit if (replaceByFirstChar(ch, c_str, i, "north", res) || replaceByFirstChar(ch, c_str, i, "south", res)) { continue; } } if (static_cast(ch) > 127) { const auto *replacement = get_ascii_replacement(c_str + i); if (replacement) { res.push_back(replacement->ascii); i += strlen(replacement->utf8) - 1; continue; } } if (matchesLowerCase(c_str + i, "_IntlFeet") && c_str[i + strlen("_IntlFeet")] == 0) { res += "feet"; break; } if (!isIgnoredChar(ch)) { res.push_back(ch); } } return res; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns whether two names are considered equivalent. * * Two names are equivalent by removing any space, underscore, dash, slash, * { or } character from them, and comparing in a case insensitive way. * * @param a first string * @param b second string * @param biggerDifferencesAllowed if true, "height" and "zone" words are * ignored, and "north" is shortened as "n" and "south" as "n". * @since 9.6 */ bool Identifier::isEquivalentName(const char *a, const char *b, bool biggerDifferencesAllowed) noexcept { size_t i = 0; size_t j = 0; char lastValidA = 0; char lastValidB = 0; while (a[i] != 0 || b[j] != 0) { char aCh = lower(a[i]); char bCh = lower(b[j]); if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ' && a[i + 3] != 0) { i += 3; continue; } if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ' && b[j + 3] != 0) { j += 3; continue; } if (matchesLowerCase(a + i, "_IntlFeet") && a[i + strlen("_IntlFeet")] == 0 && matchesLowerCase(b + j, "_Feet") && b[j + strlen("_Feet")] == 0) { return true; } else if (matchesLowerCase(a + i, "_Feet") && a[i + strlen("_Feet")] == 0 && matchesLowerCase(b + j, "_IntlFeet") && b[j + strlen("_IntlFeet")] == 0) { return true; } if (isIgnoredChar(aCh)) { ++i; continue; } if (isIgnoredChar(bCh)) { ++j; continue; } // Canonicalize "19dd" (where d is a digit) as "dd" if (aCh == '1' && !isdigit(lastValidA) && a[i + 1] == '9' && isdigit(a[i + 2]) && isdigit(a[i + 3])) { i += 2; lastValidA = '9'; continue; } if (bCh == '1' && !isdigit(lastValidB) && b[j + 1] == '9' && isdigit(b[j + 2]) && isdigit(b[j + 3])) { j += 2; lastValidB = '9'; continue; } if (biggerDifferencesAllowed) { // Skip a substring if preceding character is a space const auto skipSubString = [](char ch, const char *str, size_t &idx, const char *substr) { if (ch == substr[0] && idx > 0 && isIgnoredChar(str[idx - 1]) && matchesLowerCase(str + idx, substr)) { idx += strlen(substr); return true; } return false; }; bool skip = false; if (skipSubString(aCh, a, i, "zone")) skip = true; if (skipSubString(bCh, b, j, "zone")) skip = true; if (skip) continue; if (skipSubString(aCh, a, i, "height")) skip = true; if (skipSubString(bCh, b, j, "height")) skip = true; if (skip) continue; // Replace a substring by its first character if preceding character // is a space or a digit const auto replaceByFirstChar = [](char ch, const char *str, size_t &idx, const char *substr) { if (ch == substr[0] && idx > 0 && (isIgnoredChar(str[idx - 1]) || isdigit(str[idx - 1])) && matchesLowerCase(str + idx, substr)) { idx += strlen(substr) - 1; return true; } return false; }; if (!replaceByFirstChar(aCh, a, i, "north")) replaceByFirstChar(aCh, a, i, "south"); if (!replaceByFirstChar(bCh, b, j, "north")) replaceByFirstChar(bCh, b, j, "south"); } if (static_cast(aCh) > 127) { const auto *replacement = get_ascii_replacement(a + i); if (replacement) { aCh = replacement->ascii; i += strlen(replacement->utf8) - 1; } } if (static_cast(bCh) > 127) { const auto *replacement = get_ascii_replacement(b + j); if (replacement) { bCh = replacement->ascii; j += strlen(replacement->utf8) - 1; } } if (aCh != bCh) { return false; } lastValidA = aCh; lastValidB = bCh; if (aCh != 0) ++i; if (bCh != 0) ++j; } return true; } // --------------------------------------------------------------------------- /** \brief Returns whether two names are considered equivalent. * * Two names are equivalent by removing any space, underscore, dash, slash, * { or } character from them, and comparing in a case insensitive way. */ bool Identifier::isEquivalentName(const char *a, const char *b) noexcept { return isEquivalentName(a, b, /* biggerDifferencesAllowed = */ true); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PositionalAccuracy::Private { std::string value_{}; }; //! @endcond // --------------------------------------------------------------------------- PositionalAccuracy::PositionalAccuracy(const std::string &valueIn) : d(std::make_unique()) { d->value_ = valueIn; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PositionalAccuracy::~PositionalAccuracy() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the value of the positional accuracy. */ const std::string &PositionalAccuracy::value() PROJ_PURE_DEFN { return d->value_; } // --------------------------------------------------------------------------- /** \brief Instantiate a PositionalAccuracy. * * @param valueIn positional accuracy value. * @return a new PositionalAccuracy. */ PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) { return PositionalAccuracy::nn_make_shared(valueIn); } } // namespace metadata NS_PROJ_END proj-9.8.1/src/iso19111/c_api.cpp000664 001750 001750 00001406645 15166171715 016230 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: C API wrapper of C++ API * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include #include #include #include #include #include #include #include #include #include #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/datum_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" #include "proj_experimental.h" // clang-format on #include "geodesic.h" #include "proj_constants.h" using namespace NS_PROJ::common; using namespace NS_PROJ::coordinates; using namespace NS_PROJ::crs; using namespace NS_PROJ::cs; using namespace NS_PROJ::datum; using namespace NS_PROJ::io; using namespace NS_PROJ::internal; using namespace NS_PROJ::metadata; using namespace NS_PROJ::operation; using namespace NS_PROJ::util; using namespace NS_PROJ; // --------------------------------------------------------------------------- static void PROJ_NO_INLINE proj_log_error(PJ_CONTEXT *ctx, const char *function, const char *text) { if (ctx->debug_level != PJ_LOG_NONE) { std::string msg(function); msg += ": "; msg += text; ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, msg.c_str()); } auto previous_errno = proj_context_errno(ctx); if (previous_errno == 0) { // only set errno if it wasn't set deeper down the call stack proj_context_errno_set(ctx, PROJ_ERR_OTHER); } } // --------------------------------------------------------------------------- static void PROJ_NO_INLINE proj_log_debug(PJ_CONTEXT *ctx, const char *function, const char *text) { std::string msg(function); msg += ": "; msg += text; ctx->logger(ctx->logger_app_data, PJ_LOG_DEBUG, msg.c_str()); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- template static PROJ_STRING_LIST to_string_list(T &&set) { auto ret = new char *[set.size() + 1]; size_t i = 0; for (const auto &str : set) { try { ret[i] = new char[str.size() + 1]; } catch (const std::exception &) { while (--i > 0) { delete[] ret[i]; } delete[] ret; throw; } std::memcpy(ret[i], str.c_str(), str.size() + 1); i++; } ret[i] = nullptr; return ret; } // --------------------------------------------------------------------------- void proj_context_delete_cpp_context(struct projCppContext *cppContext) { delete cppContext; } // --------------------------------------------------------------------------- projCppContext::projCppContext(PJ_CONTEXT *ctx, const char *dbPath, const std::vector &auxDbPaths) : ctx_(ctx), dbPath_(dbPath ? dbPath : std::string()), auxDbPaths_(auxDbPaths) {} // --------------------------------------------------------------------------- std::vector projCppContext::toVector(const char *const *auxDbPaths) { std::vector res; for (auto iter = auxDbPaths; iter && *iter; ++iter) { res.emplace_back(std::string(*iter)); } return res; } // --------------------------------------------------------------------------- projCppContext *projCppContext::clone(PJ_CONTEXT *ctx) const { projCppContext *newContext = new projCppContext(ctx, getDbPath().c_str(), getAuxDbPaths()); return newContext; } // --------------------------------------------------------------------------- NS_PROJ::io::DatabaseContextNNPtr projCppContext::getDatabaseContext() { if (databaseContext_) { return NN_NO_CHECK(databaseContext_); } auto dbContext = NS_PROJ::io::DatabaseContext::create(dbPath_, auxDbPaths_, ctx_); databaseContext_ = dbContext; return dbContext; } // --------------------------------------------------------------------------- static PROJ_NO_INLINE DatabaseContextNNPtr getDBcontext(PJ_CONTEXT *ctx) { return ctx->get_cpp_context()->getDatabaseContext(); } // --------------------------------------------------------------------------- static PROJ_NO_INLINE DatabaseContextPtr getDBcontextNoException(PJ_CONTEXT *ctx, const char *function) { try { return getDBcontext(ctx).as_nullable(); } catch (const std::exception &e) { proj_log_debug(ctx, function, e.what()); return nullptr; } } // --------------------------------------------------------------------------- PJ *pj_obj_create(PJ_CONTEXT *ctx, const BaseObjectNNPtr &objIn) { auto coordop = dynamic_cast(objIn.get()); if (coordop) { auto singleOp = dynamic_cast(coordop); bool bTryToExportToProj = true; if (singleOp && singleOp->method()->nameStr() == "unnamed") { // Can happen for example when the GDAL GeoTIFF SRS builder // creates a dummy conversion when building the SRS, before setting // the final map projection. This avoids exportToPROJString() from // throwing an exception. bTryToExportToProj = false; } if (bTryToExportToProj) { try { // Use the database context if already open (e.g. when // coming from proj_create_from_database), so that // substitutePROJAlternativeGridNames() can resolve // grid names via the grid_alternatives table. // Do NOT open the database here — callers such as // proj_create() with a plain pipeline string may run // without proj.db (see commit 63c491eda3). auto dbContext = ctx->cpp_context ? ctx->get_cpp_context()->getDatabaseContextIfOpen() : nullptr; auto formatter = PROJStringFormatter::create( PROJStringFormatter::Convention::PROJ_5, dbContext); auto projString = coordop->exportToPROJString(formatter.get()); const bool defer_grid_opening_backup = ctx->defer_grid_opening; if (!defer_grid_opening_backup && proj_context_is_network_enabled(ctx)) { ctx->defer_grid_opening = true; } auto pj = pj_create_internal(ctx, projString.c_str()); ctx->defer_grid_opening = defer_grid_opening_backup; if (pj) { pj->iso_obj = objIn; pj->iso_obj_is_coordinate_operation = true; auto sourceEpoch = coordop->sourceCoordinateEpoch(); auto targetEpoch = coordop->targetCoordinateEpoch(); if (sourceEpoch.has_value()) { if (!targetEpoch.has_value()) { pj->hasCoordinateEpoch = true; pj->coordinateEpoch = sourceEpoch->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR); } } else { if (targetEpoch.has_value()) { pj->hasCoordinateEpoch = true; pj->coordinateEpoch = targetEpoch->coordinateEpoch().convertToUnit( common::UnitOfMeasure::YEAR); } } return pj; } } catch (const std::exception &) { // Silence, since we may not always be able to export as a // PROJ string. } } } auto pj = pj_new(); if (pj) { pj->ctx = ctx; pj->descr = "ISO-19111 object"; pj->iso_obj = objIn; pj->iso_obj_is_coordinate_operation = coordop != nullptr; try { auto crs = dynamic_cast(objIn.get()); if (crs) { auto geodCRS = crs->extractGeodeticCRS(); if (geodCRS) { const auto &ellps = geodCRS->ellipsoid(); const double a = ellps->semiMajorAxis().getSIValue(); const double es = ellps->squaredEccentricity(); if (!(a > 0 && es >= 0 && es < 1)) { proj_log_error(pj, _("Invalid ellipsoid parameters")); proj_errno_set(pj, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); proj_destroy(pj); return nullptr; } pj_calc_ellipsoid_params(pj, a, es); assert(pj->geod == nullptr); pj->geod = static_cast( calloc(1, sizeof(struct geod_geodesic))); if (pj->geod) { geod_init(pj->geod, pj->a, pj->es / (1 + sqrt(pj->one_es))); } } } } catch (const std::exception &) { } } return pj; } //! @endcond // --------------------------------------------------------------------------- /** \brief Opaque object representing a set of operation results. */ struct PJ_OBJ_LIST { //! @cond Doxygen_Suppress std::vector objects; explicit PJ_OBJ_LIST(std::vector &&objectsIn) : objects(std::move(objectsIn)) {} virtual ~PJ_OBJ_LIST(); PJ_OBJ_LIST(const PJ_OBJ_LIST &) = delete; PJ_OBJ_LIST &operator=(const PJ_OBJ_LIST &) = delete; //! @endcond }; //! @cond Doxygen_Suppress PJ_OBJ_LIST::~PJ_OBJ_LIST() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress #define SANITIZE_CTX(ctx) \ do { \ if (ctx == nullptr) { \ ctx = pj_get_default_ctx(); \ } \ } while (0) //! @endcond // --------------------------------------------------------------------------- /** \brief Starting with PROJ 8.1, this function does nothing. * * If you want to take into account changes to the PROJ database, you need to * re-create a new context. * * @param ctx Ignored * @param autoclose Ignored * @since 6.2 * deprecated Since 8.1 */ void proj_context_set_autoclose_database(PJ_CONTEXT *ctx, int autoclose) { (void)ctx; (void)autoclose; } // --------------------------------------------------------------------------- /** \brief Explicitly point to the main PROJ CRS and coordinate operation * definition database ("proj.db"), and potentially auxiliary databases with * same structure. * * Starting with PROJ 8.1, if the auxDbPaths parameter is an empty array, * the PROJ_AUX_DB environment variable will be used, if set. * It must contain one or several paths. If several paths are * provided, they must be separated by the colon (:) character on Unix, and * on Windows, by the semi-colon (;) character. * * @param ctx PROJ context, or NULL for default context * @param dbPath Path to main database, or NULL for default. * @param auxDbPaths NULL-terminated list of auxiliary database filenames, or * NULL. * @param options should be set to NULL for now * @return TRUE in case of success */ int proj_context_set_database_path(PJ_CONTEXT *ctx, const char *dbPath, const char *const *auxDbPaths, const char *const *options) { SANITIZE_CTX(ctx); (void)options; std::string osPrevDbPath; std::vector osPrevAuxDbPaths; if (ctx->cpp_context) { osPrevDbPath = ctx->cpp_context->getDbPath(); osPrevAuxDbPaths = ctx->cpp_context->getAuxDbPaths(); } delete ctx->cpp_context; ctx->cpp_context = nullptr; try { ctx->cpp_context = new projCppContext( ctx, dbPath, projCppContext::toVector(auxDbPaths)); ctx->cpp_context->getDatabaseContext(); return true; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); delete ctx->cpp_context; ctx->cpp_context = new projCppContext(ctx, osPrevDbPath.c_str(), osPrevAuxDbPaths); return false; } } // --------------------------------------------------------------------------- /** \brief Returns the path to the database. * * The returned pointer remains valid while ctx is valid, and until * proj_context_set_database_path() is called. * * @param ctx PROJ context, or NULL for default context * @return path, or nullptr */ const char *proj_context_get_database_path(PJ_CONTEXT *ctx) { SANITIZE_CTX(ctx); try { // temporary variable must be used as getDBcontext() might create // ctx->cpp_context const std::string osPath(getDBcontext(ctx)->getPath()); ctx->get_cpp_context()->lastDbPath_ = osPath; return ctx->cpp_context->lastDbPath_.c_str(); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Return a metadata from the database. * * The returned pointer remains valid while ctx is valid, and until * proj_context_get_database_metadata() is called. * * Available keys: * * - DATABASE.LAYOUT.VERSION.MAJOR * - DATABASE.LAYOUT.VERSION.MINOR * - EPSG.VERSION * - EPSG.DATE * - ESRI.VERSION * - ESRI.DATE * - IGNF.SOURCE * - IGNF.VERSION * - IGNF.DATE * - NKG.SOURCE * - NKG.VERSION * - NKG.DATE * - PROJ.VERSION * - PROJ_DATA.VERSION : PROJ-data version most compatible with this database. * * * @param ctx PROJ context, or NULL for default context * @param key Metadata key. Must not be NULL * @return value, or nullptr */ const char *proj_context_get_database_metadata(PJ_CONTEXT *ctx, const char *key) { SANITIZE_CTX(ctx); if (!key) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } try { // temporary variable must be used as getDBcontext() might create // ctx->cpp_context auto osVal(getDBcontext(ctx)->getMetadata(key)); if (osVal == nullptr) { return nullptr; } ctx->get_cpp_context()->lastDbMetadataItem_ = osVal; return ctx->cpp_context->lastDbMetadataItem_.c_str(); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Return the database structure * * Return SQL statements to run to initiate a new valid auxiliary empty * database. It contains definitions of tables, views and triggers, as well * as metadata for the version of the layout of the database. * * @param ctx PROJ context, or NULL for default context * @param options null-terminated list of options, or NULL. None currently. * @return list of SQL statements (to be freed with proj_string_list_destroy()), * or NULL in case of error. * @since 8.1 */ PROJ_STRING_LIST proj_context_get_database_structure(PJ_CONTEXT *ctx, const char *const *options) { SANITIZE_CTX(ctx); (void)options; try { auto ret = to_string_list(getDBcontext(ctx)->getDatabaseStructure()); return ret; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Guess the "dialect" of the WKT string. * * @param ctx PROJ context, or NULL for default context * @param wkt String (must not be NULL) */ PJ_GUESSED_WKT_DIALECT proj_context_guess_wkt_dialect(PJ_CONTEXT *ctx, const char *wkt) { (void)ctx; if (!wkt) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return PJ_GUESSED_NOT_WKT; } switch (WKTParser().guessDialect(wkt)) { case WKTParser::WKTGuessedDialect::WKT2_2019: return PJ_GUESSED_WKT2_2019; case WKTParser::WKTGuessedDialect::WKT2_2015: return PJ_GUESSED_WKT2_2015; case WKTParser::WKTGuessedDialect::WKT1_GDAL: return PJ_GUESSED_WKT1_GDAL; case WKTParser::WKTGuessedDialect::WKT1_ESRI: return PJ_GUESSED_WKT1_ESRI; case WKTParser::WKTGuessedDialect::NOT_WKT: break; } return PJ_GUESSED_NOT_WKT; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const char *getOptionValue(const char *option, const char *keyWithEqual) noexcept { if (ci_starts_with(option, keyWithEqual)) { return option + strlen(keyWithEqual); } return nullptr; } //! @endcond // --------------------------------------------------------------------------- /** \brief "Clone" an object. * * The object might be used independently of the original object, provided that * the use of context is compatible. In particular if you intend to use a * clone in a different thread than the original object, you should pass a * context that is different from the one of the original object (or later * assign a different context with proj_assign_context()). * * The returned object must be unreferenced with proj_destroy() after use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object to clone. Must not be NULL. * @return Object that must be unreferenced with proj_destroy(), or NULL in * case of error. */ PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } if (!obj->iso_obj) { if (!obj->alternativeCoordinateOperations.empty()) { auto newPj = pj_new(); if (newPj) { newPj->descr = "Set of coordinate operations"; newPj->ctx = ctx; newPj->copyStateFrom(*obj); ctx->forceOver = obj->over != 0; const int old_debug_level = ctx->debug_level; ctx->debug_level = PJ_LOG_NONE; for (const auto &altOp : obj->alternativeCoordinateOperations) { newPj->alternativeCoordinateOperations.emplace_back( PJCoordOperation(ctx, altOp)); } ctx->forceOver = false; ctx->debug_level = old_debug_level; } return newPj; } return nullptr; } try { ctx->forceOver = obj->over != 0; PJ *newPj = pj_obj_create(ctx, NN_NO_CHECK(obj->iso_obj)); ctx->forceOver = false; if (newPj) { newPj->copyStateFrom(*obj); } return newPj; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate an object from a WKT string, PROJ string, object code * (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326", * "urn:ogc:def:coordinateOperation:EPSG::1671"), a PROJJSON string, an object * name (e.g "WGS 84") of a compound CRS build from object names * (e.g "WGS 84 + EGM96 height") * * This function calls osgeo::proj::io::createFromUserInput() * * The returned object must be unreferenced with proj_destroy() after use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param text String (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL in * case of error. */ PJ *proj_create(PJ_CONTEXT *ctx, const char *text) { SANITIZE_CTX(ctx); if (!text) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } // Only connect to proj.db if needed if (strstr(text, "proj=") == nullptr || strstr(text, "init=") != nullptr) { getDBcontextNoException(ctx, __FUNCTION__); } try { auto obj = nn_dynamic_pointer_cast(createFromUserInput(text, ctx)); if (obj) { return pj_obj_create(ctx, NN_NO_CHECK(obj)); } } catch (const io::ParsingException &e) { if (proj_context_errno(ctx) == 0) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } proj_log_error(ctx, __FUNCTION__, e.what()); } catch (const NoSuchAuthorityCodeException &e) { proj_log_error(ctx, __FUNCTION__, std::string(e.what()) .append(": ") .append(e.getAuthority()) .append(":") .append(e.getAuthorityCode()) .c_str()); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate an object from a WKT string. * * This function calls osgeo::proj::io::WKTParser::createFromWKT() * * The returned object must be unreferenced with proj_destroy() after use. * It should be used by at most one thread at a time. * * The distinction between warnings and grammar errors is somewhat artificial * and does not tell much about the real criticity of the non-compliance. * Some warnings may be more concerning than some grammar errors. Human * expertise (or, by the time this comment will be read, specialized AI) is * generally needed to perform that assessment. * * @param ctx PROJ context, or NULL for default context * @param wkt WKT string (must not be NULL) * @param options null-terminated list of options, or NULL. Currently * supported options are: *
    *
  • STRICT=YES/NO. Defaults to NO. When set to YES, strict validation will * be enabled.
  • *
  • UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=YES/NO. Defaults to YES. * When set to YES, object identifiers are unset when there is * a contradiction between the definition from WKT and the one from * the database./
  • *
* @param out_warnings Pointer to a PROJ_STRING_LIST object, or NULL. * If provided, *out_warnings will contain a list of warnings, typically for * non recognized projection method or parameters, or other issues found during * WKT analys. It must be freed with proj_string_list_destroy(). * @param out_grammar_errors Pointer to a PROJ_STRING_LIST object, or NULL. * If provided, *out_grammar_errors will contain a list of errors regarding the * WKT grammar. It must be freed with proj_string_list_destroy(). * @return Object that must be unreferenced with proj_destroy(), or NULL in * case of error. */ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, const char *const *options, PROJ_STRING_LIST *out_warnings, PROJ_STRING_LIST *out_grammar_errors) { SANITIZE_CTX(ctx); if (!wkt) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } if (out_warnings) { *out_warnings = nullptr; } if (out_grammar_errors) { *out_grammar_errors = nullptr; } try { WKTParser parser; auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); if (dbContext) { parser.attachDatabaseContext(NN_NO_CHECK(dbContext)); } parser.setStrict(false); for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "STRICT="))) { parser.setStrict(ci_equal(value, "YES")); } else if ((value = getOptionValue( *iter, "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF="))) { parser.setUnsetIdentifiersIfIncompatibleDef( ci_equal(value, "YES")); } else { std::string msg("Unknown option :"); msg += *iter; proj_log_error(ctx, __FUNCTION__, msg.c_str()); return nullptr; } } auto obj = parser.createFromWKT(wkt); if (out_grammar_errors) { auto grammarErrors = parser.grammarErrorList(); if (!grammarErrors.empty()) { *out_grammar_errors = to_string_list(grammarErrors); } } if (out_warnings) { auto warnings = parser.warningList(); auto derivedCRS = dynamic_cast(obj.get()); if (derivedCRS) { auto extraWarnings = derivedCRS->derivingConversionRef()->validateParameters(); warnings.insert(warnings.end(), extraWarnings.begin(), extraWarnings.end()); } else { auto singleOp = dynamic_cast(obj.get()); if (singleOp) { auto extraWarnings = singleOp->validateParameters(); warnings.insert(warnings.end(), extraWarnings.begin(), extraWarnings.end()); } } if (!warnings.empty()) { *out_warnings = to_string_list(warnings); } } return pj_obj_create(ctx, NN_NO_CHECK(obj)); } catch (const std::exception &e) { if (out_grammar_errors) { std::list exc{e.what()}; try { *out_grammar_errors = to_string_list(exc); } catch (const std::exception &) { proj_log_error(ctx, __FUNCTION__, e.what()); } } else { proj_log_error(ctx, __FUNCTION__, e.what()); } } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate an object from a database lookup. * * The returned object must be unreferenced with proj_destroy() after use. * It should be used by at most one thread at a time. * * @param ctx Context, or NULL for default context. * @param auth_name Authority name (must not be NULL) * @param code Object code (must not be NULL) * @param category Object category * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. Only used on * transformations * @param options should be set to NULL for now * @return Object that must be unreferenced with proj_destroy(), or NULL in * case of error. */ PJ *proj_create_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *code, PJ_CATEGORY category, int usePROJAlternativeGridNames, const char *const *options) { SANITIZE_CTX(ctx); if (!auth_name || !code) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } (void)options; try { const std::string codeStr(code); auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); IdentifiedObjectPtr obj; switch (category) { case PJ_CATEGORY_ELLIPSOID: obj = factory->createEllipsoid(codeStr).as_nullable(); break; case PJ_CATEGORY_PRIME_MERIDIAN: obj = factory->createPrimeMeridian(codeStr).as_nullable(); break; case PJ_CATEGORY_DATUM: obj = factory->createDatum(codeStr).as_nullable(); break; case PJ_CATEGORY_CRS: obj = factory->createCoordinateReferenceSystem(codeStr).as_nullable(); break; case PJ_CATEGORY_COORDINATE_OPERATION: obj = factory ->createCoordinateOperation( codeStr, usePROJAlternativeGridNames != 0) .as_nullable(); break; case PJ_CATEGORY_DATUM_ENSEMBLE: obj = factory->createDatumEnsemble(codeStr).as_nullable(); break; } return pj_obj_create(ctx, NN_NO_CHECK(obj)); } catch (const NoSuchAuthorityCodeException &e) { proj_log_error(ctx, __FUNCTION__, std::string(e.what()) .append(": ") .append(e.getAuthority()) .append(":") .append(e.getAuthorityCode()) .c_str()); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const char *get_unit_category(const std::string &unit_name, UnitOfMeasure::Type type) { const char *ret = nullptr; switch (type) { case UnitOfMeasure::Type::UNKNOWN: ret = "unknown"; break; case UnitOfMeasure::Type::NONE: ret = "none"; break; case UnitOfMeasure::Type::ANGULAR: ret = unit_name.find(" per ") != std::string::npos ? "angular_per_time" : "angular"; break; case UnitOfMeasure::Type::LINEAR: ret = unit_name.find(" per ") != std::string::npos ? "linear_per_time" : "linear"; break; case UnitOfMeasure::Type::SCALE: ret = unit_name.find(" per year") != std::string::npos || unit_name.find(" per second") != std::string::npos ? "scale_per_time" : "scale"; break; case UnitOfMeasure::Type::TIME: ret = "time"; break; case UnitOfMeasure::Type::PARAMETRIC: ret = unit_name.find(" per ") != std::string::npos ? "parametric_per_time" : "parametric"; break; } return ret; } //! @endcond // --------------------------------------------------------------------------- /** \brief Get information for a unit of measure from a database lookup. * * @param ctx Context, or NULL for default context. * @param auth_name Authority name (must not be NULL) * @param code Unit of measure code (must not be NULL) * @param out_name Pointer to a string value to store the parameter name. or * NULL. This value remains valid until the next call to * proj_uom_get_info_from_database() or the context destruction. * @param out_conv_factor Pointer to a value to store the conversion * factor of the prime meridian longitude unit to radian. or NULL * @param out_category Pointer to a string value to store the parameter name. or * NULL. This value might be "unknown", "none", "linear", "linear_per_time", * "angular", "angular_per_time", "scale", "scale_per_time", "time", * "parametric" or "parametric_per_time" * @return TRUE in case of success */ int proj_uom_get_info_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *code, const char **out_name, double *out_conv_factor, const char **out_category) { SANITIZE_CTX(ctx); if (!auth_name || !code) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } try { auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); auto obj = factory->createUnitOfMeasure(code); if (out_name) { ctx->get_cpp_context()->lastUOMName_ = obj->name(); *out_name = ctx->cpp_context->lastUOMName_.c_str(); } if (out_conv_factor) { *out_conv_factor = obj->conversionToSI(); } if (out_category) { *out_category = get_unit_category(obj->name(), obj->type()); } return true; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return false; } // --------------------------------------------------------------------------- /** \brief Get information for a grid from a database lookup. * * @param ctx Context, or NULL for default context. * @param grid_name Grid name (must not be NULL) * @param out_full_name Pointer to a string value to store the grid full * filename. or NULL * @param out_package_name Pointer to a string value to store the package name * where * the grid might be found. or NULL * @param out_url Pointer to a string value to store the grid URL or the * package URL where the grid might be found. or NULL * @param out_direct_download Pointer to a int (boolean) value to store whether * *out_url can be downloaded directly. or NULL * @param out_open_license Pointer to a int (boolean) value to store whether * the grid is released with an open license. or NULL * @param out_available Pointer to a int (boolean) value to store whether the * grid is available at runtime. or NULL * @return TRUE in case of success. */ int proj_grid_get_info_from_database( PJ_CONTEXT *ctx, const char *grid_name, const char **out_full_name, const char **out_package_name, const char **out_url, int *out_direct_download, int *out_open_license, int *out_available) { SANITIZE_CTX(ctx); if (!grid_name) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } try { auto db_context = getDBcontext(ctx); bool direct_download; bool open_license; bool available; if (!db_context->lookForGridInfo( grid_name, false, ctx->get_cpp_context()->lastGridFullName_, ctx->get_cpp_context()->lastGridPackageName_, ctx->get_cpp_context()->lastGridUrl_, direct_download, open_license, available)) { return false; } if (out_full_name) *out_full_name = ctx->get_cpp_context()->lastGridFullName_.c_str(); if (out_package_name) *out_package_name = ctx->get_cpp_context()->lastGridPackageName_.c_str(); if (out_url) *out_url = ctx->get_cpp_context()->lastGridUrl_.c_str(); if (out_direct_download) *out_direct_download = direct_download ? 1 : 0; if (out_open_license) *out_open_license = open_license ? 1 : 0; if (out_available) *out_available = available ? 1 : 0; return true; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return false; } // --------------------------------------------------------------------------- /** \brief Return GeodeticCRS that use the specified datum. * * @param ctx Context, or NULL for default context. * @param crs_auth_name CRS authority name, or NULL. * @param datum_auth_name Datum authority name (must not be NULL) * @param datum_code Datum code (must not be NULL) * @param crs_type "geographic 2D", "geographic 3D", "geocentric" or NULL * @return a result set that must be unreferenced with * proj_list_destroy(), or NULL in case of error. */ PJ_OBJ_LIST *proj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_auth_name, const char *datum_auth_name, const char *datum_code, const char *crs_type) { SANITIZE_CTX(ctx); if (!datum_auth_name || !datum_code) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } try { auto factory = AuthorityFactory::create( getDBcontext(ctx), crs_auth_name ? crs_auth_name : ""); auto res = factory->createGeodeticCRSFromDatum( datum_auth_name, datum_code, crs_type ? crs_type : ""); std::vector objects; for (const auto &obj : res) { objects.push_back(obj); } return new PJ_OBJ_LIST(std::move(objects)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static AuthorityFactory::ObjectType convertPJObjectTypeToObjectType(PJ_TYPE type, bool &valid) { valid = true; AuthorityFactory::ObjectType cppType = AuthorityFactory::ObjectType::CRS; switch (type) { case PJ_TYPE_ELLIPSOID: cppType = AuthorityFactory::ObjectType::ELLIPSOID; break; case PJ_TYPE_PRIME_MERIDIAN: cppType = AuthorityFactory::ObjectType::PRIME_MERIDIAN; break; case PJ_TYPE_GEODETIC_REFERENCE_FRAME: cppType = AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME; break; case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME: cppType = AuthorityFactory::ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME; break; case PJ_TYPE_VERTICAL_REFERENCE_FRAME: cppType = AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME; break; case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME: cppType = AuthorityFactory::ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME; break; case PJ_TYPE_DATUM_ENSEMBLE: cppType = AuthorityFactory::ObjectType::DATUM_ENSEMBLE; break; case PJ_TYPE_TEMPORAL_DATUM: valid = false; break; case PJ_TYPE_ENGINEERING_DATUM: cppType = AuthorityFactory::ObjectType::ENGINEERING_DATUM; break; case PJ_TYPE_PARAMETRIC_DATUM: valid = false; break; case PJ_TYPE_CRS: cppType = AuthorityFactory::ObjectType::CRS; break; case PJ_TYPE_GEODETIC_CRS: cppType = AuthorityFactory::ObjectType::GEODETIC_CRS; break; case PJ_TYPE_GEOCENTRIC_CRS: cppType = AuthorityFactory::ObjectType::GEOCENTRIC_CRS; break; case PJ_TYPE_GEOGRAPHIC_CRS: cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_CRS; break; case PJ_TYPE_GEOGRAPHIC_2D_CRS: cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS; break; case PJ_TYPE_GEOGRAPHIC_3D_CRS: cppType = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS; break; case PJ_TYPE_VERTICAL_CRS: cppType = AuthorityFactory::ObjectType::VERTICAL_CRS; break; case PJ_TYPE_PROJECTED_CRS: cppType = AuthorityFactory::ObjectType::PROJECTED_CRS; break; case PJ_TYPE_DERIVED_PROJECTED_CRS: valid = false; break; case PJ_TYPE_COMPOUND_CRS: cppType = AuthorityFactory::ObjectType::COMPOUND_CRS; break; case PJ_TYPE_ENGINEERING_CRS: cppType = AuthorityFactory::ObjectType::ENGINEERING_CRS; break; case PJ_TYPE_TEMPORAL_CRS: valid = false; break; case PJ_TYPE_BOUND_CRS: valid = false; break; case PJ_TYPE_OTHER_CRS: cppType = AuthorityFactory::ObjectType::CRS; break; case PJ_TYPE_CONVERSION: cppType = AuthorityFactory::ObjectType::CONVERSION; break; case PJ_TYPE_TRANSFORMATION: cppType = AuthorityFactory::ObjectType::TRANSFORMATION; break; case PJ_TYPE_CONCATENATED_OPERATION: cppType = AuthorityFactory::ObjectType::CONCATENATED_OPERATION; break; case PJ_TYPE_OTHER_COORDINATE_OPERATION: cppType = AuthorityFactory::ObjectType::COORDINATE_OPERATION; break; case PJ_TYPE_UNKNOWN: valid = false; break; case PJ_TYPE_COORDINATE_METADATA: valid = false; break; } return cppType; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a list of objects by their name. * * @param ctx Context, or NULL for default context. * @param auth_name Authority name, used to restrict the search. * Or NULL for all authorities. * @param searchedName Searched name. Must be at least 2 character long. * @param types List of object types into which to search. If * NULL, all object types will be searched. * @param typesCount Number of elements in types, or 0 if types is NULL * @param approximateMatch Whether approximate name identification is allowed. * @param limitResultCount Maximum number of results to return. * Or 0 for unlimited. * @param options should be set to NULL for now * @return a result set that must be unreferenced with * proj_list_destroy(), or NULL in case of error. */ PJ_OBJ_LIST *proj_create_from_name(PJ_CONTEXT *ctx, const char *auth_name, const char *searchedName, const PJ_TYPE *types, size_t typesCount, int approximateMatch, size_t limitResultCount, const char *const *options) { SANITIZE_CTX(ctx); if (!searchedName || (types != nullptr && typesCount == 0) || (types == nullptr && typesCount > 0)) { proj_log_error(ctx, __FUNCTION__, "invalid input"); return nullptr; } (void)options; try { auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name ? auth_name : ""); std::vector allowedTypes; for (size_t i = 0; i < typesCount; ++i) { bool valid = false; auto type = convertPJObjectTypeToObjectType(types[i], valid); if (valid) { allowedTypes.push_back(type); } } auto res = factory->createObjectsFromName(searchedName, allowedTypes, approximateMatch != 0, limitResultCount); std::vector objects; for (const auto &obj : res) { objects.push_back(obj); } return new PJ_OBJ_LIST(std::move(objects)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Return the type of an object. * * @param obj Object (must not be NULL) * @return its type. */ PJ_TYPE proj_get_type(const PJ *obj) { if (!obj || !obj->iso_obj) { return PJ_TYPE_UNKNOWN; } if (obj->type != PJ_TYPE_UNKNOWN) return obj->type; const auto getType = [&obj]() { auto ptr = obj->iso_obj.get(); if (dynamic_cast(ptr)) { return PJ_TYPE_ELLIPSOID; } if (dynamic_cast(ptr)) { return PJ_TYPE_PRIME_MERIDIAN; } if (dynamic_cast(ptr)) { return PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME; } if (dynamic_cast(ptr)) { return PJ_TYPE_GEODETIC_REFERENCE_FRAME; } if (dynamic_cast(ptr)) { return PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME; } if (dynamic_cast(ptr)) { return PJ_TYPE_VERTICAL_REFERENCE_FRAME; } if (dynamic_cast(ptr)) { return PJ_TYPE_DATUM_ENSEMBLE; } if (dynamic_cast(ptr)) { return PJ_TYPE_TEMPORAL_DATUM; } if (dynamic_cast(ptr)) { return PJ_TYPE_ENGINEERING_DATUM; } if (dynamic_cast(ptr)) { return PJ_TYPE_PARAMETRIC_DATUM; } if (auto crs = dynamic_cast(ptr)) { if (crs->coordinateSystem()->axisList().size() == 2) { return PJ_TYPE_GEOGRAPHIC_2D_CRS; } else { return PJ_TYPE_GEOGRAPHIC_3D_CRS; } } if (auto crs = dynamic_cast(ptr)) { if (crs->isGeocentric()) { return PJ_TYPE_GEOCENTRIC_CRS; } else { return PJ_TYPE_GEODETIC_CRS; } } if (dynamic_cast(ptr)) { return PJ_TYPE_VERTICAL_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_PROJECTED_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_DERIVED_PROJECTED_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_COMPOUND_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_TEMPORAL_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_ENGINEERING_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_BOUND_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_OTHER_CRS; } if (dynamic_cast(ptr)) { return PJ_TYPE_CONVERSION; } if (dynamic_cast(ptr)) { return PJ_TYPE_TRANSFORMATION; } if (dynamic_cast(ptr)) { return PJ_TYPE_CONCATENATED_OPERATION; } if (dynamic_cast(ptr)) { return PJ_TYPE_OTHER_COORDINATE_OPERATION; } if (dynamic_cast(ptr)) { return PJ_TYPE_COORDINATE_METADATA; } return PJ_TYPE_UNKNOWN; }; obj->type = getType(); return obj->type; } // --------------------------------------------------------------------------- /** \brief Return whether an object is deprecated. * * @param obj Object (must not be NULL) * @return TRUE if it is deprecated, FALSE otherwise */ int proj_is_deprecated(const PJ *obj) { if (!obj) { return false; } auto identifiedObj = dynamic_cast(obj->iso_obj.get()); if (!identifiedObj) { return false; } return identifiedObj->isDeprecated(); } // --------------------------------------------------------------------------- /** \brief Return a list of non-deprecated objects related to the passed one * * @param ctx Context, or NULL for default context. * @param obj Object (of type CRS for now) for which non-deprecated objects * must be searched. Must not be NULL * @return a result set that must be unreferenced with * proj_list_destroy(), or NULL in case of error. */ PJ_OBJ_LIST *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto crs = dynamic_cast(obj->iso_obj.get()); if (!crs) { return nullptr; } try { std::vector objects; auto res = crs->getNonDeprecated(getDBcontext(ctx)); for (const auto &resObj : res) { objects.push_back(resObj); } return new PJ_OBJ_LIST(std::move(objects)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- static int proj_is_equivalent_to_internal(PJ_CONTEXT *ctx, const PJ *obj, const PJ *other, PJ_COMPARISON_CRITERION criterion) { if (!obj || !other) { if (ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); } return false; } if (obj->iso_obj == nullptr && other->iso_obj == nullptr && !obj->alternativeCoordinateOperations.empty() && obj->alternativeCoordinateOperations.size() == other->alternativeCoordinateOperations.size()) { for (size_t i = 0; i < obj->alternativeCoordinateOperations.size(); ++i) { if (obj->alternativeCoordinateOperations[i] != other->alternativeCoordinateOperations[i]) { return false; } } return true; } auto identifiedObj = dynamic_cast(obj->iso_obj.get()); if (!identifiedObj) { return false; } auto otherIdentifiedObj = dynamic_cast(other->iso_obj.get()); if (!otherIdentifiedObj) { return false; } const auto cppCriterion = ([](PJ_COMPARISON_CRITERION l_criterion) { switch (l_criterion) { case PJ_COMP_STRICT: return IComparable::Criterion::STRICT; case PJ_COMP_EQUIVALENT: return IComparable::Criterion::EQUIVALENT; case PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS: break; } return IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS; })(criterion); int res = identifiedObj->isEquivalentTo( otherIdentifiedObj, cppCriterion, ctx ? getDBcontextNoException(ctx, "proj_is_equivalent_to_with_ctx") : nullptr); return res; } // --------------------------------------------------------------------------- /** \brief Return whether two objects are equivalent. * * Use proj_is_equivalent_to_with_ctx() to be able to use database information. * * @param obj Object (must not be NULL) * @param other Other object (must not be NULL) * @param criterion Comparison criterion * @return TRUE if they are equivalent */ int proj_is_equivalent_to(const PJ *obj, const PJ *other, PJ_COMPARISON_CRITERION criterion) { return proj_is_equivalent_to_internal(nullptr, obj, other, criterion); } // --------------------------------------------------------------------------- /** \brief Return whether two objects are equivalent * * Possibly using database to check for name aliases. * * @param ctx PROJ context, or NULL for default context * @param obj Object (must not be NULL) * @param other Other object (must not be NULL) * @param criterion Comparison criterion * @return TRUE if they are equivalent * @since 6.3 */ int proj_is_equivalent_to_with_ctx(PJ_CONTEXT *ctx, const PJ *obj, const PJ *other, PJ_COMPARISON_CRITERION criterion) { SANITIZE_CTX(ctx); return proj_is_equivalent_to_internal(ctx, obj, other, criterion); } // --------------------------------------------------------------------------- /** \brief Return whether an object is a CRS * * @param obj Object (must not be NULL) */ int proj_is_crs(const PJ *obj) { if (!obj) { return false; } return dynamic_cast(obj->iso_obj.get()) != nullptr; } // --------------------------------------------------------------------------- /** \brief Get the name of an object. * * The lifetime of the returned string is the same as the input obj parameter. * * @param obj Object (must not be NULL) * @return a string, or NULL in case of error or missing name. */ const char *proj_get_name(const PJ *obj) { if (!obj) { return nullptr; } auto identifiedObj = dynamic_cast(obj->iso_obj.get()); if (!identifiedObj) { return nullptr; } const auto &desc = identifiedObj->name()->description(); if (!desc.has_value()) { return nullptr; } // The object will still be alive after the function call. // cppcheck-suppress stlcstr return desc->c_str(); } // --------------------------------------------------------------------------- /** \brief Get the remarks of an object. * * The lifetime of the returned string is the same as the input obj parameter. * * @param obj Object (must not be NULL) * @return a string, or NULL in case of error. */ const char *proj_get_remarks(const PJ *obj) { if (!obj) { return nullptr; } auto identifiedObj = dynamic_cast(obj->iso_obj.get()); if (!identifiedObj) { return nullptr; } // The object will still be alive after the function call. // cppcheck-suppress stlcstr return identifiedObj->remarks().c_str(); } // --------------------------------------------------------------------------- /** \brief Get the authority name / codespace of an identifier of an object. * * The lifetime of the returned string is the same as the input obj parameter. * * @param obj Object (must not be NULL) * @param index Index of the identifier. 0 = first identifier * @return a string, or NULL in case of error or missing name. */ const char *proj_get_id_auth_name(const PJ *obj, int index) { if (!obj) { return nullptr; } auto identifiedObj = dynamic_cast(obj->iso_obj.get()); if (!identifiedObj) { return nullptr; } const auto &ids = identifiedObj->identifiers(); if (static_cast(index) >= ids.size()) { return nullptr; } const auto &codeSpace = ids[index]->codeSpace(); if (!codeSpace.has_value()) { return nullptr; } // The object will still be alive after the function call. // cppcheck-suppress stlcstr return codeSpace->c_str(); } // --------------------------------------------------------------------------- /** \brief Get the code of an identifier of an object. * * The lifetime of the returned string is the same as the input obj parameter. * * @param obj Object (must not be NULL) * @param index Index of the identifier. 0 = first identifier * @return a string, or NULL in case of error or missing name. */ const char *proj_get_id_code(const PJ *obj, int index) { if (!obj) { return nullptr; } auto identifiedObj = dynamic_cast(obj->iso_obj.get()); if (!identifiedObj) { return nullptr; } const auto &ids = identifiedObj->identifiers(); if (static_cast(index) >= ids.size()) { return nullptr; } return ids[index]->code().c_str(); } // --------------------------------------------------------------------------- /** \brief Get a WKT representation of an object. * * The returned string is valid while the input obj parameter is valid, * and until a next call to proj_as_wkt() with the same input object. * * This function calls osgeo::proj::io::IWKTExportable::exportToWKT(). * * This function may return NULL if the object is not compatible with an * export to the requested type. * * @param ctx PROJ context, or NULL for default context * @param obj Object (must not be NULL) * @param type WKT version. * @param options null-terminated list of options, or NULL. Currently * supported options are: *
    *
  • MULTILINE=YES/NO. Defaults to YES, except for WKT1_ESRI
  • *
  • INDENTATION_WIDTH=number. Defaults to 4 (when multiline output is * on).
  • *
  • OUTPUT_AXIS=AUTO/YES/NO. In AUTO mode, axis will be output for WKT2 * variants, for WKT1_GDAL for ProjectedCRS with easting/northing ordering * (otherwise stripped), but not for WKT1_ESRI. Setting to YES will output * them unconditionally, and to NO will omit them unconditionally.
  • *
  • STRICT=YES/NO. Default is YES. If NO, a Geographic 3D CRS can be for * example exported as WKT1_GDAL with 3 axes, whereas this is normally not * allowed.
  • *
  • ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES/NO. Default is NO. If set * to YES and type == PJ_WKT1_GDAL, a Geographic 3D CRS or a Projected 3D CRS * will be exported as a compound CRS whose vertical part represents an * ellipsoidal height (for example for use with LAS 1.4 WKT1).
  • *
  • ALLOW_LINUNIT_NODE=YES/NO. Default is YES starting with PROJ 9.1. * Only taken into account with type == PJ_WKT1_ESRI on a Geographic 3D * CRS.
  • *
* @return a string, or NULL in case of error. */ const char *proj_as_wkt(PJ_CONTEXT *ctx, const PJ *obj, PJ_WKT_TYPE type, const char *const *options) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto iWKTExportable = dynamic_cast(obj->iso_obj.get()); if (!iWKTExportable) { return nullptr; } const auto convention = ([](PJ_WKT_TYPE l_type) { switch (l_type) { case PJ_WKT2_2015: return WKTFormatter::Convention::WKT2_2015; case PJ_WKT2_2015_SIMPLIFIED: return WKTFormatter::Convention::WKT2_2015_SIMPLIFIED; case PJ_WKT2_2019: return WKTFormatter::Convention::WKT2_2019; case PJ_WKT2_2019_SIMPLIFIED: return WKTFormatter::Convention::WKT2_2019_SIMPLIFIED; case PJ_WKT1_GDAL: return WKTFormatter::Convention::WKT1_GDAL; case PJ_WKT1_ESRI: break; } return WKTFormatter::Convention::WKT1_ESRI; })(type); try { auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); auto formatter = WKTFormatter::create(convention, std::move(dbContext)); for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "MULTILINE="))) { formatter->setMultiLine(ci_equal(value, "YES")); } else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) { formatter->setIndentationWidth(std::atoi(value)); } else if ((value = getOptionValue(*iter, "OUTPUT_AXIS="))) { if (!ci_equal(value, "AUTO")) { formatter->setOutputAxis( ci_equal(value, "YES") ? WKTFormatter::OutputAxisRule::YES : WKTFormatter::OutputAxisRule::NO); } } else if ((value = getOptionValue(*iter, "STRICT="))) { formatter->setStrict(ci_equal(value, "YES")); } else if ((value = getOptionValue( *iter, "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS="))) { formatter->setAllowEllipsoidalHeightAsVerticalCRS( ci_equal(value, "YES")); } else if ((value = getOptionValue(*iter, "ALLOW_LINUNIT_NODE="))) { formatter->setAllowLINUNITNode(ci_equal(value, "YES")); } else { std::string msg("Unknown option :"); msg += *iter; proj_log_error(ctx, __FUNCTION__, msg.c_str()); return nullptr; } } obj->lastWKT = iWKTExportable->exportToWKT(formatter.get()); return obj->lastWKT.c_str(); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Get a PROJ string representation of an object. * * The returned string is valid while the input obj parameter is valid, * and until a next call to proj_as_proj_string() with the same input * object. * * \warning If a CRS object was not created from a PROJ string, * exporting to a PROJ string will in most cases * cause a loss of information. This can potentially lead to * erroneous transformations. * * This function calls * osgeo::proj::io::IPROJStringExportable::exportToPROJString(). * * This function may return NULL if the object is not compatible with an * export to the requested type. * * @param ctx PROJ context, or NULL for default context * @param obj Object (must not be NULL) * @param type PROJ String version. * @param options NULL-terminated list of strings with "KEY=VALUE" format. or * NULL. Currently supported options are: *
    *
  • USE_APPROX_TMERC=YES to add the +approx flag to +proj=tmerc or * +proj=utm.
  • *
  • MULTILINE=YES/NO. Defaults to NO
  • *
  • INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is * on).
  • *
  • MAX_LINE_LENGTH=number. Defaults to 80 (when multiline output is * on).
  • *
* @return a string, or NULL in case of error. */ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj, PJ_PROJ_STRING_TYPE type, const char *const *options) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto exportable = dynamic_cast(obj->iso_obj.get()); if (!exportable) { proj_log_error(ctx, __FUNCTION__, "Object type not exportable to PROJ"); return nullptr; } // Make sure that the C and C++ enumeration match static_assert(static_cast(PJ_PROJ_5) == static_cast(PROJStringFormatter::Convention::PROJ_5), ""); static_assert(static_cast(PJ_PROJ_4) == static_cast(PROJStringFormatter::Convention::PROJ_4), ""); // Make sure we enumerate all values. If adding a new value, as we // don't have a default clause, the compiler will warn. switch (type) { case PJ_PROJ_5: case PJ_PROJ_4: break; } const PROJStringFormatter::Convention convention = static_cast(type); auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { auto formatter = PROJStringFormatter::create(convention, std::move(dbContext)); for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "MULTILINE="))) { formatter->setMultiLine(ci_equal(value, "YES")); } else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) { formatter->setIndentationWidth(std::atoi(value)); } else if ((value = getOptionValue(*iter, "MAX_LINE_LENGTH="))) { formatter->setMaxLineLength(std::atoi(value)); } else if ((value = getOptionValue(*iter, "USE_APPROX_TMERC="))) { formatter->setUseApproxTMerc(ci_equal(value, "YES")); } else { std::string msg("Unknown option :"); msg += *iter; proj_log_error(ctx, __FUNCTION__, msg.c_str()); return nullptr; } } obj->lastPROJString = exportable->exportToPROJString(formatter.get()); return obj->lastPROJString.c_str(); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Get a PROJJSON string representation of an object. * * The returned string is valid while the input obj parameter is valid, * and until a next call to proj_as_proj_string() with the same input * object. * * This function calls * osgeo::proj::io::IJSONExportable::exportToJSON(). * * This function may return NULL if the object is not compatible with an * export to the requested type. * * @param ctx PROJ context, or NULL for default context * @param obj Object (must not be NULL) * @param options NULL-terminated list of strings with "KEY=VALUE" format. or * NULL. Currently * supported options are: *
    *
  • MULTILINE=YES/NO. Defaults to YES
  • *
  • INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is * on).
  • *
  • SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to * disable it.
  • *
* @return a string, or NULL in case of error. * * @since 6.2 */ const char *proj_as_projjson(PJ_CONTEXT *ctx, const PJ *obj, const char *const *options) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto exportable = dynamic_cast(obj->iso_obj.get()); if (!exportable) { proj_log_error(ctx, __FUNCTION__, "Object type not exportable to JSON"); return nullptr; } auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { auto formatter = JSONFormatter::create(std::move(dbContext)); for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "MULTILINE="))) { formatter->setMultiLine(ci_equal(value, "YES")); } else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) { formatter->setIndentationWidth(std::atoi(value)); } else if ((value = getOptionValue(*iter, "SCHEMA="))) { formatter->setSchema(value); } else { std::string msg("Unknown option :"); msg += *iter; proj_log_error(ctx, __FUNCTION__, msg.c_str()); return nullptr; } } obj->lastJSONString = exportable->exportToJSON(formatter.get()); return obj->lastJSONString.c_str(); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Get the number of domains/usages for a given object. * * Most objects have a single domain/usage, but for some of them, there might * be multiple. * * @param obj Object (must not be NULL) * @return the number of domains, or 0 in case of error. * @since 9.2 */ int proj_get_domain_count(const PJ *obj) { if (!obj || !obj->iso_obj) { return 0; } auto objectUsage = dynamic_cast(obj->iso_obj.get()); if (!objectUsage) { return 0; } const auto &domains = objectUsage->domains(); return static_cast(domains.size()); } // --------------------------------------------------------------------------- /** \brief Get the scope of an object. * * In case of multiple usages, this will be the one of first usage. * * The lifetime of the returned string is the same as the input obj parameter. * * @param obj Object (must not be NULL) * @return a string, or NULL in case of error or missing scope. */ const char *proj_get_scope(const PJ *obj) { return proj_get_scope_ex(obj, 0); } // --------------------------------------------------------------------------- /** \brief Get the scope of an object. * * The lifetime of the returned string is the same as the input obj parameter. * * @param obj Object (must not be NULL) * @param domainIdx Index of the domain/usage. In [0,proj_get_domain_count(obj)[ * @return a string, or NULL in case of error or missing scope. * @since 9.2 */ const char *proj_get_scope_ex(const PJ *obj, int domainIdx) { if (!obj || !obj->iso_obj) { return nullptr; } auto objectUsage = dynamic_cast(obj->iso_obj.get()); if (!objectUsage) { return nullptr; } const auto &domains = objectUsage->domains(); if (domainIdx < 0 || static_cast(domainIdx) >= domains.size()) { return nullptr; } const auto &scope = domains[domainIdx]->scope(); if (!scope.has_value()) { return nullptr; } // The object will still be alive after the function call. // cppcheck-suppress stlcstr return scope->c_str(); } // --------------------------------------------------------------------------- /** \brief Return the area of use of an object. * * In case of multiple usages, this will be the one of first usage. * * @param ctx PROJ context, or NULL for default context * @param obj Object (must not be NULL) * @param out_west_lon_degree Pointer to a double to receive the west longitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_south_lat_degree Pointer to a double to receive the south latitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_east_lon_degree Pointer to a double to receive the east longitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_north_lat_degree Pointer to a double to receive the north latitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_area_name Pointer to a string to receive the name of the area of * use. Or NULL. *p_area_name is valid while obj is valid itself. * @return TRUE in case of success, FALSE in case of error or if the area * of use is unknown. */ int proj_get_area_of_use(PJ_CONTEXT *ctx, const PJ *obj, double *out_west_lon_degree, double *out_south_lat_degree, double *out_east_lon_degree, double *out_north_lat_degree, const char **out_area_name) { return proj_get_area_of_use_ex(ctx, obj, 0, out_west_lon_degree, out_south_lat_degree, out_east_lon_degree, out_north_lat_degree, out_area_name); } // --------------------------------------------------------------------------- /** \brief Return the area of use of an object. * * @param ctx PROJ context, or NULL for default context * @param obj Object (must not be NULL) * @param domainIdx Index of the domain/usage. In [0,proj_get_domain_count(obj)[ * @param out_west_lon_degree Pointer to a double to receive the west longitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_south_lat_degree Pointer to a double to receive the south latitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_east_lon_degree Pointer to a double to receive the east longitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_north_lat_degree Pointer to a double to receive the north latitude * (in degrees). Or NULL. If the returned value is -1000, the bounding box is * unknown. * @param out_area_name Pointer to a string to receive the name of the area of * use. Or NULL. *p_area_name is valid while obj is valid itself. * @return TRUE in case of success, FALSE in case of error or if the area * of use is unknown. */ int proj_get_area_of_use_ex(PJ_CONTEXT *ctx, const PJ *obj, int domainIdx, double *out_west_lon_degree, double *out_south_lat_degree, double *out_east_lon_degree, double *out_north_lat_degree, const char **out_area_name) { (void)ctx; if (out_area_name) { *out_area_name = nullptr; } auto objectUsage = dynamic_cast(obj->iso_obj.get()); if (!objectUsage) { return false; } const auto &domains = objectUsage->domains(); if (domainIdx < 0 || static_cast(domainIdx) >= domains.size()) { return false; } const auto &extent = domains[domainIdx]->domainOfValidity(); if (!extent) { return false; } const auto &desc = extent->description(); if (desc.has_value() && out_area_name) { *out_area_name = desc->c_str(); } const auto &geogElements = extent->geographicElements(); if (!geogElements.empty()) { auto bbox = dynamic_cast(geogElements[0].get()); if (bbox) { if (out_west_lon_degree) { *out_west_lon_degree = bbox->westBoundLongitude(); } if (out_south_lat_degree) { *out_south_lat_degree = bbox->southBoundLatitude(); } if (out_east_lon_degree) { *out_east_lon_degree = bbox->eastBoundLongitude(); } if (out_north_lat_degree) { *out_north_lat_degree = bbox->northBoundLatitude(); } return true; } } if (out_west_lon_degree) { *out_west_lon_degree = -1000; } if (out_south_lat_degree) { *out_south_lat_degree = -1000; } if (out_east_lon_degree) { *out_east_lon_degree = -1000; } if (out_north_lat_degree) { *out_north_lat_degree = -1000; } return true; } // --------------------------------------------------------------------------- static const GeodeticCRS *extractGeodeticCRS(PJ_CONTEXT *ctx, const PJ *crs, const char *fname) { if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, fname, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, fname, "Object is not a CRS"); return nullptr; } auto geodCRS = l_crs->extractGeodeticCRSRaw(); if (!geodCRS) { proj_log_error(ctx, fname, "CRS has no geodetic CRS"); } return geodCRS; } // --------------------------------------------------------------------------- /** \brief Get the geodeticCRS / geographicCRS from a CRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type CRS (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_crs_get_geodetic_crs(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); auto geodCRS = extractGeodeticCRS(ctx, crs, __FUNCTION__); if (!geodCRS) { return nullptr; } return pj_obj_create(ctx, NN_NO_CHECK(nn_dynamic_pointer_cast( geodCRS->shared_from_this()))); } // --------------------------------------------------------------------------- /** \brief Returns whether a CRS is a derived CRS. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type CRS (must not be NULL) * @return TRUE if the CRS is a derived CRS. * @since 8.0 */ int proj_crs_is_derived(PJ_CONTEXT *ctx, const PJ *crs) { if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); return false; } return dynamic_cast(l_crs) != nullptr; } // --------------------------------------------------------------------------- /** \brief Get a CRS component from a CompoundCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type CRS (must not be NULL) * @param index Index of the CRS component (typically 0 = horizontal, 1 = * vertical) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ *crs, int index) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CompoundCRS"); return nullptr; } const auto &components = l_crs->componentReferenceSystems(); if (static_cast(index) >= components.size()) { return nullptr; } return pj_obj_create(ctx, components[index]); } // --------------------------------------------------------------------------- /** \brief Returns a BoundCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param base_crs Base CRS (must not be NULL) * @param hub_crs Hub CRS (must not be NULL) * @param transformation Transformation (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_crs_create_bound_crs(PJ_CONTEXT *ctx, const PJ *base_crs, const PJ *hub_crs, const PJ *transformation) { SANITIZE_CTX(ctx); if (!base_crs || !hub_crs || !transformation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_base_crs = std::dynamic_pointer_cast(base_crs->iso_obj); if (!l_base_crs) { proj_log_error(ctx, __FUNCTION__, "base_crs is not a CRS"); return nullptr; } auto l_hub_crs = std::dynamic_pointer_cast(hub_crs->iso_obj); if (!l_hub_crs) { proj_log_error(ctx, __FUNCTION__, "hub_crs is not a CRS"); return nullptr; } auto l_transformation = std::dynamic_pointer_cast(transformation->iso_obj); if (!l_transformation) { proj_log_error(ctx, __FUNCTION__, "transformation is not a CRS"); return nullptr; } try { return pj_obj_create(ctx, BoundCRS::create(NN_NO_CHECK(l_base_crs), NN_NO_CHECK(l_hub_crs), NN_NO_CHECK(l_transformation))); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Returns potentially * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * This is the same as method * osgeo::proj::crs::CRS::createBoundCRSToWGS84IfPossible() * * @param ctx PROJ context, or NULL for default context * @param crs Object of type CRS (must not be NULL) * @param options null-terminated list of options, or NULL. Currently * supported options are: *
    *
  • ALLOW_INTERMEDIATE_CRS=ALWAYS/IF_NO_DIRECT_TRANSFORMATION/NEVER. Defaults * to NEVER. When set to ALWAYS/IF_NO_DIRECT_TRANSFORMATION, * intermediate CRS may be considered when computing the possible * transformations. Slower.
  • *
* @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_crs_create_bound_crs_to_WGS84(PJ_CONTEXT *ctx, const PJ *crs, const char *const *options) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); return nullptr; } auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { CoordinateOperationContext::IntermediateCRSUse allowIntermediateCRS = CoordinateOperationContext::IntermediateCRSUse::NEVER; for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "ALLOW_INTERMEDIATE_CRS="))) { if (ci_equal(value, "YES") || ci_equal(value, "ALWAYS")) { allowIntermediateCRS = CoordinateOperationContext::IntermediateCRSUse::ALWAYS; } else if (ci_equal(value, "IF_NO_DIRECT_TRANSFORMATION")) { allowIntermediateCRS = CoordinateOperationContext:: IntermediateCRSUse::IF_NO_DIRECT_TRANSFORMATION; } } else { std::string msg("Unknown option :"); msg += *iter; proj_log_error(ctx, __FUNCTION__, msg.c_str()); return nullptr; } } return pj_obj_create(ctx, l_crs->createBoundCRSToWGS84IfPossible( dbContext, allowIntermediateCRS)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Returns a BoundCRS, with a transformation to a hub geographic 3D crs * (use EPSG:4979 for WGS84 for example), using a grid. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param vert_crs Object of type VerticalCRS (must not be NULL) * @param hub_geographic_3D_crs Object of type Geographic 3D CRS (must not be * NULL) * @param grid_name Grid name (typically a .gtx file) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. * @since 6.3 */ PJ *proj_crs_create_bound_vertical_crs(PJ_CONTEXT *ctx, const PJ *vert_crs, const PJ *hub_geographic_3D_crs, const char *grid_name) { SANITIZE_CTX(ctx); if (!vert_crs || !hub_geographic_3D_crs || !grid_name) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = std::dynamic_pointer_cast(vert_crs->iso_obj); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "vert_crs is not a VerticalCRS"); return nullptr; } auto hub_crs = std::dynamic_pointer_cast(hub_geographic_3D_crs->iso_obj); if (!hub_crs) { proj_log_error(ctx, __FUNCTION__, "hub_geographic_3D_crs is not a CRS"); return nullptr; } try { auto nnCRS = NN_NO_CHECK(l_crs); auto nnHubCRS = NN_NO_CHECK(hub_crs); auto transformation = Transformation::createGravityRelatedHeightToGeographic3D( PropertyMap().set(IdentifiedObject::NAME_KEY, "unknown to " + hub_crs->nameStr() + " ellipsoidal height"), nnCRS, nnHubCRS, nullptr, std::string(grid_name), std::vector()); return pj_obj_create(ctx, BoundCRS::create(nnCRS, nnHubCRS, transformation)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Get the ellipsoid from a CRS or a GeodeticReferenceFrame. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS or GeodeticReferenceFrame (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_get_ellipsoid(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); auto ptr = obj->iso_obj.get(); if (dynamic_cast(ptr)) { auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__); if (geodCRS) { return pj_obj_create(ctx, geodCRS->ellipsoid()); } } else { auto datum = dynamic_cast(ptr); if (datum) { return pj_obj_create(ctx, datum->ellipsoid()); } } proj_log_error(ctx, __FUNCTION__, "Object is not a CRS or GeodeticReferenceFrame"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Get the name of the celestial body of this object. * * Object should be a CRS, Datum or Ellipsoid. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS, Datum or Ellipsoid.(must not be NULL) * @return the name of the celestial body, or NULL. * @since 8.1 */ const char *proj_get_celestial_body_name(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); const BaseObject *ptr = obj->iso_obj.get(); if (dynamic_cast(ptr)) { const auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__); if (!geodCRS) { // FIXME when vertical CRS can be non-EARTH... return datum::Ellipsoid::EARTH.c_str(); } return geodCRS->ellipsoid()->celestialBody().c_str(); } const auto ensemble = dynamic_cast(ptr); if (ensemble) { ptr = ensemble->datums().front().get(); // Go on } const auto geodetic_datum = dynamic_cast(ptr); if (geodetic_datum) { return geodetic_datum->ellipsoid()->celestialBody().c_str(); } const auto vertical_datum = dynamic_cast(ptr); if (vertical_datum) { // FIXME when vertical CRS can be non-EARTH... return datum::Ellipsoid::EARTH.c_str(); } const auto ellipsoid = dynamic_cast(ptr); if (ellipsoid) { return ellipsoid->celestialBody().c_str(); } proj_log_error(ctx, __FUNCTION__, "Object is not a CRS, Datum or Ellipsoid"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Get the horizontal datum from a CRS * * This function may return a Datum or DatumEnsemble object. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type CRS (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_crs_get_horizontal_datum(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); auto geodCRS = extractGeodeticCRS(ctx, crs, __FUNCTION__); if (!geodCRS) { return nullptr; } const auto &datum = geodCRS->datum(); if (datum) { return pj_obj_create(ctx, NN_NO_CHECK(datum)); } const auto &datumEnsemble = geodCRS->datumEnsemble(); if (datumEnsemble) { return pj_obj_create(ctx, NN_NO_CHECK(datumEnsemble)); } proj_log_error(ctx, __FUNCTION__, "CRS has no datum"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Return ellipsoid parameters. * * @param ctx PROJ context, or NULL for default context * @param ellipsoid Object of type Ellipsoid (must not be NULL) * @param out_semi_major_metre Pointer to a value to store the semi-major axis * in * metre. or NULL * @param out_semi_minor_metre Pointer to a value to store the semi-minor axis * in * metre. or NULL * @param out_is_semi_minor_computed Pointer to a boolean value to indicate if * the * semi-minor value was computed. If FALSE, its value comes from the * definition. or NULL * @param out_inv_flattening Pointer to a value to store the inverse * flattening. or NULL * @return TRUE in case of success. */ int proj_ellipsoid_get_parameters(PJ_CONTEXT *ctx, const PJ *ellipsoid, double *out_semi_major_metre, double *out_semi_minor_metre, int *out_is_semi_minor_computed, double *out_inv_flattening) { SANITIZE_CTX(ctx); if (!ellipsoid) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return FALSE; } auto l_ellipsoid = dynamic_cast(ellipsoid->iso_obj.get()); if (!l_ellipsoid) { proj_log_error(ctx, __FUNCTION__, "Object is not a Ellipsoid"); return FALSE; } if (out_semi_major_metre) { *out_semi_major_metre = l_ellipsoid->semiMajorAxis().getSIValue(); } if (out_semi_minor_metre) { *out_semi_minor_metre = l_ellipsoid->computeSemiMinorAxis().getSIValue(); } if (out_is_semi_minor_computed) { *out_is_semi_minor_computed = !(l_ellipsoid->semiMinorAxis().has_value()); } if (out_inv_flattening) { *out_inv_flattening = l_ellipsoid->computedInverseFlattening(); } return TRUE; } // --------------------------------------------------------------------------- /** \brief Get the prime meridian of a CRS or a GeodeticReferenceFrame. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS or GeodeticReferenceFrame (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_get_prime_meridian(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); auto ptr = obj->iso_obj.get(); if (dynamic_cast(ptr)) { auto geodCRS = extractGeodeticCRS(ctx, obj, __FUNCTION__); if (geodCRS) { return pj_obj_create(ctx, geodCRS->primeMeridian()); } } else { auto datum = dynamic_cast(ptr); if (datum) { return pj_obj_create(ctx, datum->primeMeridian()); } } proj_log_error(ctx, __FUNCTION__, "Object is not a CRS or GeodeticReferenceFrame"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Return prime meridian parameters. * * @param ctx PROJ context, or NULL for default context * @param prime_meridian Object of type PrimeMeridian (must not be NULL) * @param out_longitude Pointer to a value to store the longitude of the prime * meridian, in its native unit. or NULL * @param out_unit_conv_factor Pointer to a value to store the conversion * factor of the prime meridian longitude unit to radian. or NULL * @param out_unit_name Pointer to a string value to store the unit name. * or NULL * @return TRUE in case of success. */ int proj_prime_meridian_get_parameters(PJ_CONTEXT *ctx, const PJ *prime_meridian, double *out_longitude, double *out_unit_conv_factor, const char **out_unit_name) { SANITIZE_CTX(ctx); if (!prime_meridian) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto l_pm = dynamic_cast(prime_meridian->iso_obj.get()); if (!l_pm) { proj_log_error(ctx, __FUNCTION__, "Object is not a PrimeMeridian"); return false; } const auto &longitude = l_pm->longitude(); if (out_longitude) { *out_longitude = longitude.value(); } const auto &unit = longitude.unit(); if (out_unit_conv_factor) { *out_unit_conv_factor = unit.conversionToSI(); } if (out_unit_name) { *out_unit_name = unit.name().c_str(); } return true; } // --------------------------------------------------------------------------- /** \brief Return the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, or * the source CRS of a CoordinateOperation, or the CRS of a CoordinateMetadata. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type BoundCRS or CoordinateOperation (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error, or missing source CRS. */ PJ *proj_get_source_crs(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { return nullptr; } auto ptr = obj->iso_obj.get(); auto boundCRS = dynamic_cast(ptr); if (boundCRS) { return pj_obj_create(ctx, boundCRS->baseCRS()); } auto derivedCRS = dynamic_cast(ptr); if (derivedCRS) { return pj_obj_create(ctx, derivedCRS->baseCRS()); } auto co = dynamic_cast(ptr); if (co) { auto sourceCRS = co->sourceCRS(); if (sourceCRS) { return pj_obj_create(ctx, NN_NO_CHECK(sourceCRS)); } return nullptr; } if (!obj->alternativeCoordinateOperations.empty()) { return proj_get_source_crs(ctx, obj->alternativeCoordinateOperations[0].pj); } auto coordinateMetadata = dynamic_cast(ptr); if (coordinateMetadata) { return pj_obj_create(ctx, coordinateMetadata->crs()); } proj_log_error(ctx, __FUNCTION__, "Object is not a BoundCRS, a CoordinateOperation or a " "CoordinateMetadata"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Return the hub CRS of a BoundCRS or the target CRS of a * CoordinateOperation. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type BoundCRS or CoordinateOperation (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error, or missing target CRS. */ PJ *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto ptr = obj->iso_obj.get(); auto boundCRS = dynamic_cast(ptr); if (boundCRS) { return pj_obj_create(ctx, boundCRS->hubCRS()); } auto co = dynamic_cast(ptr); if (co) { auto targetCRS = co->targetCRS(); if (targetCRS) { return pj_obj_create(ctx, NN_NO_CHECK(targetCRS)); } return nullptr; } if (!obj->alternativeCoordinateOperations.empty()) { return proj_get_target_crs(ctx, obj->alternativeCoordinateOperations[0].pj); } proj_log_error(ctx, __FUNCTION__, "Object is not a BoundCRS or a CoordinateOperation"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Identify the CRS with reference CRSs. * * The candidate CRSs are either hard-coded, or looked in the database when * it is available. * * Note that the implementation uses a set of heuristics to have a good * compromise of successful identifications over execution time. It might miss * legitimate matches in some circumstances. * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. *
    *
  • 100% means that the name of the reference entry * perfectly matches the CRS name, and both are equivalent. In which case a * single result is returned. * Note: in the case of a GeographicCRS whose axis * order is implicit in the input definition (for example ESRI WKT), then axis * order is ignored for the purpose of identification. That is the CRS built * from * GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]], * PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] * will be identified to EPSG:4326, but will not pass a * isEquivalentTo(EPSG_4326, util::IComparable::Criterion::EQUIVALENT) test, * but rather isEquivalentTo(EPSG_4326, * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) *
  • *
  • 90% means that CRS are equivalent, but the names are not exactly the * same.
  • *
  • 70% means that CRS are equivalent, but the names are not equivalent. *
  • *
  • 25% means that the CRS are not equivalent, but there is some similarity * in * the names.
  • *
* Other confidence values may be returned by some specialized implementations. * * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and * CompoundCRS. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS. Must not be NULL * @param auth_name Authority name, or NULL for all authorities * @param options Placeholder for future options. Should be set to NULL. * @param out_confidence Output parameter. Pointer to an array of integers that * will be allocated by the function and filled with the confidence values * (0-100). There are as many elements in this array as * proj_list_get_count() * returns on the return value of this function. *confidence should be * released with proj_int_list_destroy(). * @return a list of matching reference CRS, or nullptr in case of error. */ PJ_OBJ_LIST *proj_identify(PJ_CONTEXT *ctx, const PJ *obj, const char *auth_name, const char *const *options, int **out_confidence) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } (void)options; if (out_confidence) { *out_confidence = nullptr; } auto ptr = obj->iso_obj.get(); auto crs = dynamic_cast(ptr); if (!crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); } else { int *confidenceTemp = nullptr; try { auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name ? auth_name : ""); auto res = crs->identify(factory); std::vector objects; confidenceTemp = out_confidence ? new int[res.size()] : nullptr; size_t i = 0; for (const auto &pair : res) { objects.push_back(pair.first); if (confidenceTemp) { confidenceTemp[i] = pair.second; ++i; } } auto ret = std::make_unique(std::move(objects)); if (out_confidence) { *out_confidence = confidenceTemp; confidenceTemp = nullptr; } return ret.release(); } catch (const std::exception &e) { delete[] confidenceTemp; proj_log_error(ctx, __FUNCTION__, e.what()); } } return nullptr; } // --------------------------------------------------------------------------- /** \brief Free an array of integer. */ void proj_int_list_destroy(int *list) { delete[] list; } // --------------------------------------------------------------------------- /** \brief Return the list of authorities used in the database. * * The returned list is NULL terminated and must be freed with * proj_string_list_destroy(). * * @param ctx PROJ context, or NULL for default context * * @return a NULL terminated list of NUL-terminated strings that must be * freed with proj_string_list_destroy(), or NULL in case of error. */ PROJ_STRING_LIST proj_get_authorities_from_database(PJ_CONTEXT *ctx) { SANITIZE_CTX(ctx); try { auto ret = to_string_list(getDBcontext(ctx)->getAuthorities()); return ret; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Returns the set of authority codes of the given object type. * * The returned list is NULL terminated and must be freed with * proj_string_list_destroy(). * * @param ctx PROJ context, or NULL for default context. * @param auth_name Authority name (must not be NULL) * @param type Object type. * @param allow_deprecated whether we should return deprecated objects as well. * * @return a NULL terminated list of NUL-terminated strings that must be * freed with proj_string_list_destroy(), or NULL in case of error. * * @see proj_get_crs_info_list_from_database() */ PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx, const char *auth_name, PJ_TYPE type, int allow_deprecated) { SANITIZE_CTX(ctx); if (!auth_name) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } try { auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); bool valid = false; auto typeInternal = convertPJObjectTypeToObjectType(type, valid); if (!valid) { return nullptr; } auto ret = to_string_list( factory->getAuthorityCodes(typeInternal, allow_deprecated != 0)); return ret; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Enumerate celestial bodies from the database. * * The returned object is an array of PROJ_CELESTIAL_BODY_INFO* pointers, whose * last entry is NULL. This array should be freed with * proj_celestial_body_list_destroy() * * @param ctx PROJ context, or NULL for default context * @param auth_name Authority name, used to restrict the search. * Or NULL for all authorities. * @param out_result_count Output parameter pointing to an integer to receive * the size of the result list. Might be NULL * @return an array of PROJ_CELESTIAL_BODY_INFO* pointers to be freed with * proj_celestial_body_list_destroy(), or NULL in case of error. * @since 8.1 */ PROJ_CELESTIAL_BODY_INFO **proj_get_celestial_body_list_from_database( PJ_CONTEXT *ctx, const char *auth_name, int *out_result_count) { SANITIZE_CTX(ctx); PROJ_CELESTIAL_BODY_INFO **ret = nullptr; int i = 0; try { auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name ? auth_name : ""); auto list = factory->getCelestialBodyList(); ret = new PROJ_CELESTIAL_BODY_INFO *[list.size() + 1]; for (const auto &info : list) { ret[i] = new PROJ_CELESTIAL_BODY_INFO; ret[i]->auth_name = pj_strdup(info.authName.c_str()); ret[i]->name = pj_strdup(info.name.c_str()); i++; } ret[i] = nullptr; if (out_result_count) *out_result_count = i; return ret; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); if (ret) { ret[i + 1] = nullptr; proj_celestial_body_list_destroy(ret); } if (out_result_count) *out_result_count = 0; } return nullptr; } // --------------------------------------------------------------------------- /** \brief Destroy the result returned by * proj_get_celestial_body_list_from_database(). * * @since 8.1 */ void proj_celestial_body_list_destroy(PROJ_CELESTIAL_BODY_INFO **list) { if (list) { for (int i = 0; list[i] != nullptr; i++) { free(list[i]->auth_name); free(list[i]->name); delete list[i]; } delete[] list; } } // --------------------------------------------------------------------------- /** Free a list of NULL terminated strings. */ void proj_string_list_destroy(PROJ_STRING_LIST list) { if (list) { for (size_t i = 0; list[i] != nullptr; i++) { delete[] list[i]; } delete[] list; } } // --------------------------------------------------------------------------- /** \brief Instantiate a default set of parameters to be used by * proj_get_crs_list(). * * @return a new object to free with proj_get_crs_list_parameters_destroy() */ PROJ_CRS_LIST_PARAMETERS *proj_get_crs_list_parameters_create() { auto ret = new (std::nothrow) PROJ_CRS_LIST_PARAMETERS(); if (ret) { ret->types = nullptr; ret->typesCount = 0; ret->crs_area_of_use_contains_bbox = TRUE; ret->bbox_valid = FALSE; ret->west_lon_degree = 0.0; ret->south_lat_degree = 0.0; ret->east_lon_degree = 0.0; ret->north_lat_degree = 0.0; ret->allow_deprecated = FALSE; ret->celestial_body_name = nullptr; } return ret; } // --------------------------------------------------------------------------- /** \brief Destroy an object returned by proj_get_crs_list_parameters_create() */ void proj_get_crs_list_parameters_destroy(PROJ_CRS_LIST_PARAMETERS *params) { delete params; } // --------------------------------------------------------------------------- /** \brief Enumerate CRS objects from the database, taking into account various * criteria. * * The returned object is an array of PROJ_CRS_INFO* pointers, whose last * entry is NULL. This array should be freed with proj_crs_info_list_destroy() * * When no filter parameters are set, this is functionally equivalent to * proj_get_codes_from_database(), instantiating a PJ* object for each * of the codes with proj_create_from_database() and retrieving information * with the various getters. However this function will be much faster. * * @param ctx PROJ context, or NULL for default context * @param auth_name Authority name, used to restrict the search. * Or NULL for all authorities. * @param params Additional criteria, or NULL. If not-NULL, params SHOULD * have been allocated by proj_get_crs_list_parameters_create(), as the * PROJ_CRS_LIST_PARAMETERS structure might grow over time. * @param out_result_count Output parameter pointing to an integer to receive * the size of the result list. Might be NULL * @return an array of PROJ_CRS_INFO* pointers to be freed with * proj_crs_info_list_destroy(), or NULL in case of error. */ PROJ_CRS_INFO ** proj_get_crs_info_list_from_database(PJ_CONTEXT *ctx, const char *auth_name, const PROJ_CRS_LIST_PARAMETERS *params, int *out_result_count) { SANITIZE_CTX(ctx); PROJ_CRS_INFO **ret = nullptr; int i = 0; try { auto dbContext = getDBcontext(ctx); std::string authName = auth_name ? auth_name : ""; auto actualAuthNames = dbContext->getVersionedAuthoritiesFromName(authName); if (actualAuthNames.empty()) actualAuthNames.push_back(std::move(authName)); std::list concatList; for (const auto &actualAuthName : actualAuthNames) { auto factory = AuthorityFactory::create(dbContext, actualAuthName); auto list = factory->getCRSInfoList(); concatList.splice(concatList.end(), std::move(list)); } ret = new PROJ_CRS_INFO *[concatList.size() + 1]; GeographicBoundingBoxPtr bbox; if (params && params->bbox_valid) { bbox = GeographicBoundingBox::create( params->west_lon_degree, params->south_lat_degree, params->east_lon_degree, params->north_lat_degree) .as_nullable(); } for (const auto &info : concatList) { auto type = PJ_TYPE_CRS; if (info.type == AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS) { type = PJ_TYPE_GEOGRAPHIC_2D_CRS; } else if (info.type == AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS) { type = PJ_TYPE_GEOGRAPHIC_3D_CRS; } else if (info.type == AuthorityFactory::ObjectType::GEOCENTRIC_CRS) { type = PJ_TYPE_GEOCENTRIC_CRS; } else if (info.type == AuthorityFactory::ObjectType::GEODETIC_CRS) { type = PJ_TYPE_GEODETIC_CRS; } else if (info.type == AuthorityFactory::ObjectType::PROJECTED_CRS) { type = PJ_TYPE_PROJECTED_CRS; } else if (info.type == AuthorityFactory::ObjectType::VERTICAL_CRS) { type = PJ_TYPE_VERTICAL_CRS; } else if (info.type == AuthorityFactory::ObjectType::COMPOUND_CRS) { type = PJ_TYPE_COMPOUND_CRS; } if (params && params->typesCount) { bool typeValid = false; for (size_t j = 0; j < params->typesCount; j++) { if (params->types[j] == type) { typeValid = true; break; } else if (params->types[j] == PJ_TYPE_GEOGRAPHIC_CRS && (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || type == PJ_TYPE_GEOGRAPHIC_3D_CRS)) { typeValid = true; break; } else if (params->types[j] == PJ_TYPE_GEODETIC_CRS && (type == PJ_TYPE_GEOCENTRIC_CRS || type == PJ_TYPE_GEOGRAPHIC_2D_CRS || type == PJ_TYPE_GEOGRAPHIC_3D_CRS)) { typeValid = true; break; } } if (!typeValid) { continue; } } if (params && !params->allow_deprecated && info.deprecated) { continue; } if (params && params->bbox_valid) { if (!info.bbox_valid) { continue; } if (info.west_lon_degree <= info.east_lon_degree && params->west_lon_degree <= params->east_lon_degree) { if (params->crs_area_of_use_contains_bbox) { if (params->west_lon_degree < info.west_lon_degree || params->east_lon_degree > info.east_lon_degree || params->south_lat_degree < info.south_lat_degree || params->north_lat_degree > info.north_lat_degree) { continue; } } else { if (info.east_lon_degree < params->west_lon_degree || info.west_lon_degree > params->east_lon_degree || info.north_lat_degree < params->south_lat_degree || info.south_lat_degree > params->north_lat_degree) { continue; } } } else { auto crsExtent = GeographicBoundingBox::create( info.west_lon_degree, info.south_lat_degree, info.east_lon_degree, info.north_lat_degree); if (params->crs_area_of_use_contains_bbox) { if (!crsExtent->contains(NN_NO_CHECK(bbox))) { continue; } } else { if (!bbox->intersects(crsExtent)) { continue; } } } } if (params && params->celestial_body_name && params->celestial_body_name != info.celestialBodyName) { continue; } ret[i] = new PROJ_CRS_INFO; ret[i]->auth_name = pj_strdup(info.authName.c_str()); ret[i]->code = pj_strdup(info.code.c_str()); ret[i]->name = pj_strdup(info.name.c_str()); ret[i]->type = type; ret[i]->deprecated = info.deprecated; ret[i]->bbox_valid = info.bbox_valid; ret[i]->west_lon_degree = info.west_lon_degree; ret[i]->south_lat_degree = info.south_lat_degree; ret[i]->east_lon_degree = info.east_lon_degree; ret[i]->north_lat_degree = info.north_lat_degree; ret[i]->area_name = pj_strdup(info.areaName.c_str()); ret[i]->projection_method_name = info.projectionMethodName.empty() ? nullptr : pj_strdup(info.projectionMethodName.c_str()); ret[i]->celestial_body_name = pj_strdup(info.celestialBodyName.c_str()); i++; } ret[i] = nullptr; if (out_result_count) *out_result_count = i; return ret; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); if (ret) { ret[i + 1] = nullptr; proj_crs_info_list_destroy(ret); } if (out_result_count) *out_result_count = 0; } return nullptr; } // --------------------------------------------------------------------------- /** \brief Destroy the result returned by * proj_get_crs_info_list_from_database(). */ void proj_crs_info_list_destroy(PROJ_CRS_INFO **list) { if (list) { for (int i = 0; list[i] != nullptr; i++) { free(list[i]->auth_name); free(list[i]->code); free(list[i]->name); free(list[i]->area_name); free(list[i]->projection_method_name); free(list[i]->celestial_body_name); delete list[i]; } delete[] list; } } // --------------------------------------------------------------------------- /** \brief Enumerate units from the database, taking into account various * criteria. * * The returned object is an array of PROJ_UNIT_INFO* pointers, whose last * entry is NULL. This array should be freed with proj_unit_list_destroy() * * @param ctx PROJ context, or NULL for default context * @param auth_name Authority name, used to restrict the search. * Or NULL for all authorities. * @param category Filter by category, if this parameter is not NULL. Category * is one of "linear", "linear_per_time", "angular", "angular_per_time", * "scale", "scale_per_time" or "time" * @param allow_deprecated whether we should return deprecated objects as well. * @param out_result_count Output parameter pointing to an integer to receive * the size of the result list. Might be NULL * @return an array of PROJ_UNIT_INFO* pointers to be freed with * proj_unit_list_destroy(), or NULL in case of error. * * @since 7.1 */ PROJ_UNIT_INFO **proj_get_units_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *category, int allow_deprecated, int *out_result_count) { SANITIZE_CTX(ctx); PROJ_UNIT_INFO **ret = nullptr; int i = 0; try { auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name ? auth_name : ""); auto list = factory->getUnitList(); ret = new PROJ_UNIT_INFO *[list.size() + 1]; for (const auto &info : list) { if (category && info.category != category) { continue; } if (!allow_deprecated && info.deprecated) { continue; } ret[i] = new PROJ_UNIT_INFO; ret[i]->auth_name = pj_strdup(info.authName.c_str()); ret[i]->code = pj_strdup(info.code.c_str()); ret[i]->name = pj_strdup(info.name.c_str()); ret[i]->category = pj_strdup(info.category.c_str()); ret[i]->conv_factor = info.convFactor; ret[i]->proj_short_name = info.projShortName.empty() ? nullptr : pj_strdup(info.projShortName.c_str()); ret[i]->deprecated = info.deprecated; i++; } ret[i] = nullptr; if (out_result_count) *out_result_count = i; return ret; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); if (ret) { ret[i + 1] = nullptr; proj_unit_list_destroy(ret); } if (out_result_count) *out_result_count = 0; } return nullptr; } // --------------------------------------------------------------------------- /** \brief Destroy the result returned by * proj_get_units_from_database(). * * @since 7.1 */ void proj_unit_list_destroy(PROJ_UNIT_INFO **list) { if (list) { for (int i = 0; list[i] != nullptr; i++) { free(list[i]->auth_name); free(list[i]->code); free(list[i]->name); free(list[i]->category); free(list[i]->proj_short_name); delete list[i]; } delete[] list; } } // --------------------------------------------------------------------------- /** \brief Return the Conversion of a DerivedCRS (such as a ProjectedCRS), * or the Transformation from the baseCRS to the hubCRS of a BoundCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type DerivedCRS or BoundCRSs (must not be NULL) * @return Object of type SingleOperation that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_crs_get_coordoperation(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } SingleOperationPtr co; auto derivedCRS = dynamic_cast(crs->iso_obj.get()); if (derivedCRS) { co = derivedCRS->derivingConversion().as_nullable(); } else { auto boundCRS = dynamic_cast(crs->iso_obj.get()); if (boundCRS) { co = boundCRS->transformation().as_nullable(); } else { proj_log_error(ctx, __FUNCTION__, "Object is not a DerivedCRS or BoundCRS"); return nullptr; } } return pj_obj_create(ctx, NN_NO_CHECK(co)); } // --------------------------------------------------------------------------- /** \brief Return information on the operation method of the SingleOperation. * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type SingleOperation (typically a Conversion * or Transformation) (must not be NULL) * @param out_method_name Pointer to a string value to store the method * (projection) name. or NULL * @param out_method_auth_name Pointer to a string value to store the method * authority name. or NULL * @param out_method_code Pointer to a string value to store the method * code. or NULL * @return TRUE in case of success. */ int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx, const PJ *coordoperation, const char **out_method_name, const char **out_method_auth_name, const char **out_method_code) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto singleOp = dynamic_cast(coordoperation->iso_obj.get()); if (!singleOp) { proj_log_error(ctx, __FUNCTION__, "Object is not a DerivedCRS or BoundCRS"); return false; } const auto &method = singleOp->method(); const auto &method_ids = method->identifiers(); if (out_method_name) { *out_method_name = method->name()->description()->c_str(); } if (out_method_auth_name) { if (!method_ids.empty()) { *out_method_auth_name = method_ids[0]->codeSpace()->c_str(); } else { *out_method_auth_name = nullptr; } } if (out_method_code) { if (!method_ids.empty()) { *out_method_code = method_ids[0]->code().c_str(); } else { *out_method_code = nullptr; } } return true; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static PropertyMap createPropertyMapName(const char *c_name, const char *auth_name = nullptr, const char *code = nullptr) { std::string name(c_name ? c_name : "unnamed"); PropertyMap properties; if (ends_with(name, " (deprecated)")) { name.resize(name.size() - strlen(" (deprecated)")); properties.set(common::IdentifiedObject::DEPRECATED_KEY, true); } if (auth_name && code) { properties.set(metadata::Identifier::CODESPACE_KEY, auth_name); properties.set(metadata::Identifier::CODE_KEY, code); } return properties.set(common::IdentifiedObject::NAME_KEY, name); } // --------------------------------------------------------------------------- static UnitOfMeasure createLinearUnit(const char *name, double convFactor, const char *unit_auth_name = nullptr, const char *unit_code = nullptr) { return name == nullptr ? UnitOfMeasure::METRE : UnitOfMeasure(name, convFactor, UnitOfMeasure::Type::LINEAR, unit_auth_name ? unit_auth_name : "", unit_code ? unit_code : ""); } // --------------------------------------------------------------------------- static UnitOfMeasure createAngularUnit(const char *name, double convFactor, const char *unit_auth_name = nullptr, const char *unit_code = nullptr) { return name ? (ci_equal(name, "degree") ? UnitOfMeasure::DEGREE : ci_equal(name, "grad") ? UnitOfMeasure::GRAD : UnitOfMeasure(name, convFactor, UnitOfMeasure::Type::ANGULAR, unit_auth_name ? unit_auth_name : "", unit_code ? unit_code : "")) : UnitOfMeasure::DEGREE; } // --------------------------------------------------------------------------- static GeodeticReferenceFrameNNPtr createGeodeticReferenceFrame( PJ_CONTEXT *ctx, const char *datum_name, const char *ellps_name, double semi_major_metre, double inv_flattening, const char *prime_meridian_name, double prime_meridian_offset, const char *angular_units, double angular_units_conv) { const UnitOfMeasure angUnit( createAngularUnit(angular_units, angular_units_conv)); auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); auto body = Ellipsoid::guessBodyName(dbContext, semi_major_metre); auto ellpsName = createPropertyMapName(ellps_name); auto ellps = inv_flattening != 0.0 ? Ellipsoid::createFlattenedSphere( ellpsName, Length(semi_major_metre), Scale(inv_flattening), body) : Ellipsoid::createSphere(ellpsName, Length(semi_major_metre), body); auto pm = PrimeMeridian::create( PropertyMap().set( common::IdentifiedObject::NAME_KEY, prime_meridian_name ? prime_meridian_name : prime_meridian_offset == 0.0 ? (ellps->celestialBody() == Ellipsoid::EARTH ? PrimeMeridian::GREENWICH->nameStr().c_str() : PrimeMeridian::REFERENCE_MERIDIAN->nameStr().c_str()) : "unnamed"), Angle(prime_meridian_offset, angUnit)); std::string datumName(datum_name ? datum_name : "unnamed"); if (datumName == "WGS_1984") { datumName = GeodeticReferenceFrame::EPSG_6326->nameStr(); } else if (datumName.find('_') != std::string::npos) { // Likely coming from WKT1 if (dbContext) { auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); auto res = authFactory->createObjectsFromName( datumName, {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true, 1); if (!res.empty()) { const auto &refDatum = res.front(); if (metadata::Identifier::isEquivalentName( datumName.c_str(), refDatum->nameStr().c_str())) { datumName = refDatum->nameStr(); } else if (refDatum->identifiers().size() == 1) { const auto &id = refDatum->identifiers()[0]; const auto aliases = authFactory->databaseContext()->getAliases( *id->codeSpace(), id->code(), refDatum->nameStr(), "geodetic_datum", std::string()); for (const auto &alias : aliases) { if (metadata::Identifier::isEquivalentName( datumName.c_str(), alias.c_str())) { datumName = refDatum->nameStr(); break; } } } } } } return GeodeticReferenceFrame::create( createPropertyMapName(datumName.c_str()), ellps, util::optional(), pm); } //! @endcond // --------------------------------------------------------------------------- /** \brief Create a GeographicCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param datum_name Name of the GeodeticReferenceFrame. Or NULL * @param ellps_name Name of the Ellipsoid. Or NULL * @param semi_major_metre Ellipsoid semi-major axis, in metres. * @param inv_flattening Ellipsoid inverse flattening. Or 0 for a sphere. * @param prime_meridian_name Name of the PrimeMeridian. Or NULL * @param prime_meridian_offset Offset of the prime meridian, expressed in the * specified angular units. * @param pm_angular_units Name of the angular units. Or NULL for Degree * @param pm_angular_units_conv Conversion factor from the angular unit to * radian. * Or * 0 for Degree if pm_angular_units == NULL. Otherwise should be not NULL * @param ellipsoidal_cs Coordinate system. Must not be NULL. * * @return Object of type GeographicCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_geographic_crs(PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *ellps_name, double semi_major_metre, double inv_flattening, const char *prime_meridian_name, double prime_meridian_offset, const char *pm_angular_units, double pm_angular_units_conv, const PJ *ellipsoidal_cs) { SANITIZE_CTX(ctx); auto cs = std::dynamic_pointer_cast(ellipsoidal_cs->iso_obj); if (!cs) { return nullptr; } try { auto datum = createGeodeticReferenceFrame( ctx, datum_name, ellps_name, semi_major_metre, inv_flattening, prime_meridian_name, prime_meridian_offset, pm_angular_units, pm_angular_units_conv); auto geogCRS = GeographicCRS::create(createPropertyMapName(crs_name), datum, NN_NO_CHECK(cs)); return pj_obj_create(ctx, geogCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Create a GeographicCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param datum_or_datum_ensemble Datum or DatumEnsemble (DatumEnsemble possible * since 7.2). Must not be NULL. * @param ellipsoidal_cs Coordinate system. Must not be NULL. * * @return Object of type GeographicCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_geographic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name, const PJ *datum_or_datum_ensemble, const PJ *ellipsoidal_cs) { SANITIZE_CTX(ctx); if (datum_or_datum_ensemble == nullptr) { proj_log_error(ctx, __FUNCTION__, "Missing input datum_or_datum_ensemble"); return nullptr; } auto l_datum = std::dynamic_pointer_cast( datum_or_datum_ensemble->iso_obj); auto l_datum_ensemble = std::dynamic_pointer_cast( datum_or_datum_ensemble->iso_obj); auto cs = std::dynamic_pointer_cast(ellipsoidal_cs->iso_obj); if (!cs) { return nullptr; } try { auto geogCRS = GeographicCRS::create(createPropertyMapName(crs_name), l_datum, l_datum_ensemble, NN_NO_CHECK(cs)); return pj_obj_create(ctx, geogCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Create a GeodeticCRS of geocentric type. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param datum_name Name of the GeodeticReferenceFrame. Or NULL * @param ellps_name Name of the Ellipsoid. Or NULL * @param semi_major_metre Ellipsoid semi-major axis, in metres. * @param inv_flattening Ellipsoid inverse flattening. Or 0 for a sphere. * @param prime_meridian_name Name of the PrimeMeridian. Or NULL * @param prime_meridian_offset Offset of the prime meridian, expressed in the * specified angular units. * @param angular_units Name of the angular units. Or NULL for Degree * @param angular_units_conv Conversion factor from the angular unit to radian. * Or * 0 for Degree if angular_units == NULL. Otherwise should be not NULL * @param linear_units Name of the linear units. Or NULL for Metre * @param linear_units_conv Conversion factor from the linear unit to metre. Or * 0 for Metre if linear_units == NULL. Otherwise should be not NULL * * @return Object of type GeodeticCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_geocentric_crs( PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *ellps_name, double semi_major_metre, double inv_flattening, const char *prime_meridian_name, double prime_meridian_offset, const char *angular_units, double angular_units_conv, const char *linear_units, double linear_units_conv) { SANITIZE_CTX(ctx); try { const UnitOfMeasure linearUnit( createLinearUnit(linear_units, linear_units_conv)); auto datum = createGeodeticReferenceFrame( ctx, datum_name, ellps_name, semi_major_metre, inv_flattening, prime_meridian_name, prime_meridian_offset, angular_units, angular_units_conv); auto geodCRS = GeodeticCRS::create(createPropertyMapName(crs_name), datum, cs::CartesianCS::createGeocentric(linearUnit)); return pj_obj_create(ctx, geodCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Create a GeodeticCRS of geocentric type. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param datum_or_datum_ensemble Datum or DatumEnsemble (DatumEnsemble possible * since 7.2). Must not be NULL. * @param linear_units Name of the linear units. Or NULL for Metre * @param linear_units_conv Conversion factor from the linear unit to metre. Or * 0 for Metre if linear_units == NULL. Otherwise should be not NULL * * @return Object of type GeodeticCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_geocentric_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name, const PJ *datum_or_datum_ensemble, const char *linear_units, double linear_units_conv) { SANITIZE_CTX(ctx); if (datum_or_datum_ensemble == nullptr) { proj_log_error(ctx, __FUNCTION__, "Missing input datum_or_datum_ensemble"); return nullptr; } auto l_datum = std::dynamic_pointer_cast( datum_or_datum_ensemble->iso_obj); auto l_datum_ensemble = std::dynamic_pointer_cast( datum_or_datum_ensemble->iso_obj); try { const UnitOfMeasure linearUnit( createLinearUnit(linear_units, linear_units_conv)); auto geodCRS = GeodeticCRS::create( createPropertyMapName(crs_name), l_datum, l_datum_ensemble, cs::CartesianCS::createGeocentric(linearUnit)); return pj_obj_create(ctx, geodCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Create a DerivedGeograhicCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param base_geographic_crs Base Geographic CRS. Must not be NULL. * @param conversion Conversion from the base Geographic to the * DerivedGeograhicCRS. Must not be NULL. * @param ellipsoidal_cs Coordinate system. Must not be NULL. * * @return Object of type GeodeticCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. * * @since 7.0 */ PJ *proj_create_derived_geographic_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *base_geographic_crs, const PJ *conversion, const PJ *ellipsoidal_cs) { SANITIZE_CTX(ctx); auto base_crs = std::dynamic_pointer_cast(base_geographic_crs->iso_obj); auto conversion_cpp = std::dynamic_pointer_cast(conversion->iso_obj); auto cs = std::dynamic_pointer_cast(ellipsoidal_cs->iso_obj); if (!base_crs || !conversion_cpp || !cs) { return nullptr; } try { auto derivedCRS = DerivedGeographicCRS::create( createPropertyMapName(crs_name), NN_NO_CHECK(base_crs), NN_NO_CHECK(conversion_cpp), NN_NO_CHECK(cs)); return pj_obj_create(ctx, derivedCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Return whether a CRS is a Derived CRS. * * @param ctx PROJ context, or NULL for default context * @param crs CRS. Must not be NULL. * * @return whether a CRS is a Derived CRS. * * @since 7.0 */ int proj_is_derived_crs(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); return dynamic_cast(crs->iso_obj.get()) != nullptr; } // --------------------------------------------------------------------------- /** \brief Create a VerticalCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param datum_name Name of the VerticalReferenceFrame. Or NULL * @param linear_units Name of the linear units. Or NULL for Metre * @param linear_units_conv Conversion factor from the linear unit to metre. Or * 0 for Metre if linear_units == NULL. Otherwise should be not NULL * * @return Object of type VerticalCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *linear_units, double linear_units_conv) { return proj_create_vertical_crs_ex( ctx, crs_name, datum_name, nullptr, nullptr, linear_units, linear_units_conv, nullptr, nullptr, nullptr, nullptr, nullptr); } // --------------------------------------------------------------------------- /** \brief Create a VerticalCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * This is an extended (_ex) version of proj_create_vertical_crs() that adds * the capability of defining a geoid model. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param datum_name Name of the VerticalReferenceFrame. Or NULL * @param datum_auth_name Authority name of the VerticalReferenceFrame. Or NULL * @param datum_code Code of the VerticalReferenceFrame. Or NULL * @param linear_units Name of the linear units. Or NULL for Metre * @param linear_units_conv Conversion factor from the linear unit to metre. Or * 0 for Metre if linear_units == NULL. Otherwise should be not NULL * @param geoid_model_name Geoid model name, or NULL. Can be a name from the * geoid_model name or a string "PROJ foo.gtx" * @param geoid_model_auth_name Authority name of the transformation for * the geoid model. or NULL * @param geoid_model_code Code of the transformation for * the geoid model. or NULL * @param geoid_geog_crs Geographic CRS for the geoid transformation, or NULL. * @param options NULL-terminated list of strings with "KEY=VALUE" format. or * NULL. * The currently recognized option is ACCURACY=value, where value is in metre. * @return Object of type VerticalCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_vertical_crs_ex( PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *datum_auth_name, const char *datum_code, const char *linear_units, double linear_units_conv, const char *geoid_model_name, const char *geoid_model_auth_name, const char *geoid_model_code, const PJ *geoid_geog_crs, const char *const *options) { SANITIZE_CTX(ctx); try { const UnitOfMeasure linearUnit( createLinearUnit(linear_units, linear_units_conv)); auto datum = VerticalReferenceFrame::create( createPropertyMapName(datum_name, datum_auth_name, datum_code)); auto props = createPropertyMapName(crs_name); auto cs = cs::VerticalCS::createGravityRelatedHeight(linearUnit); if (geoid_model_name) { auto propsModel = createPropertyMapName( geoid_model_name, geoid_model_auth_name, geoid_model_code); const auto vertCRSWithoutGeoid = VerticalCRS::create(props, datum, cs); const auto interpCRS = geoid_geog_crs && std::dynamic_pointer_cast( geoid_geog_crs->iso_obj) ? std::dynamic_pointer_cast(geoid_geog_crs->iso_obj) : nullptr; std::vector accuracies; for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "ACCURACY="))) { accuracies.emplace_back( metadata::PositionalAccuracy::create(value)); } } const auto model(Transformation::create( propsModel, vertCRSWithoutGeoid, GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored interpCRS, OperationMethod::create(PropertyMap(), std::vector()), {}, accuracies)); props.set("GEOID_MODEL", model); } auto vertCRS = VerticalCRS::create(props, datum, cs); return pj_obj_create(ctx, vertCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Create a CompoundCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL * @param horiz_crs Horizontal CRS. must not be NULL. * @param vert_crs Vertical CRS. must not be NULL. * * @return Object of type CompoundCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *horiz_crs, const PJ *vert_crs) { SANITIZE_CTX(ctx); if (!horiz_crs || !vert_crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_horiz_crs = std::dynamic_pointer_cast(horiz_crs->iso_obj); if (!l_horiz_crs) { return nullptr; } auto l_vert_crs = std::dynamic_pointer_cast(vert_crs->iso_obj); if (!l_vert_crs) { return nullptr; } try { auto compoundCRS = CompoundCRS::create( createPropertyMapName(crs_name), {NN_NO_CHECK(l_horiz_crs), NN_NO_CHECK(l_vert_crs)}); return pj_obj_create(ctx, compoundCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Return a copy of the object with its name changed * * Currently, only implemented on CRS objects. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS. Must not be NULL * @param name New name. Must not be NULL * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_alter_name(PJ_CONTEXT *ctx, const PJ *obj, const char *name) { SANITIZE_CTX(ctx); if (!obj || !name) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto crs = dynamic_cast(obj->iso_obj.get()); if (!crs) { return nullptr; } try { return pj_obj_create(ctx, crs->alterName(name)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Return a copy of the object with its identifier changed/set * * Currently, only implemented on CRS objects. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS. Must not be NULL * @param auth_name Authority name. Must not be NULL * @param code Code. Must not be NULL * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_alter_id(PJ_CONTEXT *ctx, const PJ *obj, const char *auth_name, const char *code) { SANITIZE_CTX(ctx); if (!obj || !auth_name || !code) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto crs = dynamic_cast(obj->iso_obj.get()); if (!crs) { return nullptr; } try { return pj_obj_create(ctx, crs->alterId(auth_name, code)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Return a copy of the CRS with its geodetic CRS changed * * Currently, when obj is a GeodeticCRS, it returns a clone of new_geod_crs * When obj is a ProjectedCRS, it replaces its base CRS with new_geod_crs. * When obj is a CompoundCRS, it replaces the GeodeticCRS part of the horizontal * CRS with new_geod_crs. * In other cases, it returns a clone of obj. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS. Must not be NULL * @param new_geod_crs Object of type GeodeticCRS. Must not be NULL * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_crs_alter_geodetic_crs(PJ_CONTEXT *ctx, const PJ *obj, const PJ *new_geod_crs) { SANITIZE_CTX(ctx); if (!obj || !new_geod_crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_new_geod_crs = std::dynamic_pointer_cast(new_geod_crs->iso_obj); if (!l_new_geod_crs) { proj_log_error(ctx, __FUNCTION__, "new_geod_crs is not a GeodeticCRS"); return nullptr; } auto crs = dynamic_cast(obj->iso_obj.get()); if (!crs) { proj_log_error(ctx, __FUNCTION__, "obj is not a CRS"); return nullptr; } try { return pj_obj_create( ctx, crs->alterGeodeticCRS(NN_NO_CHECK(l_new_geod_crs))); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Return a copy of the CRS with its angular units changed * * The CRS must be or contain a GeographicCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS. Must not be NULL * @param angular_units Name of the angular units. Or NULL for Degree * @param angular_units_conv Conversion factor from the angular unit to radian. * Or 0 for Degree if angular_units == NULL. Otherwise should be not NULL * @param unit_auth_name Unit authority name. Or NULL. * @param unit_code Unit code. Or NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_crs_alter_cs_angular_unit(PJ_CONTEXT *ctx, const PJ *obj, const char *angular_units, double angular_units_conv, const char *unit_auth_name, const char *unit_code) { SANITIZE_CTX(ctx); auto geodCRS = proj_crs_get_geodetic_crs(ctx, obj); if (!geodCRS) { return nullptr; } auto geogCRS = dynamic_cast(geodCRS->iso_obj.get()); if (!geogCRS) { proj_destroy(geodCRS); return nullptr; } PJ *geogCRSAltered = nullptr; try { const UnitOfMeasure angUnit(createAngularUnit( angular_units, angular_units_conv, unit_auth_name, unit_code)); geogCRSAltered = pj_obj_create( ctx, GeographicCRS::create( createPropertyMapName(proj_get_name(geodCRS)), geogCRS->datum(), geogCRS->datumEnsemble(), geogCRS->coordinateSystem()->alterAngularUnit(angUnit))); proj_destroy(geodCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); proj_destroy(geodCRS); return nullptr; } auto ret = proj_crs_alter_geodetic_crs(ctx, obj, geogCRSAltered); proj_destroy(geogCRSAltered); return ret; } // --------------------------------------------------------------------------- /** \brief Return a copy of the CRS with the linear units of its coordinate * system changed * * The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS. Must not be NULL * @param linear_units Name of the linear units. Or NULL for Metre * @param linear_units_conv Conversion factor from the linear unit to metre. Or * 0 for Metre if linear_units == NULL. Otherwise should be not NULL * @param unit_auth_name Unit authority name. Or NULL. * @param unit_code Unit code. Or NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ *obj, const char *linear_units, double linear_units_conv, const char *unit_auth_name, const char *unit_code) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto crs = dynamic_cast(obj->iso_obj.get()); if (!crs) { return nullptr; } try { const UnitOfMeasure linearUnit(createLinearUnit( linear_units, linear_units_conv, unit_auth_name, unit_code)); return pj_obj_create(ctx, crs->alterCSLinearUnit(linearUnit)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Return a copy of the CRS with the linear units of the parameters * of its conversion modified. * * The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type ProjectedCRS. Must not be NULL * @param linear_units Name of the linear units. Or NULL for Metre * @param linear_units_conv Conversion factor from the linear unit to metre. Or * 0 for Metre if linear_units == NULL. Otherwise should be not NULL * @param unit_auth_name Unit authority name. Or NULL. * @param unit_code Unit code. Or NULL. * @param convert_to_new_unit TRUE if existing values should be converted from * their current unit to the new unit. If FALSE, their value will be left * unchanged and the unit overridden (so the resulting CRS will not be * equivalent to the original one for reprojection purposes). * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_crs_alter_parameters_linear_unit(PJ_CONTEXT *ctx, const PJ *obj, const char *linear_units, double linear_units_conv, const char *unit_auth_name, const char *unit_code, int convert_to_new_unit) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto crs = dynamic_cast(obj->iso_obj.get()); if (!crs) { return nullptr; } try { const UnitOfMeasure linearUnit(createLinearUnit( linear_units, linear_units_conv, unit_auth_name, unit_code)); return pj_obj_create(ctx, crs->alterParametersLinearUnit( linearUnit, convert_to_new_unit == TRUE)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Create a 3D CRS from an existing 2D CRS. * * The new axis will be ellipsoidal height, oriented upwards, and with metre * units. * * See osgeo::proj::crs::CRS::promoteTo3D(). * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_3D_name CRS name. Or NULL (in which case the name of crs_2D * will be used) * @param crs_2D 2D CRS to be "promoted" to 3D. Must not be NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * @since 6.3 */ PJ *proj_crs_promote_to_3D(PJ_CONTEXT *ctx, const char *crs_3D_name, const PJ *crs_2D) { SANITIZE_CTX(ctx); if (!crs_2D) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto cpp_2D_crs = dynamic_cast(crs_2D->iso_obj.get()); if (!cpp_2D_crs) { auto coordinateMetadata = dynamic_cast(crs_2D->iso_obj.get()); if (!coordinateMetadata) { proj_log_error(ctx, __FUNCTION__, "crs_2D is not a CRS or a CoordinateMetadata"); return nullptr; } try { auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); auto crs = coordinateMetadata->crs(); auto crs_3D = crs->promoteTo3D( crs_3D_name ? std::string(crs_3D_name) : crs->nameStr(), dbContext); if (coordinateMetadata->coordinateEpoch().has_value()) { return pj_obj_create( ctx, CoordinateMetadata::create( crs_3D, coordinateMetadata->coordinateEpochAsDecimalYear(), dbContext)); } else { return pj_obj_create(ctx, CoordinateMetadata::create(crs_3D)); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } else { try { auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); return pj_obj_create(ctx, cpp_2D_crs->promoteTo3D( crs_3D_name ? std::string(crs_3D_name) : cpp_2D_crs->nameStr(), dbContext)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } } // --------------------------------------------------------------------------- /** \brief Create a projected 3D CRS from an existing projected 2D CRS. * * The passed projected_2D_crs is used so that its name is replaced by * crs_name and its base geographic CRS is replaced by geog_3D_crs. The vertical * axis of geog_3D_crs (ellipsoidal height) will be added as the 3rd axis of * the resulting projected 3D CRS. * Normally, the passed geog_3D_crs should be the 3D counterpart of the original * 2D base geographic CRS of projected_2D_crs, but such no check is done. * * It is also possible to invoke this function with a NULL geog_3D_crs. In which * case, the existing base geographic 2D CRS of projected_2D_crs will be * automatically promoted to 3D by assuming a 3rd axis being an ellipsoidal * height, oriented upwards, and with metre units. This is equivalent to using * proj_crs_promote_to_3D(). * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name CRS name. Or NULL (in which case the name of projected_2D_crs * will be used) * @param projected_2D_crs Projected 2D CRS to be "promoted" to 3D. Must not be * NULL. * @param geog_3D_crs Base geographic 3D CRS for the new CRS. May be NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * @since 6.3 */ PJ *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx, const char *crs_name, const PJ *projected_2D_crs, const PJ *geog_3D_crs) { SANITIZE_CTX(ctx); if (!projected_2D_crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto cpp_projected_2D_crs = dynamic_cast(projected_2D_crs->iso_obj.get()); if (!cpp_projected_2D_crs) { proj_log_error(ctx, __FUNCTION__, "projected_2D_crs is not a Projected CRS"); return nullptr; } const auto &oldCS = cpp_projected_2D_crs->coordinateSystem(); const auto &oldCSAxisList = oldCS->axisList(); if (geog_3D_crs && geog_3D_crs->iso_obj) { auto cpp_geog_3D_CRS = std::dynamic_pointer_cast(geog_3D_crs->iso_obj); if (!cpp_geog_3D_CRS) { proj_log_error(ctx, __FUNCTION__, "geog_3D_crs is not a Geographic CRS"); return nullptr; } const auto &geogCS = cpp_geog_3D_CRS->coordinateSystem(); const auto &geogCSAxisList = geogCS->axisList(); if (geogCSAxisList.size() != 3) { proj_log_error(ctx, __FUNCTION__, "geog_3D_crs is not a Geographic 3D CRS"); return nullptr; } try { auto newCS = cs::CartesianCS::create(PropertyMap(), oldCSAxisList[0], oldCSAxisList[1], geogCSAxisList[2]); return pj_obj_create( ctx, ProjectedCRS::create( createPropertyMapName( crs_name ? crs_name : cpp_projected_2D_crs->nameStr().c_str()), NN_NO_CHECK(cpp_geog_3D_CRS), cpp_projected_2D_crs->derivingConversion(), newCS)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } else { try { auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); return pj_obj_create(ctx, cpp_projected_2D_crs->promoteTo3D( crs_name ? std::string(crs_name) : cpp_projected_2D_crs->nameStr(), dbContext)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } } // --------------------------------------------------------------------------- /** \brief Create a 2D CRS from an existing 3D CRS. * * See osgeo::proj::crs::CRS::demoteTo2D(). * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_2D_name CRS name. Or NULL (in which case the name of crs_3D * will be used) * @param crs_3D 3D CRS to be "demoted" to 2D. Must not be NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * @since 6.3 */ PJ *proj_crs_demote_to_2D(PJ_CONTEXT *ctx, const char *crs_2D_name, const PJ *crs_3D) { SANITIZE_CTX(ctx); if (!crs_3D) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto cpp_3D_crs = dynamic_cast(crs_3D->iso_obj.get()); if (!cpp_3D_crs) { proj_log_error(ctx, __FUNCTION__, "crs_3D is not a CRS"); return nullptr; } try { auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); return pj_obj_create( ctx, cpp_3D_crs->demoteTo2D(crs_2D_name ? std::string(crs_2D_name) : cpp_3D_crs->nameStr(), dbContext)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Instantiate a EngineeringCRS with just a name * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name CRS name. Or NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_engineering_crs(PJ_CONTEXT *ctx, const char *crs_name) { SANITIZE_CTX(ctx); try { return pj_obj_create( ctx, EngineeringCRS::create( createPropertyMapName(crs_name), EngineeringDatum::create( createPropertyMapName(UNKNOWN_ENGINEERING_DATUM)), CartesianCS::createEastingNorthing(UnitOfMeasure::METRE))); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static void setSingleOperationElements( const char *name, const char *auth_name, const char *code, const char *method_name, const char *method_auth_name, const char *method_code, int param_count, const PJ_PARAM_DESCRIPTION *params, PropertyMap &propSingleOp, PropertyMap &propMethod, std::vector ¶meters, std::vector &values) { propSingleOp.set(common::IdentifiedObject::NAME_KEY, name ? name : "unnamed"); if (auth_name && code) { propSingleOp.set(metadata::Identifier::CODESPACE_KEY, auth_name) .set(metadata::Identifier::CODE_KEY, code); } propMethod.set(common::IdentifiedObject::NAME_KEY, method_name ? method_name : "unnamed"); if (method_auth_name && method_code) { propMethod.set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code); } for (int i = 0; i < param_count; i++) { PropertyMap propParam; propParam.set(common::IdentifiedObject::NAME_KEY, params[i].name ? params[i].name : "unnamed"); if (params[i].auth_name && params[i].code) { propParam .set(metadata::Identifier::CODESPACE_KEY, params[i].auth_name) .set(metadata::Identifier::CODE_KEY, params[i].code); } parameters.emplace_back(OperationParameter::create(propParam)); auto unit_type = UnitOfMeasure::Type::UNKNOWN; switch (params[i].unit_type) { case PJ_UT_ANGULAR: unit_type = UnitOfMeasure::Type::ANGULAR; break; case PJ_UT_LINEAR: unit_type = UnitOfMeasure::Type::LINEAR; break; case PJ_UT_SCALE: unit_type = UnitOfMeasure::Type::SCALE; break; case PJ_UT_TIME: unit_type = UnitOfMeasure::Type::TIME; break; case PJ_UT_PARAMETRIC: unit_type = UnitOfMeasure::Type::PARAMETRIC; break; } Measure measure( params[i].value, params[i].unit_type == PJ_UT_ANGULAR ? createAngularUnit(params[i].unit_name, params[i].unit_conv_factor) : params[i].unit_type == PJ_UT_LINEAR ? createLinearUnit(params[i].unit_name, params[i].unit_conv_factor) : UnitOfMeasure(params[i].unit_name ? params[i].unit_name : "unnamed", params[i].unit_conv_factor, unit_type)); values.emplace_back(ParameterValue::create(measure)); } } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a Conversion * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param name Conversion name. Or NULL. * @param auth_name Conversion authority name. Or NULL. * @param code Conversion code. Or NULL. * @param method_name Method name. Or NULL. * @param method_auth_name Method authority name. Or NULL. * @param method_code Method code. Or NULL. * @param param_count Number of parameters (size of params argument) * @param params Parameter descriptions (array of size param_count) * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_conversion(PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code, const char *method_name, const char *method_auth_name, const char *method_code, int param_count, const PJ_PARAM_DESCRIPTION *params) { SANITIZE_CTX(ctx); try { PropertyMap propSingleOp; PropertyMap propMethod; std::vector parameters; std::vector values; setSingleOperationElements( name, auth_name, code, method_name, method_auth_name, method_code, param_count, params, propSingleOp, propMethod, parameters, values); return pj_obj_create(ctx, Conversion::create(propSingleOp, propMethod, parameters, values)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Instantiate a Transformation * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param name Transformation name. Or NULL. * @param auth_name Transformation authority name. Or NULL. * @param code Transformation code. Or NULL. * @param source_crs Object of type CRS representing the source CRS. * Must not be NULL. * @param target_crs Object of type CRS representing the target CRS. * Must not be NULL. * @param interpolation_crs Object of type CRS representing the interpolation * CRS. Or NULL. * @param method_name Method name. Or NULL. * @param method_auth_name Method authority name. Or NULL. * @param method_code Method code. Or NULL. * @param param_count Number of parameters (size of params argument) * @param params Parameter descriptions (array of size param_count) * @param accuracy Accuracy of the transformation in meters. A negative * values means unknown. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_transformation( PJ_CONTEXT *ctx, const char *name, const char *auth_name, const char *code, const PJ *source_crs, const PJ *target_crs, const PJ *interpolation_crs, const char *method_name, const char *method_auth_name, const char *method_code, int param_count, const PJ_PARAM_DESCRIPTION *params, double accuracy) { SANITIZE_CTX(ctx); if (!source_crs || !target_crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_sourceCRS = std::dynamic_pointer_cast(source_crs->iso_obj); if (!l_sourceCRS) { proj_log_error(ctx, __FUNCTION__, "source_crs is not a CRS"); return nullptr; } auto l_targetCRS = std::dynamic_pointer_cast(target_crs->iso_obj); if (!l_targetCRS) { proj_log_error(ctx, __FUNCTION__, "target_crs is not a CRS"); return nullptr; } CRSPtr l_interpolationCRS; if (interpolation_crs) { l_interpolationCRS = std::dynamic_pointer_cast(interpolation_crs->iso_obj); if (!l_interpolationCRS) { proj_log_error(ctx, __FUNCTION__, "interpolation_crs is not a CRS"); return nullptr; } } try { PropertyMap propSingleOp; PropertyMap propMethod; std::vector parameters; std::vector values; setSingleOperationElements( name, auth_name, code, method_name, method_auth_name, method_code, param_count, params, propSingleOp, propMethod, parameters, values); std::vector accuracies; if (accuracy >= 0.0) { accuracies.emplace_back( PositionalAccuracy::create(toString(accuracy))); } return pj_obj_create( ctx, Transformation::create(propSingleOp, NN_NO_CHECK(l_sourceCRS), NN_NO_CHECK(l_targetCRS), l_interpolationCRS, propMethod, parameters, values, accuracies)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** * \brief Return an equivalent projection. * * Currently implemented: *
    *
  • EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)
  • *
  • EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)
  • *
  • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP
  • *
  • EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP
  • *
* * @param ctx PROJ context, or NULL for default context * @param conversion Object of type Conversion. Must not be NULL. * @param new_method_epsg_code EPSG code of the target method. Or 0 (in which * case new_method_name must be specified). * @param new_method_name EPSG or PROJ target method name. Or nullptr (in which * case new_method_epsg_code must be specified). * @return new conversion that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_convert_conversion_to_other_method(PJ_CONTEXT *ctx, const PJ *conversion, int new_method_epsg_code, const char *new_method_name) { SANITIZE_CTX(ctx); if (!conversion) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto conv = dynamic_cast(conversion->iso_obj.get()); if (!conv) { proj_log_error(ctx, __FUNCTION__, "not a Conversion"); return nullptr; } if (new_method_epsg_code == 0) { if (!new_method_name) { return nullptr; } if (metadata::Identifier::isEquivalentName( new_method_name, EPSG_NAME_METHOD_MERCATOR_VARIANT_A)) { new_method_epsg_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_A; } else if (metadata::Identifier::isEquivalentName( new_method_name, EPSG_NAME_METHOD_MERCATOR_VARIANT_B)) { new_method_epsg_code = EPSG_CODE_METHOD_MERCATOR_VARIANT_B; } else if (metadata::Identifier::isEquivalentName( new_method_name, EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { new_method_epsg_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP; } else if (metadata::Identifier::isEquivalentName( new_method_name, EPSG_NAME_METHOD_LAMBERT_CONIC_CONFORMAL_2SP)) { new_method_epsg_code = EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP; } } try { auto new_conv = conv->convertToOtherMethod(new_method_epsg_code); if (!new_conv) return nullptr; return pj_obj_create(ctx, NN_NO_CHECK(new_conv)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static CoordinateSystemAxisNNPtr createAxis(const PJ_AXIS_DESCRIPTION &axis) { const auto dir = axis.direction ? AxisDirection::valueOf(axis.direction) : nullptr; if (dir == nullptr) throw Exception("invalid value for axis direction"); auto unit_type = UnitOfMeasure::Type::UNKNOWN; switch (axis.unit_type) { case PJ_UT_ANGULAR: unit_type = UnitOfMeasure::Type::ANGULAR; break; case PJ_UT_LINEAR: unit_type = UnitOfMeasure::Type::LINEAR; break; case PJ_UT_SCALE: unit_type = UnitOfMeasure::Type::SCALE; break; case PJ_UT_TIME: unit_type = UnitOfMeasure::Type::TIME; break; case PJ_UT_PARAMETRIC: unit_type = UnitOfMeasure::Type::PARAMETRIC; break; } const common::UnitOfMeasure unit( axis.unit_type == PJ_UT_ANGULAR ? createAngularUnit(axis.unit_name, axis.unit_conv_factor) : axis.unit_type == PJ_UT_LINEAR ? createLinearUnit(axis.unit_name, axis.unit_conv_factor) : UnitOfMeasure(axis.unit_name ? axis.unit_name : "unnamed", axis.unit_conv_factor, unit_type)); return CoordinateSystemAxis::create( createPropertyMapName(axis.name), axis.abbreviation ? axis.abbreviation : std::string(), *dir, unit); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateSystem. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param type Coordinate system type. * @param axis_count Number of axis * @param axis Axis description (array of size axis_count) * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_cs(PJ_CONTEXT *ctx, PJ_COORDINATE_SYSTEM_TYPE type, int axis_count, const PJ_AXIS_DESCRIPTION *axis) { SANITIZE_CTX(ctx); try { switch (type) { case PJ_CS_TYPE_UNKNOWN: return nullptr; case PJ_CS_TYPE_CARTESIAN: { if (axis_count == 2) { return pj_obj_create( ctx, CartesianCS::create(PropertyMap(), createAxis(axis[0]), createAxis(axis[1]))); } else if (axis_count == 3) { return pj_obj_create( ctx, CartesianCS::create(PropertyMap(), createAxis(axis[0]), createAxis(axis[1]), createAxis(axis[2]))); } break; } case PJ_CS_TYPE_ELLIPSOIDAL: { if (axis_count == 2) { return pj_obj_create( ctx, EllipsoidalCS::create(PropertyMap(), createAxis(axis[0]), createAxis(axis[1]))); } else if (axis_count == 3) { return pj_obj_create( ctx, EllipsoidalCS::create( PropertyMap(), createAxis(axis[0]), createAxis(axis[1]), createAxis(axis[2]))); } break; } case PJ_CS_TYPE_VERTICAL: { if (axis_count == 1) { return pj_obj_create( ctx, VerticalCS::create(PropertyMap(), createAxis(axis[0]))); } break; } case PJ_CS_TYPE_SPHERICAL: { if (axis_count == 3) { return pj_obj_create( ctx, EllipsoidalCS::create( PropertyMap(), createAxis(axis[0]), createAxis(axis[1]), createAxis(axis[2]))); } break; } case PJ_CS_TYPE_PARAMETRIC: { if (axis_count == 1) { return pj_obj_create( ctx, ParametricCS::create(PropertyMap(), createAxis(axis[0]))); } break; } case PJ_CS_TYPE_ORDINAL: { std::vector axisVector; for (int i = 0; i < axis_count; i++) { axisVector.emplace_back(createAxis(axis[i])); } return pj_obj_create(ctx, OrdinalCS::create(PropertyMap(), axisVector)); } case PJ_CS_TYPE_DATETIMETEMPORAL: { if (axis_count == 1) { return pj_obj_create( ctx, DateTimeTemporalCS::create(PropertyMap(), createAxis(axis[0]))); } break; } case PJ_CS_TYPE_TEMPORALCOUNT: { if (axis_count == 1) { return pj_obj_create( ctx, TemporalCountCS::create(PropertyMap(), createAxis(axis[0]))); } break; } case PJ_CS_TYPE_TEMPORALMEASURE: { if (axis_count == 1) { return pj_obj_create( ctx, TemporalMeasureCS::create(PropertyMap(), createAxis(axis[0]))); } break; } } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } proj_log_error(ctx, __FUNCTION__, "Wrong value for axis_count"); return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesiansCS 2D * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param type Coordinate system type. * @param unit_name Unit name. * @param unit_conv_factor Unit conversion factor to SI. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_cartesian_2D_cs(PJ_CONTEXT *ctx, PJ_CARTESIAN_CS_2D_TYPE type, const char *unit_name, double unit_conv_factor) { SANITIZE_CTX(ctx); try { switch (type) { case PJ_CART2D_EASTING_NORTHING: return pj_obj_create( ctx, CartesianCS::createEastingNorthing( createLinearUnit(unit_name, unit_conv_factor))); case PJ_CART2D_NORTHING_EASTING: return pj_obj_create( ctx, CartesianCS::createNorthingEasting( createLinearUnit(unit_name, unit_conv_factor))); case PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH: return pj_obj_create( ctx, CartesianCS::createNorthPoleEastingSouthNorthingSouth( createLinearUnit(unit_name, unit_conv_factor))); case PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH: return pj_obj_create( ctx, CartesianCS::createSouthPoleEastingNorthNorthingNorth( createLinearUnit(unit_name, unit_conv_factor))); case PJ_CART2D_WESTING_SOUTHING: return pj_obj_create( ctx, CartesianCS::createWestingSouthing( createLinearUnit(unit_name, unit_conv_factor))); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a Ellipsoidal 2D * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param type Coordinate system type. * @param unit_name Name of the angular units. Or NULL for Degree * @param unit_conv_factor Conversion factor from the angular unit to radian. * Or 0 for Degree if unit_name == NULL. Otherwise should be not NULL * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_ellipsoidal_2D_cs(PJ_CONTEXT *ctx, PJ_ELLIPSOIDAL_CS_2D_TYPE type, const char *unit_name, double unit_conv_factor) { SANITIZE_CTX(ctx); try { switch (type) { case PJ_ELLPS2D_LONGITUDE_LATITUDE: return pj_obj_create( ctx, EllipsoidalCS::createLongitudeLatitude( createAngularUnit(unit_name, unit_conv_factor))); case PJ_ELLPS2D_LATITUDE_LONGITUDE: return pj_obj_create( ctx, EllipsoidalCS::createLatitudeLongitude( createAngularUnit(unit_name, unit_conv_factor))); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a Ellipsoidal 3D * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param type Coordinate system type. * @param horizontal_angular_unit_name Name of the angular units. Or NULL for * Degree. * @param horizontal_angular_unit_conv_factor Conversion factor from the angular * unit to radian. Or 0 for Degree if horizontal_angular_unit_name == NULL. * Otherwise should be not NULL * @param vertical_linear_unit_name Vertical linear unit name. Or NULL for * Metre. * @param vertical_linear_unit_conv_factor Vertical linear unit conversion * factor to metre. Or 0 for Metre if vertical_linear_unit_name == NULL. * Otherwise should be not NULL * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * @since 6.3 */ PJ *proj_create_ellipsoidal_3D_cs(PJ_CONTEXT *ctx, PJ_ELLIPSOIDAL_CS_3D_TYPE type, const char *horizontal_angular_unit_name, double horizontal_angular_unit_conv_factor, const char *vertical_linear_unit_name, double vertical_linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { switch (type) { case PJ_ELLPS3D_LONGITUDE_LATITUDE_HEIGHT: return pj_obj_create( ctx, EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight( createAngularUnit(horizontal_angular_unit_name, horizontal_angular_unit_conv_factor), createLinearUnit(vertical_linear_unit_name, vertical_linear_unit_conv_factor))); case PJ_ELLPS3D_LATITUDE_LONGITUDE_HEIGHT: return pj_obj_create( ctx, EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( createAngularUnit(horizontal_angular_unit_name, horizontal_angular_unit_conv_factor), createLinearUnit(vertical_linear_unit_name, vertical_linear_unit_conv_factor))); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name CRS name. Or NULL * @param geodetic_crs Base GeodeticCRS. Must not be NULL. * @param conversion Conversion. Must not be NULL. * @param coordinate_system Cartesian coordinate system. Must not be NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_projected_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *geodetic_crs, const PJ *conversion, const PJ *coordinate_system) { SANITIZE_CTX(ctx); if (!geodetic_crs || !conversion || !coordinate_system) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto geodCRS = std::dynamic_pointer_cast(geodetic_crs->iso_obj); if (!geodCRS) { return nullptr; } auto conv = std::dynamic_pointer_cast(conversion->iso_obj); if (!conv) { return nullptr; } auto cs = std::dynamic_pointer_cast(coordinate_system->iso_obj); if (!cs) { return nullptr; } try { return pj_obj_create( ctx, ProjectedCRS::create(createPropertyMapName(crs_name), NN_NO_CHECK(geodCRS), NN_NO_CHECK(conv), NN_NO_CHECK(cs))); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static PJ *proj_create_conversion(PJ_CONTEXT *ctx, const ConversionNNPtr &conv) { return pj_obj_create(ctx, conv); } //! @endcond /* BEGIN: Generated by scripts/create_c_api_projections.py*/ // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a Universal Transverse Mercator * conversion. * * See osgeo::proj::operation::Conversion::createUTM(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). */ PJ *proj_create_conversion_utm(PJ_CONTEXT *ctx, int zone, int north) { SANITIZE_CTX(ctx); try { auto conv = Conversion::createUTM(PropertyMap(), zone, north != 0); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Transverse * Mercator projection method. * * See osgeo::proj::operation::Conversion::createTransverseMercator(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_transverse_mercator( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createTransverseMercator( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Gauss * Schreiber Transverse Mercator projection method. * * See * osgeo::proj::operation::Conversion::createGaussSchreiberTransverseMercator(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_gauss_schreiber_transverse_mercator( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGaussSchreiberTransverseMercator( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Transverse * Mercator South Orientated projection method. * * See * osgeo::proj::operation::Conversion::createTransverseMercatorSouthOriented(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_transverse_mercator_south_oriented( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createTransverseMercatorSouthOriented( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Two Point * Equidistant projection method. * * See osgeo::proj::operation::Conversion::createTwoPointEquidistant(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_two_point_equidistant( PJ_CONTEXT *ctx, double latitude_first_point, double longitude_first_point, double latitude_second_point, double longitude_secon_point, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createTwoPointEquidistant( PropertyMap(), Angle(latitude_first_point, angUnit), Angle(longitude_first_point, angUnit), Angle(latitude_second_point, angUnit), Angle(longitude_secon_point, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Tunisia * Mining Grid projection method. * * See osgeo::proj::operation::Conversion::createTunisiaMiningGrid(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). * * @since 9.2 */ PJ *proj_create_conversion_tunisia_mining_grid( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createTunisiaMiningGrid( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Tunisia * Mining Grid projection method. * * See osgeo::proj::operation::Conversion::createTunisiaMiningGrid(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). * * @deprecated Replaced by proj_create_conversion_tunisia_mining_grid */ PJ *proj_create_conversion_tunisia_mapping_grid( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createTunisiaMiningGrid( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Albers * Conic Equal Area projection method. * * See osgeo::proj::operation::Conversion::createAlbersEqualArea(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_albers_equal_area( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createAlbersEqualArea( PropertyMap(), Angle(latitude_false_origin, angUnit), Angle(longitude_false_origin, angUnit), Angle(latitude_first_parallel, angUnit), Angle(latitude_second_parallel, angUnit), Length(easting_false_origin, linearUnit), Length(northing_false_origin, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Conic Conformal 1SP projection method. * * See osgeo::proj::operation::Conversion::createLambertConicConformal_1SP(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_conic_conformal_1sp( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertConicConformal_1SP( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Conic Conformal (1SP Variant B) projection method. * * See * osgeo::proj::operation::Conversion::createLambertConicConformal_1SP_VariantB(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). * @since 9.2.1 */ PJ *proj_create_conversion_lambert_conic_conformal_1sp_variant_b( PJ_CONTEXT *ctx, double latitude_nat_origin, double scale, double latitude_false_origin, double longitude_false_origin, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertConicConformal_1SP_VariantB( PropertyMap(), Angle(latitude_nat_origin, angUnit), Scale(scale), Angle(latitude_false_origin, angUnit), Angle(longitude_false_origin, angUnit), Length(easting_false_origin, linearUnit), Length(northing_false_origin, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Conic Conformal (2SP) projection method. * * See osgeo::proj::operation::Conversion::createLambertConicConformal_2SP(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_conic_conformal_2sp( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertConicConformal_2SP( PropertyMap(), Angle(latitude_false_origin, angUnit), Angle(longitude_false_origin, angUnit), Angle(latitude_first_parallel, angUnit), Angle(latitude_second_parallel, angUnit), Length(easting_false_origin, linearUnit), Length(northing_false_origin, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Conic Conformal (2SP Michigan) projection method. * * See * osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Michigan(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_conic_conformal_2sp_michigan( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, double ellipsoid_scaling_factor, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertConicConformal_2SP_Michigan( PropertyMap(), Angle(latitude_false_origin, angUnit), Angle(longitude_false_origin, angUnit), Angle(latitude_first_parallel, angUnit), Angle(latitude_second_parallel, angUnit), Length(easting_false_origin, linearUnit), Length(northing_false_origin, linearUnit), Scale(ellipsoid_scaling_factor)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Conic Conformal (2SP Belgium) projection method. * * See * osgeo::proj::operation::Conversion::createLambertConicConformal_2SP_Belgium(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_conic_conformal_2sp_belgium( PJ_CONTEXT *ctx, double latitude_false_origin, double longitude_false_origin, double latitude_first_parallel, double latitude_second_parallel, double easting_false_origin, double northing_false_origin, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertConicConformal_2SP_Belgium( PropertyMap(), Angle(latitude_false_origin, angUnit), Angle(longitude_false_origin, angUnit), Angle(latitude_first_parallel, angUnit), Angle(latitude_second_parallel, angUnit), Length(easting_false_origin, linearUnit), Length(northing_false_origin, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Modified * Azimuthal Equidistant projection method. * * See osgeo::proj::operation::Conversion::createAzimuthalEquidistant(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_azimuthal_equidistant( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createAzimuthalEquidistant( PropertyMap(), Angle(latitude_nat_origin, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Guam * Projection projection method. * * See osgeo::proj::operation::Conversion::createGuamProjection(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_guam_projection( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGuamProjection( PropertyMap(), Angle(latitude_nat_origin, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Bonne * projection method. * * See osgeo::proj::operation::Conversion::createBonne(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_bonne(PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createBonne( PropertyMap(), Angle(latitude_nat_origin, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Cylindrical Equal Area (Spherical) projection method. * * See * osgeo::proj::operation::Conversion::createLambertCylindricalEqualAreaSpherical(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_cylindrical_equal_area_spherical( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertCylindricalEqualAreaSpherical( PropertyMap(), Angle(latitude_first_parallel, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Cylindrical Equal Area (ellipsoidal form) projection method. * * See osgeo::proj::operation::Conversion::createLambertCylindricalEqualArea(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_cylindrical_equal_area( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertCylindricalEqualArea( PropertyMap(), Angle(latitude_first_parallel, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * Cassini-Soldner projection method. * * See osgeo::proj::operation::Conversion::createCassiniSoldner(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_cassini_soldner( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createCassiniSoldner( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Equidistant * Conic projection method. * * See osgeo::proj::operation::Conversion::createEquidistantConic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_equidistant_conic( PJ_CONTEXT *ctx, double center_lat, double center_long, double latitude_first_parallel, double latitude_second_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEquidistantConic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Angle(latitude_first_parallel, angUnit), Angle(latitude_second_parallel, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Eckert I * projection method. * * See osgeo::proj::operation::Conversion::createEckertI(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_eckert_i(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEckertI( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Eckert II * projection method. * * See osgeo::proj::operation::Conversion::createEckertII(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_eckert_ii(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEckertII( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Eckert III * projection method. * * See osgeo::proj::operation::Conversion::createEckertIII(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_eckert_iii(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEckertIII( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Eckert IV * projection method. * * See osgeo::proj::operation::Conversion::createEckertIV(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_eckert_iv(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEckertIV( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Eckert V * projection method. * * See osgeo::proj::operation::Conversion::createEckertV(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_eckert_v(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEckertV( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Eckert VI * projection method. * * See osgeo::proj::operation::Conversion::createEckertVI(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_eckert_vi(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEckertVI( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Equidistant * Cylindrical projection method. * * See osgeo::proj::operation::Conversion::createEquidistantCylindrical(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_equidistant_cylindrical( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEquidistantCylindrical( PropertyMap(), Angle(latitude_first_parallel, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Equidistant * Cylindrical (Spherical) projection method. * * See * osgeo::proj::operation::Conversion::createEquidistantCylindricalSpherical(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_equidistant_cylindrical_spherical( PJ_CONTEXT *ctx, double latitude_first_parallel, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEquidistantCylindricalSpherical( PropertyMap(), Angle(latitude_first_parallel, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Gall * (Stereographic) projection method. * * See osgeo::proj::operation::Conversion::createGall(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_gall(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGall(PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Goode * Homolosine projection method. * * See osgeo::proj::operation::Conversion::createGoodeHomolosine(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_goode_homolosine(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGoodeHomolosine( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Interrupted * Goode Homolosine projection method. * * See osgeo::proj::operation::Conversion::createInterruptedGoodeHomolosine(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_interrupted_goode_homolosine( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createInterruptedGoodeHomolosine( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * Geostationary Satellite View projection method, with the sweep angle axis of * the viewing instrument being x. * * See osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepX(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_geostationary_satellite_sweep_x( PJ_CONTEXT *ctx, double center_long, double height, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGeostationarySatelliteSweepX( PropertyMap(), Angle(center_long, angUnit), Length(height, linearUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * Geostationary Satellite View projection method, with the sweep angle axis of * the viewing instrument being y. * * See osgeo::proj::operation::Conversion::createGeostationarySatelliteSweepY(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_geostationary_satellite_sweep_y( PJ_CONTEXT *ctx, double center_long, double height, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGeostationarySatelliteSweepY( PropertyMap(), Angle(center_long, angUnit), Length(height, linearUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Gnomonic * projection method. * * See osgeo::proj::operation::Conversion::createGnomonic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_gnomonic(PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createGnomonic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Hotine * Oblique Mercator (Variant A) projection method. * * See * osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantA(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_hotine_oblique_mercator_variant_a( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_projection_centre, double azimuth_initial_line, double angle_from_rectified_to_skrew_grid, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createHotineObliqueMercatorVariantA( PropertyMap(), Angle(latitude_projection_centre, angUnit), Angle(longitude_projection_centre, angUnit), Angle(azimuth_initial_line, angUnit), Angle(angle_from_rectified_to_skrew_grid, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Hotine * Oblique Mercator (Variant B) projection method. * * See * osgeo::proj::operation::Conversion::createHotineObliqueMercatorVariantB(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_hotine_oblique_mercator_variant_b( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_projection_centre, double azimuth_initial_line, double angle_from_rectified_to_skrew_grid, double scale, double easting_projection_centre, double northing_projection_centre, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createHotineObliqueMercatorVariantB( PropertyMap(), Angle(latitude_projection_centre, angUnit), Angle(longitude_projection_centre, angUnit), Angle(azimuth_initial_line, angUnit), Angle(angle_from_rectified_to_skrew_grid, angUnit), Scale(scale), Length(easting_projection_centre, linearUnit), Length(northing_projection_centre, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Hotine * Oblique Mercator Two Point Natural Origin projection method. * * See * osgeo::proj::operation::Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin( PJ_CONTEXT *ctx, double latitude_projection_centre, double latitude_point1, double longitude_point1, double latitude_point2, double longitude_point2, double scale, double easting_projection_centre, double northing_projection_centre, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin( PropertyMap(), Angle(latitude_projection_centre, angUnit), Angle(latitude_point1, angUnit), Angle(longitude_point1, angUnit), Angle(latitude_point2, angUnit), Angle(longitude_point2, angUnit), Scale(scale), Length(easting_projection_centre, linearUnit), Length(northing_projection_centre, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Laborde * Oblique Mercator projection method. * * See * osgeo::proj::operation::Conversion::createLabordeObliqueMercator(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_laborde_oblique_mercator( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_projection_centre, double azimuth_initial_line, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLabordeObliqueMercator( PropertyMap(), Angle(latitude_projection_centre, angUnit), Angle(longitude_projection_centre, angUnit), Angle(azimuth_initial_line, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * International Map of the World Polyconic projection method. * * See * osgeo::proj::operation::Conversion::createInternationalMapWorldPolyconic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_international_map_world_polyconic( PJ_CONTEXT *ctx, double center_long, double latitude_first_parallel, double latitude_second_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createInternationalMapWorldPolyconic( PropertyMap(), Angle(center_long, angUnit), Angle(latitude_first_parallel, angUnit), Angle(latitude_second_parallel, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Krovak * (north oriented) projection method. * * See osgeo::proj::operation::Conversion::createKrovakNorthOriented(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_krovak_north_oriented( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_of_origin, double colatitude_cone_axis, double latitude_pseudo_standard_parallel, double scale_factor_pseudo_standard_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createKrovakNorthOriented( PropertyMap(), Angle(latitude_projection_centre, angUnit), Angle(longitude_of_origin, angUnit), Angle(colatitude_cone_axis, angUnit), Angle(latitude_pseudo_standard_parallel, angUnit), Scale(scale_factor_pseudo_standard_parallel), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Krovak * projection method. * * See osgeo::proj::operation::Conversion::createKrovak(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_krovak( PJ_CONTEXT *ctx, double latitude_projection_centre, double longitude_of_origin, double colatitude_cone_axis, double latitude_pseudo_standard_parallel, double scale_factor_pseudo_standard_parallel, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createKrovak( PropertyMap(), Angle(latitude_projection_centre, angUnit), Angle(longitude_of_origin, angUnit), Angle(colatitude_cone_axis, angUnit), Angle(latitude_pseudo_standard_parallel, angUnit), Scale(scale_factor_pseudo_standard_parallel), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Lambert * Azimuthal Equal Area projection method. * * See osgeo::proj::operation::Conversion::createLambertAzimuthalEqualArea(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_lambert_azimuthal_equal_area( PJ_CONTEXT *ctx, double latitude_nat_origin, double longitude_nat_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLambertAzimuthalEqualArea( PropertyMap(), Angle(latitude_nat_origin, angUnit), Angle(longitude_nat_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Miller * Cylindrical projection method. * * See osgeo::proj::operation::Conversion::createMillerCylindrical(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_miller_cylindrical( PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createMillerCylindrical( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Mercator * projection method. * * See osgeo::proj::operation::Conversion::createMercatorVariantA(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_mercator_variant_a( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createMercatorVariantA( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Mercator * projection method. * * See osgeo::proj::operation::Conversion::createMercatorVariantB(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_mercator_variant_b( PJ_CONTEXT *ctx, double latitude_first_parallel, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createMercatorVariantB( PropertyMap(), Angle(latitude_first_parallel, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Popular * Visualisation Pseudo Mercator projection method. * * See * osgeo::proj::operation::Conversion::createPopularVisualisationPseudoMercator(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_popular_visualisation_pseudo_mercator( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createPopularVisualisationPseudoMercator( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Mollweide * projection method. * * See osgeo::proj::operation::Conversion::createMollweide(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_mollweide(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createMollweide( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the New Zealand * Map Grid projection method. * * See osgeo::proj::operation::Conversion::createNewZealandMappingGrid(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_new_zealand_mapping_grid( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createNewZealandMappingGrid( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Oblique * Stereographic (Alternative) projection method. * * See osgeo::proj::operation::Conversion::createObliqueStereographic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_oblique_stereographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createObliqueStereographic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * Orthographic projection method. * * See osgeo::proj::operation::Conversion::createOrthographic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_orthographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createOrthographic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Local * Orthographic projection method. * * See osgeo::proj::operation::Conversion::createLocalOrthographic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_local_orthographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double azimuth, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createLocalOrthographic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Angle(azimuth, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the American * Polyconic projection method. * * See osgeo::proj::operation::Conversion::createAmericanPolyconic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_american_polyconic( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createAmericanPolyconic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Polar * Stereographic (Variant A) projection method. * * See osgeo::proj::operation::Conversion::createPolarStereographicVariantA(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_polar_stereographic_variant_a( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createPolarStereographicVariantA( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Polar * Stereographic (Variant B) projection method. * * See osgeo::proj::operation::Conversion::createPolarStereographicVariantB(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_polar_stereographic_variant_b( PJ_CONTEXT *ctx, double latitude_standard_parallel, double longitude_of_origin, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createPolarStereographicVariantB( PropertyMap(), Angle(latitude_standard_parallel, angUnit), Angle(longitude_of_origin, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Robinson * projection method. * * See osgeo::proj::operation::Conversion::createRobinson(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_robinson(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createRobinson( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Sinusoidal * projection method. * * See osgeo::proj::operation::Conversion::createSinusoidal(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_sinusoidal(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createSinusoidal( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * Stereographic projection method. * * See osgeo::proj::operation::Conversion::createStereographic(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_stereographic( PJ_CONTEXT *ctx, double center_lat, double center_long, double scale, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createStereographic( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Scale(scale), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Van der * Grinten projection method. * * See osgeo::proj::operation::Conversion::createVanDerGrinten(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_van_der_grinten(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createVanDerGrinten( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner I * projection method. * * See osgeo::proj::operation::Conversion::createWagnerI(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_i(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerI( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner II * projection method. * * See osgeo::proj::operation::Conversion::createWagnerII(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_ii(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerII( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner III * projection method. * * See osgeo::proj::operation::Conversion::createWagnerIII(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_iii( PJ_CONTEXT *ctx, double latitude_true_scale, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerIII( PropertyMap(), Angle(latitude_true_scale, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner IV * projection method. * * See osgeo::proj::operation::Conversion::createWagnerIV(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_iv(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerIV( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner V * projection method. * * See osgeo::proj::operation::Conversion::createWagnerV(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_v(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerV( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner VI * projection method. * * See osgeo::proj::operation::Conversion::createWagnerVI(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_vi(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerVI( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Wagner VII * projection method. * * See osgeo::proj::operation::Conversion::createWagnerVII(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_wagner_vii(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createWagnerVII( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the * Quadrilateralized Spherical Cube projection method. * * See * osgeo::proj::operation::Conversion::createQuadrilateralizedSphericalCube(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_quadrilateralized_spherical_cube( PJ_CONTEXT *ctx, double center_lat, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createQuadrilateralizedSphericalCube( PropertyMap(), Angle(center_lat, angUnit), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Spherical * Cross-Track Height projection method. * * See osgeo::proj::operation::Conversion::createSphericalCrossTrackHeight(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_spherical_cross_track_height( PJ_CONTEXT *ctx, double peg_point_lat, double peg_point_long, double peg_point_heading, double peg_point_height, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createSphericalCrossTrackHeight( PropertyMap(), Angle(peg_point_lat, angUnit), Angle(peg_point_long, angUnit), Angle(peg_point_heading, angUnit), Length(peg_point_height, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS with a conversion based on the Equal Earth * projection method. * * See osgeo::proj::operation::Conversion::createEqualEarth(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_equal_earth(PJ_CONTEXT *ctx, double center_long, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createEqualEarth( PropertyMap(), Angle(center_long, angUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Vertical Perspective projection * method. * * See osgeo::proj::operation::Conversion::createVerticalPerspective(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). * * @since 6.3 */ PJ *proj_create_conversion_vertical_perspective( PJ_CONTEXT *ctx, double topo_origin_lat, double topo_origin_long, double topo_origin_height, double view_point_height, double false_easting, double false_northing, const char *ang_unit_name, double ang_unit_conv_factor, const char *linear_unit_name, double linear_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure linearUnit( createLinearUnit(linear_unit_name, linear_unit_conv_factor)); UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createVerticalPerspective( PropertyMap(), Angle(topo_origin_lat, angUnit), Angle(topo_origin_long, angUnit), Length(topo_origin_height, linearUnit), Length(view_point_height, linearUnit), Length(false_easting, linearUnit), Length(false_northing, linearUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Pole Rotation method, using the * conventions of the GRIB 1 and GRIB 2 data formats. * * See osgeo::proj::operation::Conversion::createPoleRotationGRIBConvention(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_pole_rotation_grib_convention( PJ_CONTEXT *ctx, double south_pole_lat_in_unrotated_crs, double south_pole_long_in_unrotated_crs, double axis_rotation, const char *ang_unit_name, double ang_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createPoleRotationGRIBConvention( PropertyMap(), Angle(south_pole_lat_in_unrotated_crs, angUnit), Angle(south_pole_long_in_unrotated_crs, angUnit), Angle(axis_rotation, angUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion based on the Pole Rotation method, using * the conventions of the netCDF CF convention for the netCDF format. * * See * osgeo::proj::operation::Conversion::createPoleRotationNetCDFCFConvention(). * * Linear parameters are expressed in (linear_unit_name, * linear_unit_conv_factor). * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). */ PJ *proj_create_conversion_pole_rotation_netcdf_cf_convention( PJ_CONTEXT *ctx, double grid_north_pole_latitude, double grid_north_pole_longitude, double north_pole_grid_longitude, const char *ang_unit_name, double ang_unit_conv_factor) { SANITIZE_CTX(ctx); try { UnitOfMeasure angUnit( createAngularUnit(ang_unit_name, ang_unit_conv_factor)); auto conv = Conversion::createPoleRotationNetCDFCFConvention( PropertyMap(), Angle(grid_north_pole_latitude, angUnit), Angle(grid_north_pole_longitude, angUnit), Angle(north_pole_grid_longitude, angUnit)); return proj_create_conversion(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } /* END: Generated by scripts/create_c_api_projections.py*/ // --------------------------------------------------------------------------- /** \brief Return whether a coordinate operation can be instantiated as * a PROJ pipeline, checking in particular that referenced grids are * available. * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type CoordinateOperation or derived classes * (must not be NULL) * @return TRUE or FALSE. */ int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto op = dynamic_cast( coordoperation->iso_obj.get()); if (!op) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation"); return 0; } auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { auto ret = op->isPROJInstantiable( dbContext, proj_context_is_network_enabled(ctx) != FALSE) ? 1 : 0; return ret; } catch (const std::exception &) { return 0; } } // --------------------------------------------------------------------------- /** \brief Return whether a coordinate operation has a "ballpark" * transformation, * that is a very approximate one, due to lack of more accurate transformations. * * Typically a null geographic offset between two horizontal datum, or a * null vertical offset (or limited to unit changes) between two vertical * datum. Errors of several tens to one hundred meters might be expected, * compared to more accurate transformations. * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type CoordinateOperation or derived classes * (must not be NULL) * @return TRUE or FALSE. */ int proj_coordoperation_has_ballpark_transformation(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto op = dynamic_cast( coordoperation->iso_obj.get()); if (!op) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation"); return 0; } return op->hasBallparkTransformation(); } // --------------------------------------------------------------------------- /** \brief Return whether a coordinate operation requires coordinate tuples * to have a valid input time for the coordinate transformation to succeed. * (this applies for the forward direction) * * Note: in the case of a time-dependent Helmert transformation, this function * will return true, but when executing proj_trans(), execution will still * succeed if the time information is missing, due to the transformation central * epoch being used as a fallback. * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type CoordinateOperation or derived classes * (must not be NULL) * @return TRUE or FALSE. * @since 9.5 */ int proj_coordoperation_requires_per_coordinate_input_time( PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto op = dynamic_cast( coordoperation->iso_obj.get()); if (!op) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation"); return false; } return op->requiresPerCoordinateInputTime(); } // --------------------------------------------------------------------------- /** \brief Return the number of parameters of a SingleOperation * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type SingleOperation or derived classes * (must not be NULL) */ int proj_coordoperation_get_param_count(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto op = dynamic_cast(coordoperation->iso_obj.get()); if (!op) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleOperation"); return 0; } return static_cast(op->parameterValues().size()); } // --------------------------------------------------------------------------- /** \brief Return the index of a parameter of a SingleOperation * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type SingleOperation or derived classes * (must not be NULL) * @param name Parameter name. Must not be NULL * @return index (>=0), or -1 in case of error. */ int proj_coordoperation_get_param_index(PJ_CONTEXT *ctx, const PJ *coordoperation, const char *name) { SANITIZE_CTX(ctx); if (!coordoperation || !name) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } auto op = dynamic_cast(coordoperation->iso_obj.get()); if (!op) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleOperation"); return -1; } int index = 0; for (const auto &genParam : op->method()->parameters()) { if (Identifier::isEquivalentName(genParam->nameStr().c_str(), name)) { return index; } index++; } return -1; } // --------------------------------------------------------------------------- /** \brief Return a parameter of a SingleOperation * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type SingleOperation or derived classes * (must not be NULL) * @param index Parameter index. * @param out_name Pointer to a string value to store the parameter name. or * NULL * @param out_auth_name Pointer to a string value to store the parameter * authority name. or NULL * @param out_code Pointer to a string value to store the parameter * code. or NULL * @param out_value Pointer to a double value to store the parameter * value (if numeric). or NULL * @param out_value_string Pointer to a string value to store the parameter * value (if of type string). or NULL * @param out_unit_conv_factor Pointer to a double value to store the parameter * unit conversion factor. or NULL * @param out_unit_name Pointer to a string value to store the parameter * unit name. or NULL * @param out_unit_auth_name Pointer to a string value to store the * unit authority name. or NULL * @param out_unit_code Pointer to a string value to store the * unit code. or NULL * @param out_unit_category Pointer to a string value to store the parameter * name. or * NULL. This value might be "unknown", "none", "linear", "linear_per_time", * "angular", "angular_per_time", "scale", "scale_per_time", "time", * "parametric" or "parametric_per_time" * @return TRUE in case of success. */ int proj_coordoperation_get_param( PJ_CONTEXT *ctx, const PJ *coordoperation, int index, const char **out_name, const char **out_auth_name, const char **out_code, double *out_value, const char **out_value_string, double *out_unit_conv_factor, const char **out_unit_name, const char **out_unit_auth_name, const char **out_unit_code, const char **out_unit_category) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto op = dynamic_cast(coordoperation->iso_obj.get()); if (!op) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleOperation"); return false; } const auto ¶meters = op->method()->parameters(); const auto &values = op->parameterValues(); if (static_cast(index) >= parameters.size() || static_cast(index) >= values.size()) { proj_log_error(ctx, __FUNCTION__, "Invalid index"); return false; } const auto ¶m = parameters[index]; const auto ¶m_ids = param->identifiers(); if (out_name) { *out_name = param->name()->description()->c_str(); } if (out_auth_name) { if (!param_ids.empty()) { *out_auth_name = param_ids[0]->codeSpace()->c_str(); } else { *out_auth_name = nullptr; } } if (out_code) { if (!param_ids.empty()) { *out_code = param_ids[0]->code().c_str(); } else { *out_code = nullptr; } } const auto &value = values[index]; ParameterValuePtr paramValue = nullptr; auto opParamValue = dynamic_cast(value.get()); if (opParamValue) { paramValue = opParamValue->parameterValue().as_nullable(); } if (out_value) { *out_value = 0; if (paramValue) { if (paramValue->type() == ParameterValue::Type::MEASURE) { *out_value = paramValue->value().value(); } } } if (out_value_string) { *out_value_string = nullptr; if (paramValue) { if (paramValue->type() == ParameterValue::Type::FILENAME) { *out_value_string = paramValue->valueFile().c_str(); } else if (paramValue->type() == ParameterValue::Type::STRING) { *out_value_string = paramValue->stringValue().c_str(); } } } if (out_unit_conv_factor) { *out_unit_conv_factor = 0; } if (out_unit_name) { *out_unit_name = nullptr; } if (out_unit_auth_name) { *out_unit_auth_name = nullptr; } if (out_unit_code) { *out_unit_code = nullptr; } if (out_unit_category) { *out_unit_category = nullptr; } if (paramValue) { if (paramValue->type() == ParameterValue::Type::MEASURE) { const auto &unit = paramValue->value().unit(); if (out_unit_conv_factor) { *out_unit_conv_factor = unit.conversionToSI(); } if (out_unit_name) { *out_unit_name = unit.name().c_str(); } if (out_unit_auth_name) { *out_unit_auth_name = unit.codeSpace().c_str(); } if (out_unit_code) { *out_unit_code = unit.code().c_str(); } if (out_unit_category) { *out_unit_category = get_unit_category(unit.name(), unit.type()); } } } return true; } // --------------------------------------------------------------------------- /** \brief Return the parameters of a Helmert transformation as WKT1 TOWGS84 * values. * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type Transformation, that can be represented * as a WKT1 TOWGS84 node (must not be NULL) * @param out_values Pointer to an array of value_count double values. * @param value_count Size of out_values array. The suggested size is 7 to get * translation, rotation and scale difference parameters. Rotation and scale * difference terms might be zero if the transformation only includes * translation * parameters. In that case, value_count could be set to 3. * @param emit_error_if_incompatible Boolean to indicate if an error must be * logged if coordoperation is not compatible with a WKT1 TOWGS84 * representation. * @return TRUE in case of success, or FALSE if coordoperation is not * compatible with a WKT1 TOWGS84 representation. */ int proj_coordoperation_get_towgs84_values(PJ_CONTEXT *ctx, const PJ *coordoperation, double *out_values, int value_count, int emit_error_if_incompatible) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto transf = dynamic_cast(coordoperation->iso_obj.get()); if (!transf) { if (emit_error_if_incompatible) { proj_log_error(ctx, __FUNCTION__, "Object is not a Transformation"); } return FALSE; } const auto values = transf->getTOWGS84Parameters(false); if (!values.empty()) { for (int i = 0; i < value_count && static_cast(i) < values.size(); i++) { out_values[i] = values[i]; } return TRUE; } else { if (emit_error_if_incompatible) { proj_log_error(ctx, __FUNCTION__, "Transformation cannot be formatted as WKT1 TOWGS84 " "parameters"); } return FALSE; } } // --------------------------------------------------------------------------- /** \brief Return the number of grids used by a CoordinateOperation * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type CoordinateOperation or derived classes * (must not be NULL) */ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto co = dynamic_cast( coordoperation->iso_obj.get()); if (!co) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation"); return 0; } auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { if (!coordoperation->gridsNeededAsked) { coordoperation->gridsNeededAsked = true; const auto gridsNeeded = co->gridsNeeded( dbContext, proj_context_is_network_enabled(ctx) != FALSE); for (const auto &gridDesc : gridsNeeded) { coordoperation->gridsNeeded.emplace_back(gridDesc); } } return static_cast(coordoperation->gridsNeeded.size()); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return 0; } } // --------------------------------------------------------------------------- /** \brief Return a parameter of a SingleOperation * * @param ctx PROJ context, or NULL for default context * @param coordoperation Object of type SingleOperation or derived classes * (must not be NULL) * @param index Parameter index. * @param out_short_name Pointer to a string value to store the grid short name. * or NULL * @param out_full_name Pointer to a string value to store the grid full * filename. or NULL * @param out_package_name Pointer to a string value to store the package name * where * the grid might be found. or NULL * @param out_url Pointer to a string value to store the grid URL or the * package URL where the grid might be found. or NULL * @param out_direct_download Pointer to a int (boolean) value to store whether * *out_url can be downloaded directly. or NULL * @param out_open_license Pointer to a int (boolean) value to store whether * the grid is released with an open license. or NULL * @param out_available Pointer to a int (boolean) value to store whether the * grid is available at runtime. or NULL * @return TRUE in case of success. */ int proj_coordoperation_get_grid_used( PJ_CONTEXT *ctx, const PJ *coordoperation, int index, const char **out_short_name, const char **out_full_name, const char **out_package_name, const char **out_url, int *out_direct_download, int *out_open_license, int *out_available) { SANITIZE_CTX(ctx); const int count = proj_coordoperation_get_grid_used_count(ctx, coordoperation); if (index < 0 || index >= count) { proj_log_error(ctx, __FUNCTION__, "Invalid index"); return false; } const auto &gridDesc = coordoperation->gridsNeeded[index]; if (out_short_name) { *out_short_name = gridDesc.shortName.c_str(); } if (out_full_name) { *out_full_name = gridDesc.fullName.c_str(); } if (out_package_name) { *out_package_name = gridDesc.packageName.c_str(); } if (out_url) { *out_url = gridDesc.url.c_str(); } if (out_direct_download) { *out_direct_download = gridDesc.directDownload; } if (out_open_license) { *out_open_license = gridDesc.openLicense; } if (out_available) { *out_available = gridDesc.available; } return true; } // --------------------------------------------------------------------------- /** \brief Opaque object representing an operation factory context. */ struct PJ_OPERATION_FACTORY_CONTEXT { //! @cond Doxygen_Suppress CoordinateOperationContextNNPtr operationContext; explicit PJ_OPERATION_FACTORY_CONTEXT( CoordinateOperationContextNNPtr &&operationContextIn) : operationContext(std::move(operationContextIn)) {} PJ_OPERATION_FACTORY_CONTEXT(const PJ_OPERATION_FACTORY_CONTEXT &) = delete; PJ_OPERATION_FACTORY_CONTEXT & operator=(const PJ_OPERATION_FACTORY_CONTEXT &) = delete; //! @endcond }; // --------------------------------------------------------------------------- /** \brief Instantiate a context for building coordinate operations between * two CRS. * * The returned object must be unreferenced with * proj_operation_factory_context_destroy() after use. * * If authority is NULL or the empty string, then coordinate * operations from any authority will be searched, with the restrictions set * in the authority_to_authority_preference database table. * If authority is set to "any", then coordinate * operations from any authority will be searched * If authority is a non-empty string different of "any", * then coordinate operations will be searched only in that authority namespace. * * @param ctx Context, or NULL for default context. * @param authority Name of authority to which to restrict the search of * candidate operations. * @return Object that must be unreferenced with * proj_operation_factory_context_destroy(), or NULL in * case of error. */ PJ_OPERATION_FACTORY_CONTEXT * proj_create_operation_factory_context(PJ_CONTEXT *ctx, const char *authority) { SANITIZE_CTX(ctx); auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { if (dbContext) { auto factory = CoordinateOperationFactory::create(); auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string(authority ? authority : "")); auto operationContext = CoordinateOperationContext::create(authFactory, nullptr, 0.0); return new PJ_OPERATION_FACTORY_CONTEXT( std::move(operationContext)); } else { auto operationContext = CoordinateOperationContext::create(nullptr, nullptr, 0.0); return new PJ_OPERATION_FACTORY_CONTEXT( std::move(operationContext)); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Drops a reference on an object. * * This method should be called one and exactly one for each function * returning a PJ_OPERATION_FACTORY_CONTEXT* * * @param ctx Object, or NULL. */ void proj_operation_factory_context_destroy(PJ_OPERATION_FACTORY_CONTEXT *ctx) { delete ctx; } // --------------------------------------------------------------------------- /** \brief Set the desired accuracy of the resulting coordinate transformations. * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param accuracy Accuracy in meter (or 0 to disable the filter). */ void proj_operation_factory_context_set_desired_accuracy( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, double accuracy) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { factory_ctx->operationContext->setDesiredAccuracy(accuracy); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set the desired area of interest for the resulting coordinate * transformations. * * For an area of interest crossing the anti-meridian, west_lon_degree will be * greater than east_lon_degree. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param west_lon_degree West longitude (in degrees). * @param south_lat_degree South latitude (in degrees). * @param east_lon_degree East longitude (in degrees). * @param north_lat_degree North latitude (in degrees). */ void proj_operation_factory_context_set_area_of_interest( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, double west_lon_degree, double south_lat_degree, double east_lon_degree, double north_lat_degree) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { factory_ctx->operationContext->setAreaOfInterest( Extent::createFromBBOX(west_lon_degree, south_lat_degree, east_lon_degree, north_lat_degree)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set the name of the desired area of interest for the resulting * coordinate transformations. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param area_name Area name. Must be known of the database. */ void proj_operation_factory_context_set_area_of_interest_name( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, const char *area_name) { SANITIZE_CTX(ctx); if (!factory_ctx || !area_name) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { auto extent = factory_ctx->operationContext->getAreaOfInterest(); if (extent == nullptr) { auto dbContext = getDBcontext(ctx); auto factory = AuthorityFactory::create(dbContext, std::string()); auto res = factory->listAreaOfUseFromName(area_name, false); if (res.size() == 1) { factory_ctx->operationContext->setAreaOfInterest( AuthorityFactory::create(dbContext, res.front().first) ->createExtent(res.front().second) .as_nullable()); } else { proj_log_error(ctx, __FUNCTION__, "cannot find area"); return; } } else { factory_ctx->operationContext->setAreaOfInterest( metadata::Extent::create(util::optional(area_name), extent->geographicElements(), extent->verticalElements(), extent->temporalElements()) .as_nullable()); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set how source and target CRS extent should be used * when considering if a transformation can be used (only takes effect if * no area of interest is explicitly defined). * * The default is PJ_CRS_EXTENT_SMALLEST. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param use How source and target CRS extent should be used. */ void proj_operation_factory_context_set_crs_extent_use( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_CRS_EXTENT_USE use) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { switch (use) { case PJ_CRS_EXTENT_NONE: factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse::NONE); break; case PJ_CRS_EXTENT_BOTH: factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse::BOTH); break; case PJ_CRS_EXTENT_INTERSECTION: factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse:: INTERSECTION); break; case PJ_CRS_EXTENT_SMALLEST: factory_ctx->operationContext->setSourceAndTargetCRSExtentUse( CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST); break; } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set the spatial criterion to use when comparing the area of * validity of coordinate operations with the area of interest / area of * validity of * source and target CRS. * * The default is PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param criterion spatial criterion to use */ void proj_operation_factory_context_set_spatial_criterion( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_SPATIAL_CRITERION criterion) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { switch (criterion) { case PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT: factory_ctx->operationContext->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion:: STRICT_CONTAINMENT); break; case PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION: factory_ctx->operationContext->setSpatialCriterion( CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION); break; } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set how grid availability is used. * * The default is USE_FOR_SORTING. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param use how grid availability is used. */ void proj_operation_factory_context_set_grid_availability_use( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_GRID_AVAILABILITY_USE use) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { switch (use) { case PROJ_GRID_AVAILABILITY_USED_FOR_SORTING: factory_ctx->operationContext->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: USE_FOR_SORTING); break; case PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID: factory_ctx->operationContext->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: DISCARD_OPERATION_IF_MISSING_GRID); break; case PROJ_GRID_AVAILABILITY_IGNORED: factory_ctx->operationContext->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: IGNORE_GRID_AVAILABILITY); break; case PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE: factory_ctx->operationContext->setGridAvailabilityUse( CoordinateOperationContext::GridAvailabilityUse:: KNOWN_AVAILABLE); break; } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set whether PROJ alternative grid names should be substituted to * the official authority names. * * The default is true. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param usePROJNames whether PROJ alternative grid names should be used */ void proj_operation_factory_context_set_use_proj_alternative_grid_names( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int usePROJNames) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { factory_ctx->operationContext->setUsePROJAlternativeGridNames( usePROJNames != 0); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set whether an intermediate pivot CRS can be used for researching * coordinate operations between a source and target CRS. * * Concretely if in the database there is an operation from A to C * (or C to A), and another one from C to B (or B to C), but no direct * operation between A and B, setting this parameter to true, allow * chaining both operations. * * The current implementation is limited to researching one intermediate * step. * * By default, with the IF_NO_DIRECT_TRANSFORMATION strategy, all potential * C candidates will be used if there is no direct transformation. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param use whether and how intermediate CRS may be used. */ void proj_operation_factory_context_set_allow_use_intermediate_crs( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, PROJ_INTERMEDIATE_CRS_USE use) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { switch (use) { case PROJ_INTERMEDIATE_CRS_USE_ALWAYS: factory_ctx->operationContext->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::ALWAYS); break; case PROJ_INTERMEDIATE_CRS_USE_IF_NO_DIRECT_TRANSFORMATION: factory_ctx->operationContext->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse:: IF_NO_DIRECT_TRANSFORMATION); break; case PROJ_INTERMEDIATE_CRS_USE_NEVER: factory_ctx->operationContext->setAllowUseIntermediateCRS( CoordinateOperationContext::IntermediateCRSUse::NEVER); break; } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Restrict the potential pivot CRSs that can be used when trying to * build a coordinate operation between two CRS that have no direct operation. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param list_of_auth_name_codes an array of strings NLL terminated, * with the format { "auth_name1", "code1", "auth_name2", "code2", ... NULL } */ void proj_operation_factory_context_set_allowed_intermediate_crs( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, const char *const *list_of_auth_name_codes) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { std::vector> pivots; for (auto iter = list_of_auth_name_codes; iter && iter[0] && iter[1]; iter += 2) { pivots.emplace_back(std::pair( std::string(iter[0]), std::string(iter[1]))); } factory_ctx->operationContext->setIntermediateCRS(pivots); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set whether transformations that are superseded (but not deprecated) * should be discarded. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param discard superseded crs or not */ void proj_operation_factory_context_set_discard_superseded( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int discard) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { factory_ctx->operationContext->setDiscardSuperseded(discard != 0); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- /** \brief Set whether ballpark transformations are allowed. * * @param ctx PROJ context, or NULL for default context * @param factory_ctx Operation factory context. must not be NULL * @param allow set to TRUE to allow ballpark transformations. * @since 7.1 */ void proj_operation_factory_context_set_allow_ballpark_transformations( PJ_CONTEXT *ctx, PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, int allow) { SANITIZE_CTX(ctx); if (!factory_ctx) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return; } try { factory_ctx->operationContext->setAllowBallparkTransformations(allow != 0); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Opaque object representing a set of operation results. */ struct PJ_OPERATION_LIST : PJ_OBJ_LIST { PJ *source_crs; PJ *target_crs; bool hasPreparedOperation = false; std::vector preparedOperations{}; explicit PJ_OPERATION_LIST(PJ_CONTEXT *ctx, const PJ *source_crsIn, const PJ *target_crsIn, std::vector &&objectsIn); ~PJ_OPERATION_LIST() override; PJ_OPERATION_LIST(const PJ_OPERATION_LIST &) = delete; PJ_OPERATION_LIST &operator=(const PJ_OPERATION_LIST &) = delete; const std::vector &getPreparedOperations(PJ_CONTEXT *ctx); }; // --------------------------------------------------------------------------- PJ_OPERATION_LIST::PJ_OPERATION_LIST( PJ_CONTEXT *ctx, const PJ *source_crsIn, const PJ *target_crsIn, std::vector &&objectsIn) : PJ_OBJ_LIST(std::move(objectsIn)), source_crs(proj_clone(ctx, source_crsIn)), target_crs(proj_clone(ctx, target_crsIn)) {} // --------------------------------------------------------------------------- PJ_OPERATION_LIST::~PJ_OPERATION_LIST() { auto tmpCtxt = proj_context_create(); proj_assign_context(source_crs, tmpCtxt); proj_assign_context(target_crs, tmpCtxt); proj_destroy(source_crs); proj_destroy(target_crs); proj_context_destroy(tmpCtxt); } // --------------------------------------------------------------------------- const std::vector & PJ_OPERATION_LIST::getPreparedOperations(PJ_CONTEXT *ctx) { if (!hasPreparedOperation) { hasPreparedOperation = true; preparedOperations = pj_create_prepared_operations(ctx, source_crs, target_crs, this); } return preparedOperations; } //! @endcond // --------------------------------------------------------------------------- /** \brief Find a list of CoordinateOperation from source_crs to target_crs. * * The operations are sorted with the most relevant ones first: by * descending * area (intersection of the transformation area with the area of interest, * or intersection of the transformation with the area of use of the CRS), * and * by increasing accuracy. Operations with unknown accuracy are sorted last, * whatever their area. * * Starting with PROJ 9.1, vertical transformations are only done if both * source CRS and target CRS are 3D CRS or Compound CRS with a vertical * component. You may need to use proj_crs_promote_to_3D(). * * @param ctx PROJ context, or NULL for default context * @param source_crs source CRS or CoordinateMetadata. Must not be NULL. * @param target_crs target CRS or CoordinateMetadata. Must not be NULL. * @param operationContext Search context. Must not be NULL. * @return a result set that must be unreferenced with * proj_list_destroy(), or NULL in case of error. */ PJ_OBJ_LIST * proj_create_operations(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, const PJ_OPERATION_FACTORY_CONTEXT *operationContext) { SANITIZE_CTX(ctx); if (!source_crs || !target_crs || !operationContext) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto sourceCRS = std::dynamic_pointer_cast(source_crs->iso_obj); CoordinateMetadataPtr sourceCoordinateMetadata; if (!sourceCRS) { sourceCoordinateMetadata = std::dynamic_pointer_cast(source_crs->iso_obj); if (!sourceCoordinateMetadata) { proj_log_error(ctx, __FUNCTION__, "source_crs is not a CRS or a CoordinateMetadata"); return nullptr; } if (!sourceCoordinateMetadata->coordinateEpoch().has_value()) { sourceCRS = sourceCoordinateMetadata->crs().as_nullable(); sourceCoordinateMetadata.reset(); } } auto targetCRS = std::dynamic_pointer_cast(target_crs->iso_obj); CoordinateMetadataPtr targetCoordinateMetadata; if (!targetCRS) { targetCoordinateMetadata = std::dynamic_pointer_cast(target_crs->iso_obj); if (!targetCoordinateMetadata) { proj_log_error(ctx, __FUNCTION__, "target_crs is not a CRS or a CoordinateMetadata"); return nullptr; } if (!targetCoordinateMetadata->coordinateEpoch().has_value()) { targetCRS = targetCoordinateMetadata->crs().as_nullable(); targetCoordinateMetadata.reset(); } } try { auto factory = CoordinateOperationFactory::create(); std::vector objects; auto ops = sourceCoordinateMetadata != nullptr ? (targetCoordinateMetadata != nullptr ? factory->createOperations( NN_NO_CHECK(sourceCoordinateMetadata), NN_NO_CHECK(targetCoordinateMetadata), operationContext->operationContext) : factory->createOperations( NN_NO_CHECK(sourceCoordinateMetadata), NN_NO_CHECK(targetCRS), operationContext->operationContext)) : targetCoordinateMetadata != nullptr ? factory->createOperations( NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCoordinateMetadata), operationContext->operationContext) : factory->createOperations( NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operationContext->operationContext); for (const auto &op : ops) { objects.emplace_back(op); } return new PJ_OPERATION_LIST(ctx, source_crs, target_crs, std::move(objects)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** Return the index of the operation that would be the most appropriate to * transform the specified coordinates. * * This operation may use resources that are not locally available, depending * on the search criteria used by proj_create_operations(). * * This could be done by using proj_create_operations() with a punctual bounding * box, but this function is faster when one needs to evaluate on many points * with the same (source_crs, target_crs) tuple. * * @param ctx PROJ context, or NULL for default context * @param operations List of operations returned by proj_create_operations() * @param direction Direction into which to transform the point. * @param coord Coordinate to transform * @return the index in operations that would be used to transform coord. Or -1 * in case of error, or no match. * * @since 7.1 */ int proj_get_suggested_operation(PJ_CONTEXT *ctx, PJ_OBJ_LIST *operations, // cppcheck-suppress passedByValue PJ_DIRECTION direction, PJ_COORD coord) { SANITIZE_CTX(ctx); auto opList = dynamic_cast(operations); if (opList == nullptr) { proj_log_error(ctx, __FUNCTION__, "operations is not a list of operations"); return -1; } // Special case: // proj_create_crs_to_crs_from_pj() always use the unique operation // if there's a single one if (opList->objects.size() == 1) { return 0; } int iExcluded[2] = {-1, -1}; const auto &preparedOps = opList->getPreparedOperations(ctx); int idx = pj_get_suggested_operation(ctx, preparedOps, iExcluded, /* skipNonInstantiable= */ false, direction, coord); if (idx >= 0) { idx = preparedOps[idx].idxInOriginalList; } return idx; } // --------------------------------------------------------------------------- /** \brief Return the number of objects in the result set * * @param result Object of type PJ_OBJ_LIST (must not be NULL) */ int proj_list_get_count(const PJ_OBJ_LIST *result) { if (!result) { return 0; } return static_cast(result->objects.size()); } // --------------------------------------------------------------------------- /** \brief Return an object from the result set * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param result Object of type PJ_OBJ_LIST (must not be NULL) * @param index Index * @return a new object that must be unreferenced with proj_destroy(), * or nullptr in case of error. */ PJ *proj_list_get(PJ_CONTEXT *ctx, const PJ_OBJ_LIST *result, int index) { SANITIZE_CTX(ctx); if (!result) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } if (index < 0 || index >= proj_list_get_count(result)) { proj_log_error(ctx, __FUNCTION__, "Invalid index"); return nullptr; } return pj_obj_create(ctx, result->objects[index]); } // --------------------------------------------------------------------------- /** \brief Drops a reference on the result set. * * This method should be called one and exactly one for each function * returning a PJ_OBJ_LIST* * * @param result Object, or NULL. */ void proj_list_destroy(PJ_OBJ_LIST *result) { delete result; } // --------------------------------------------------------------------------- /** \brief Return the accuracy (in metre) of a coordinate operation. * * @param ctx PROJ context, or NULL for default context * @param coordoperation Coordinate operation. Must not be NULL. * @return the accuracy, or a negative value if unknown or in case of error. */ double proj_coordoperation_get_accuracy(PJ_CONTEXT *ctx, const PJ *coordoperation) { SANITIZE_CTX(ctx); if (!coordoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } auto co = dynamic_cast( coordoperation->iso_obj.get()); if (!co) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation"); return -1; } const auto &accuracies = co->coordinateOperationAccuracies(); if (accuracies.empty()) { return -1; } try { return c_locale_stod(accuracies[0]->value()); } catch (const std::exception &) { } return -1; } // --------------------------------------------------------------------------- /** \brief Returns the datum of a SingleCRS. * * If that function returns NULL, @see proj_crs_get_datum_ensemble() to * potentially get a DatumEnsemble instead. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type SingleCRS (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error (or if there is no datum) */ PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); return nullptr; } const auto &datum = l_crs->datum(); if (!datum) { return nullptr; } return pj_obj_create(ctx, NN_NO_CHECK(datum)); } // --------------------------------------------------------------------------- /** \brief Returns the datum ensemble of a SingleCRS. * * If that function returns NULL, @see proj_crs_get_datum() to * potentially get a Datum instead. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type SingleCRS (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error (or if there is no datum ensemble) * * @since 7.2 */ PJ *proj_crs_get_datum_ensemble(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); return nullptr; } const auto &datumEnsemble = l_crs->datumEnsemble(); if (!datumEnsemble) { return nullptr; } return pj_obj_create(ctx, NN_NO_CHECK(datumEnsemble)); } // --------------------------------------------------------------------------- /** \brief Returns the number of members of a datum ensemble. * * @param ctx PROJ context, or NULL for default context * @param datum_ensemble Object of type DatumEnsemble (must not be NULL) * * @since 7.2 */ int proj_datum_ensemble_get_member_count(PJ_CONTEXT *ctx, const PJ *datum_ensemble) { SANITIZE_CTX(ctx); if (!datum_ensemble) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return 0; } auto l_datum_ensemble = dynamic_cast(datum_ensemble->iso_obj.get()); if (!l_datum_ensemble) { proj_log_error(ctx, __FUNCTION__, "Object is not a DatumEnsemble"); return 0; } return static_cast(l_datum_ensemble->datums().size()); } // --------------------------------------------------------------------------- /** \brief Returns the positional accuracy of the datum ensemble. * * @param ctx PROJ context, or NULL for default context * @param datum_ensemble Object of type DatumEnsemble (must not be NULL) * @return the accuracy, or -1 in case of error. * * @since 7.2 */ double proj_datum_ensemble_get_accuracy(PJ_CONTEXT *ctx, const PJ *datum_ensemble) { SANITIZE_CTX(ctx); if (!datum_ensemble) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } auto l_datum_ensemble = dynamic_cast(datum_ensemble->iso_obj.get()); if (!l_datum_ensemble) { proj_log_error(ctx, __FUNCTION__, "Object is not a DatumEnsemble"); return -1; } const auto &accuracy = l_datum_ensemble->positionalAccuracy(); try { return c_locale_stod(accuracy->value()); } catch (const std::exception &) { } return -1; } // --------------------------------------------------------------------------- /** \brief Returns a member from a datum ensemble. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param datum_ensemble Object of type DatumEnsemble (must not be NULL) * @param member_index Index of the datum member to extract (between 0 and * proj_datum_ensemble_get_member_count()-1) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error (or if there is no datum ensemble) * * @since 7.2 */ PJ *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble, int member_index) { SANITIZE_CTX(ctx); if (!datum_ensemble) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_datum_ensemble = dynamic_cast(datum_ensemble->iso_obj.get()); if (!l_datum_ensemble) { proj_log_error(ctx, __FUNCTION__, "Object is not a DatumEnsemble"); return nullptr; } if (member_index < 0 || member_index >= static_cast(l_datum_ensemble->datums().size())) { proj_log_error(ctx, __FUNCTION__, "Invalid member_index"); return nullptr; } return pj_obj_create(ctx, l_datum_ensemble->datums()[member_index]); } // --------------------------------------------------------------------------- /** \brief Returns a datum for a SingleCRS. * * If the SingleCRS has a datum, then this datum is returned. * Otherwise, the SingleCRS has a datum ensemble, and this datum ensemble is * returned as a regular datum instead of a datum ensemble. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type SingleCRS (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error (or if there is no datum) * * @since 7.2 */ PJ *proj_crs_get_datum_forced(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); return nullptr; } const auto &datum = l_crs->datum(); if (datum) { return pj_obj_create(ctx, NN_NO_CHECK(datum)); } const auto &datumEnsemble = l_crs->datumEnsemble(); assert(datumEnsemble); auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); try { return pj_obj_create(ctx, datumEnsemble->asDatum(dbContext)); } catch (const std::exception &e) { proj_log_debug(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Returns the frame reference epoch of a dynamic geodetic or vertical * reference frame. * * @param ctx PROJ context, or NULL for default context * @param datum Object of type DynamicGeodeticReferenceFrame or * DynamicVerticalReferenceFrame (must not be NULL) * @return the frame reference epoch as decimal year, or -1 in case of error. * * @since 7.2 */ double proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx, const PJ *datum) { SANITIZE_CTX(ctx); if (!datum) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } auto dgrf = dynamic_cast( datum->iso_obj.get()); auto dvrf = dynamic_cast( datum->iso_obj.get()); if (!dgrf && !dvrf) { proj_log_error(ctx, __FUNCTION__, "Object is not a " "DynamicGeodeticReferenceFrame or " "DynamicVerticalReferenceFrame"); return -1; } const auto &frameReferenceEpoch = dgrf ? dgrf->frameReferenceEpoch() : dvrf->frameReferenceEpoch(); return frameReferenceEpoch.value(); } // --------------------------------------------------------------------------- /** \brief Returns the coordinate system of a SingleCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs Object of type SingleCRS (must not be NULL) * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); return nullptr; } return pj_obj_create(ctx, l_crs->coordinateSystem()); } // --------------------------------------------------------------------------- /** \brief Returns the type of the coordinate system. * * @param ctx PROJ context, or NULL for default context * @param cs Object of type CoordinateSystem (must not be NULL) * @return type, or PJ_CS_TYPE_UNKNOWN in case of error. */ PJ_COORDINATE_SYSTEM_TYPE proj_cs_get_type(PJ_CONTEXT *ctx, const PJ *cs) { SANITIZE_CTX(ctx); if (!cs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return PJ_CS_TYPE_UNKNOWN; } auto l_cs = dynamic_cast(cs->iso_obj.get()); if (!l_cs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem"); return PJ_CS_TYPE_UNKNOWN; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_CARTESIAN; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_ELLIPSOIDAL; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_VERTICAL; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_SPHERICAL; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_ORDINAL; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_PARAMETRIC; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_DATETIMETEMPORAL; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_TEMPORALCOUNT; } if (dynamic_cast(l_cs)) { return PJ_CS_TYPE_TEMPORALMEASURE; } return PJ_CS_TYPE_UNKNOWN; } // --------------------------------------------------------------------------- /** \brief Returns the number of axis of the coordinate system. * * @param ctx PROJ context, or NULL for default context * @param cs Object of type CoordinateSystem (must not be NULL) * @return number of axis, or -1 in case of error. */ int proj_cs_get_axis_count(PJ_CONTEXT *ctx, const PJ *cs) { SANITIZE_CTX(ctx); if (!cs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return -1; } auto l_cs = dynamic_cast(cs->iso_obj.get()); if (!l_cs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem"); return -1; } return static_cast(l_cs->axisList().size()); } // --------------------------------------------------------------------------- /** \brief Returns information on an axis * * @param ctx PROJ context, or NULL for default context * @param cs Object of type CoordinateSystem (must not be NULL) * @param index Index of the coordinate system (between 0 and * proj_cs_get_axis_count() - 1) * @param out_name Pointer to a string value to store the axis name. or NULL * @param out_abbrev Pointer to a string value to store the axis abbreviation. * or NULL * @param out_direction Pointer to a string value to store the axis direction. * or NULL * @param out_unit_conv_factor Pointer to a double value to store the axis * unit conversion factor. or NULL * @param out_unit_name Pointer to a string value to store the axis * unit name. or NULL * @param out_unit_auth_name Pointer to a string value to store the axis * unit authority name. or NULL * @param out_unit_code Pointer to a string value to store the axis * unit code. or NULL * @return TRUE in case of success */ int proj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ *cs, int index, const char **out_name, const char **out_abbrev, const char **out_direction, double *out_unit_conv_factor, const char **out_unit_name, const char **out_unit_auth_name, const char **out_unit_code) { SANITIZE_CTX(ctx); if (!cs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto l_cs = dynamic_cast(cs->iso_obj.get()); if (!l_cs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateSystem"); return false; } const auto &axisList = l_cs->axisList(); if (index < 0 || static_cast(index) >= axisList.size()) { proj_log_error(ctx, __FUNCTION__, "Invalid index"); return false; } const auto &axis = axisList[index]; if (out_name) { *out_name = axis->nameStr().c_str(); } if (out_abbrev) { *out_abbrev = axis->abbreviation().c_str(); } if (out_direction) { *out_direction = axis->direction().toString().c_str(); } if (out_unit_conv_factor) { *out_unit_conv_factor = axis->unit().conversionToSI(); } if (out_unit_name) { *out_unit_name = axis->unit().name().c_str(); } if (out_unit_auth_name) { *out_unit_auth_name = axis->unit().codeSpace().c_str(); } if (out_unit_code) { *out_unit_code = axis->unit().code().c_str(); } return true; } // --------------------------------------------------------------------------- /** \brief Returns a PJ* object whose axis order is the one expected for * visualization purposes. * * The input object must be either: *
    *
  • a coordinate operation, that has been created with * proj_create_crs_to_crs(). If the axis order of its source or target CRS * is northing,easting, then an axis swap operation will be inserted.
  • *
  • or a CRS. The axis order of geographic CRS will be longitude, latitude * [,height], and the one of projected CRS will be easting, northing * [, height]
  • *
* * @param ctx PROJ context, or NULL for default context * @param obj Object of type CRS, or CoordinateOperation created with * proj_create_crs_to_crs() (must not be NULL) * @return a new PJ* object to free with proj_destroy() in case of success, or * nullptr in case of error */ PJ *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj->alternativeCoordinateOperations.empty()) { try { auto pjNew = std::unique_ptr(pj_new()); if (!pjNew) return nullptr; pjNew->ctx = ctx; pjNew->descr = "Set of coordinate operations"; pjNew->left = obj->left; pjNew->right = obj->right; pjNew->copyStateFrom(*obj); for (const auto &alt : obj->alternativeCoordinateOperations) { auto co = dynamic_cast( alt.pj->iso_obj.get()); if (co) { double minxSrc = alt.minxSrc; double minySrc = alt.minySrc; double maxxSrc = alt.maxxSrc; double maxySrc = alt.maxySrc; double minxDst = alt.minxDst; double minyDst = alt.minyDst; double maxxDst = alt.maxxDst; double maxyDst = alt.maxyDst; auto l_sourceCRS = co->sourceCRS(); auto l_targetCRS = co->targetCRS(); if (l_sourceCRS && l_targetCRS) { const bool swapSource = l_sourceCRS ->mustAxisOrderBeSwitchedForVisualization(); if (swapSource) { std::swap(minxSrc, minySrc); std::swap(maxxSrc, maxySrc); } const bool swapTarget = l_targetCRS ->mustAxisOrderBeSwitchedForVisualization(); if (swapTarget) { std::swap(minxDst, minyDst); std::swap(maxxDst, maxyDst); } } ctx->forceOver = alt.pj->over != 0; auto pjNormalized = pj_obj_create(ctx, co->normalizeForVisualization()); ctx->forceOver = false; pjNormalized->copyStateFrom(*(alt.pj)); pjNew->alternativeCoordinateOperations.emplace_back( alt.idxInOriginalList, minxSrc, minySrc, maxxSrc, maxySrc, minxDst, minyDst, maxxDst, maxyDst, pjNormalized, co->nameStr(), alt.accuracy, alt.pseudoArea, alt.areaName.c_str(), alt.pjSrcGeocentricToLonLat, alt.pjDstGeocentricToLonLat); } } return pjNew.release(); } catch (const std::exception &e) { ctx->forceOver = false; proj_log_debug(ctx, __FUNCTION__, e.what()); return nullptr; } } auto crs = dynamic_cast(obj->iso_obj.get()); if (crs) { try { return pj_obj_create(ctx, crs->normalizeForVisualization()); } catch (const std::exception &e) { proj_log_debug(ctx, __FUNCTION__, e.what()); return nullptr; } } auto co = dynamic_cast(obj->iso_obj.get()); if (!co) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation " "created with " "proj_create_crs_to_crs"); return nullptr; } try { ctx->forceOver = obj->over != 0; auto pjNormalized = pj_obj_create(ctx, co->normalizeForVisualization()); pjNormalized->over = obj->over; ctx->forceOver = false; return pjNormalized; } catch (const std::exception &e) { ctx->forceOver = false; proj_log_debug(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Returns a PJ* coordinate operation object which represents the * inverse operation of the specified coordinate operation. * * @param ctx PROJ context, or NULL for default context * @param obj Object of type CoordinateOperation (must not be NULL) * @return a new PJ* object to free with proj_destroy() in case of success, or * nullptr in case of error * @since 6.3 */ PJ *proj_coordoperation_create_inverse(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto co = dynamic_cast(obj->iso_obj.get()); if (!co) { proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation"); return nullptr; } try { return pj_obj_create(ctx, co->inverse()); } catch (const std::exception &e) { proj_log_debug(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Returns the number of steps of a concatenated operation. * * The input object must be a concatenated operation. * * @param ctx PROJ context, or NULL for default context * @param concatoperation Concatenated operation (must not be NULL) * @return the number of steps, or 0 in case of error. */ int proj_concatoperation_get_step_count(PJ_CONTEXT *ctx, const PJ *concatoperation) { SANITIZE_CTX(ctx); if (!concatoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto l_co = dynamic_cast( concatoperation->iso_obj.get()); if (!l_co) { proj_log_error(ctx, __FUNCTION__, "Object is not a ConcatenatedOperation"); return false; } return static_cast(l_co->operations().size()); } // --------------------------------------------------------------------------- /** \brief Returns a step of a concatenated operation. * * The input object must be a concatenated operation. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param concatoperation Concatenated operation (must not be NULL) * @param i_step Index of the step to extract. Between 0 and * proj_concatoperation_get_step_count()-1 * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. */ PJ *proj_concatoperation_get_step(PJ_CONTEXT *ctx, const PJ *concatoperation, int i_step) { SANITIZE_CTX(ctx); if (!concatoperation) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto l_co = dynamic_cast( concatoperation->iso_obj.get()); if (!l_co) { proj_log_error(ctx, __FUNCTION__, "Object is not a ConcatenatedOperation"); return nullptr; } const auto &steps = l_co->operations(); if (i_step < 0 || static_cast(i_step) >= steps.size()) { proj_log_error(ctx, __FUNCTION__, "Invalid step index"); return nullptr; } return pj_obj_create(ctx, steps[i_step]); } // --------------------------------------------------------------------------- /** \brief Opaque object representing an insertion session. */ struct PJ_INSERT_SESSION { //! @cond Doxygen_Suppress PJ_CONTEXT *ctx = nullptr; //!  @endcond }; // --------------------------------------------------------------------------- /** \brief Starts a session for proj_get_insert_statements() * * Starts a new session for one or several calls to * proj_get_insert_statements(). * * An insertion session guarantees that the inserted objects will not create * conflicting intermediate objects. * * The session must be stopped with proj_insert_object_session_destroy(). * * Only one session may be active at a time for a given context. * * @param ctx PROJ context, or NULL for default context * @return the session, or NULL in case of error. * * @since 8.1 */ PJ_INSERT_SESSION *proj_insert_object_session_create(PJ_CONTEXT *ctx) { SANITIZE_CTX(ctx); try { auto dbContext = getDBcontext(ctx); dbContext->startInsertStatementsSession(); PJ_INSERT_SESSION *session = new PJ_INSERT_SESSION; session->ctx = ctx; return session; } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Stops an insertion session started with * proj_insert_object_session_create() * * @param ctx PROJ context, or NULL for default context * @param session The insertion session. * @since 8.1 */ void proj_insert_object_session_destroy(PJ_CONTEXT *ctx, PJ_INSERT_SESSION *session) { SANITIZE_CTX(ctx); if (session) { try { if (session->ctx != ctx) { proj_log_error(ctx, __FUNCTION__, "proj_insert_object_session_destroy() called " "with a context different from the one of " "proj_insert_object_session_create()"); } else { auto dbContext = getDBcontext(ctx); dbContext->stopInsertStatementsSession(); } } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } delete session; } } // --------------------------------------------------------------------------- /** \brief Suggests a database code for the passed object. * * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble, * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion. * * @param ctx PROJ context, or NULL for default context * @param object Object for which to suggest a code. * @param authority Authority name into which the object will be inserted. * @param numeric_code Whether the code should be numeric, or derived from the * object name. * @param options NULL terminated list of options, or NULL. * No options are supported currently. * @return the suggested code, that is guaranteed to not conflict with an * existing one (to be freed with proj_string_destroy), * or nullptr in case of error. * * @since 8.1 */ char *proj_suggests_code_for(PJ_CONTEXT *ctx, const PJ *object, const char *authority, int numeric_code, const char *const *options) { SANITIZE_CTX(ctx); (void)options; if (!object || !authority) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto identifiedObject = std::dynamic_pointer_cast(object->iso_obj); if (!identifiedObject) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "Object is not a IdentifiedObject"); return nullptr; } try { auto dbContext = getDBcontext(ctx); return pj_strdup(dbContext ->suggestsCodeFor(NN_NO_CHECK(identifiedObject), std::string(authority), numeric_code != FALSE) .c_str()); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Free a string. * * Only to be used with functions that document using this function. * * @param str String to free. * * @since 8.1 */ void proj_string_destroy(char *str) { free(str); } // --------------------------------------------------------------------------- /** \brief Returns SQL statements needed to insert the passed object into the * database. * * proj_insert_object_session_create() may have been called previously. * * It is strongly recommended that new objects should not be added in common * registries, such as "EPSG", "ESRI", "IAU", etc. Users should use a custom * authority name instead. If a new object should be * added to the official EPSG registry, users are invited to follow the * procedure explained at https://epsg.org/dataset-change-requests.html. * * Combined with proj_context_get_database_structure(), users can create * auxiliary databases, instead of directly modifying the main proj.db database. * Those auxiliary databases can be specified through * proj_context_set_database_path() or the PROJ_AUX_DB environment variable. * * @param ctx PROJ context, or NULL for default context * @param session The insertion session. May be NULL if a single object must be * inserted. * @param object The object to insert into the database. Currently only * PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS, * VerticalCRS, CompoundCRS or BoundCRS are supported. * @param authority Authority name into which the object will be inserted. * Must not be NULL. * @param code Code with which the object will be inserted.Must not be NULL. * @param numeric_codes Whether intermediate objects that can be created should * use numeric codes (true), or may be alphanumeric (false) * @param allowed_authorities NULL terminated list of authority names, or NULL. * Authorities to which intermediate objects are * allowed to refer to. "authority" will be * implicitly added to it. Note that unit, * coordinate systems, projection methods and * parameters will in any case be allowed to refer * to EPSG. * If NULL, allowed_authorities defaults to * {"EPSG", "PROJ", nullptr} * @param options NULL terminated list of options, or NULL. * No options are supported currently. * * @return a list of insert statements (to be freed with * proj_string_list_destroy()), or NULL in case of error. * @since 8.1 */ PROJ_STRING_LIST proj_get_insert_statements( PJ_CONTEXT *ctx, PJ_INSERT_SESSION *session, const PJ *object, const char *authority, const char *code, int numeric_codes, const char *const *allowed_authorities, const char *const *options) { SANITIZE_CTX(ctx); (void)options; struct TempSessionHolder { private: PJ_CONTEXT *m_ctx; PJ_INSERT_SESSION *m_tempSession; TempSessionHolder(const TempSessionHolder &) = delete; TempSessionHolder &operator=(const TempSessionHolder &) = delete; public: TempSessionHolder(PJ_CONTEXT *ctx, PJ_INSERT_SESSION *session) : m_ctx(ctx), m_tempSession(session ? nullptr : proj_insert_object_session_create(ctx)) {} ~TempSessionHolder() { if (m_tempSession) { proj_insert_object_session_destroy(m_ctx, m_tempSession); } } inline PJ_INSERT_SESSION *GetTempSession() const { return m_tempSession; } }; try { TempSessionHolder oHolder(ctx, session); if (!session) { session = oHolder.GetTempSession(); if (!session) { return nullptr; } } if (!object || !authority || !code) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto identifiedObject = std::dynamic_pointer_cast(object->iso_obj); if (!identifiedObject) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "Object is not a IdentifiedObject"); return nullptr; } auto dbContext = getDBcontext(ctx); std::vector allowedAuthorities{"EPSG", "PROJ"}; if (allowed_authorities) { allowedAuthorities.clear(); for (auto iter = allowed_authorities; *iter; ++iter) { allowedAuthorities.emplace_back(*iter); } } auto statements = dbContext->getInsertStatementsFor( NN_NO_CHECK(identifiedObject), authority, code, numeric_codes != FALSE, allowedAuthorities); return to_string_list(std::move(statements)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Returns a list of geoid models available for that crs * * The list includes the geoid models connected directly with the crs, * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations. * The returned list is NULL terminated and must be freed with * proj_string_list_destroy(). * * @param ctx Context, or NULL for default context. * @param auth_name Authority name (must not be NULL) * @param code Object code (must not be NULL) * @param options should be set to NULL for now * @return list of geoid models names (to be freed with * proj_string_list_destroy()), or NULL in case of error. * @since 8.1 */ PROJ_STRING_LIST proj_get_geoid_models_from_database(PJ_CONTEXT *ctx, const char *auth_name, const char *code, const char *const *options) { SANITIZE_CTX(ctx); if (!auth_name || !code) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } (void)options; try { const std::string codeStr(code); auto factory = AuthorityFactory::create(getDBcontext(ctx), auth_name); auto geoidModels = factory->getGeoidModels(codeStr); return to_string_list(std::move(geoidModels)); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateMetadata object * * @since 9.4 */ PJ *proj_coordinate_metadata_create(PJ_CONTEXT *ctx, const PJ *crs, double epoch) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto crsCast = std::dynamic_pointer_cast(crs->iso_obj); if (!crsCast) { proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); return nullptr; } try { auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); return pj_obj_create(ctx, CoordinateMetadata::create( NN_NO_CHECK(crsCast), epoch, dbContext)); } catch (const std::exception &e) { proj_log_debug(ctx, __FUNCTION__, e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** \brief Return the coordinate epoch associated with a CoordinateMetadata. * * It may return a NaN value if there is no associated coordinate epoch. * * @since 9.2 */ double proj_coordinate_metadata_get_epoch(PJ_CONTEXT *ctx, const PJ *obj) { SANITIZE_CTX(ctx); if (!obj) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return std::numeric_limits::quiet_NaN(); } auto ptr = obj->iso_obj.get(); auto coordinateMetadata = dynamic_cast(ptr); if (coordinateMetadata) { if (coordinateMetadata->coordinateEpoch().has_value()) { return coordinateMetadata->coordinateEpochAsDecimalYear(); } return std::numeric_limits::quiet_NaN(); } proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateMetadata"); return std::numeric_limits::quiet_NaN(); } // --------------------------------------------------------------------------- /** \brief Return whether a CRS has an associated PointMotionOperation * * @since 9.4 */ int proj_crs_has_point_motion_operation(PJ_CONTEXT *ctx, const PJ *crs) { SANITIZE_CTX(ctx); if (!crs) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return false; } auto l_crs = dynamic_cast(crs->iso_obj.get()); if (!l_crs) { proj_log_error(ctx, __FUNCTION__, "Object is not a CRS"); return false; } auto geodeticCRS = l_crs->extractGeodeticCRS(); if (!geodeticCRS) return false; try { auto factory = AuthorityFactory::create(getDBcontext(ctx), std::string()); return !factory ->getPointMotionOperationsFor(NN_NO_CHECK(geodeticCRS), false) .empty(); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return false; } // --------------------------------------------------------------------------- static UnitOfMeasure createScaleUnit(const char *name, double convFactor, const char *unit_auth_name = nullptr, const char *unit_code = nullptr) { return name == nullptr ? UnitOfMeasure::SCALE_UNITY : UnitOfMeasure(name, convFactor, UnitOfMeasure::Type::SCALE, unit_auth_name ? unit_auth_name : "", unit_code ? unit_code : ""); } // --------------------------------------------------------------------------- /** \brief Instantiate a conversion with method Affine Parametric, assuming * it works in linear coordinate space * * This method is defined as * * EPSG:9624. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param name Conversion name, or nullptr * @param A0 translation term for output first axis * @param A0_unit_name Name of the linear unit for A0. Or NULL for metre. * @param A0_unit_conv_factor Conversion factor to metre for A0. Note: this is * indicative only, and not taken into account in coordinate conversion * @param A1 coefficient term for output first axis taking that is multiplied * with the value along the source first axis * @param A1_unit_name Name of the scale unit for A1. Or NULL for unity. * @param A1_unit_conv_factor Conversion factor to unity for A1. Note: this is * indicative only, and not taken into account in coordinate conversion * @param A2 coefficient term for output first axis taking that is multiplied * with the value along the source second axis * @param A2_unit_name Name of the scale unit for A2. Or NULL for unity. * @param A2_unit_conv_factor Conversion factor to unity for A2. Note: this is * indicative only, and not taken into account in coordinate conversion * @param B0 translation term for output second axis * @param B0_unit_name Name of the linear unit for B0. Or NULL for metre. * @param B0_unit_conv_factor Conversion factor to metre for B0. Note: this is * indicative only, and not taken into account in coordinate conversion * @param B1 coefficient term for output second axis taking that is multiplied * with the value along the source first axis * @param B1_unit_name Name of the scale unit for B1. Or NULL for unity. * @param B1_unit_conv_factor Conversion factor to unity for B1. Note: this is * indicative only, and not taken into account in coordinate conversion * @param B2 coefficient term for output second axis taking that is multiplied * with the value along the source second axis * @param B2_unit_name Name of the scale unit for B2. Or NULL for unity. * @param B2_unit_conv_factor Conversion factor to unity for B2. Note: this is * indicative only, and not taken into account in coordinate conversion * @return Object that must be unreferenced with proj_destroy(), or NULL * in case of error. * * @since 9.8 */ /* clang-format off */ PJ PROJ_DLL *proj_create_linear_affine_parametric_conversion( PJ_CONTEXT *ctx, const char* name, double A0, const char *A0_unit_name, double A0_unit_conv_factor, double A1, const char *A1_unit_name, double A1_unit_conv_factor, double A2, const char *A2_unit_name, double A2_unit_conv_factor, double B0, const char *B0_unit_name, double B0_unit_conv_factor, double B1, const char *B1_unit_name, double B1_unit_conv_factor, double B2, const char *B2_unit_name, double B2_unit_conv_factor) /* clang-format on */ { SANITIZE_CTX(ctx); try { auto conv = Conversion::createAffineParametric( createPropertyMapName(name), Length(A0, createLinearUnit(A0_unit_name, A0_unit_conv_factor)), Scale(A1, createScaleUnit(A1_unit_name, A1_unit_conv_factor)), Scale(A2, createScaleUnit(A2_unit_name, A2_unit_conv_factor)), Length(B0, createLinearUnit(B0_unit_name, B0_unit_conv_factor)), Scale(B1, createScaleUnit(B1_unit_name, B1_unit_conv_factor)), Scale(B2, createScaleUnit(B2_unit_name, B2_unit_conv_factor))); return pj_obj_create(ctx, conv); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Instantiate a DerivedProjectedCRS * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name CRS name. Or NULL * @param base_proj_crs Base ProjectedCRS. Must not be NULL. * @param deriving_conversion Conversion. Must not be NULL. * @param coordinate_system Cartesian coordinate system. Must not be NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * * @since 9.8 */ PJ *proj_create_derived_projected_crs(PJ_CONTEXT *ctx, const char *crs_name, const PJ *base_proj_crs, const PJ *deriving_conversion, const PJ *coordinate_system) { SANITIZE_CTX(ctx); if (!base_proj_crs || !deriving_conversion || !coordinate_system) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } auto baseProjCRS = std::dynamic_pointer_cast(base_proj_crs->iso_obj); if (!baseProjCRS) { return nullptr; } auto conv = std::dynamic_pointer_cast(deriving_conversion->iso_obj); if (!conv) { return nullptr; } auto cs = std::dynamic_pointer_cast(coordinate_system->iso_obj); if (!cs) { return nullptr; } try { return pj_obj_create(ctx, DerivedProjectedCRS::create( createPropertyMapName(crs_name), NN_NO_CHECK(baseProjCRS), NN_NO_CHECK(conv), NN_NO_CHECK(cs))); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Add a deriving conversion to a horizontal CRS * * This is for now restricting when the horizontal CRS is a ProjectedCRS, * a CompoundCRS with a ProjectedCRS, or a BoundCRS of a ProjectedCRS. * * The returned object must be unreferenced with proj_destroy() after * use. * It should be used by at most one thread at a time. * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the derived projected CRS. Or NULL * @param base_crs ProjectedCRS, CompoundCRS with a ProjectedCRS, or * BoundCRS of a ProjectedCRS. Must not be NULL. * @param deriving_conversion Conversion. Must not be NULL. * @param coordinate_system Cartesian coordinate system. Must not be NULL. * * @return Object that must be unreferenced with * proj_destroy(), or NULL in case of error. * * @since 9.8 */ PJ *proj_crs_add_horizontal_derived_conversion(PJ_CONTEXT *ctx, const char *crs_name, const PJ *base_crs, const PJ *deriving_conversion, const PJ *coordinate_system) { SANITIZE_CTX(ctx); if (!base_crs || !deriving_conversion || !coordinate_system) { proj_context_errno_set(ctx, PROJ_ERR_OTHER_API_MISUSE); proj_log_error(ctx, __FUNCTION__, "missing required input"); return nullptr; } CRSPtr baseCRS = std::dynamic_pointer_cast(base_crs->iso_obj); if (!baseCRS) return nullptr; auto conv = std::dynamic_pointer_cast(deriving_conversion->iso_obj); if (!conv) { return nullptr; } auto cs = std::dynamic_pointer_cast(coordinate_system->iso_obj); if (!cs) { return nullptr; } auto compoundCRS = std::dynamic_pointer_cast(baseCRS); CRSPtr verticalCRS; if (compoundCRS) { const auto &components = compoundCRS->componentReferenceSystems(); if (components.size() == 2) { baseCRS = components[0]; verticalCRS = components[1]; } } auto boundCRS = std::dynamic_pointer_cast(baseCRS); if (boundCRS) { baseCRS = boundCRS->baseCRS(); } auto baseProjCRS = std::dynamic_pointer_cast(baseCRS); if (!baseProjCRS) { return nullptr; } try { CRSNNPtr retCRS = DerivedProjectedCRS::create( createPropertyMapName(crs_name), NN_NO_CHECK(baseProjCRS), NN_NO_CHECK(conv), NN_NO_CHECK(cs)); if (boundCRS) { retCRS = BoundCRS::create(retCRS, boundCRS->hubCRS(), boundCRS->transformation()); } if (verticalCRS) { retCRS = CompoundCRS::create( createPropertyMapName( (retCRS->nameStr() + " + " + verticalCRS->nameStr()) .c_str()), {retCRS, NN_NO_CHECK(verticalCRS)}); } return pj_obj_create(ctx, retCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); } return nullptr; } proj-9.8.1/src/iso19111/internal.cpp000664 001750 001750 00000027570 15166171715 016764 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/internal/internal.hpp" #include #include #include #ifdef _MSC_VER #include #else #include #endif #include #include // std::setprecision #include #include // std::istringstream and std::ostringstream #include #include "sqlite3.h" NS_PROJ_START namespace internal { // --------------------------------------------------------------------------- /** * Replace all occurrences of before with after. */ std::string replaceAll(const std::string &str, const std::string &before, const std::string &after) { std::string ret(str); const size_t nBeforeSize = before.size(); const size_t nAfterSize = after.size(); if (nBeforeSize) { size_t nStartPos = 0; while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) { ret.replace(nStartPos, nBeforeSize, after); nStartPos += nAfterSize; } } return ret; } // --------------------------------------------------------------------------- inline static bool EQUALN(const char *a, const char *b, size_t size) { #ifdef _MSC_VER return _strnicmp(a, b, size) == 0; #else return strncasecmp(a, b, size) == 0; #endif } /** * Case-insensitive equality test */ bool ci_equal(const std::string &a, const std::string &b) noexcept { const auto size = a.size(); if (size != b.size()) { return false; } return EQUALN(a.c_str(), b.c_str(), size); } bool ci_equal(const std::string &a, const char *b) noexcept { const auto size = a.size(); if (size != strlen(b)) { return false; } return EQUALN(a.c_str(), b, size); } bool ci_equal(const char *a, const char *b) noexcept { const auto size = strlen(a); if (size != strlen(b)) { return false; } return EQUALN(a, b, size); } // --------------------------------------------------------------------------- bool ci_less(const std::string &a, const std::string &b) noexcept { #ifdef _MSC_VER return _stricmp(a.c_str(), b.c_str()) < 0; #else return strcasecmp(a.c_str(), b.c_str()) < 0; #endif } // --------------------------------------------------------------------------- /** * Convert to lower case. */ std::string tolower(const std::string &str) { std::string ret(str); for (char &ch : ret) ch = (ch >= 'A' && ch <= 'Z') ? static_cast(ch + ('a' - 'A')) : ch; return ret; } // --------------------------------------------------------------------------- /** * Convert to upper case. */ std::string toupper(const std::string &str) { std::string ret(str); for (char &ch : ret) ch = (ch >= 'a' && ch <= 'z') ? static_cast(ch - ('a' - 'A')) : ch; return ret; } // --------------------------------------------------------------------------- /** Strip leading and trailing double quote characters */ std::string stripQuotes(const std::string &str) { if (str.size() >= 2 && str[0] == '"' && str.back() == '"') { return str.substr(1, str.size() - 2); } return str; } // --------------------------------------------------------------------------- size_t ci_find(const std::string &str, const char *needle) noexcept { const size_t needleSize = strlen(needle); for (size_t i = 0; i + needleSize <= str.size(); i++) { if (EQUALN(str.c_str() + i, needle, needleSize)) { return i; } } return std::string::npos; } // --------------------------------------------------------------------------- size_t ci_find(const std::string &str, const std::string &needle, size_t startPos) noexcept { const size_t needleSize = needle.size(); for (size_t i = startPos; i + needleSize <= str.size(); i++) { if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) { return i; } } return std::string::npos; } // --------------------------------------------------------------------------- /* bool starts_with(const std::string &str, const std::string &prefix) noexcept { if (str.size() < prefix.size()) { return false; } return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; } */ // --------------------------------------------------------------------------- /* bool starts_with(const std::string &str, const char *prefix) noexcept { const size_t prefixSize = std::strlen(prefix); if (str.size() < prefixSize) { return false; } return std::memcmp(str.c_str(), prefix, prefixSize) == 0; } */ // --------------------------------------------------------------------------- bool ci_starts_with(const char *str, const char *prefix) noexcept { const auto str_size = strlen(str); const auto prefix_size = strlen(prefix); if (str_size < prefix_size) { return false; } return EQUALN(str, prefix, prefix_size); } // --------------------------------------------------------------------------- bool ci_starts_with(const std::string &str, const std::string &prefix) noexcept { if (str.size() < prefix.size()) { return false; } return EQUALN(str.c_str(), prefix.c_str(), prefix.size()); } // --------------------------------------------------------------------------- bool ends_with(const std::string &str, const std::string &suffix) noexcept { if (str.size() < suffix.size()) { return false; } return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(), suffix.size()) == 0; } // --------------------------------------------------------------------------- double c_locale_stod(const std::string &s) { bool success; double val = c_locale_stod(s, success); if (!success) { throw std::invalid_argument("non double value"); } return val; } double c_locale_stod(const std::string &s, bool &success) { success = true; const auto s_size = s.size(); // Fast path if (s_size > 0 && s_size < 15) { std::int64_t acc = 0; std::int64_t div = 1; bool afterDot = false; size_t i = 0; if (s[0] == '-') { ++i; div = -1; } else if (s[0] == '+') { ++i; } for (; i < s_size; ++i) { const auto ch = s[i]; if (ch >= '0' && ch <= '9') { acc = acc * 10 + ch - '0'; if (afterDot) { div *= 10; } } else if (ch == '.') { afterDot = true; } else { div = 0; } } if (div) { return static_cast(acc) / div; } } std::istringstream iss(s); iss.imbue(std::locale::classic()); double d; iss >> d; if (!iss.eof() || iss.fail()) { success = false; d = 0; } return d; } // --------------------------------------------------------------------------- std::vector split(const std::string &str, char separator) { std::vector res; size_t lastPos = 0; size_t newPos = 0; while ((newPos = str.find(separator, lastPos)) != std::string::npos) { res.push_back(str.substr(lastPos, newPos - lastPos)); lastPos = newPos + 1; } res.push_back(str.substr(lastPos)); return res; } // --------------------------------------------------------------------------- std::vector split(const std::string &str, const std::string &separator) { std::vector res; size_t lastPos = 0; size_t newPos = 0; while ((newPos = str.find(separator, lastPos)) != std::string::npos) { res.push_back(str.substr(lastPos, newPos - lastPos)); lastPos = newPos + separator.size(); } res.push_back(str.substr(lastPos)); return res; } // --------------------------------------------------------------------------- #ifdef _WIN32 // For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor // doesn't round identically to the Unix builds, and thus breaks a number of // unit test. So to avoid this, use the stdlib formatting std::string toString(int val) { std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << val; return buffer.str(); } std::string toString(double val, int precision) { std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << std::setprecision(precision); buffer << val; auto str = buffer.str(); if (precision == 15 && str.find("9999999999") != std::string::npos) { buffer.str(""); buffer.clear(); buffer << std::setprecision(14); buffer << val; return buffer.str(); } return str; } #else std::string toString(int val) { // use sqlite3 API that is slightly faster than std::ostringstream // with forcing the C locale. sqlite3_snprintf() emulates a C locale. constexpr int BUF_SIZE = 16; char szBuffer[BUF_SIZE]; sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val); return szBuffer; } std::string toString(double val, int precision) { // use sqlite3 API that is slightly faster than std::ostringstream // with forcing the C locale. sqlite3_snprintf() emulates a C locale. constexpr int BUF_SIZE = 32; char szBuffer[BUF_SIZE]; sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val); if (precision == 15 && strstr(szBuffer, "9999999999")) { sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val); } return szBuffer; } #endif // --------------------------------------------------------------------------- std::string concat(const char *a, const std::string &b) { std::string res(a); res += b; return res; } std::string concat(const char *a, const std::string &b, const char *c) { std::string res(a); res += b; res += c; return res; } // --------------------------------------------------------------------------- // Avoid rounding issues due to year -> second (SI unit) -> year roundtrips double getRoundedEpochInDecimalYear(double year) { // Try to see if the value is close to xxxx.yyy decimal year. if (std::fabs(1000 * year - std::round(1000 * year)) <= 1e-3) { year = std::round(1000 * year) / 1000.0; } return year; } // --------------------------------------------------------------------------- } // namespace internal NS_PROJ_END proj-9.8.1/src/iso19111/common.cpp000664 001750 001750 00000132221 15166171715 016426 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj.h" #include "proj_internal.h" #include "proj_json_streaming_writer.hpp" #include // M_PI #include #include #include #include using namespace NS_PROJ::internal; using namespace NS_PROJ::io; using namespace NS_PROJ::metadata; using namespace NS_PROJ::util; #if 0 namespace dropbox{ namespace oxygen { template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; }} #endif NS_PROJ_START namespace common { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct UnitOfMeasure::Private { std::string name_{}; double toSI_ = 1.0; UnitOfMeasure::Type type_{UnitOfMeasure::Type::UNKNOWN}; std::string codeSpace_{}; std::string code_{}; Private(const std::string &nameIn, double toSIIn, UnitOfMeasure::Type typeIn, const std::string &codeSpaceIn, const std::string &codeIn) : name_(nameIn), toSI_(toSIIn), type_(typeIn), codeSpace_(codeSpaceIn), code_(codeIn) {} }; //! @endcond // --------------------------------------------------------------------------- /** \brief Creates a UnitOfMeasure. */ UnitOfMeasure::UnitOfMeasure(const std::string &nameIn, double toSIIn, UnitOfMeasure::Type typeIn, const std::string &codeSpaceIn, const std::string &codeIn) : d(std::make_unique(nameIn, toSIIn, typeIn, codeSpaceIn, codeIn)) {} // --------------------------------------------------------------------------- UnitOfMeasure::UnitOfMeasure(const UnitOfMeasure &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress UnitOfMeasure::~UnitOfMeasure() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress UnitOfMeasure &UnitOfMeasure::operator=(const UnitOfMeasure &other) { if (this != &other) { *d = *(other.d); } return *this; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress UnitOfMeasure &UnitOfMeasure::operator=(UnitOfMeasure &&other) { *d = std::move(*(other.d)); other.d = nullptr; BaseObject::operator=(std::move(static_cast(other))); return *this; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress UnitOfMeasureNNPtr UnitOfMeasure::create(const UnitOfMeasure &other) { return util::nn_make_shared(other); } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the name of the unit of measure. */ const std::string &UnitOfMeasure::name() PROJ_PURE_DEFN { return d->name_; } // --------------------------------------------------------------------------- /** \brief Return the conversion factor to the unit of the * International System of Units of the same Type. * * For example, for foot, this would be 0.3048 (metre) * * @return the conversion factor, or 0 if no conversion exists. */ double UnitOfMeasure::conversionToSI() PROJ_PURE_DEFN { return d->toSI_; } // --------------------------------------------------------------------------- /** \brief Return the type of the unit of measure. */ UnitOfMeasure::Type UnitOfMeasure::type() PROJ_PURE_DEFN { return d->type_; } // --------------------------------------------------------------------------- /** \brief Return the code space of the unit of measure. * * For example "EPSG" * * @return the code space, or empty string. */ const std::string &UnitOfMeasure::codeSpace() PROJ_PURE_DEFN { return d->codeSpace_; } // --------------------------------------------------------------------------- /** \brief Return the code of the unit of measure. * * @return the code, or empty string. */ const std::string &UnitOfMeasure::code() PROJ_PURE_DEFN { return d->code_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void UnitOfMeasure::_exportToWKT( WKTFormatter *formatter, const std::string &unitType) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; const auto l_type = type(); if (!unitType.empty()) { formatter->startNode(unitType, !codeSpace().empty()); } else if (formatter->forceUNITKeyword() && l_type != Type::PARAMETRIC) { formatter->startNode(WKTConstants::UNIT, !codeSpace().empty()); } else { if (isWKT2 && l_type == Type::LINEAR) { formatter->startNode(WKTConstants::LENGTHUNIT, !codeSpace().empty()); } else if (isWKT2 && l_type == Type::ANGULAR) { formatter->startNode(WKTConstants::ANGLEUNIT, !codeSpace().empty()); } else if (isWKT2 && l_type == Type::SCALE) { formatter->startNode(WKTConstants::SCALEUNIT, !codeSpace().empty()); } else if (isWKT2 && l_type == Type::TIME) { formatter->startNode(WKTConstants::TIMEUNIT, !codeSpace().empty()); } else if (isWKT2 && l_type == Type::PARAMETRIC) { formatter->startNode(WKTConstants::PARAMETRICUNIT, !codeSpace().empty()); } else { formatter->startNode(WKTConstants::UNIT, !codeSpace().empty()); } } { const auto &l_name = name(); const bool esri = formatter->useESRIDialect(); if (esri) { if (ci_equal(l_name, "degree")) { formatter->addQuotedString("Degree"); } else if (ci_equal(l_name, "grad")) { formatter->addQuotedString("Grad"); } else if (ci_equal(l_name, "metre")) { formatter->addQuotedString("Meter"); } else { formatter->addQuotedString(l_name); } } else { formatter->addQuotedString(l_name); } const auto &factor = conversionToSI(); if (!isWKT2 || l_type != Type::TIME || factor != 0.0) { // Some TIMEUNIT do not have a conversion factor formatter->add(factor); } if (!codeSpace().empty() && formatter->outputId()) { formatter->startNode( isWKT2 ? WKTConstants::ID : WKTConstants::AUTHORITY, false); formatter->addQuotedString(codeSpace()); const auto &l_code = code(); if (isWKT2) { try { (void)std::stoi(l_code); formatter->add(l_code); } catch (const std::exception &) { formatter->addQuotedString(l_code); } } else { formatter->addQuotedString(l_code); } formatter->endNode(); } } formatter->endNode(); } // --------------------------------------------------------------------------- void UnitOfMeasure::_exportToJSON( JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); const auto &l_codeSpace = codeSpace(); auto objContext( formatter->MakeObjectContext(nullptr, !l_codeSpace.empty())); writer->AddObjKey("type"); const auto l_type = type(); if (l_type == Type::LINEAR) { writer->Add("LinearUnit"); } else if (l_type == Type::ANGULAR) { writer->Add("AngularUnit"); } else if (l_type == Type::SCALE) { writer->Add("ScaleUnit"); } else if (l_type == Type::TIME) { writer->Add("TimeUnit"); } else if (l_type == Type::PARAMETRIC) { writer->Add("ParametricUnit"); } else { writer->Add("Unit"); } writer->AddObjKey("name"); const auto &l_name = name(); writer->Add(l_name); const auto &factor = conversionToSI(); writer->AddObjKey("conversion_factor"); writer->Add(factor, 15); if (!l_codeSpace.empty() && formatter->outputId()) { writer->AddObjKey("id"); auto idContext(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("authority"); writer->Add(l_codeSpace); writer->AddObjKey("code"); const auto &l_code = code(); try { writer->Add(std::stoi(l_code)); } catch (const std::exception &) { writer->Add(l_code); } } } //! @endcond // --------------------------------------------------------------------------- /** Returns whether two units of measures are equal. * * The comparison is based on the name. */ bool UnitOfMeasure::operator==(const UnitOfMeasure &other) PROJ_PURE_DEFN { return name() == other.name(); } // --------------------------------------------------------------------------- /** Returns whether two units of measures are different. * * The comparison is based on the name. */ bool UnitOfMeasure::operator!=(const UnitOfMeasure &other) PROJ_PURE_DEFN { return name() != other.name(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::string UnitOfMeasure::exportToPROJString() const { if (type() == Type::LINEAR) { auto proj_units = pj_list_linear_units(); for (int i = 0; proj_units[i].id != nullptr; i++) { if (::fabs(proj_units[i].factor - conversionToSI()) < 1e-10 * conversionToSI()) { return proj_units[i].id; } } } else if (type() == Type::ANGULAR) { auto proj_angular_units = pj_list_angular_units(); for (int i = 0; proj_angular_units[i].id != nullptr; i++) { if (::fabs(proj_angular_units[i].factor - conversionToSI()) < 1e-10 * conversionToSI()) { return proj_angular_units[i].id; } } } return std::string(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool UnitOfMeasure::_isEquivalentTo( const UnitOfMeasure &other, util::IComparable::Criterion criterion) const { if (criterion == util::IComparable::Criterion::STRICT) { return operator==(other); } return std::fabs(conversionToSI() - other.conversionToSI()) <= 1e-10 * std::fabs(conversionToSI()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Measure::Private { double value_ = 0.0; UnitOfMeasure unit_{}; Private(double valueIn, const UnitOfMeasure &unitIn) : value_(valueIn), unit_(unitIn) {} }; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a Measure. */ Measure::Measure(double valueIn, const UnitOfMeasure &unitIn) : d(std::make_unique(valueIn, unitIn)) {} // --------------------------------------------------------------------------- Measure::Measure(const Measure &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Measure::~Measure() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the unit of the Measure. */ const UnitOfMeasure &Measure::unit() PROJ_PURE_DEFN { return d->unit_; } // --------------------------------------------------------------------------- /** \brief Return the value of the Measure, after conversion to the * corresponding * unit of the International System. */ double Measure::getSIValue() PROJ_PURE_DEFN { return d->value_ * d->unit_.conversionToSI(); } // --------------------------------------------------------------------------- /** \brief Return the value of the measure, expressed in the unit() */ double Measure::value() PROJ_PURE_DEFN { return d->value_; } // --------------------------------------------------------------------------- /** \brief Return the value of this measure expressed into the provided unit. */ double Measure::convertToUnit(const UnitOfMeasure &otherUnit) PROJ_PURE_DEFN { return getSIValue() / otherUnit.conversionToSI(); } // --------------------------------------------------------------------------- /** \brief Return whether two measures are equal. * * The comparison is done both on the value and the unit. */ bool Measure::operator==(const Measure &other) PROJ_PURE_DEFN { return d->value_ == other.d->value_ && d->unit_ == other.d->unit_; } // --------------------------------------------------------------------------- /** \brief Returns whether an object is equivalent to another one. * @param other other object to compare to * @param criterion comparison criterion. * @param maxRelativeError Maximum relative error allowed. * @return true if objects are equivalent. */ bool Measure::_isEquivalentTo(const Measure &other, util::IComparable::Criterion criterion, double maxRelativeError) const { if (criterion == util::IComparable::Criterion::STRICT) { return operator==(other); } const double SIValue = getSIValue(); const double otherSIValue = other.getSIValue(); // It is arguable that we have to deal with infinite values, but this // helps robustify some situations. if (std::isinf(SIValue) && std::isinf(otherSIValue)) return SIValue * otherSIValue > 0; return std::fabs(SIValue - otherSIValue) <= maxRelativeError * std::fabs(SIValue); } // --------------------------------------------------------------------------- /** \brief Instantiate a Scale. * * @param valueIn value */ Scale::Scale(double valueIn) : Measure(valueIn, UnitOfMeasure::SCALE_UNITY) {} // --------------------------------------------------------------------------- /** \brief Instantiate a Scale. * * @param valueIn value * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::SCALE */ Scale::Scale(double valueIn, const UnitOfMeasure &unitIn) : Measure(valueIn, unitIn) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Scale::Scale(const Scale &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Scale::~Scale() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a Angle. * * @param valueIn value */ Angle::Angle(double valueIn) : Measure(valueIn, UnitOfMeasure::DEGREE) {} // --------------------------------------------------------------------------- /** \brief Instantiate a Angle. * * @param valueIn value * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::ANGULAR */ Angle::Angle(double valueIn, const UnitOfMeasure &unitIn) : Measure(valueIn, unitIn) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Angle::Angle(const Angle &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Angle::~Angle() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a Length. * * @param valueIn value */ Length::Length(double valueIn) : Measure(valueIn, UnitOfMeasure::METRE) {} // --------------------------------------------------------------------------- /** \brief Instantiate a Length. * * @param valueIn value * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::LINEAR */ Length::Length(double valueIn, const UnitOfMeasure &unitIn) : Measure(valueIn, unitIn) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Length::Length(const Length &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Length::~Length() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DateTime::Private { std::string str_{}; explicit Private(const std::string &str) : str_(str) {} }; //! @endcond // --------------------------------------------------------------------------- DateTime::DateTime() : d(std::make_unique(std::string())) {} // --------------------------------------------------------------------------- DateTime::DateTime(const std::string &str) : d(std::make_unique(str)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DateTime::DateTime(const DateTime &other) : d(std::make_unique(*(other.d))) {} //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DateTime &DateTime::operator=(const DateTime &other) { d->str_ = other.d->str_; return *this; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DateTime::~DateTime() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a DateTime. */ DateTime DateTime::create(const std::string &str) { return DateTime(str); } // --------------------------------------------------------------------------- /** \brief Return whether the DateTime is ISO:8601 compliant. * * \remark The current implementation is really simplistic, and aimed at * detecting date-times that are not ISO:8601 compliant. */ bool DateTime::isISO_8601() const { return !d->str_.empty() && d->str_[0] >= '0' && d->str_[0] <= '9' && d->str_.find(' ') == std::string::npos; } // --------------------------------------------------------------------------- /** \brief Return the DateTime as a string. */ std::string DateTime::toString() const { return d->str_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // cppcheck-suppress copyCtorAndEqOperator struct IdentifiedObject::Private { IdentifierNNPtr name{Identifier::create()}; std::vector identifiers{}; std::vector aliases{}; std::string remarks{}; bool isDeprecated{}; void setIdentifiers(const PropertyMap &properties); void setName(const PropertyMap &properties); void setAliases(const PropertyMap &properties); }; //! @endcond // --------------------------------------------------------------------------- IdentifiedObject::IdentifiedObject() : d(std::make_unique()) {} // --------------------------------------------------------------------------- IdentifiedObject::IdentifiedObject(const IdentifiedObject &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress IdentifiedObject::~IdentifiedObject() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the name of the object. * * Generally, the only interesting field of the name will be * name()->description(). */ const IdentifierNNPtr &IdentifiedObject::name() PROJ_PURE_DEFN { return d->name; } // --------------------------------------------------------------------------- /** \brief Return the name of the object. * * Return *(name()->description()) */ const std::string &IdentifiedObject::nameStr() PROJ_PURE_DEFN { return *(d->name->description()); } // --------------------------------------------------------------------------- /** \brief Return the identifier(s) of the object * * Generally, those will have Identifier::code() and Identifier::codeSpace() * filled. */ const std::vector & IdentifiedObject::identifiers() PROJ_PURE_DEFN { return d->identifiers; } // --------------------------------------------------------------------------- /** \brief Return the alias(es) of the object. */ const std::vector & IdentifiedObject::aliases() PROJ_PURE_DEFN { return d->aliases; } // --------------------------------------------------------------------------- /** \brief Return the (first) alias of the object as a string. * * Shortcut for aliases()[0]->toFullyQualifiedName()->toString() */ std::string IdentifiedObject::alias() PROJ_PURE_DEFN { if (d->aliases.empty()) return std::string(); return d->aliases[0]->toFullyQualifiedName()->toString(); } // --------------------------------------------------------------------------- /** \brief Return the EPSG code. * @return code, or 0 if not found */ int IdentifiedObject::getEPSGCode() PROJ_PURE_DEFN { for (const auto &id : identifiers()) { if (ci_equal(*(id->codeSpace()), metadata::Identifier::EPSG)) { return ::atoi(id->code().c_str()); } } return 0; } // --------------------------------------------------------------------------- /** \brief Return the remarks. */ const std::string &IdentifiedObject::remarks() PROJ_PURE_DEFN { return d->remarks; } // --------------------------------------------------------------------------- /** \brief Return whether the object is deprecated. * * \remark Extension of \ref ISO_19111_2019 */ bool IdentifiedObject::isDeprecated() PROJ_PURE_DEFN { return d->isDeprecated; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void IdentifiedObject::Private::setName( const PropertyMap &properties) // throw(InvalidValueTypeException) { const auto pVal = properties.get(NAME_KEY); if (!pVal) { return; } if (const auto genVal = dynamic_cast(pVal->get())) { if (genVal->type() == BoxedValue::Type::STRING) { name = Identifier::createFromDescription(genVal->stringValue()); } else { throw InvalidValueTypeException("Invalid value type for " + NAME_KEY); } } else { if (auto identifier = util::nn_dynamic_pointer_cast(*pVal)) { name = NN_NO_CHECK(identifier); } else { throw InvalidValueTypeException("Invalid value type for " + NAME_KEY); } } } // --------------------------------------------------------------------------- void IdentifiedObject::Private::setIdentifiers( const PropertyMap &properties) // throw(InvalidValueTypeException) { auto pVal = properties.get(IDENTIFIERS_KEY); if (!pVal) { pVal = properties.get(Identifier::CODE_KEY); if (pVal) { identifiers.clear(); identifiers.push_back( Identifier::create(std::string(), properties)); } return; } if (auto identifier = util::nn_dynamic_pointer_cast(*pVal)) { identifiers.clear(); identifiers.push_back(NN_NO_CHECK(identifier)); } else { if (auto array = dynamic_cast(pVal->get())) { identifiers.clear(); for (const auto &val : *array) { identifier = util::nn_dynamic_pointer_cast(val); if (identifier) { identifiers.push_back(NN_NO_CHECK(identifier)); } else { throw InvalidValueTypeException("Invalid value type for " + IDENTIFIERS_KEY); } } } else { throw InvalidValueTypeException("Invalid value type for " + IDENTIFIERS_KEY); } } } // --------------------------------------------------------------------------- void IdentifiedObject::Private::setAliases( const PropertyMap &properties) // throw(InvalidValueTypeException) { const auto pVal = properties.get(ALIAS_KEY); if (!pVal) { return; } if (auto l_name = util::nn_dynamic_pointer_cast(*pVal)) { aliases.clear(); aliases.push_back(NN_NO_CHECK(l_name)); } else { if (const auto array = dynamic_cast(pVal->get())) { aliases.clear(); for (const auto &val : *array) { l_name = util::nn_dynamic_pointer_cast(val); if (l_name) { aliases.push_back(NN_NO_CHECK(l_name)); } else { if (auto genVal = dynamic_cast(val.get())) { if (genVal->type() == BoxedValue::Type::STRING) { aliases.push_back(NameFactory::createLocalName( nullptr, genVal->stringValue())); } else { throw InvalidValueTypeException( "Invalid value type for " + ALIAS_KEY); } } else { throw InvalidValueTypeException( "Invalid value type for " + ALIAS_KEY); } } } } else { std::string temp; if (properties.getStringValue(ALIAS_KEY, temp)) { aliases.clear(); aliases.push_back(NameFactory::createLocalName(nullptr, temp)); } else { throw InvalidValueTypeException("Invalid value type for " + ALIAS_KEY); } } } } //! @endcond // --------------------------------------------------------------------------- void IdentifiedObject::setProperties( const PropertyMap &properties) // throw(InvalidValueTypeException) { d->setName(properties); d->setIdentifiers(properties); d->setAliases(properties); properties.getStringValue(REMARKS_KEY, d->remarks); { const auto pVal = properties.get(DEPRECATED_KEY); if (pVal) { if (const auto genVal = dynamic_cast(pVal->get())) { if (genVal->type() == BoxedValue::Type::BOOLEAN) { d->isDeprecated = genVal->booleanValue(); } else { throw InvalidValueTypeException("Invalid value type for " + DEPRECATED_KEY); } } else { throw InvalidValueTypeException("Invalid value type for " + DEPRECATED_KEY); } } } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void IdentifiedObject::formatID(WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; for (const auto &id : identifiers()) { id->_exportToWKT(formatter); if (!isWKT2) { break; } } } // --------------------------------------------------------------------------- void IdentifiedObject::formatRemarks(WKTFormatter *formatter) const { if (!remarks().empty()) { formatter->startNode(WKTConstants::REMARK, false); formatter->addQuotedString(remarks()); formatter->endNode(); } } // --------------------------------------------------------------------------- void IdentifiedObject::formatID(JSONFormatter *formatter) const { const auto &ids(identifiers()); auto writer = formatter->writer(); if (ids.size() == 1) { writer->AddObjKey("id"); ids.front()->_exportToJSON(formatter); } else if (!ids.empty()) { writer->AddObjKey("ids"); auto arrayContext(writer->MakeArrayContext()); for (const auto &id : ids) { id->_exportToJSON(formatter); } } } // --------------------------------------------------------------------------- void IdentifiedObject::formatRemarks(JSONFormatter *formatter) const { if (!remarks().empty()) { auto writer = formatter->writer(); writer->AddObjKey("remarks"); writer->Add(remarks()); } } // --------------------------------------------------------------------------- bool IdentifiedObject::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherIdObj = dynamic_cast(other); if (!otherIdObj) return false; return _isEquivalentTo(otherIdObj, criterion, dbContext); } // --------------------------------------------------------------------------- bool IdentifiedObject::_isEquivalentTo( const IdentifiedObject *otherIdObj, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) PROJ_PURE_DEFN { if (criterion == util::IComparable::Criterion::STRICT) { if (!ci_equal(nameStr(), otherIdObj->nameStr())) { return false; } // TODO test id etc } else { if (!metadata::Identifier::isEquivalentName( nameStr().c_str(), otherIdObj->nameStr().c_str())) { return hasEquivalentNameToUsingAlias(otherIdObj, dbContext); } } return true; } // --------------------------------------------------------------------------- bool IdentifiedObject::hasEquivalentNameToUsingAlias( const IdentifiedObject *, const io::DatabaseContextPtr &) const { return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ObjectDomain::Private { optional scope_{}; ExtentPtr domainOfValidity_{}; Private(const optional &scopeIn, const ExtentPtr &extent) : scope_(scopeIn), domainOfValidity_(extent) {} }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ObjectDomain::ObjectDomain(const optional &scopeIn, const ExtentPtr &extent) : d(std::make_unique(scopeIn, extent)) {} //! @endcond // --------------------------------------------------------------------------- ObjectDomain::ObjectDomain(const ObjectDomain &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ObjectDomain::~ObjectDomain() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the scope. * * @return the scope, or empty. */ const optional &ObjectDomain::scope() PROJ_PURE_DEFN { return d->scope_; } // --------------------------------------------------------------------------- /** \brief Return the domain of validity. * * @return the domain of validity, or nullptr. */ const ExtentPtr &ObjectDomain::domainOfValidity() PROJ_PURE_DEFN { return d->domainOfValidity_; } // --------------------------------------------------------------------------- /** \brief Instantiate a ObjectDomain. */ ObjectDomainNNPtr ObjectDomain::create(const optional &scopeIn, const ExtentPtr &extent) { return ObjectDomain::nn_make_shared(scopeIn, extent); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ObjectDomain::_exportToWKT(WKTFormatter *formatter) const { if (d->scope_.has_value()) { formatter->startNode(WKTConstants::SCOPE, false); formatter->addQuotedString(*(d->scope_)); formatter->endNode(); } else if (formatter->use2019Keywords()) { formatter->startNode(WKTConstants::SCOPE, false); formatter->addQuotedString("unknown"); formatter->endNode(); } if (d->domainOfValidity_) { if (d->domainOfValidity_->description().has_value()) { formatter->startNode(WKTConstants::AREA, false); formatter->addQuotedString(*(d->domainOfValidity_->description())); formatter->endNode(); } if (d->domainOfValidity_->geographicElements().size() == 1) { const auto bbox = dynamic_cast( d->domainOfValidity_->geographicElements()[0].get()); if (bbox) { formatter->startNode(WKTConstants::BBOX, false); formatter->add(bbox->southBoundLatitude()); formatter->add(bbox->westBoundLongitude()); formatter->add(bbox->northBoundLatitude()); formatter->add(bbox->eastBoundLongitude()); formatter->endNode(); } } if (d->domainOfValidity_->verticalElements().size() == 1) { auto extent = d->domainOfValidity_->verticalElements()[0]; formatter->startNode(WKTConstants::VERTICALEXTENT, false); formatter->add(extent->minimumValue()); formatter->add(extent->maximumValue()); extent->unit()->_exportToWKT(formatter); formatter->endNode(); } if (d->domainOfValidity_->temporalElements().size() == 1) { auto extent = d->domainOfValidity_->temporalElements()[0]; formatter->startNode(WKTConstants::TIMEEXTENT, false); if (DateTime::create(extent->start()).isISO_8601()) { formatter->add(extent->start()); } else { formatter->addQuotedString(extent->start()); } if (DateTime::create(extent->stop()).isISO_8601()) { formatter->add(extent->stop()); } else { formatter->addQuotedString(extent->stop()); } formatter->endNode(); } } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ObjectDomain::_exportToJSON(JSONFormatter *formatter) const { auto writer = formatter->writer(); if (d->scope_.has_value()) { writer->AddObjKey("scope"); writer->Add(*(d->scope_)); } if (d->domainOfValidity_) { if (d->domainOfValidity_->description().has_value()) { writer->AddObjKey("area"); writer->Add(*(d->domainOfValidity_->description())); } if (d->domainOfValidity_->geographicElements().size() == 1) { const auto bbox = dynamic_cast( d->domainOfValidity_->geographicElements()[0].get()); if (bbox) { writer->AddObjKey("bbox"); auto bboxContext(writer->MakeObjectContext()); writer->AddObjKey("south_latitude"); writer->Add(bbox->southBoundLatitude(), 15); writer->AddObjKey("west_longitude"); writer->Add(bbox->westBoundLongitude(), 15); writer->AddObjKey("north_latitude"); writer->Add(bbox->northBoundLatitude(), 15); writer->AddObjKey("east_longitude"); writer->Add(bbox->eastBoundLongitude(), 15); } } if (d->domainOfValidity_->verticalElements().size() == 1) { const auto &verticalExtent = d->domainOfValidity_->verticalElements().front(); writer->AddObjKey("vertical_extent"); auto bboxContext(writer->MakeObjectContext()); writer->AddObjKey("minimum"); writer->Add(verticalExtent->minimumValue(), 15); writer->AddObjKey("maximum"); writer->Add(verticalExtent->maximumValue(), 15); const auto &unit = verticalExtent->unit(); if (*unit != common::UnitOfMeasure::METRE) { writer->AddObjKey("unit"); unit->_exportToJSON(formatter); } } if (d->domainOfValidity_->temporalElements().size() == 1) { const auto &temporalExtent = d->domainOfValidity_->temporalElements().front(); writer->AddObjKey("temporal_extent"); auto bboxContext(writer->MakeObjectContext()); writer->AddObjKey("start"); writer->Add(temporalExtent->start()); writer->AddObjKey("end"); writer->Add(temporalExtent->stop()); } } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool ObjectDomain::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDomain = dynamic_cast(other); if (!otherDomain) return false; if (scope().has_value() != otherDomain->scope().has_value()) return false; if (*scope() != *otherDomain->scope()) return false; if ((domainOfValidity().get() != nullptr) ^ (otherDomain->domainOfValidity().get() != nullptr)) return false; return domainOfValidity().get() == nullptr || domainOfValidity()->_isEquivalentTo( otherDomain->domainOfValidity().get(), criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ObjectUsage::Private { std::vector domains_{}; }; //! @endcond // --------------------------------------------------------------------------- ObjectUsage::ObjectUsage() : d(std::make_unique()) {} // --------------------------------------------------------------------------- ObjectUsage::ObjectUsage(const ObjectUsage &other) : IdentifiedObject(other), d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ObjectUsage::~ObjectUsage() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the domains of the object. */ const std::vector &ObjectUsage::domains() PROJ_PURE_DEFN { return d->domains_; } // --------------------------------------------------------------------------- void ObjectUsage::setProperties( const PropertyMap &properties) // throw(InvalidValueTypeException) { IdentifiedObject::setProperties(properties); optional scope; properties.getStringValue(SCOPE_KEY, scope); ExtentPtr domainOfValidity; { const auto pVal = properties.get(DOMAIN_OF_VALIDITY_KEY); if (pVal) { domainOfValidity = util::nn_dynamic_pointer_cast(*pVal); if (!domainOfValidity) { throw InvalidValueTypeException("Invalid value type for " + DOMAIN_OF_VALIDITY_KEY); } } } if (scope.has_value() || domainOfValidity) { d->domains_.emplace_back(ObjectDomain::create(scope, domainOfValidity)); } { const auto pVal = properties.get(OBJECT_DOMAIN_KEY); if (pVal) { if (auto objectDomain = util::nn_dynamic_pointer_cast(*pVal)) { d->domains_.emplace_back(NN_NO_CHECK(objectDomain)); } else if (const auto array = dynamic_cast( pVal->get())) { for (const auto &val : *array) { objectDomain = util::nn_dynamic_pointer_cast(val); if (objectDomain) { d->domains_.emplace_back(NN_NO_CHECK(objectDomain)); } else { throw InvalidValueTypeException( "Invalid value type for " + OBJECT_DOMAIN_KEY); } } } else { throw InvalidValueTypeException("Invalid value type for " + OBJECT_DOMAIN_KEY); } } } } // --------------------------------------------------------------------------- void ObjectUsage::baseExportToWKT(WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; if (isWKT2 && formatter->outputUsage()) { auto l_domains = domains(); if (!l_domains.empty()) { if (formatter->use2019Keywords()) { for (const auto &domain : l_domains) { formatter->startNode(WKTConstants::USAGE, false); domain->_exportToWKT(formatter); formatter->endNode(); } } else { l_domains[0]->_exportToWKT(formatter); } } } if (formatter->outputId()) { formatID(formatter); } if (isWKT2 && formatter->outputUsage()) { formatRemarks(formatter); } } // --------------------------------------------------------------------------- void ObjectUsage::baseExportToJSON(JSONFormatter *formatter) const { auto writer = formatter->writer(); if (formatter->outputUsage()) { const auto &l_domains = domains(); if (l_domains.size() == 1) { l_domains[0]->_exportToJSON(formatter); } else if (!l_domains.empty()) { writer->AddObjKey("usages"); auto arrayContext(writer->MakeArrayContext(false)); for (const auto &domain : l_domains) { auto objContext(writer->MakeObjectContext()); domain->_exportToJSON(formatter); } } } if (formatter->outputId()) { formatID(formatter); } formatRemarks(formatter); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool ObjectUsage::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherObjUsage = dynamic_cast(other); if (!otherObjUsage) return false; // TODO: incomplete return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DataEpoch::Private { Measure coordinateEpoch_{}; explicit Private(const Measure &coordinateEpochIn) : coordinateEpoch_(coordinateEpochIn) {} }; //! @endcond // --------------------------------------------------------------------------- DataEpoch::DataEpoch() : d(std::make_unique(Measure())) {} // --------------------------------------------------------------------------- DataEpoch::DataEpoch(const Measure &coordinateEpochIn) : d(std::make_unique(coordinateEpochIn)) {} // --------------------------------------------------------------------------- DataEpoch::DataEpoch(const DataEpoch &other) : d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DataEpoch::~DataEpoch() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the coordinate epoch, as a measure in decimal year. */ const Measure &DataEpoch::coordinateEpoch() const { return d->coordinateEpoch_; } } // namespace common NS_PROJ_END proj-9.8.1/src/iso19111/datum.cpp000664 001750 001750 00000317335 15166171715 016263 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/datum.hpp" #include "proj/common.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/datum_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // clang-format on #include "proj_json_streaming_writer.hpp" #include #include #include #include using namespace NS_PROJ::internal; #if 0 namespace dropbox{ namespace oxygen { template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; }} #endif NS_PROJ_START namespace datum { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { return util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, code); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Datum::Private { util::optional anchorDefinition{}; std::shared_ptr> anchorEpoch = std::make_shared>(); util::optional publicationDate{}; common::IdentifiedObjectPtr conventionalRS{}; // cppcheck-suppress functionStatic void exportAnchorDefinition(io::WKTFormatter *formatter) const; // cppcheck-suppress functionStatic void exportAnchorEpoch(io::WKTFormatter *formatter) const; // cppcheck-suppress functionStatic void exportAnchorDefinition(io::JSONFormatter *formatter) const; // cppcheck-suppress functionStatic void exportAnchorEpoch(io::JSONFormatter *formatter) const; }; // --------------------------------------------------------------------------- void Datum::Private::exportAnchorDefinition(io::WKTFormatter *formatter) const { if (anchorDefinition) { formatter->startNode(io::WKTConstants::ANCHOR, false); formatter->addQuotedString(*anchorDefinition); formatter->endNode(); } } // --------------------------------------------------------------------------- void Datum::Private::exportAnchorEpoch(io::WKTFormatter *formatter) const { if (anchorEpoch->has_value()) { formatter->startNode(io::WKTConstants::ANCHOREPOCH, false); const double year = (*anchorEpoch)->convertToUnit(common::UnitOfMeasure::YEAR); formatter->add(getRoundedEpochInDecimalYear(year)); formatter->endNode(); } } // --------------------------------------------------------------------------- void Datum::Private::exportAnchorDefinition( io::JSONFormatter *formatter) const { if (anchorDefinition) { auto writer = formatter->writer(); writer->AddObjKey("anchor"); writer->Add(*anchorDefinition); } } // --------------------------------------------------------------------------- void Datum::Private::exportAnchorEpoch(io::JSONFormatter *formatter) const { if (anchorEpoch->has_value()) { auto writer = formatter->writer(); writer->AddObjKey("anchor_epoch"); const double year = (*anchorEpoch)->convertToUnit(common::UnitOfMeasure::YEAR); writer->Add(getRoundedEpochInDecimalYear(year)); } } //! @endcond // --------------------------------------------------------------------------- Datum::Datum() : d(std::make_unique()) {} // --------------------------------------------------------------------------- #ifdef notdef Datum::Datum(const Datum &other) : ObjectUsage(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Datum::~Datum() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the anchor definition. * * A description - possibly including coordinates of an identified point or * points - of the relationship used to anchor a coordinate system to the * Earth or alternate object. *
    *
  • For modern geodetic reference frames the anchor may be a set of station * coordinates; if the reference frame is dynamic it will also include * coordinate velocities. For a traditional geodetic datum, this anchor may be * a point known as the fundamental point, which is traditionally the point * where the relationship between geoid and ellipsoid is defined, together * with a direction from that point.
  • *
  • For a vertical reference frame the anchor may be the zero level at one * or more defined locations or a conventionally defined surface.
  • *
  • For an engineering datum, the anchor may be an identified physical point * with the orientation defined relative to the object.
  • *
* * @return the anchor definition, or empty. */ const util::optional &Datum::anchorDefinition() const { return d->anchorDefinition; } // --------------------------------------------------------------------------- /** \brief Return the anchor epoch. * * Epoch at which a static reference frame matches a dynamic reference frame * from which it has been derived. * * Note: Not to be confused with the frame reference epoch of dynamic geodetic * and dynamic vertical reference frames. Nor with the epoch at which a * reference frame is defined to be aligned with another reference frame; * this information should be included in the datum anchor definition. * * @return the anchor epoch, or empty. * @since 9.2 */ const util::optional &Datum::anchorEpoch() const { return *(d->anchorEpoch); } // --------------------------------------------------------------------------- /** \brief Return the date on which the datum definition was published. * * \note Departure from \ref ISO_19111_2019 : we return a DateTime instead of * a Citation::Date. * * @return the publication date, or empty. */ const util::optional &Datum::publicationDate() const { return d->publicationDate; } // --------------------------------------------------------------------------- /** \brief Return the conventional reference system. * * This is the name, identifier, alias and remarks for the terrestrial * reference system or vertical reference system realized by this reference * frame, for example "ITRS" for ITRF88 through ITRF2008 and ITRF2014, or * "EVRS" for EVRF2000 and EVRF2007. * * @return the conventional reference system, or nullptr. */ const common::IdentifiedObjectPtr &Datum::conventionalRS() const { return d->conventionalRS; } // --------------------------------------------------------------------------- void Datum::setAnchor(const util::optional &anchor) { d->anchorDefinition = anchor; } // --------------------------------------------------------------------------- void Datum::setAnchorEpoch(const util::optional &anchorEpoch) { d->anchorEpoch = std::make_shared>(anchorEpoch); } // --------------------------------------------------------------------------- void Datum::setProperties( const util::PropertyMap &properties) // throw(InvalidValueTypeException) { std::string publicationDateResult; properties.getStringValue("PUBLICATION_DATE", publicationDateResult); if (!publicationDateResult.empty()) { d->publicationDate = common::DateTime::create(publicationDateResult); } std::string anchorEpoch; properties.getStringValue("ANCHOR_EPOCH", anchorEpoch); if (!anchorEpoch.empty()) { bool success = false; const double anchorEpochYear = c_locale_stod(anchorEpoch, success); if (success) { setAnchorEpoch(util::optional( common::Measure(anchorEpochYear, common::UnitOfMeasure::YEAR))); } } ObjectUsage::setProperties(properties); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool Datum::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDatum = dynamic_cast(other); if (otherDatum == nullptr || !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { if ((anchorDefinition().has_value() ^ otherDatum->anchorDefinition().has_value())) { return false; } if (anchorDefinition().has_value() && otherDatum->anchorDefinition().has_value() && *anchorDefinition() != *otherDatum->anchorDefinition()) { return false; } if ((publicationDate().has_value() ^ otherDatum->publicationDate().has_value())) { return false; } if (publicationDate().has_value() && otherDatum->publicationDate().has_value() && publicationDate()->toString() != otherDatum->publicationDate()->toString()) { return false; } if (((conventionalRS() != nullptr) ^ (otherDatum->conventionalRS() != nullptr))) { return false; } if (conventionalRS() && otherDatum->conventionalRS() && conventionalRS()->_isEquivalentTo( otherDatum->conventionalRS().get(), criterion, dbContext)) { return false; } } return true; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PrimeMeridian::Private { common::Angle longitude_{}; explicit Private(const common::Angle &longitude) : longitude_(longitude) {} }; //! @endcond // --------------------------------------------------------------------------- PrimeMeridian::PrimeMeridian(const common::Angle &longitudeIn) : d(std::make_unique(longitudeIn)) {} // --------------------------------------------------------------------------- #ifdef notdef PrimeMeridian::PrimeMeridian(const PrimeMeridian &other) : common::IdentifiedObject(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress PrimeMeridian::~PrimeMeridian() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the longitude of the prime meridian. * * It is measured from the internationally-recognised reference meridian * ('Greenwich meridian'), positive eastward. * The default value is 0 degrees. * * @return the longitude of the prime meridian. */ const common::Angle &PrimeMeridian::longitude() PROJ_PURE_DEFN { return d->longitude_; } // --------------------------------------------------------------------------- /** \brief Instantiate a PrimeMeridian. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param longitudeIn the longitude of the prime meridian. * @return new PrimeMeridian. */ PrimeMeridianNNPtr PrimeMeridian::create(const util::PropertyMap &properties, const common::Angle &longitudeIn) { auto pm(PrimeMeridian::nn_make_shared(longitudeIn)); pm->setProperties(properties); return pm; } // --------------------------------------------------------------------------- const PrimeMeridianNNPtr PrimeMeridian::createGREENWICH() { return create(createMapNameEPSGCode("Greenwich", 8901), common::Angle(0)); } // --------------------------------------------------------------------------- const PrimeMeridianNNPtr PrimeMeridian::createREFERENCE_MERIDIAN() { return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, "Reference meridian"), common::Angle(0)); } // --------------------------------------------------------------------------- const PrimeMeridianNNPtr PrimeMeridian::createPARIS() { return create(createMapNameEPSGCode("Paris", 8903), common::Angle(2.5969213, common::UnitOfMeasure::GRAD)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void PrimeMeridian::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; std::string l_name(name()->description().has_value() ? nameStr() : "Greenwich"); if (!(isWKT2 && formatter->primeMeridianOmittedIfGreenwich() && l_name == "Greenwich")) { formatter->startNode(io::WKTConstants::PRIMEM, !identifiers().empty()); if (formatter->useESRIDialect()) { bool aliasFound = false; const auto &dbContext = formatter->databaseContext(); if (dbContext) { auto l_alias = dbContext->getAliasFromOfficialName( l_name, "prime_meridian", "ESRI"); if (!l_alias.empty()) { l_name = std::move(l_alias); aliasFound = true; } } if (!aliasFound && dbContext) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "ESRI"); aliasFound = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType::PRIME_MERIDIAN}, false // approximateMatch ) .size() == 1; } if (!aliasFound) { l_name = io::WKTFormatter::morphNameToESRI(l_name); } } formatter->addQuotedString(l_name); const auto &l_long = longitude(); if (formatter->primeMeridianInDegree()) { formatter->add(l_long.convertToUnit(common::UnitOfMeasure::DEGREE)); } else { formatter->add(l_long.value()); } const auto &unit = l_long.unit(); if (isWKT2) { if (!(formatter ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && unit == *(formatter->axisAngularUnit()))) { unit._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT); } } else if (!formatter->primeMeridianInDegree()) { unit._exportToWKT(formatter); } if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void PrimeMeridian::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("PrimeMeridian", !identifiers().empty())); writer->AddObjKey("name"); std::string l_name = name()->description().has_value() ? nameStr() : "Greenwich"; writer->Add(l_name); const auto &l_long = longitude(); writer->AddObjKey("longitude"); const auto &unit = l_long.unit(); if (unit == common::UnitOfMeasure::DEGREE) { writer->Add(l_long.value(), 15); } else { auto longitudeContext(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("value"); writer->Add(l_long.value(), 15); writer->AddObjKey("unit"); unit._exportToJSON(formatter); } if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::string PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) { const double valRad = angle.getSIValue(); std::string projPMName; PJ_CONTEXT *ctxt = proj_context_create(); auto proj_pm = proj_list_prime_meridians(); for (int i = 0; proj_pm[i].id != nullptr; ++i) { double valRefRad = dmstor_ctx(ctxt, proj_pm[i].defn, nullptr); if (::fabs(valRad - valRefRad) < 1e-10) { projPMName = proj_pm[i].id; break; } } proj_context_destroy(ctxt); return projPMName; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void PrimeMeridian::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(FormattingException) { if (longitude().getSIValue() != 0) { std::string projPMName(getPROJStringWellKnownName(longitude())); if (!projPMName.empty()) { formatter->addParam("pm", projPMName); } else { const double valDeg = longitude().convertToUnit(common::UnitOfMeasure::DEGREE); formatter->addParam("pm", valDeg); } } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool PrimeMeridian::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherPM = dynamic_cast(other); if (otherPM == nullptr || !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } // In MapInfo, the Paris prime meridian is returned as 2.3372291666667 // instead of the official value of 2.33722917, which is a relative // error in the 1e-9 range. return longitude()._isEquivalentTo(otherPM->longitude(), criterion, 1e-8); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Ellipsoid::Private { common::Length semiMajorAxis_{}; util::optional inverseFlattening_{}; util::optional semiMinorAxis_{}; util::optional semiMedianAxis_{}; std::string celestialBody_{}; explicit Private(const common::Length &radius, const std::string &celestialBody) : semiMajorAxis_(radius), celestialBody_(celestialBody) {} Private(const common::Length &semiMajorAxisIn, const common::Scale &invFlattening, const std::string &celestialBody) : semiMajorAxis_(semiMajorAxisIn), inverseFlattening_(invFlattening), celestialBody_(celestialBody) {} Private(const common::Length &semiMajorAxisIn, const common::Length &semiMinorAxisIn, const std::string &celestialBody) : semiMajorAxis_(semiMajorAxisIn), semiMinorAxis_(semiMinorAxisIn), celestialBody_(celestialBody) {} }; //! @endcond // --------------------------------------------------------------------------- Ellipsoid::Ellipsoid(const common::Length &radius, const std::string &celestialBodyIn) : d(std::make_unique(radius, celestialBodyIn)) {} // --------------------------------------------------------------------------- Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn, const common::Scale &invFlattening, const std::string &celestialBodyIn) : d(std::make_unique(semiMajorAxisIn, invFlattening, celestialBodyIn)) {} // --------------------------------------------------------------------------- Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn, const common::Length &semiMinorAxisIn, const std::string &celestialBodyIn) : d(std::make_unique(semiMajorAxisIn, semiMinorAxisIn, celestialBodyIn)) {} // --------------------------------------------------------------------------- #ifdef notdef Ellipsoid::Ellipsoid(const Ellipsoid &other) : common::IdentifiedObject(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Ellipsoid::~Ellipsoid() = default; Ellipsoid::Ellipsoid(const Ellipsoid &other) : IdentifiedObject(other), d(std::make_unique(*(other.d))) {} //! @endcond // --------------------------------------------------------------------------- /** \brief Return the length of the semi-major axis of the ellipsoid. * * @return the semi-major axis. */ const common::Length &Ellipsoid::semiMajorAxis() PROJ_PURE_DEFN { return d->semiMajorAxis_; } // --------------------------------------------------------------------------- /** \brief Return the inverse flattening value of the ellipsoid, if the * ellipsoid * has been defined with this value. * * @see computeInverseFlattening() that will always return a valid value of the * inverse flattening, whether the ellipsoid has been defined through inverse * flattening or semi-minor axis. * * @return the inverse flattening value of the ellipsoid, or empty. */ const util::optional & Ellipsoid::inverseFlattening() PROJ_PURE_DEFN { return d->inverseFlattening_; } // --------------------------------------------------------------------------- /** \brief Return the length of the semi-minor axis of the ellipsoid, if the * ellipsoid * has been defined with this value. * * @see computeSemiMinorAxis() that will always return a valid value of the * semi-minor axis, whether the ellipsoid has been defined through inverse * flattening or semi-minor axis. * * @return the semi-minor axis of the ellipsoid, or empty. */ const util::optional & Ellipsoid::semiMinorAxis() PROJ_PURE_DEFN { return d->semiMinorAxis_; } // --------------------------------------------------------------------------- /** \brief Return whether the ellipsoid is spherical. * * That is to say is semiMajorAxis() == computeSemiMinorAxis(). * * A sphere is completely defined by the semi-major axis, which is the radius * of the sphere. * * @return true if the ellipsoid is spherical. */ bool Ellipsoid::isSphere() PROJ_PURE_DEFN { if (d->inverseFlattening_.has_value()) { return d->inverseFlattening_->value() == 0; } if (semiMinorAxis().has_value()) { return semiMajorAxis() == *semiMinorAxis(); } return true; } // --------------------------------------------------------------------------- /** \brief Return the length of the semi-median axis of a triaxial ellipsoid * * This parameter is not required for a biaxial ellipsoid. * * @return the semi-median axis of the ellipsoid, or empty. */ const util::optional & Ellipsoid::semiMedianAxis() PROJ_PURE_DEFN { return d->semiMedianAxis_; } // --------------------------------------------------------------------------- /** \brief Return or compute the inverse flattening value of the ellipsoid. * * If computed, the inverse flattening is the result of a / (a - b), * where a is the semi-major axis and b the semi-minor axis. * * @return the inverse flattening value of the ellipsoid, or 0 for a sphere. */ double Ellipsoid::computedInverseFlattening() PROJ_PURE_DEFN { if (d->inverseFlattening_.has_value()) { return d->inverseFlattening_->getSIValue(); } if (d->semiMinorAxis_.has_value()) { const double a = d->semiMajorAxis_.getSIValue(); const double b = d->semiMinorAxis_->getSIValue(); return (a == b) ? 0.0 : a / (a - b); } return 0.0; } // --------------------------------------------------------------------------- /** \brief Return the squared eccentricity of the ellipsoid. * * @return the squared eccentricity, or a negative value if invalid. */ double Ellipsoid::squaredEccentricity() PROJ_PURE_DEFN { const double rf = computedInverseFlattening(); // coverity[divide_by_zero] const double f = rf != 0.0 ? 1. / rf : 0.0; const double e2 = f * (2 - f); return e2; } // --------------------------------------------------------------------------- /** \brief Return or compute the length of the semi-minor axis of the ellipsoid. * * If computed, the semi-minor axis is the result of a * (1 - 1 / rf) * where a is the semi-major axis and rf the reverse/inverse flattening. * @return the semi-minor axis of the ellipsoid. */ common::Length Ellipsoid::computeSemiMinorAxis() const { if (d->semiMinorAxis_.has_value()) { return *d->semiMinorAxis_; } if (inverseFlattening().has_value()) { return common::Length( (1.0 - 1.0 / d->inverseFlattening_->getSIValue()) * d->semiMajorAxis_.value(), d->semiMajorAxis_.unit()); } return d->semiMajorAxis_; } // --------------------------------------------------------------------------- /** \brief Return the name of the celestial body on which the ellipsoid refers * to. */ const std::string &Ellipsoid::celestialBody() PROJ_PURE_DEFN { return d->celestialBody_; } // --------------------------------------------------------------------------- /** \brief Instantiate a Ellipsoid as a sphere. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param radius the sphere radius (semi-major axis). * @param celestialBody Name of the celestial body on which the ellipsoid refers * to. * @return new Ellipsoid. */ EllipsoidNNPtr Ellipsoid::createSphere(const util::PropertyMap &properties, const common::Length &radius, const std::string &celestialBody) { auto ellipsoid(Ellipsoid::nn_make_shared(radius, celestialBody)); ellipsoid->setProperties(properties); return ellipsoid; } // --------------------------------------------------------------------------- /** \brief Instantiate a Ellipsoid from its inverse/reverse flattening. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param semiMajorAxisIn the semi-major axis. * @param invFlattening the inverse/reverse flattening. If set to 0, this will * be considered as a sphere. * @param celestialBody Name of the celestial body on which the ellipsoid refers * to. * @return new Ellipsoid. */ EllipsoidNNPtr Ellipsoid::createFlattenedSphere( const util::PropertyMap &properties, const common::Length &semiMajorAxisIn, const common::Scale &invFlattening, const std::string &celestialBody) { if (invFlattening.value() == 0) { auto ellipsoid(Ellipsoid::nn_make_shared(semiMajorAxisIn, celestialBody)); ellipsoid->setProperties(properties); return ellipsoid; } else { auto ellipsoid(Ellipsoid::nn_make_shared( semiMajorAxisIn, invFlattening, celestialBody)); ellipsoid->setProperties(properties); return ellipsoid; } } // --------------------------------------------------------------------------- /** \brief Instantiate a Ellipsoid from the value of its two semi axis. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param semiMajorAxisIn the semi-major axis. * @param semiMinorAxisIn the semi-minor axis. * @param celestialBody Name of the celestial body on which the ellipsoid refers * to. * @return new Ellipsoid. */ EllipsoidNNPtr Ellipsoid::createTwoAxis(const util::PropertyMap &properties, const common::Length &semiMajorAxisIn, const common::Length &semiMinorAxisIn, const std::string &celestialBody) { auto ellipsoid(Ellipsoid::nn_make_shared( semiMajorAxisIn, semiMinorAxisIn, celestialBody)); ellipsoid->setProperties(properties); return ellipsoid; } // --------------------------------------------------------------------------- const EllipsoidNNPtr Ellipsoid::createCLARKE_1866() { return createTwoAxis(createMapNameEPSGCode("Clarke 1866", 7008), common::Length(6378206.4), common::Length(6356583.8)); } // --------------------------------------------------------------------------- const EllipsoidNNPtr Ellipsoid::createWGS84() { return createFlattenedSphere(createMapNameEPSGCode("WGS 84", 7030), common::Length(6378137), common::Scale(298.257223563)); } // --------------------------------------------------------------------------- const EllipsoidNNPtr Ellipsoid::createGRS1980() { return createFlattenedSphere(createMapNameEPSGCode("GRS 1980", 7019), common::Length(6378137), common::Scale(298.257222101)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Ellipsoid::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::ELLIPSOID : io::WKTConstants::SPHEROID, !identifiers().empty()); { std::string l_name(nameStr()); if (l_name.empty()) { formatter->addQuotedString("unnamed"); } else { if (formatter->useESRIDialect()) { if (l_name == "WGS 84") { l_name = "WGS_1984"; } else { bool aliasFound = false; const auto &dbContext = formatter->databaseContext(); if (dbContext) { auto l_alias = dbContext->getAliasFromOfficialName( l_name, "ellipsoid", "ESRI"); if (!l_alias.empty()) { l_name = std::move(l_alias); aliasFound = true; } } if (!aliasFound && dbContext) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "ESRI"); aliasFound = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType:: ELLIPSOID}, false // approximateMatch ) .size() == 1; } if (!aliasFound) { l_name = io::WKTFormatter::morphNameToESRI(l_name); } } } formatter->addQuotedString(l_name); } const auto &semiMajor = semiMajorAxis(); if (isWKT2) { formatter->add(semiMajor.value()); } else { formatter->add(semiMajor.getSIValue()); } formatter->add(computedInverseFlattening()); const auto &unit = semiMajor.unit(); if (isWKT2 && !(formatter->ellipsoidUnitOmittedIfMetre() && unit == common::UnitOfMeasure::METRE)) { unit._exportToWKT(formatter, io::WKTConstants::LENGTHUNIT); } if (formatter->outputId()) { formatID(formatter); } } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Ellipsoid::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("Ellipsoid", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } const auto &semiMajor = semiMajorAxis(); const auto &semiMajorUnit = semiMajor.unit(); writer->AddObjKey(isSphere() ? "radius" : "semi_major_axis"); if (semiMajorUnit == common::UnitOfMeasure::METRE) { writer->Add(semiMajor.value(), 15); } else { auto objContext(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("value"); writer->Add(semiMajor.value(), 15); writer->AddObjKey("unit"); semiMajorUnit._exportToJSON(formatter); } if (!isSphere()) { const auto &l_inverseFlattening = inverseFlattening(); if (l_inverseFlattening.has_value()) { writer->AddObjKey("inverse_flattening"); writer->Add(l_inverseFlattening->getSIValue(), 15); } else { writer->AddObjKey("semi_minor_axis"); const auto &l_semiMinorAxis(semiMinorAxis()); const auto &semiMinorAxisUnit(l_semiMinorAxis->unit()); if (semiMinorAxisUnit == common::UnitOfMeasure::METRE) { writer->Add(l_semiMinorAxis->value(), 15); } else { auto objContext(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("value"); writer->Add(l_semiMinorAxis->value(), 15); writer->AddObjKey("unit"); semiMinorAxisUnit._exportToJSON(formatter); } } } if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- bool Ellipsoid::lookForProjWellKnownEllps(std::string &projEllpsName, std::string &ellpsName) const { const double a = semiMajorAxis().getSIValue(); const double b = computeSemiMinorAxis().getSIValue(); const double rf = computedInverseFlattening(); auto proj_ellps = proj_list_ellps(); for (int i = 0; proj_ellps[i].id != nullptr; i++) { assert(strncmp(proj_ellps[i].major, "a=", 2) == 0); const double a_iter = c_locale_stod(proj_ellps[i].major + 2); if (::fabs(a - a_iter) < 1e-10 * a_iter) { if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) { const double b_iter = c_locale_stod(proj_ellps[i].ell + 2); if (::fabs(b - b_iter) < 1e-10 * b_iter) { projEllpsName = proj_ellps[i].id; ellpsName = proj_ellps[i].name; if (starts_with(ellpsName, "GRS 1980")) { ellpsName = "GRS 1980"; } return true; } } else { assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0); const double rf_iter = c_locale_stod(proj_ellps[i].ell + 3); if (::fabs(rf - rf_iter) < 1e-10 * rf_iter) { projEllpsName = proj_ellps[i].id; ellpsName = proj_ellps[i].name; if (starts_with(ellpsName, "GRS 1980")) { ellpsName = "GRS 1980"; } return true; } } } } return false; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Ellipsoid::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(FormattingException) { const double a = semiMajorAxis().getSIValue(); std::string projEllpsName; std::string ellpsName; if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) { formatter->addParam("ellps", projEllpsName); return; } if (isSphere()) { formatter->addParam("R", a); } else { formatter->addParam("a", a); if (inverseFlattening().has_value()) { const double rf = computedInverseFlattening(); formatter->addParam("rf", rf); } else { const double b = computeSemiMinorAxis().getSIValue(); formatter->addParam("b", b); } } } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a Ellipsoid object where some parameters are better * identified. * * @return a new Ellipsoid. */ EllipsoidNNPtr Ellipsoid::identify() const { auto newEllipsoid = Ellipsoid::nn_make_shared(*this); newEllipsoid->assignSelf( util::nn_static_pointer_cast(newEllipsoid)); if (name()->description()->empty() || nameStr() == "unknown") { std::string projEllpsName; std::string ellpsName; if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) { newEllipsoid->setProperties( util::PropertyMap().set(IdentifiedObject::NAME_KEY, ellpsName)); } } return newEllipsoid; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool Ellipsoid::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherEllipsoid = dynamic_cast(other); if (otherEllipsoid == nullptr || (criterion == util::IComparable::Criterion::STRICT && !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext))) { return false; } // PROJ "clrk80" name is "Clarke 1880 mod." and GDAL tends to // export to it a number of Clarke 1880 variants, so be lax if (criterion != util::IComparable::Criterion::STRICT && (nameStr() == "Clarke 1880 mod." || otherEllipsoid->nameStr() == "Clarke 1880 mod.")) { return std::fabs(semiMajorAxis().getSIValue() - otherEllipsoid->semiMajorAxis().getSIValue()) < 1e-8 * semiMajorAxis().getSIValue() && std::fabs(computedInverseFlattening() - otherEllipsoid->computedInverseFlattening()) < 1e-5 * computedInverseFlattening(); } if (!semiMajorAxis()._isEquivalentTo(otherEllipsoid->semiMajorAxis(), criterion)) { return false; } const auto &l_semiMinorAxis = semiMinorAxis(); const auto &l_other_semiMinorAxis = otherEllipsoid->semiMinorAxis(); if (l_semiMinorAxis.has_value() && l_other_semiMinorAxis.has_value()) { if (!l_semiMinorAxis->_isEquivalentTo(*l_other_semiMinorAxis, criterion)) { return false; } } const auto &l_inverseFlattening = inverseFlattening(); const auto &l_other_sinverseFlattening = otherEllipsoid->inverseFlattening(); if (l_inverseFlattening.has_value() && l_other_sinverseFlattening.has_value()) { if (!l_inverseFlattening->_isEquivalentTo(*l_other_sinverseFlattening, criterion)) { return false; } } if (criterion == util::IComparable::Criterion::STRICT) { if ((l_semiMinorAxis.has_value() ^ l_other_semiMinorAxis.has_value())) { return false; } if ((l_inverseFlattening.has_value() ^ l_other_sinverseFlattening.has_value())) { return false; } } else { if (!computeSemiMinorAxis()._isEquivalentTo( otherEllipsoid->computeSemiMinorAxis(), criterion)) { return false; } } const auto &l_semiMedianAxis = semiMedianAxis(); const auto &l_other_semiMedianAxis = otherEllipsoid->semiMedianAxis(); if ((l_semiMedianAxis.has_value() ^ l_other_semiMedianAxis.has_value())) { return false; } if (l_semiMedianAxis.has_value() && l_other_semiMedianAxis.has_value()) { if (!l_semiMedianAxis->_isEquivalentTo(*l_other_semiMedianAxis, criterion)) { return false; } } return true; } //! @endcond // --------------------------------------------------------------------------- std::string Ellipsoid::guessBodyName(const io::DatabaseContextPtr &dbContext, double a, const std::string &ellpsName) { constexpr double earthMeanRadius = 6375000.0; if (std::fabs(a - earthMeanRadius) < REL_ERROR_FOR_SAME_CELESTIAL_BODY * earthMeanRadius) { return Ellipsoid::EARTH; } if (dbContext) { try { auto factory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); if (!ellpsName.empty()) { auto matches = factory->createObjectsFromName( ellpsName, {io::AuthorityFactory::ObjectType::ELLIPSOID}, true, 1); if (!matches.empty()) { auto ellps = static_cast(matches.front().get()); if (std::fabs(a - ellps->semiMajorAxis().getSIValue()) < REL_ERROR_FOR_SAME_CELESTIAL_BODY * a) { return ellps->celestialBody(); } } } return factory->identifyBodyFromSemiMajorAxis( a, REL_ERROR_FOR_SAME_CELESTIAL_BODY); } catch (const std::exception &) { } } return NON_EARTH_BODY; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeodeticReferenceFrame::Private { PrimeMeridianNNPtr primeMeridian_; EllipsoidNNPtr ellipsoid_; Private(const EllipsoidNNPtr &ellipsoidIn, const PrimeMeridianNNPtr &primeMeridianIn) : primeMeridian_(primeMeridianIn), ellipsoid_(ellipsoidIn) {} }; //! @endcond // --------------------------------------------------------------------------- GeodeticReferenceFrame::GeodeticReferenceFrame( const EllipsoidNNPtr &ellipsoidIn, const PrimeMeridianNNPtr &primeMeridianIn) : d(std::make_unique(ellipsoidIn, primeMeridianIn)) {} // --------------------------------------------------------------------------- #ifdef notdef GeodeticReferenceFrame::GeodeticReferenceFrame( const GeodeticReferenceFrame &other) : Datum(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeodeticReferenceFrame::~GeodeticReferenceFrame() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the PrimeMeridian associated with a GeodeticReferenceFrame. * * @return the PrimeMeridian. */ const PrimeMeridianNNPtr & GeodeticReferenceFrame::primeMeridian() PROJ_PURE_DEFN { return d->primeMeridian_; } // --------------------------------------------------------------------------- /** \brief Return the Ellipsoid associated with a GeodeticReferenceFrame. * * \note The \ref ISO_19111_2019 modelling allows (but discourages) a * GeodeticReferenceFrame * to not be associated with a Ellipsoid in the case where it is used by a * geocentric crs::GeodeticCRS. We have made the choice of making the ellipsoid * specification compulsory. * * @return the Ellipsoid. */ const EllipsoidNNPtr &GeodeticReferenceFrame::ellipsoid() PROJ_PURE_DEFN { return d->ellipsoid_; } // --------------------------------------------------------------------------- /** \brief Instantiate a GeodeticReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param ellipsoid the Ellipsoid. * @param anchor the anchor definition, or empty. * @param primeMeridian the PrimeMeridian. * @return new GeodeticReferenceFrame. */ GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::create(const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, const util::optional &anchor, const PrimeMeridianNNPtr &primeMeridian) { GeodeticReferenceFrameNNPtr grf( GeodeticReferenceFrame::nn_make_shared( ellipsoid, primeMeridian)); grf->setAnchor(anchor); grf->setProperties(properties); return grf; } // --------------------------------------------------------------------------- /** \brief Instantiate a GeodeticReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param ellipsoid the Ellipsoid. * @param anchor the anchor definition, or empty. * @param anchorEpoch the anchor epoch, or empty. * @param primeMeridian the PrimeMeridian. * @return new GeodeticReferenceFrame. * @since 9.2 */ GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::create( const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, const util::optional &anchor, const util::optional &anchorEpoch, const PrimeMeridianNNPtr &primeMeridian) { GeodeticReferenceFrameNNPtr grf( GeodeticReferenceFrame::nn_make_shared( ellipsoid, primeMeridian)); grf->setAnchor(anchor); grf->setAnchorEpoch(anchorEpoch); grf->setProperties(properties); return grf; } // --------------------------------------------------------------------------- const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6267() { return create(createMapNameEPSGCode("North American Datum 1927", 6267), Ellipsoid::CLARKE_1866, util::optional(), PrimeMeridian::GREENWICH); } // --------------------------------------------------------------------------- const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6269() { return create(createMapNameEPSGCode("North American Datum 1983", 6269), Ellipsoid::GRS1980, util::optional(), PrimeMeridian::GREENWICH); } // --------------------------------------------------------------------------- const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6326() { return create(createMapNameEPSGCode("World Geodetic System 1984", 6326), Ellipsoid::WGS84, util::optional(), PrimeMeridian::GREENWICH); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticReferenceFrame::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const auto &ids = identifiers(); formatter->startNode(io::WKTConstants::DATUM, !ids.empty()); std::string l_name(nameStr()); if (l_name.empty()) { l_name = "unnamed"; } if (!isWKT2) { if (formatter->useESRIDialect()) { if (l_name == "World Geodetic System 1984") { l_name = "D_WGS_1984"; } else { bool aliasFound = false; const auto &dbContext = formatter->databaseContext(); if (dbContext) { auto l_alias = dbContext->getAliasFromOfficialName( l_name, "geodetic_datum", "ESRI"); size_t pos; if (!l_alias.empty()) { l_name = std::move(l_alias); aliasFound = true; } else if ((pos = l_name.find(" (")) != std::string::npos) { l_alias = dbContext->getAliasFromOfficialName( l_name.substr(0, pos), "geodetic_datum", "ESRI"); if (!l_alias.empty()) { l_name = std::move(l_alias); aliasFound = true; } } } if (!aliasFound && dbContext) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "ESRI"); aliasFound = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType:: GEODETIC_REFERENCE_FRAME}, false // approximateMatch ) .size() == 1; } if (!aliasFound && dbContext && !ids.empty()) { // Case for example for ETRS89-NOR [EUREF89] that has no // ESRI alias. Fallback to ETRS89 const auto EPSGOldAliases = dbContext->getAliases( *(ids[0]->codeSpace()), ids[0]->code(), std::string(), // officialName, "geodetic_datum", "EPSG_OLD"); if (EPSGOldAliases.size() == 1) { std::string EPSGName = EPSGOldAliases.front(); if (EPSGName == "European Terrestrial Reference System 1989") { EPSGName += " ensemble"; } auto authFactoryEPSG = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "EPSG"); auto objCandidates = authFactoryEPSG->createObjectsFromNameEx( EPSGName, {io::AuthorityFactory::ObjectType:: GEODETIC_REFERENCE_FRAME}, false, // approximateMatch 0, // limitResultCount false // useAliases ); for (const auto &[obj, name] : objCandidates) { (void)name; const auto &objIdentifiers = obj->identifiers(); if (!objIdentifiers.empty()) { const auto ESRIAliases = dbContext->getAliases( *(objIdentifiers[0]->codeSpace()), objIdentifiers[0]->code(), std::string(), // officialName, "geodetic_datum", "ESRI"); if (ESRIAliases.size() == 1) { l_name = ESRIAliases.front(); aliasFound = true; break; } } } } } if (!aliasFound) { l_name = io::WKTFormatter::morphNameToESRI(l_name); if (!starts_with(l_name, "D_")) { l_name = "D_" + l_name; } } } } else { // Replace spaces by underscore for datum names coming from EPSG // so as to emulate GDAL < 3 importFromEPSG() if (ids.size() == 1 && *(ids.front()->codeSpace()) == "EPSG") { l_name = io::WKTFormatter::morphNameToESRI(l_name); } else if (ids.empty()) { const auto &dbContext = formatter->databaseContext(); if (dbContext) { auto factory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string()); // We use anonymous authority and approximate matching, so // as to trigger the caching done in createObjectsFromName() // in that case. auto matches = factory->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType:: GEODETIC_REFERENCE_FRAME}, true, 2); if (matches.size() == 1) { const auto &match = matches.front(); const auto &matchId = match->identifiers(); if (matchId.size() == 1 && *(matchId.front()->codeSpace()) == "EPSG" && metadata::Identifier::isEquivalentName( l_name.c_str(), match->nameStr().c_str())) { l_name = io::WKTFormatter::morphNameToESRI(l_name); } } } } if (l_name == "World_Geodetic_System_1984") { l_name = "WGS_1984"; } } } formatter->addQuotedString(l_name); ellipsoid()->_exportToWKT(formatter); if (isWKT2) { Datum::getPrivate()->exportAnchorDefinition(formatter); if (formatter->use2019Keywords()) { Datum::getPrivate()->exportAnchorEpoch(formatter); } } else { const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); if (TOWGS84Params.size() == 7) { formatter->startNode(io::WKTConstants::TOWGS84, false); for (const auto &val : TOWGS84Params) { formatter->add(val, 12); } formatter->endNode(); } std::string extension = formatter->getHDatumExtension(); if (!extension.empty()) { formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4_GRIDS"); formatter->addQuotedString(extension); formatter->endNode(); } } if (formatter->outputId()) { formatID(formatter); } // the PRIMEM is exported as a child of the CRS formatter->endNode(); if (formatter->isAtTopLevel()) { const auto &l_primeMeridian(primeMeridian()); if (l_primeMeridian->nameStr() != "Greenwich") { l_primeMeridian->_exportToWKT(formatter); } } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticReferenceFrame::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto dynamicGRF = dynamic_cast(this); auto objectContext(formatter->MakeObjectContext( dynamicGRF ? "DynamicGeodeticReferenceFrame" : "GeodeticReferenceFrame", !identifiers().empty())); auto writer = formatter->writer(); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } Datum::getPrivate()->exportAnchorDefinition(formatter); Datum::getPrivate()->exportAnchorEpoch(formatter); if (dynamicGRF) { writer->AddObjKey("frame_reference_epoch"); writer->Add(dynamicGRF->frameReferenceEpoch().value()); } writer->AddObjKey("ellipsoid"); formatter->setOmitTypeInImmediateChild(); ellipsoid()->_exportToJSON(formatter); const auto &l_primeMeridian(primeMeridian()); if (l_primeMeridian->nameStr() != "Greenwich") { writer->AddObjKey("prime_meridian"); formatter->setOmitTypeInImmediateChild(); primeMeridian()->_exportToJSON(formatter); } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool GeodeticReferenceFrame::isEquivalentToNoExactTypeCheck( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherGRF = dynamic_cast(other); if (otherGRF == nullptr || !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return primeMeridian()->_isEquivalentTo(otherGRF->primeMeridian().get(), criterion, dbContext) && ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion, dbContext); } // --------------------------------------------------------------------------- bool GeodeticReferenceFrame::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (criterion == Criterion::STRICT && !util::isOfExactType(*other)) { return false; } return isEquivalentToNoExactTypeCheck(other, criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- bool GeodeticReferenceFrame::hasEquivalentNameToUsingAlias( const IdentifiedObject *other, const io::DatabaseContextPtr &dbContext) const { const auto compareFromThisId = [&dbContext](const GeodeticReferenceFrame &self, const std::string &thisName, const std::string &otherName) { const auto &id = self.identifiers().front(); const std::string officialNameFromId = dbContext->getName( "geodetic_datum", *(id->codeSpace()), id->code()); const auto aliasesResult = dbContext->getAliases(*(id->codeSpace()), id->code(), thisName, "geodetic_datum", std::string()); const auto isNameMatching = [&aliasesResult, &officialNameFromId](const std::string &name) { const char *nameCstr = name.c_str(); if (metadata::Identifier::isEquivalentName( nameCstr, officialNameFromId.c_str())) { return true; } else { for (const auto &aliasResult : aliasesResult) { if (metadata::Identifier::isEquivalentName( nameCstr, aliasResult.c_str())) { return true; } } } return false; }; return isNameMatching(thisName) && isNameMatching(otherName); }; const auto compareFromThisName = [&dbContext]( const std::string &thisName, const std::string &otherName) { auto aliasesResult = dbContext->getAliases(std::string(), std::string(), thisName, "geodetic_datum", std::string()); const char *otherNamePtr = otherName.c_str(); for (const auto &aliasResult : aliasesResult) { if (metadata::Identifier::isEquivalentName(otherNamePtr, aliasResult.c_str())) { return true; } } return false; }; const auto compare = [this, other, &dbContext, &compareFromThisId, &compareFromThisName](const std::string &thisName, const std::string &otherName) { if (thisName == otherName || thisName == "unknown" || otherName == "unknown") { return true; } if (ci_starts_with(thisName, UNKNOWN_BASED_ON) || ci_starts_with(otherName, UNKNOWN_BASED_ON)) { // Note: they cannot be equal based on initial test. return false; } if (dbContext) { if (!identifiers().empty()) { if (compareFromThisId(*this, thisName, otherName)) { return true; } } if (!other->identifiers().empty()) { auto otherGRF = dynamic_cast(other); if (otherGRF) { if (compareFromThisId(*otherGRF, otherName, thisName)) { return true; } } } if (compareFromThisName(thisName, otherName) || compareFromThisName(otherName, thisName)) { return true; } } return false; }; // Try to work around issues with Esri style "D_" name prefixing // Cf https://github.com/OSGeo/PROJ/issues/4514 const bool thisStartsWithDUnderscore = ci_starts_with(nameStr(), "D_"); const bool otherStartsWithDUnderscore = ci_starts_with(other->nameStr(), "D_"); if (thisStartsWithDUnderscore && !otherStartsWithDUnderscore) { const std::string thisNameMod = nameStr().substr(2); return metadata::Identifier::isEquivalentName( thisNameMod.c_str(), other->nameStr().c_str()) || compare(thisNameMod, other->nameStr()); } else if (!thisStartsWithDUnderscore && otherStartsWithDUnderscore) { const std::string otherNameMod = other->nameStr().substr(2); return metadata::Identifier::isEquivalentName(nameStr().c_str(), otherNameMod.c_str()) || compare(nameStr(), otherNameMod); } else { return compare(nameStr(), other->nameStr()); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DynamicGeodeticReferenceFrame::Private { common::Measure frameReferenceEpoch{}; util::optional deformationModelName{}; explicit Private(const common::Measure &frameReferenceEpochIn) : frameReferenceEpoch(frameReferenceEpochIn) {} }; //! @endcond // --------------------------------------------------------------------------- DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame( const EllipsoidNNPtr &ellipsoidIn, const PrimeMeridianNNPtr &primeMeridianIn, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn) : GeodeticReferenceFrame(ellipsoidIn, primeMeridianIn), d(std::make_unique(frameReferenceEpochIn)) { d->deformationModelName = deformationModelNameIn; } // --------------------------------------------------------------------------- #ifdef notdef DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame( const DynamicGeodeticReferenceFrame &other) : GeodeticReferenceFrame(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DynamicGeodeticReferenceFrame::~DynamicGeodeticReferenceFrame() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the epoch to which the coordinates of stations defining the * dynamic geodetic reference frame are referenced. * * Usually given as a decimal year e.g. 2016.47. * * @return the frame reference epoch. */ const common::Measure & DynamicGeodeticReferenceFrame::frameReferenceEpoch() const { return d->frameReferenceEpoch; } // --------------------------------------------------------------------------- /** \brief Return the name of the deformation model. * * @note This is an extension to the \ref ISO_19111_2019 modeling, to * hold the content of the DYNAMIC.MODEL WKT2 node. * * @return the name of the deformation model. */ const util::optional & DynamicGeodeticReferenceFrame::deformationModelName() const { return d->deformationModelName; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool DynamicGeodeticReferenceFrame::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (criterion == Criterion::STRICT && !util::isOfExactType(*other)) { return false; } if (!GeodeticReferenceFrame::isEquivalentToNoExactTypeCheck( other, criterion, dbContext)) { return false; } auto otherDGRF = dynamic_cast(other); if (otherDGRF == nullptr) { // we can go here only if criterion != Criterion::STRICT, and thus // given the above check we can consider the objects equivalent. return true; } return frameReferenceEpoch()._isEquivalentTo( otherDGRF->frameReferenceEpoch(), criterion) && metadata::Identifier::isEquivalentName( deformationModelName()->c_str(), otherDGRF->deformationModelName()->c_str()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DynamicGeodeticReferenceFrame::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (isWKT2 && formatter->use2019Keywords()) { formatter->startNode(io::WKTConstants::DYNAMIC, false); formatter->startNode(io::WKTConstants::FRAMEEPOCH, false); formatter->add( frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR)); formatter->endNode(); if (deformationModelName().has_value() && !deformationModelName()->empty()) { formatter->startNode(io::WKTConstants::MODEL, false); formatter->addQuotedString(*deformationModelName()); formatter->endNode(); } formatter->endNode(); } GeodeticReferenceFrame::_exportToWKT(formatter); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a DynamicGeodeticReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param ellipsoid the Ellipsoid. * @param anchor the anchor definition, or empty. * @param primeMeridian the PrimeMeridian. * @param frameReferenceEpochIn the frame reference epoch. * @param deformationModelNameIn deformation model name, or empty * @return new DynamicGeodeticReferenceFrame. */ DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create( const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, const util::optional &anchor, const PrimeMeridianNNPtr &primeMeridian, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn) { DynamicGeodeticReferenceFrameNNPtr grf( DynamicGeodeticReferenceFrame::nn_make_shared< DynamicGeodeticReferenceFrame>(ellipsoid, primeMeridian, frameReferenceEpochIn, deformationModelNameIn)); grf->setAnchor(anchor); grf->setProperties(properties); return grf; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DatumEnsemble::Private { std::vector datums{}; metadata::PositionalAccuracyNNPtr positionalAccuracy; Private(const std::vector &datumsIn, const metadata::PositionalAccuracyNNPtr &accuracy) : datums(datumsIn), positionalAccuracy(accuracy) {} }; //! @endcond // --------------------------------------------------------------------------- DatumEnsemble::DatumEnsemble(const std::vector &datumsIn, const metadata::PositionalAccuracyNNPtr &accuracy) : d(std::make_unique(datumsIn, accuracy)) {} // --------------------------------------------------------------------------- #ifdef notdef DatumEnsemble::DatumEnsemble(const DatumEnsemble &other) : common::ObjectUsage(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DatumEnsemble::~DatumEnsemble() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the set of datums which may be considered to be * insignificantly different from each other. * * @return the set of datums of the DatumEnsemble. */ const std::vector &DatumEnsemble::datums() const { return d->datums; } // --------------------------------------------------------------------------- /** \brief Return the inaccuracy introduced through use of this collection of * datums. * * It is an indication of the differences in coordinate values at all points * between the various realizations that have been grouped into this datum * ensemble. * * @return the accuracy. */ const metadata::PositionalAccuracyNNPtr & DatumEnsemble::positionalAccuracy() const { return d->positionalAccuracy; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /* static */ std::string DatumEnsemble::ensembleNameToNonEnsembleName(const std::string &s) { if (s == "World Geodetic System 1984 ensemble") { return "World Geodetic System 1984"; } else if (s == "European Terrestrial Reference System 1989 ensemble") { return "European Terrestrial Reference System 1989"; } else if (s == "Greenland Reference 1996 ensemble") { return "Greenland 1996"; } return std::string(); } //! @endcond // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DatumNNPtr DatumEnsemble::asDatum(const io::DatabaseContextPtr &dbContext) const { const auto &l_datums = datums(); auto *grf = dynamic_cast(l_datums[0].get()); const auto &l_identifiers = identifiers(); if (dbContext) { if (!l_identifiers.empty()) { const auto &id = l_identifiers[0]; try { auto factory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), *(id->codeSpace())); if (grf) { return factory->createGeodeticDatum(id->code()); } else { return factory->createVerticalDatum(id->code()); } } catch (const std::exception &) { } } } std::string l_name(nameStr()); if (grf) { // Remap to traditional datum names auto oldName = ensembleNameToNonEnsembleName(l_name); if (!oldName.empty()) l_name = std::move(oldName); } auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, l_name); if (isDeprecated()) { props.set(common::IdentifiedObject::DEPRECATED_KEY, true); } if (!l_identifiers.empty()) { const auto &id = l_identifiers[0]; props.set(metadata::Identifier::CODESPACE_KEY, *(id->codeSpace())) .set(metadata::Identifier::CODE_KEY, id->code()); } const auto &l_usages = domains(); if (!l_usages.empty()) { auto array(util::ArrayOfBaseObject::create()); for (const auto &usage : l_usages) { array->add(usage); } props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, util::nn_static_pointer_cast(array)); } const auto anchor = util::optional(); if (grf) { return GeodeticReferenceFrame::create(props, grf->ellipsoid(), anchor, grf->primeMeridian()); } else { assert(dynamic_cast(l_datums[0].get())); return datum::VerticalReferenceFrame::create(props, anchor); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DatumEnsemble::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2 || !formatter->use2019Keywords()) { return asDatum(formatter->databaseContext())->_exportToWKT(formatter); } const auto &l_datums = datums(); assert(!l_datums.empty()); formatter->startNode(io::WKTConstants::ENSEMBLE, false); const auto &l_name = nameStr(); if (!l_name.empty()) { formatter->addQuotedString(l_name); } else { formatter->addQuotedString("unnamed"); } for (const auto &datum : l_datums) { formatter->startNode(io::WKTConstants::MEMBER, !datum->identifiers().empty()); const auto &l_datum_name = datum->nameStr(); if (!l_datum_name.empty()) { formatter->addQuotedString(l_datum_name); } else { formatter->addQuotedString("unnamed"); } if (formatter->outputId()) { datum->formatID(formatter); } formatter->endNode(); } auto grfFirst = std::dynamic_pointer_cast( l_datums[0].as_nullable()); if (grfFirst) { grfFirst->ellipsoid()->_exportToWKT(formatter); } formatter->startNode(io::WKTConstants::ENSEMBLEACCURACY, false); formatter->add(positionalAccuracy()->value()); formatter->endNode(); // In theory, we should do the following, but currently the WKT grammar // doesn't allow this // ObjectUsage::baseExportToWKT(formatter); if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DatumEnsemble::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto objectContext( formatter->MakeObjectContext("DatumEnsemble", !identifiers().empty())); auto writer = formatter->writer(); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } const auto &l_datums = datums(); writer->AddObjKey("members"); { auto membersContext(writer->MakeArrayContext(false)); for (const auto &datum : l_datums) { auto memberContext(writer->MakeObjectContext()); writer->AddObjKey("name"); const auto &l_datum_name = datum->nameStr(); if (!l_datum_name.empty()) { writer->Add(l_datum_name); } else { writer->Add("unnamed"); } datum->formatID(formatter); } } auto grfFirst = std::dynamic_pointer_cast( l_datums[0].as_nullable()); if (grfFirst) { writer->AddObjKey("ellipsoid"); formatter->setOmitTypeInImmediateChild(); grfFirst->ellipsoid()->_exportToJSON(formatter); } writer->AddObjKey("accuracy"); writer->Add(positionalAccuracy()->value()); formatID(formatter); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a DatumEnsemble. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datumsIn Array of at least 2 datums. * @param accuracy Accuracy of the datum ensemble * @return new DatumEnsemble. * @throw util::Exception in case of error. */ DatumEnsembleNNPtr DatumEnsemble::create( const util::PropertyMap &properties, const std::vector &datumsIn, const metadata::PositionalAccuracyNNPtr &accuracy) // throw(Exception) { if (datumsIn.size() < 2) { throw util::Exception("ensemble should have at least 2 datums"); } if (auto grfFirst = dynamic_cast(datumsIn[0].get())) { for (size_t i = 1; i < datumsIn.size(); i++) { auto grf = dynamic_cast(datumsIn[i].get()); if (!grf) { throw util::Exception( "ensemble should have consistent datum types"); } if (!grfFirst->ellipsoid()->_isEquivalentTo( grf->ellipsoid().get())) { throw util::Exception( "ensemble should have datums with identical ellipsoid"); } if (!grfFirst->primeMeridian()->_isEquivalentTo( grf->primeMeridian().get())) { throw util::Exception( "ensemble should have datums with identical " "prime meridian"); } } } else if (dynamic_cast(datumsIn[0].get())) { for (size_t i = 1; i < datumsIn.size(); i++) { if (!dynamic_cast(datumsIn[i].get())) { throw util::Exception( "ensemble should have consistent datum types"); } } } auto ensemble( DatumEnsemble::nn_make_shared(datumsIn, accuracy)); ensemble->setProperties(properties); return ensemble; } // --------------------------------------------------------------------------- RealizationMethod::RealizationMethod(const std::string &nameIn) : CodeList(nameIn) {} // --------------------------------------------------------------------------- RealizationMethod & RealizationMethod::operator=(const RealizationMethod &other) { CodeList::operator=(other); return *this; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct VerticalReferenceFrame::Private { util::optional realizationMethod_{}; // 2005 = CS_VD_GeoidModelDerived from OGC 01-009 std::string wkt1DatumType_{"2005"}; }; //! @endcond // --------------------------------------------------------------------------- VerticalReferenceFrame::VerticalReferenceFrame( const util::optional &realizationMethodIn) : d(std::make_unique()) { if (!realizationMethodIn->toString().empty()) { d->realizationMethod_ = *realizationMethodIn; } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress VerticalReferenceFrame::~VerticalReferenceFrame() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the method through which this vertical reference frame is * realized. * * @return the realization method. */ const util::optional & VerticalReferenceFrame::realizationMethod() const { return d->realizationMethod_; } // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param anchor the anchor definition, or empty. * @param realizationMethodIn the realization method, or empty. * @return new VerticalReferenceFrame. */ VerticalReferenceFrameNNPtr VerticalReferenceFrame::create( const util::PropertyMap &properties, const util::optional &anchor, const util::optional &realizationMethodIn) { auto rf(VerticalReferenceFrame::nn_make_shared( realizationMethodIn)); rf->setAnchor(anchor); rf->setProperties(properties); properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_); return rf; } // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param anchor the anchor definition, or empty. * @param anchorEpoch the anchor epoch, or empty. * @param realizationMethodIn the realization method, or empty. * @return new VerticalReferenceFrame. * @since 9.2 */ VerticalReferenceFrameNNPtr VerticalReferenceFrame::create( const util::PropertyMap &properties, const util::optional &anchor, const util::optional &anchorEpoch, const util::optional &realizationMethodIn) { auto rf(VerticalReferenceFrame::nn_make_shared( realizationMethodIn)); rf->setAnchor(anchor); rf->setAnchorEpoch(anchorEpoch); rf->setProperties(properties); properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_); return rf; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const std::string &VerticalReferenceFrame::getWKT1DatumType() const { return d->wkt1DatumType_; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void VerticalReferenceFrame::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::VDATUM : formatter->useESRIDialect() ? io::WKTConstants::VDATUM : io::WKTConstants::VERT_DATUM, !identifiers().empty()); std::string l_name(nameStr()); if (!l_name.empty()) { if (!isWKT2 && formatter->useESRIDialect()) { bool aliasFound = false; const auto &dbContext = formatter->databaseContext(); if (dbContext) { auto l_alias = dbContext->getAliasFromOfficialName( l_name, "vertical_datum", "ESRI"); if (!l_alias.empty()) { l_name = std::move(l_alias); aliasFound = true; } } if (!aliasFound && dbContext) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "ESRI"); aliasFound = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType:: VERTICAL_REFERENCE_FRAME}, false // approximateMatch ) .size() == 1; } if (!aliasFound) { l_name = io::WKTFormatter::morphNameToESRI(l_name); } } formatter->addQuotedString(l_name); } else { formatter->addQuotedString("unnamed"); } if (isWKT2) { Datum::getPrivate()->exportAnchorDefinition(formatter); if (formatter->use2019Keywords()) { Datum::getPrivate()->exportAnchorEpoch(formatter); } } else if (!formatter->useESRIDialect()) { formatter->add(d->wkt1DatumType_); const auto &extension = formatter->getVDatumExtension(); if (!extension.empty()) { formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4_GRIDS"); formatter->addQuotedString(extension); formatter->endNode(); } } if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void VerticalReferenceFrame::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto dynamicGRF = dynamic_cast(this); auto objectContext(formatter->MakeObjectContext( dynamicGRF ? "DynamicVerticalReferenceFrame" : "VerticalReferenceFrame", !identifiers().empty())); auto writer = formatter->writer(); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } Datum::getPrivate()->exportAnchorDefinition(formatter); Datum::getPrivate()->exportAnchorEpoch(formatter); if (dynamicGRF) { writer->AddObjKey("frame_reference_epoch"); writer->Add(dynamicGRF->frameReferenceEpoch().value()); } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool VerticalReferenceFrame::isEquivalentToNoExactTypeCheck( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherVRF = dynamic_cast(other); if (otherVRF == nullptr || !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } if ((realizationMethod().has_value() ^ otherVRF->realizationMethod().has_value())) { return false; } if (realizationMethod().has_value() && otherVRF->realizationMethod().has_value()) { if (*(realizationMethod()) != *(otherVRF->realizationMethod())) { return false; } } return true; } // --------------------------------------------------------------------------- bool VerticalReferenceFrame::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (criterion == Criterion::STRICT && !util::isOfExactType(*other)) { return false; } return isEquivalentToNoExactTypeCheck(other, criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DynamicVerticalReferenceFrame::Private { common::Measure frameReferenceEpoch{}; util::optional deformationModelName{}; explicit Private(const common::Measure &frameReferenceEpochIn) : frameReferenceEpoch(frameReferenceEpochIn) {} }; //! @endcond // --------------------------------------------------------------------------- DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame( const util::optional &realizationMethodIn, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn) : VerticalReferenceFrame(realizationMethodIn), d(std::make_unique(frameReferenceEpochIn)) { d->deformationModelName = deformationModelNameIn; } // --------------------------------------------------------------------------- #ifdef notdef DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame( const DynamicVerticalReferenceFrame &other) : VerticalReferenceFrame(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DynamicVerticalReferenceFrame::~DynamicVerticalReferenceFrame() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the epoch to which the coordinates of stations defining the * dynamic geodetic reference frame are referenced. * * Usually given as a decimal year e.g. 2016.47. * * @return the frame reference epoch. */ const common::Measure & DynamicVerticalReferenceFrame::frameReferenceEpoch() const { return d->frameReferenceEpoch; } // --------------------------------------------------------------------------- /** \brief Return the name of the deformation model. * * @note This is an extension to the \ref ISO_19111_2019 modeling, to * hold the content of the DYNAMIC.MODEL WKT2 node. * * @return the name of the deformation model. */ const util::optional & DynamicVerticalReferenceFrame::deformationModelName() const { return d->deformationModelName; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool DynamicVerticalReferenceFrame::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (criterion == Criterion::STRICT && !util::isOfExactType(*other)) { return false; } if (!VerticalReferenceFrame::isEquivalentToNoExactTypeCheck( other, criterion, dbContext)) { return false; } auto otherDGRF = dynamic_cast(other); if (otherDGRF == nullptr) { // we can go here only if criterion != Criterion::STRICT, and thus // given the above check we can consider the objects equivalent. return true; } return frameReferenceEpoch()._isEquivalentTo( otherDGRF->frameReferenceEpoch(), criterion) && metadata::Identifier::isEquivalentName( deformationModelName()->c_str(), otherDGRF->deformationModelName()->c_str()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DynamicVerticalReferenceFrame::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (isWKT2 && formatter->use2019Keywords()) { formatter->startNode(io::WKTConstants::DYNAMIC, false); formatter->startNode(io::WKTConstants::FRAMEEPOCH, false); formatter->add( frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR)); formatter->endNode(); if (!deformationModelName()->empty()) { formatter->startNode(io::WKTConstants::MODEL, false); formatter->addQuotedString(*deformationModelName()); formatter->endNode(); } formatter->endNode(); } VerticalReferenceFrame::_exportToWKT(formatter); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a DynamicVerticalReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param anchor the anchor definition, or empty. * @param realizationMethodIn the realization method, or empty. * @param frameReferenceEpochIn the frame reference epoch. * @param deformationModelNameIn deformation model name, or empty * @return new DynamicVerticalReferenceFrame. */ DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create( const util::PropertyMap &properties, const util::optional &anchor, const util::optional &realizationMethodIn, const common::Measure &frameReferenceEpochIn, const util::optional &deformationModelNameIn) { DynamicVerticalReferenceFrameNNPtr grf( DynamicVerticalReferenceFrame::nn_make_shared< DynamicVerticalReferenceFrame>(realizationMethodIn, frameReferenceEpochIn, deformationModelNameIn)); grf->setAnchor(anchor); grf->setProperties(properties); return grf; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct TemporalDatum::Private { common::DateTime temporalOrigin_; std::string calendar_; Private(const common::DateTime &temporalOriginIn, const std::string &calendarIn) : temporalOrigin_(temporalOriginIn), calendar_(calendarIn) {} }; //! @endcond // --------------------------------------------------------------------------- TemporalDatum::TemporalDatum(const common::DateTime &temporalOriginIn, const std::string &calendarIn) : d(std::make_unique(temporalOriginIn, calendarIn)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalDatum::~TemporalDatum() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the date and time to which temporal coordinates are * referenced, expressed in conformance with ISO 8601. * * @return the temporal origin. */ const common::DateTime &TemporalDatum::temporalOrigin() const { return d->temporalOrigin_; } // --------------------------------------------------------------------------- /** \brief Return the calendar to which the temporal origin is referenced * * Default value: TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN. * * @return the calendar. */ const std::string &TemporalDatum::calendar() const { return d->calendar_; } // --------------------------------------------------------------------------- /** \brief Instantiate a TemporalDatum * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param temporalOriginIn the temporal origin into which temporal coordinates * are referenced. * @param calendarIn the calendar (generally * TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN) * @return new TemporalDatum. */ TemporalDatumNNPtr TemporalDatum::create(const util::PropertyMap &properties, const common::DateTime &temporalOriginIn, const std::string &calendarIn) { auto datum(TemporalDatum::nn_make_shared(temporalOriginIn, calendarIn)); datum->setProperties(properties); return datum; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void TemporalDatum::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { throw io::FormattingException( "TemporalDatum can only be exported to WKT2"); } formatter->startNode(io::WKTConstants::TDATUM, !identifiers().empty()); formatter->addQuotedString(nameStr()); if (formatter->use2019Keywords()) { formatter->startNode(io::WKTConstants::CALENDAR, false); formatter->addQuotedString(calendar()); formatter->endNode(); } const auto &timeOriginStr = temporalOrigin().toString(); if (!timeOriginStr.empty()) { formatter->startNode(io::WKTConstants::TIMEORIGIN, false); if (temporalOrigin().isISO_8601()) { formatter->add(timeOriginStr); } else { formatter->addQuotedString(timeOriginStr); } formatter->endNode(); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void TemporalDatum::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto objectContext( formatter->MakeObjectContext("TemporalDatum", !identifiers().empty())); auto writer = formatter->writer(); writer->AddObjKey("name"); writer->Add(nameStr()); writer->AddObjKey("calendar"); writer->Add(calendar()); const auto &timeOriginStr = temporalOrigin().toString(); if (!timeOriginStr.empty()) { writer->AddObjKey("time_origin"); writer->Add(timeOriginStr); } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool TemporalDatum::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherTD = dynamic_cast(other); if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return temporalOrigin().toString() == otherTD->temporalOrigin().toString() && calendar() == otherTD->calendar(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct EngineeringDatum::Private {}; //! @endcond // --------------------------------------------------------------------------- EngineeringDatum::EngineeringDatum() : d(nullptr) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress EngineeringDatum::~EngineeringDatum() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a EngineeringDatum * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param anchor the anchor definition, or empty. * @return new EngineeringDatum. */ EngineeringDatumNNPtr EngineeringDatum::create(const util::PropertyMap &properties, const util::optional &anchor) { auto datum(EngineeringDatum::nn_make_shared()); datum->setAnchor(anchor); datum->setProperties(properties); return datum; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void EngineeringDatum::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::EDATUM : io::WKTConstants::LOCAL_DATUM, !identifiers().empty()); formatter->addQuotedString(nameStr()); if (isWKT2) { Datum::getPrivate()->exportAnchorDefinition(formatter); } else { // Somewhat picked up arbitrarily from OGC 01-009: // CS_LD_Max (Attribute) : 32767 // Highest possible value for local datum types. formatter->add(32767); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void EngineeringDatum::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto objectContext(formatter->MakeObjectContext("EngineeringDatum", !identifiers().empty())); auto writer = formatter->writer(); writer->AddObjKey("name"); writer->Add(nameStr()); Datum::getPrivate()->exportAnchorDefinition(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool EngineeringDatum::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDatum = dynamic_cast(other); if (otherDatum == nullptr) { return false; } if (criterion != util::IComparable::Criterion::STRICT && (nameStr().empty() || nameStr() == UNKNOWN_ENGINEERING_DATUM) && (otherDatum->nameStr().empty() || otherDatum->nameStr() == UNKNOWN_ENGINEERING_DATUM)) { return true; } return Datum::_isEquivalentTo(other, criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ParametricDatum::Private {}; //! @endcond // --------------------------------------------------------------------------- ParametricDatum::ParametricDatum() : d(nullptr) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ParametricDatum::~ParametricDatum() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a ParametricDatum * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param anchor the anchor definition, or empty. * @return new ParametricDatum. */ ParametricDatumNNPtr ParametricDatum::create(const util::PropertyMap &properties, const util::optional &anchor) { auto datum(ParametricDatum::nn_make_shared()); datum->setAnchor(anchor); datum->setProperties(properties); return datum; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ParametricDatum::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { throw io::FormattingException( "ParametricDatum can only be exported to WKT2"); } formatter->startNode(io::WKTConstants::PDATUM, !identifiers().empty()); formatter->addQuotedString(nameStr()); Datum::getPrivate()->exportAnchorDefinition(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ParametricDatum::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto objectContext(formatter->MakeObjectContext("ParametricDatum", !identifiers().empty())); auto writer = formatter->writer(); writer->AddObjKey("name"); writer->Add(nameStr()); Datum::getPrivate()->exportAnchorDefinition(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool ParametricDatum::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherTD = dynamic_cast(other); if (otherTD == nullptr || !Datum::_isEquivalentTo(other, criterion, dbContext)) { return false; } return true; } //! @endcond } // namespace datum NS_PROJ_END proj-9.8.1/src/iso19111/factory.cpp000664 001750 001750 00001501477 15166171715 016623 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinates.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/lru_cache.hpp" #include "proj/internal/tracing.hpp" #include "operation/coordinateoperation_internal.hpp" #include "operation/parammappings.hpp" #include "filemanager.hpp" #include "sqlite3_utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include // std::ostringstream #include #include #include "proj_constants.h" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" // clang-format on #include #ifdef EMBED_RESOURCE_FILES #include "embedded_resources.h" #endif // Custom SQLite VFS as our database is not supposed to be modified in // parallel. This is slightly faster #define ENABLE_CUSTOM_LOCKLESS_VFS #if defined(_WIN32) && defined(PROJ_HAS_PTHREADS) #undef PROJ_HAS_PTHREADS #endif /* SQLite3 might use seak()+read() or pread[64]() to read data */ /* The later allows the same SQLite handle to be safely used in forked */ /* children of a parent process, while the former doesn't. */ /* So we use pthread_atfork() to set a flag in forked children, to ask them */ /* to close and reopen their database handle. */ #if defined(PROJ_HAS_PTHREADS) && !defined(SQLITE_USE_PREAD) #include #define REOPEN_SQLITE_DB_AFTER_FORK #endif using namespace NS_PROJ::internal; using namespace NS_PROJ::common; NS_PROJ_START namespace io { //! @cond Doxygen_Suppress // CRS subtypes #define GEOG_2D "geographic 2D" #define GEOG_3D "geographic 3D" #define GEOCENTRIC "geocentric" #define OTHER "other" #define PROJECTED "projected" #define ENGINEERING "engineering" #define VERTICAL "vertical" #define COMPOUND "compound" #define GEOG_2D_SINGLE_QUOTED "'geographic 2D'" #define GEOG_3D_SINGLE_QUOTED "'geographic 3D'" #define GEOCENTRIC_SINGLE_QUOTED "'geocentric'" // Coordinate system types constexpr const char *CS_TYPE_ELLIPSOIDAL = cs::EllipsoidalCS::WKT2_TYPE; constexpr const char *CS_TYPE_CARTESIAN = cs::CartesianCS::WKT2_TYPE; constexpr const char *CS_TYPE_SPHERICAL = cs::SphericalCS::WKT2_TYPE; constexpr const char *CS_TYPE_VERTICAL = cs::VerticalCS::WKT2_TYPE; constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE; // See data/sql/metadata.sql for the semantics of those constants constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1; // If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR // must be incremented. constexpr int DATABASE_LAYOUT_VERSION_MINOR = 6; constexpr size_t N_MAX_PARAMS = 7; #ifdef EMBED_RESOURCE_FILES constexpr const char *EMBEDDED_PROJ_DB = "__embedded_proj_db__"; #endif // --------------------------------------------------------------------------- struct SQLValues { enum class Type { STRING, INT, DOUBLE }; // cppcheck-suppress noExplicitConstructor SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {} // cppcheck-suppress noExplicitConstructor SQLValues(int value) : type_(Type::INT), int_(value) {} // cppcheck-suppress noExplicitConstructor SQLValues(double value) : type_(Type::DOUBLE), double_(value) {} const Type &type() const { return type_; } // cppcheck-suppress functionStatic const std::string &stringValue() const { return str_; } // cppcheck-suppress functionStatic int intValue() const { return int_; } // cppcheck-suppress functionStatic double doubleValue() const { return double_; } private: Type type_; std::string str_{}; int int_ = 0; double double_ = 0.0; }; // --------------------------------------------------------------------------- using SQLRow = std::vector; using SQLResultSet = std::list; using ListOfParams = std::list; // --------------------------------------------------------------------------- static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) { switch (sqlite3_value_type(val)) { case SQLITE_FLOAT: gotVal = true; return sqlite3_value_double(val); case SQLITE_INTEGER: gotVal = true; return static_cast(sqlite3_value_int64(val)); default: gotVal = false; return 0.0; } } // --------------------------------------------------------------------------- static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext, int /* argc */, sqlite3_value **argv) { bool b0, b1, b2, b3; double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0); double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1); double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2); double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3); if (!b0 || !b1 || !b2 || !b3) { sqlite3_result_null(pContext); return; } // Deal with area crossing antimeridian if (east_lon < west_lon) { east_lon += 360.0; } // Integrate cos(lat) between south_lat and north_lat double pseudo_area = (east_lon - west_lon) * (std::sin(common::Angle(north_lat).getSIValue()) - std::sin(common::Angle(south_lat).getSIValue())); sqlite3_result_double(pContext, pseudo_area); } // --------------------------------------------------------------------------- static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext, int /* argc */, sqlite3_value **argv) { bool b0, b1, b2, b3, b4, b5, b6, b7; double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0); double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1); double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2); double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3); double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4); double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5); double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6); double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7); if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) { sqlite3_result_null(pContext); return; } auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1, east_lon1, north_lat1); auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2, east_lon2, north_lat2); sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0); } // --------------------------------------------------------------------------- class SQLiteHandle { std::string path_{}; sqlite3 *sqlite_handle_ = nullptr; bool close_handle_ = true; #ifdef REOPEN_SQLITE_DB_AFTER_FORK bool is_valid_ = true; #endif int nLayoutVersionMajor_ = 0; int nLayoutVersionMinor_ = 0; #if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES) std::unique_ptr vfs_{}; #endif SQLiteHandle(const SQLiteHandle &) = delete; SQLiteHandle &operator=(const SQLiteHandle &) = delete; SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle) : sqlite_handle_(sqlite_handle), close_handle_(close_handle) { assert(sqlite_handle_); } // cppcheck-suppress functionStatic void initialize(); SQLResultSet run(const std::string &sql, const ListOfParams ¶meters = ListOfParams(), bool useMaxFloatPrecision = false); public: ~SQLiteHandle(); const std::string &path() const { return path_; } sqlite3 *handle() { return sqlite_handle_; } #ifdef REOPEN_SQLITE_DB_AFTER_FORK bool isValid() const { return is_valid_; } void invalidate() { is_valid_ = false; } #endif static std::shared_ptr open(PJ_CONTEXT *ctx, const std::string &path); // might not be shared between thread depending how the handle was opened! static std::shared_ptr initFromExisting(sqlite3 *sqlite_handle, bool close_handle, int nLayoutVersionMajor, int nLayoutVersionMinor); static std::unique_ptr initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle); void checkDatabaseLayout(const std::string &mainDbPath, const std::string &path, const std::string &dbNamePrefix); SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql, const ListOfParams ¶meters = ListOfParams(), bool useMaxFloatPrecision = false); inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; } inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; } }; // --------------------------------------------------------------------------- SQLiteHandle::~SQLiteHandle() { if (close_handle_) { sqlite3_close(sqlite_handle_); } } // --------------------------------------------------------------------------- std::shared_ptr SQLiteHandle::open(PJ_CONTEXT *ctx, const std::string &pathIn) { std::string path(pathIn); const int sqlite3VersionNumber = sqlite3_libversion_number(); // Minimum version for correct performance: 3.11 if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) { pj_log(ctx, PJ_LOG_ERROR, "SQLite3 version is %s, whereas at least 3.11 should be used", sqlite3_libversion()); } std::string vfsName; #if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES) std::unique_ptr vfs; #endif #ifdef EMBED_RESOURCE_FILES if (path == EMBEDDED_PROJ_DB && ctx->custom_sqlite3_vfs_name.empty()) { unsigned int proj_db_size = 0; const unsigned char *proj_db = pj_get_embedded_proj_db(&proj_db_size); vfs = SQLite3VFS::createMem(proj_db, proj_db_size); if (vfs == nullptr) { throw FactoryException("Open of " + path + " failed"); } std::ostringstream buffer; buffer << "file:/proj.db?immutable=1&ptr="; buffer << reinterpret_cast(proj_db); buffer << "&sz="; buffer << proj_db_size; buffer << "&max="; buffer << proj_db_size; buffer << "&vfs="; buffer << vfs->name(); path = buffer.str(); } else #endif #ifdef ENABLE_CUSTOM_LOCKLESS_VFS if (ctx->custom_sqlite3_vfs_name.empty()) { vfs = SQLite3VFS::create(false, true, true); if (vfs == nullptr) { throw FactoryException("Open of " + path + " failed"); } vfsName = vfs->name(); } else #endif { vfsName = ctx->custom_sqlite3_vfs_name; } sqlite3 *sqlite_handle = nullptr; // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads if (sqlite3_open_v2( path.c_str(), &sqlite_handle, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI, vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK || !sqlite_handle) { if (sqlite_handle != nullptr) { sqlite3_close(sqlite_handle); } throw FactoryException("Open of " + path + " failed"); } auto handle = std::shared_ptr(new SQLiteHandle(sqlite_handle, true)); #if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES) handle->vfs_ = std::move(vfs); #endif handle->initialize(); handle->path_ = path; handle->checkDatabaseLayout(path, path, std::string()); return handle; } // --------------------------------------------------------------------------- std::shared_ptr SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle, int nLayoutVersionMajor, int nLayoutVersionMinor) { auto handle = std::shared_ptr( new SQLiteHandle(sqlite_handle, close_handle)); handle->nLayoutVersionMajor_ = nLayoutVersionMajor; handle->nLayoutVersionMinor_ = nLayoutVersionMinor; handle->initialize(); return handle; } // --------------------------------------------------------------------------- std::unique_ptr SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle) { auto handle = std::unique_ptr( new SQLiteHandle(sqlite_handle, close_handle)); handle->initialize(); return handle; } // --------------------------------------------------------------------------- SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql, const ListOfParams ¶meters, bool useMaxFloatPrecision) { int nBindField = 1; for (const auto ¶m : parameters) { const auto ¶mType = param.type(); if (paramType == SQLValues::Type::STRING) { const auto &strValue = param.stringValue(); sqlite3_bind_text(stmt, nBindField, strValue.c_str(), static_cast(strValue.size()), SQLITE_TRANSIENT); } else if (paramType == SQLValues::Type::INT) { sqlite3_bind_int(stmt, nBindField, param.intValue()); } else { assert(paramType == SQLValues::Type::DOUBLE); sqlite3_bind_double(stmt, nBindField, param.doubleValue()); } nBindField++; } #ifdef TRACE_DATABASE size_t nPos = 0; std::string sqlSubst(sql); for (const auto ¶m : parameters) { nPos = sqlSubst.find('?', nPos); assert(nPos != std::string::npos); std::string strValue; const auto paramType = param.type(); if (paramType == SQLValues::Type::STRING) { strValue = '\'' + param.stringValue() + '\''; } else if (paramType == SQLValues::Type::INT) { strValue = toString(param.intValue()); } else { strValue = toString(param.doubleValue()); } sqlSubst = sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1); nPos += strValue.size(); } logTrace(sqlSubst, "DATABASE"); #endif SQLResultSet result; const int column_count = sqlite3_column_count(stmt); while (true) { int ret = sqlite3_step(stmt); if (ret == SQLITE_ROW) { SQLRow row(column_count); for (int i = 0; i < column_count; i++) { if (useMaxFloatPrecision && sqlite3_column_type(stmt, i) == SQLITE_FLOAT) { // sqlite3_column_text() does not use maximum precision std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << std::setprecision(18); buffer << sqlite3_column_double(stmt, i); row[i] = buffer.str(); } else { const char *txt = reinterpret_cast( sqlite3_column_text(stmt, i)); if (txt) { row[i] = txt; } } } result.emplace_back(std::move(row)); } else if (ret == SQLITE_DONE) { break; } else { throw FactoryException(std::string("SQLite error [ ") .append("code = ") .append(internal::toString(ret)) .append(", msg = ") .append(sqlite3_errmsg(sqlite_handle_)) .append(" ] on ") .append(sql)); } } return result; } // --------------------------------------------------------------------------- SQLResultSet SQLiteHandle::run(const std::string &sql, const ListOfParams ¶meters, bool useMaxFloatPrecision) { sqlite3_stmt *stmt = nullptr; try { if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(), static_cast(sql.size()), &stmt, nullptr) != SQLITE_OK) { throw FactoryException(std::string("SQLite error [ ") .append(sqlite3_errmsg(sqlite_handle_)) .append(" ] on ") .append(sql)); } auto ret = run(stmt, sql, parameters, useMaxFloatPrecision); sqlite3_finalize(stmt); return ret; } catch (const std::exception &) { if (stmt) sqlite3_finalize(stmt); throw; } } // --------------------------------------------------------------------------- void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath, const std::string &path, const std::string &dbNamePrefix) { if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix + "sqlite_master WHERE name = 'metadata'") .empty()) { // Accept auxiliary databases without metadata table (sparse DBs) return; } auto res = run("SELECT key, value FROM " + dbNamePrefix + "metadata WHERE key IN " "('DATABASE.LAYOUT.VERSION.MAJOR', " "'DATABASE.LAYOUT.VERSION.MINOR')"); if (res.empty() && !dbNamePrefix.empty()) { // Accept auxiliary databases without layout metadata. return; } if (res.size() != 2) { throw FactoryException( path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / " "DATABASE.LAYOUT.VERSION.MINOR " "metadata. It comes from another PROJ installation."); } int major = 0; int minor = 0; for (const auto &row : res) { if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") { major = atoi(row[1].c_str()); } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") { minor = atoi(row[1].c_str()); } } if (major != DATABASE_LAYOUT_VERSION_MAJOR) { throw FactoryException( path + " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) + " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) + " is expected. " "It comes from another PROJ installation."); } if (minor < DATABASE_LAYOUT_VERSION_MINOR) { throw FactoryException( path + " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) + " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) + " is expected. " "It comes from another PROJ installation."); } if (dbNamePrefix.empty()) { nLayoutVersionMajor_ = major; nLayoutVersionMinor_ = minor; } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) { throw FactoryException( "Auxiliary database " + path + " contains a DATABASE.LAYOUT.VERSION = " + toString(major) + '.' + toString(minor) + " which is different from the one from the main database " + mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' + toString(nLayoutVersionMinor_)); } } // --------------------------------------------------------------------------- #ifndef SQLITE_DETERMINISTIC #define SQLITE_DETERMINISTIC 0 #endif void SQLiteHandle::initialize() { // There is a bug in sqlite 3.38.0 with some complex queries. // Cf https://github.com/OSGeo/PROJ/issues/3077 // Disabling Bloom-filter pull-down optimization as suggested in // https://sqlite.org/forum/forumpost/7d3a75438c const int sqlite3VersionNumber = sqlite3_libversion_number(); if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) { sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_, 0x100000); } sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, PROJ_SQLITE_pseudo_area_from_swne, nullptr, nullptr); sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, PROJ_SQLITE_intersects_bbox, nullptr, nullptr); } // --------------------------------------------------------------------------- class SQLiteHandleCache { #ifdef REOPEN_SQLITE_DB_AFTER_FORK bool firstTime_ = true; #endif std::mutex sMutex_{}; // Map dbname to SQLiteHandle lru11::Cache> cache_{}; public: static SQLiteHandleCache &get(); std::shared_ptr getHandle(const std::string &path, PJ_CONTEXT *ctx); void clear(); #ifdef REOPEN_SQLITE_DB_AFTER_FORK void invalidateHandles(); #endif }; // --------------------------------------------------------------------------- SQLiteHandleCache &SQLiteHandleCache::get() { // Global cache static SQLiteHandleCache gSQLiteHandleCache; return gSQLiteHandleCache; } // --------------------------------------------------------------------------- void SQLiteHandleCache::clear() { std::lock_guard lock(sMutex_); cache_.clear(); } // --------------------------------------------------------------------------- std::shared_ptr SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) { std::lock_guard lock(sMutex_); #ifdef REOPEN_SQLITE_DB_AFTER_FORK if (firstTime_) { firstTime_ = false; pthread_atfork( []() { // This mutex needs to be acquired by 'invalidateHandles()'. // The forking thread needs to own this mutex during the fork. // Otherwise there's an opporunity for another thread to own // the mutex during the fork, leaving the child process unable // to acquire the mutex in invalidateHandles(). SQLiteHandleCache::get().sMutex_.lock(); }, []() { SQLiteHandleCache::get().sMutex_.unlock(); }, []() { SQLiteHandleCache::get().sMutex_.unlock(); SQLiteHandleCache::get().invalidateHandles(); }); } #endif std::shared_ptr handle; std::string key = path + ctx->custom_sqlite3_vfs_name; if (!cache_.tryGet(key, handle)) { handle = SQLiteHandle::open(ctx, path); cache_.insert(key, handle); } return handle; } #ifdef REOPEN_SQLITE_DB_AFTER_FORK // --------------------------------------------------------------------------- void SQLiteHandleCache::invalidateHandles() { std::lock_guard lock(sMutex_); const auto lambda = [](const lru11::KeyValuePair> &kvp) { kvp.value->invalidate(); }; cache_.cwalk(lambda); cache_.clear(); } #endif // --------------------------------------------------------------------------- struct DatabaseContext::Private { Private(); ~Private(); void open(const std::string &databasePath, PJ_CONTEXT *ctx); void setHandle(sqlite3 *sqlite_handle); const std::shared_ptr &handle(); PJ_CONTEXT *pjCtxt() const { return pjCtxt_; } void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; } SQLResultSet run(const std::string &sql, const ListOfParams ¶meters = ListOfParams(), bool useMaxFloatPrecision = false); std::vector getDatabaseStructure(); // cppcheck-suppress functionStatic const std::string &getPath() const { return databasePath_; } void attachExtraDatabases( const std::vector &auxiliaryDatabasePaths); // Mechanism to detect recursion in calls from // AuthorityFactory::createXXX() -> createFromUserInput() -> // AuthorityFactory::createXXX() struct RecursionDetector { explicit RecursionDetector(const DatabaseContextNNPtr &context) : dbContext_(context) { if (dbContext_->getPrivate()->recLevel_ == 2) { // Throw exception before incrementing, since the destructor // will not be called throw FactoryException("Too many recursive calls"); } ++dbContext_->getPrivate()->recLevel_; } ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; } private: DatabaseContextNNPtr dbContext_; }; std::map> &getMapCanonicalizeGRFName() { return mapCanonicalizeGRFName_; } // cppcheck-suppress functionStatic common::UnitOfMeasurePtr getUOMFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom); // cppcheck-suppress functionStatic crs::CRSPtr getCRSFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const crs::CRSNNPtr &crs); datum::GeodeticReferenceFramePtr // cppcheck-suppress functionStatic getGeodeticDatumFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum); datum::DatumEnsemblePtr // cppcheck-suppress functionStatic getDatumEnsembleFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble); datum::EllipsoidPtr // cppcheck-suppress functionStatic getEllipsoidFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid); datum::PrimeMeridianPtr // cppcheck-suppress functionStatic getPrimeMeridianFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm); // cppcheck-suppress functionStatic cs::CoordinateSystemPtr getCoordinateSystemFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs); // cppcheck-suppress functionStatic metadata::ExtentPtr getExtentFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const metadata::ExtentNNPtr &extent); // cppcheck-suppress functionStatic bool getCRSToCRSCoordOpFromCache( const std::string &code, std::vector &list); // cppcheck-suppress functionStatic void cache(const std::string &code, const std::vector &list); struct GridInfoCache { std::string fullFilename{}; std::string packageName{}; std::string url{}; bool found = false; bool directDownload = false; bool openLicense = false; bool gridAvailable = false; }; // cppcheck-suppress functionStatic bool getGridInfoFromCache(const std::string &code, GridInfoCache &info); // cppcheck-suppress functionStatic void evictGridInfoFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const GridInfoCache &info); struct VersionedAuthName { std::string versionedAuthName{}; std::string authName{}; std::string version{}; int priority = 0; }; const std::vector &getCacheAuthNameWithVersion(); private: friend class DatabaseContext; // This is a manual implementation of std::enable_shared_from_this<> that // avoids publicly deriving from it. std::weak_ptr self_{}; std::string databasePath_{}; std::vector auxiliaryDatabasePaths_{}; std::shared_ptr sqlite_handle_{}; unsigned int queryCounter_ = 0; std::map mapSqlToStatement_{}; PJ_CONTEXT *pjCtxt_ = nullptr; int recLevel_ = 0; bool detach_ = false; std::string lastMetadataValue_{}; std::map> mapCanonicalizeGRFName_{}; // Used by startInsertStatementsSession() and related functions std::string memoryDbForInsertPath_{}; std::unique_ptr memoryDbHandle_{}; using LRUCacheOfObjects = lru11::Cache; static constexpr size_t CACHE_SIZE = 128; LRUCacheOfObjects cacheUOM_{CACHE_SIZE}; LRUCacheOfObjects cacheCRS_{CACHE_SIZE}; LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE}; LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE}; LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE}; LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE}; LRUCacheOfObjects cacheCS_{CACHE_SIZE}; LRUCacheOfObjects cacheExtent_{CACHE_SIZE}; lru11::Cache> cacheCRSToCrsCoordOp_{CACHE_SIZE}; lru11::Cache cacheGridInfo_{CACHE_SIZE}; std::map> cacheAllowedAuthorities_{}; lru11::Cache> cacheAliasNames_{ CACHE_SIZE}; lru11::Cache cacheNames_{CACHE_SIZE}; std::vector cacheAuthNameWithVersion_{}; static void insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj); static void getFromCache(LRUCacheOfObjects &cache, const std::string &code, util::BaseObjectPtr &obj); void closeDB() noexcept; void clearCaches(); std::string findFreeCode(const std::string &tableName, const std::string &authName, const std::string &codePrototype); void identify(const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj, std::string &authName, std::string &code); void identifyOrInsert(const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj, const std::string &ownerType, const std::string &ownerAuthName, const std::string &ownerCode, std::string &authName, std::string &code, std::vector &sqlStatements); void identify(const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &obj, std::string &authName, std::string &code); void identifyOrInsert(const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit, const std::string &ownerAuthName, std::string &authName, std::string &code, std::vector &sqlStatements); void appendSql(std::vector &sqlStatements, const std::string &sql); void identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj, const std::string &tableName, const std::string &authName, const std::string &code, const std::vector &allowedAuthorities, std::vector &sqlStatements); std::vector getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); std::vector getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities); Private(const Private &) = delete; Private &operator=(const Private &) = delete; }; // --------------------------------------------------------------------------- DatabaseContext::Private::Private() = default; // --------------------------------------------------------------------------- DatabaseContext::Private::~Private() { assert(recLevel_ == 0); closeDB(); } // --------------------------------------------------------------------------- void DatabaseContext::Private::closeDB() noexcept { if (detach_) { // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes // a crash in TEST(factory, attachExtraDatabases_auxiliary) // due to possible wrong caching of key info. // The bug is specific to using a memory file with shared cache as an // auxiliary DB. // The fix was likely in 3.8.8 // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb // But just after 3.8.2, // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19 // also seemed to hide the issue. // Detaching a database hides the issue, not sure if it is by chance... try { run("DETACH DATABASE db_0"); } catch (...) { } detach_ = false; } for (auto &pair : mapSqlToStatement_) { sqlite3_finalize(pair.second); } mapSqlToStatement_.clear(); sqlite_handle_.reset(); } // --------------------------------------------------------------------------- void DatabaseContext::Private::clearCaches() { cacheUOM_.clear(); cacheCRS_.clear(); cacheEllipsoid_.clear(); cacheGeodeticDatum_.clear(); cacheDatumEnsemble_.clear(); cachePrimeMeridian_.clear(); cacheCS_.clear(); cacheExtent_.clear(); cacheCRSToCrsCoordOp_.clear(); cacheGridInfo_.clear(); cacheAllowedAuthorities_.clear(); cacheAliasNames_.clear(); cacheNames_.clear(); } // --------------------------------------------------------------------------- const std::shared_ptr &DatabaseContext::Private::handle() { #ifdef REOPEN_SQLITE_DB_AFTER_FORK if (sqlite_handle_ && !sqlite_handle_->isValid()) { closeDB(); open(databasePath_, pjCtxt_); if (!auxiliaryDatabasePaths_.empty()) { attachExtraDatabases(auxiliaryDatabasePaths_); } } #endif return sqlite_handle_; } // --------------------------------------------------------------------------- void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj) { cache.insert(code, obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache, const std::string &code, util::BaseObjectPtr &obj) { cache.tryGet(code, obj); } // --------------------------------------------------------------------------- bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache( const std::string &code, std::vector &list) { return cacheCRSToCrsCoordOp_.tryGet(code, list); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache( const std::string &code, const std::vector &list) { cacheCRSToCrsCoordOp_.insert(code, list); } // --------------------------------------------------------------------------- crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheCRS_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const crs::CRSNNPtr &crs) { insertIntoCache(cacheCRS_, code, crs.as_nullable()); } // --------------------------------------------------------------------------- common::UnitOfMeasurePtr DatabaseContext::Private::getUOMFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheUOM_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom) { insertIntoCache(cacheUOM_, code, uom.as_nullable()); } // --------------------------------------------------------------------------- datum::GeodeticReferenceFramePtr DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheGeodeticDatum_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache( const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) { insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable()); } // --------------------------------------------------------------------------- datum::DatumEnsemblePtr DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheDatumEnsemble_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache( const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) { insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable()); } // --------------------------------------------------------------------------- datum::EllipsoidPtr DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheEllipsoid_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const datum::EllipsoidNNPtr &ellps) { insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable()); } // --------------------------------------------------------------------------- datum::PrimeMeridianPtr DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cachePrimeMeridian_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm) { insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable()); } // --------------------------------------------------------------------------- cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache( const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheCS_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs) { insertIntoCache(cacheCS_, code, cs.as_nullable()); } // --------------------------------------------------------------------------- metadata::ExtentPtr DatabaseContext::Private::getExtentFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheExtent_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const metadata::ExtentNNPtr &extent) { insertIntoCache(cacheExtent_, code, extent.as_nullable()); } // --------------------------------------------------------------------------- bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code, GridInfoCache &info) { return cacheGridInfo_.tryGet(code, info); } // --------------------------------------------------------------------------- void DatabaseContext::Private::evictGridInfoFromCache(const std::string &code) { cacheGridInfo_.remove(code); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const GridInfoCache &info) { cacheGridInfo_.insert(code, info); } // --------------------------------------------------------------------------- void DatabaseContext::Private::open(const std::string &databasePath, PJ_CONTEXT *ctx) { if (!ctx) { ctx = pj_get_default_ctx(); } setPjCtxt(ctx); std::string path(databasePath); if (path.empty()) { #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES path.resize(2048); const bool found = pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0; path.resize(strlen(path.c_str())); if (!found) #endif { #ifdef EMBED_RESOURCE_FILES path = EMBEDDED_PROJ_DB; #else throw FactoryException("Cannot find proj.db"); #endif } } sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx); databasePath_ = sqlite_handle_->path(); } // --------------------------------------------------------------------------- void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) { assert(sqlite_handle); assert(!sqlite_handle_); sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0); } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getDatabaseStructure() { const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() && memoryDbForInsertPath_.empty() ? "" : "db_0."); const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix + "sqlite_master WHERE type = "); const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'"; const char *const objectTypes[] = {tableType, "'view'", "'trigger'"}; std::vector res; for (const auto &objectType : objectTypes) { const auto sqlRes = run(sqlBegin + objectType); for (const auto &row : sqlRes) { res.emplace_back(row[0]); } } if (sqlite_handle_->getLayoutVersionMajor() > 0) { res.emplace_back( "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," + toString(sqlite_handle_->getLayoutVersionMajor()) + ");"); res.emplace_back( "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," + toString(sqlite_handle_->getLayoutVersionMinor()) + ");"); } return res; } // --------------------------------------------------------------------------- void DatabaseContext::Private::attachExtraDatabases( const std::vector &auxiliaryDatabasePaths) { auto l_handle = handle(); assert(l_handle); auto tables = run("SELECT name, type, sql FROM sqlite_master WHERE type IN " "('table', 'view') " "AND name NOT LIKE 'sqlite_stat%'"); struct TableStructure { std::string name{}; bool isTable = false; std::string sql{}; std::vector columns{}; }; std::vector tablesStructure; for (const auto &rowTable : tables) { TableStructure tableStructure; tableStructure.name = rowTable[0]; tableStructure.isTable = rowTable[1] == "table"; tableStructure.sql = rowTable[2]; auto tableInfo = run("PRAGMA table_info(\"" + replaceAll(tableStructure.name, "\"", "\"\"") + "\")"); for (const auto &rowCol : tableInfo) { const auto &colName = rowCol[1]; tableStructure.columns.push_back(colName); } tablesStructure.push_back(std::move(tableStructure)); } const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor(); const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor(); closeDB(); if (auxiliaryDatabasePaths.empty()) { open(databasePath_, pjCtxt()); return; } sqlite3 *sqlite_handle = nullptr; sqlite3_open_v2( ":memory:", &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr); if (!sqlite_handle) { throw FactoryException("cannot create in memory database"); } sqlite_handle_ = SQLiteHandle::initFromExisting( sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor); l_handle = sqlite_handle_; run("ATTACH DATABASE ? AS db_0", {databasePath_}); detach_ = true; int count = 1; for (const auto &otherDbPath : auxiliaryDatabasePaths) { const auto attachedDbName("db_" + toString(static_cast(count))); std::string sql = "ATTACH DATABASE ? AS "; sql += attachedDbName; count++; run(sql, {otherDbPath}); l_handle->checkDatabaseLayout(databasePath_, otherDbPath, attachedDbName + '.'); } for (const auto &tableStructure : tablesStructure) { if (tableStructure.isTable) { std::string sql("CREATE TEMP VIEW "); sql += tableStructure.name; sql += " AS "; for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) { std::string selectFromAux("SELECT "); bool firstCol = true; for (const auto &colName : tableStructure.columns) { if (!firstCol) { selectFromAux += ", "; } firstCol = false; selectFromAux += colName; } selectFromAux += " FROM db_"; selectFromAux += toString(static_cast(i)); selectFromAux += "."; selectFromAux += tableStructure.name; try { // Check that the request will succeed. In case of 'sparse' // databases... run(selectFromAux + " LIMIT 0"); if (i > 0) { if (tableStructure.name == "conversion_method") sql += " UNION "; else sql += " UNION ALL "; } sql += selectFromAux; } catch (const std::exception &) { } } run(sql); } else { run(replaceAll(tableStructure.sql, "CREATE VIEW", "CREATE TEMP VIEW")); } } } // --------------------------------------------------------------------------- SQLResultSet DatabaseContext::Private::run(const std::string &sql, const ListOfParams ¶meters, bool useMaxFloatPrecision) { auto l_handle = handle(); assert(l_handle); sqlite3_stmt *stmt = nullptr; auto iter = mapSqlToStatement_.find(sql); if (iter != mapSqlToStatement_.end()) { stmt = iter->second; sqlite3_reset(stmt); } else { if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(), static_cast(sql.size()), &stmt, nullptr) != SQLITE_OK) { throw FactoryException( std::string("SQLite error [ ") .append(sqlite3_errmsg(l_handle->handle())) .append(" ] on ") .append(sql)); } mapSqlToStatement_.insert( std::pair(sql, stmt)); } ++queryCounter_; return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision); } // --------------------------------------------------------------------------- static std::string formatStatement(const char *fmt, ...) { std::string res; va_list args; va_start(args, fmt); for (int i = 0; fmt[i] != '\0'; ++i) { if (fmt[i] == '%') { if (fmt[i + 1] == '%') { res += '%'; } else if (fmt[i + 1] == 'q') { const char *arg = va_arg(args, const char *); for (int j = 0; arg[j] != '\0'; ++j) { if (arg[j] == '\'') res += arg[j]; res += arg[j]; } } else if (fmt[i + 1] == 'Q') { const char *arg = va_arg(args, const char *); if (arg == nullptr) res += "NULL"; else { res += '\''; for (int j = 0; arg[j] != '\0'; ++j) { if (arg[j] == '\'') res += arg[j]; res += arg[j]; } res += '\''; } } else if (fmt[i + 1] == 's') { const char *arg = va_arg(args, const char *); res += arg; } else if (fmt[i + 1] == 'f') { const double arg = va_arg(args, double); res += toString(arg); } else if (fmt[i + 1] == 'd') { const int arg = va_arg(args, int); res += toString(arg); } else { va_end(args); throw FactoryException( "Unsupported formatter in formatStatement()"); } ++i; } else { res += fmt[i]; } } va_end(args); return res; } // --------------------------------------------------------------------------- void DatabaseContext::Private::appendSql( std::vector &sqlStatements, const std::string &sql) { sqlStatements.emplace_back(sql); char *errMsg = nullptr; if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr, &errMsg) != SQLITE_OK) { std::string s("Cannot execute " + sql); if (errMsg) { s += " : "; s += errMsg; } sqlite3_free(errMsg); throw FactoryException(s); } sqlite3_free(errMsg); } // --------------------------------------------------------------------------- static void identifyFromNameOrCode( const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj, std::function( const AuthorityFactoryNNPtr &authFactory, const std::string &)> instantiateFunc, AuthorityFactory::ObjectType objType, std::string &authName, std::string &code) { auto allowedAuthoritiesTmp(allowedAuthorities); allowedAuthoritiesTmp.emplace_back(authNameParent); for (const auto &id : obj->identifiers()) { try { const auto &idAuthName = *(id->codeSpace()); if (std::find(allowedAuthoritiesTmp.begin(), allowedAuthoritiesTmp.end(), idAuthName) != allowedAuthoritiesTmp.end()) { const auto factory = AuthorityFactory::create(dbContext, idAuthName); if (instantiateFunc(factory, id->code()) ->isEquivalentTo( obj.get(), util::IComparable::Criterion::EQUIVALENT)) { authName = idAuthName; code = id->code(); return; } } } catch (const std::exception &) { } } for (const auto &allowedAuthority : allowedAuthoritiesTmp) { const auto factory = AuthorityFactory::create(dbContext, allowedAuthority); const auto candidates = factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0); for (const auto &candidate : candidates) { const auto &ids = candidate->identifiers(); if (!ids.empty() && candidate->isEquivalentTo( obj.get(), util::IComparable::Criterion::EQUIVALENT)) { const auto &id = ids.front(); authName = *(id->codeSpace()); code = id->code(); return; } } } } // --------------------------------------------------------------------------- static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const datum::DatumEnsembleNNPtr &obj, std::string &authName, std::string &code) { const char *type = "geodetic_datum"; if (!obj->datums().empty() && dynamic_cast( obj->datums().front().get())) { type = "vertical_datum"; } const auto instantiateFunc = [&type](const AuthorityFactoryNNPtr &authFactory, const std::string &lCode) { return util::nn_static_pointer_cast( authFactory->createDatumEnsemble(lCode, type)); }; identifyFromNameOrCode( dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc, AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code); } // --------------------------------------------------------------------------- static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const datum::GeodeticReferenceFrameNNPtr &obj, std::string &authName, std::string &code) { const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, const std::string &lCode) { return util::nn_static_pointer_cast( authFactory->createGeodeticDatum(lCode)); }; identifyFromNameOrCode( dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc, AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code); } // --------------------------------------------------------------------------- static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const datum::EllipsoidNNPtr &obj, std::string &authName, std::string &code) { const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, const std::string &lCode) { return util::nn_static_pointer_cast( authFactory->createEllipsoid(lCode)); }; identifyFromNameOrCode( dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc, AuthorityFactory::ObjectType::ELLIPSOID, authName, code); } // --------------------------------------------------------------------------- static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const datum::PrimeMeridianNNPtr &obj, std::string &authName, std::string &code) { const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, const std::string &lCode) { return util::nn_static_pointer_cast( authFactory->createPrimeMeridian(lCode)); }; identifyFromNameOrCode( dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc, AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code); } // --------------------------------------------------------------------------- static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const datum::VerticalReferenceFrameNNPtr &obj, std::string &authName, std::string &code) { const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, const std::string &lCode) { return util::nn_static_pointer_cast( authFactory->createVerticalDatum(lCode)); }; identifyFromNameOrCode( dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc, AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code); } // --------------------------------------------------------------------------- static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, const std::vector &allowedAuthorities, const std::string &authNameParent, const datum::DatumNNPtr &obj, std::string &authName, std::string &code) { if (const auto geodeticDatum = util::nn_dynamic_pointer_cast(obj)) { identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent, NN_NO_CHECK(geodeticDatum), authName, code); } else if (const auto verticalDatum = util::nn_dynamic_pointer_cast( obj)) { identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent, NN_NO_CHECK(verticalDatum), authName, code); } else { throw FactoryException("Unhandled type of datum"); } } // --------------------------------------------------------------------------- static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) { if (dynamic_cast(obj.get())) { return CS_TYPE_ELLIPSOIDAL; } else if (dynamic_cast(obj.get())) { return CS_TYPE_CARTESIAN; } else if (dynamic_cast(obj.get())) { return CS_TYPE_VERTICAL; } return nullptr; } // --------------------------------------------------------------------------- std::string DatabaseContext::Private::findFreeCode(const std::string &tableName, const std::string &authName, const std::string &codePrototype) { std::string code(codePrototype); if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?", {authName, code}) .empty()) { return code; } for (int counter = 2; counter < 10; counter++) { code = codePrototype + '_' + toString(counter); if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?", {authName, code}) .empty()) { return code; } } // shouldn't happen hopefully... throw FactoryException("Cannot insert " + tableName + ": too many similar codes"); } // --------------------------------------------------------------------------- static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) { switch (unit.type()) { case common::UnitOfMeasure::Type::LINEAR: return "length"; case common::UnitOfMeasure::Type::ANGULAR: return "angle"; case common::UnitOfMeasure::Type::SCALE: return "scale"; case common::UnitOfMeasure::Type::TIME: return "time"; default: break; } return nullptr; } // --------------------------------------------------------------------------- void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &obj, std::string &authName, std::string &code) { // Identify quickly a few well-known units const double convFactor = obj.conversionToSI(); switch (obj.type()) { case common::UnitOfMeasure::Type::LINEAR: { if (convFactor == 1.0) { authName = metadata::Identifier::EPSG; code = "9001"; return; } break; } case common::UnitOfMeasure::Type::ANGULAR: { constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02; if (std::abs(convFactor - CONV_FACTOR_DEGREE) <= 1e-10 * CONV_FACTOR_DEGREE) { authName = metadata::Identifier::EPSG; code = "9102"; return; } break; } case common::UnitOfMeasure::Type::SCALE: { if (convFactor == 1.0) { authName = metadata::Identifier::EPSG; code = "9201"; return; } break; } default: break; } std::string sql("SELECT auth_name, code FROM unit_of_measure " "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor"); ListOfParams params{convFactor}; const char *type = getUnitDatabaseType(obj); if (type) { sql += " AND type = ?"; params.emplace_back(std::string(type)); } sql += " ORDER BY auth_name, code"; const auto res = run(sql, params); for (const auto &row : res) { const auto &rowAuthName = row[0]; const auto &rowCode = row[1]; const auto tmpAuthFactory = AuthorityFactory::create(dbContext, rowAuthName); try { tmpAuthFactory->createUnitOfMeasure(rowCode); authName = rowAuthName; code = rowCode; return; } catch (const std::exception &) { } } } // --------------------------------------------------------------------------- void DatabaseContext::Private::identifyOrInsert( const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit, const std::string &ownerAuthName, std::string &authName, std::string &code, std::vector &sqlStatements) { authName = unit.codeSpace(); code = unit.code(); if (authName.empty()) { identify(dbContext, unit, authName, code); } if (!authName.empty()) { return; } const char *type = getUnitDatabaseType(unit); if (type == nullptr) { throw FactoryException("Cannot insert this type of UnitOfMeasure"); } // Insert new record authName = ownerAuthName; const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_")); code = findFreeCode("unit_of_measure", authName, codePrototype); const auto sql = formatStatement( "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);", authName.c_str(), code.c_str(), unit.name().c_str(), type, unit.conversionToSI()); appendSql(sqlStatements, sql); } // --------------------------------------------------------------------------- void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj, std::string &authName, std::string &code) { const auto &axisList = obj->axisList(); if (axisList.size() == 1U && axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) && &(axisList[0]->direction()) == &cs::AxisDirection::UP && (axisList[0]->nameStr() == "Up" || axisList[0]->nameStr() == "Gravity-related height")) { // preferred coordinate system for gravity-related height authName = metadata::Identifier::EPSG; code = "6499"; return; } std::string sql( "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?"); ListOfParams params{static_cast(axisList.size())}; const char *type = getCSDatabaseType(obj); if (type) { sql += " AND type = ?"; params.emplace_back(std::string(type)); } sql += " ORDER BY auth_name, code"; const auto res = run(sql, params); for (const auto &row : res) { const auto &rowAuthName = row[0]; const auto &rowCode = row[1]; const auto tmpAuthFactory = AuthorityFactory::create(dbContext, rowAuthName); try { const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode); if (cs->_isEquivalentTo(obj.get(), util::IComparable::Criterion::EQUIVALENT)) { authName = rowAuthName; code = rowCode; if (authName == metadata::Identifier::EPSG && code == "4400") { // preferred coordinate system for cartesian // Easting, Northing return; } if (authName == metadata::Identifier::EPSG && code == "6422") { // preferred coordinate system for geographic lat, long return; } if (authName == metadata::Identifier::EPSG && code == "6423") { // preferred coordinate system for geographic lat, long, h return; } } } catch (const std::exception &) { } } } // --------------------------------------------------------------------------- void DatabaseContext::Private::identifyOrInsert( const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj, const std::string &ownerType, const std::string &ownerAuthName, const std::string &ownerCode, std::string &authName, std::string &code, std::vector &sqlStatements) { identify(dbContext, obj, authName, code); if (!authName.empty()) { return; } const char *type = getCSDatabaseType(obj); if (type == nullptr) { throw FactoryException("Cannot insert this type of CoordinateSystem"); } // Insert new record in coordinate_system authName = ownerAuthName; const std::string codePrototype("CS_" + ownerType + '_' + ownerCode); code = findFreeCode("coordinate_system", authName, codePrototype); const auto &axisList = obj->axisList(); { const auto sql = formatStatement( "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);", authName.c_str(), code.c_str(), type, static_cast(axisList.size())); appendSql(sqlStatements, sql); } // Insert new records for the axis for (int i = 0; i < static_cast(axisList.size()); ++i) { const auto &axis = axisList[i]; std::string uomAuthName; std::string uomCode; identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName, uomCode, sqlStatements); const auto sql = formatStatement( "INSERT INTO axis VALUES(" "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');", authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(), axis->nameStr().c_str(), axis->abbreviation().c_str(), axis->direction().toString().c_str(), authName.c_str(), code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str()); appendSql(sqlStatements, sql); } } // --------------------------------------------------------------------------- static void addAllowedAuthoritiesCond(const std::vector &allowedAuthorities, const std::string &authName, std::string &sql, ListOfParams ¶ms) { sql += "auth_name IN (?"; params.emplace_back(authName); for (const auto &allowedAuthority : allowedAuthorities) { sql += ",?"; params.emplace_back(allowedAuthority); } sql += ')'; } // --------------------------------------------------------------------------- void DatabaseContext::Private::identifyOrInsertUsages( const common::ObjectUsageNNPtr &obj, const std::string &tableName, const std::string &authName, const std::string &code, const std::vector &allowedAuthorities, std::vector &sqlStatements) { std::string usageCode("USAGE_"); const std::string upperTableName(toupper(tableName)); if (!starts_with(code, upperTableName)) { usageCode += upperTableName; usageCode += '_'; } usageCode += code; const auto &domains = obj->domains(); if (domains.empty()) { const auto sql = formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q'," "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');", authName.c_str(), usageCode.c_str(), tableName.c_str(), authName.c_str(), code.c_str()); appendSql(sqlStatements, sql); return; } int usageCounter = 1; for (const auto &domain : domains) { std::string scopeAuthName; std::string scopeCode; const auto &scope = domain->scope(); if (scope.has_value()) { std::string sql = "SELECT auth_name, code, " "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) " "AS order_idx " "FROM scope WHERE scope = ? AND deprecated = 0 AND "; ListOfParams params{*scope}; addAllowedAuthoritiesCond(allowedAuthorities, authName, sql, params); sql += " ORDER BY order_idx, auth_name, code"; const auto rows = run(sql, params); if (!rows.empty()) { const auto &row = rows.front(); scopeAuthName = row[0]; scopeCode = row[1]; } else { scopeAuthName = authName; scopeCode = "SCOPE_"; scopeCode += tableName; scopeCode += '_'; scopeCode += code; const auto sqlToInsert = formatStatement( "INSERT INTO scope VALUES('%q','%q','%q',0);", scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str()); appendSql(sqlStatements, sqlToInsert); } } else { scopeAuthName = "PROJ"; scopeCode = "SCOPE_UNKNOWN"; } std::string extentAuthName("PROJ"); std::string extentCode("EXTENT_UNKNOWN"); const auto &extent = domain->domainOfValidity(); if (extent) { const auto &geogElts = extent->geographicElements(); if (!geogElts.empty()) { const auto bbox = dynamic_cast( geogElts.front().get()); if (bbox) { std::string sql = "SELECT auth_name, code, " "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) " "AS order_idx " "FROM extent WHERE south_lat = ? AND north_lat = ? " "AND west_lon = ? AND east_lon = ? AND deprecated = 0 " "AND "; ListOfParams params{ bbox->southBoundLatitude(), bbox->northBoundLatitude(), bbox->westBoundLongitude(), bbox->eastBoundLongitude()}; addAllowedAuthoritiesCond(allowedAuthorities, authName, sql, params); sql += " ORDER BY order_idx, auth_name, code"; const auto rows = run(sql, params); if (!rows.empty()) { const auto &row = rows.front(); extentAuthName = row[0]; extentCode = row[1]; } else { extentAuthName = authName; extentCode = "EXTENT_"; extentCode += tableName; extentCode += '_'; extentCode += code; std::string description(*(extent->description())); if (description.empty()) { description = "unknown"; } const auto sqlToInsert = formatStatement( "INSERT INTO extent " "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);", extentAuthName.c_str(), extentCode.c_str(), description.c_str(), description.c_str(), bbox->southBoundLatitude(), bbox->northBoundLatitude(), bbox->westBoundLongitude(), bbox->eastBoundLongitude()); appendSql(sqlStatements, sqlToInsert); } } } } if (domains.size() > 1) { usageCode += '_'; usageCode += toString(usageCounter); } const auto sql = formatStatement( "INSERT INTO usage VALUES('%q','%q','%q','%q','%q'," "'%q','%q','%q','%q');", authName.c_str(), usageCode.c_str(), tableName.c_str(), authName.c_str(), code.c_str(), extentAuthName.c_str(), extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str()); appendSql(sqlStatements, sql); usageCounter++; } } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const datum::PrimeMeridianNNPtr &pm, const std::string &authName, const std::string &code, bool /*numericCode*/, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); // Check if the object is already known under that code std::string pmAuthName; std::string pmCode; identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName, pmCode); if (pmAuthName == authName && pmCode == code) { return {}; } std::vector sqlStatements; // Insert new record in prime_meridian table std::string uomAuthName; std::string uomCode; identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName, uomCode, sqlStatements); const auto sql = formatStatement( "INSERT INTO prime_meridian VALUES(" "'%q','%q','%q',%f,'%q','%q',0);", authName.c_str(), code.c_str(), pm->nameStr().c_str(), pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str()); appendSql(sqlStatements, sql); return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName, const std::string &code, bool /*numericCode*/, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); // Check if the object is already known under that code std::string ellipsoidAuthName; std::string ellipsoidCode; identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid, ellipsoidAuthName, ellipsoidCode); if (ellipsoidAuthName == authName && ellipsoidCode == code) { return {}; } std::vector sqlStatements; // Find or insert celestial body const auto &semiMajorAxis = ellipsoid->semiMajorAxis(); const double semiMajorAxisMetre = semiMajorAxis.getSIValue(); constexpr double tolerance = 0.005; std::string bodyAuthName; std::string bodyCode; auto res = run("SELECT auth_name, code, " "(ABS(semi_major_axis - ?) / semi_major_axis ) " "AS rel_error FROM celestial_body WHERE rel_error <= ?", {semiMajorAxisMetre, tolerance}); if (!res.empty()) { const auto &row = res.front(); bodyAuthName = row[0]; bodyCode = row[1]; } else { bodyAuthName = authName; bodyCode = "BODY_" + code; const auto bodyName = "Body of " + ellipsoid->nameStr(); const auto sql = formatStatement( "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);", bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(), semiMajorAxisMetre); appendSql(sqlStatements, sql); } // Insert new record in ellipsoid table std::string uomAuthName; std::string uomCode; identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode, sqlStatements); std::string invFlattening("NULL"); std::string semiMinorAxis("NULL"); if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) { semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value()); } else { invFlattening = toString(ellipsoid->computedInverseFlattening()); } const auto sql = formatStatement( "INSERT INTO ellipsoid VALUES(" "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);", authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(), "", // description bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(), uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(), semiMinorAxis.c_str()); appendSql(sqlStatements, sql); return sqlStatements; } // --------------------------------------------------------------------------- static std::string anchorEpochToStr(double val) { constexpr int BUF_SIZE = 16; char szBuffer[BUF_SIZE]; sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val); return szBuffer; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const datum::GeodeticReferenceFrameNNPtr &datum, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); // Check if the object is already known under that code std::string datumAuthName; std::string datumCode; identifyFromNameOrCode(self, allowedAuthorities, authName, datum, datumAuthName, datumCode); if (datumAuthName == authName && datumCode == code) { return {}; } std::vector sqlStatements; // Find or insert ellipsoid std::string ellipsoidAuthName; std::string ellipsoidCode; const auto &ellipsoidOfDatum = datum->ellipsoid(); identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode); if (ellipsoidAuthName.empty()) { ellipsoidAuthName = authName; if (numericCode) { ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum, ellipsoidAuthName, true); } else { ellipsoidCode = "ELLPS_" + code; } sqlStatements = self->getInsertStatementsFor( ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode, allowedAuthorities); } // Find or insert prime meridian std::string pmAuthName; std::string pmCode; const auto &pmOfDatum = datum->primeMeridian(); identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum, pmAuthName, pmCode); if (pmAuthName.empty()) { pmAuthName = authName; if (numericCode) { pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true); } else { pmCode = "PM_" + code; } const auto sqlStatementsTmp = self->getInsertStatementsFor( pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities); sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(), sqlStatementsTmp.end()); } // Insert new record in geodetic_datum table std::string publicationDate("NULL"); if (datum->publicationDate().has_value()) { publicationDate = '\''; publicationDate += replaceAll(datum->publicationDate()->toString(), "'", "''"); publicationDate += '\''; } std::string frameReferenceEpoch("NULL"); const auto dynamicDatum = dynamic_cast(datum.get()); if (dynamicDatum) { frameReferenceEpoch = toString(dynamicDatum->frameReferenceEpoch().value()); } const std::string anchor(*(datum->anchorDefinition())); const util::optional &anchorEpoch = datum->anchorEpoch(); const auto sql = formatStatement( "INSERT INTO geodetic_datum VALUES(" "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);", authName.c_str(), code.c_str(), datum->nameStr().c_str(), "", // description ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(), pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(), anchor.empty() ? nullptr : anchor.c_str(), anchorEpoch.has_value() ? anchorEpochToStr( anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR)) .c_str() : "NULL"); appendSql(sqlStatements, sql); identifyOrInsertUsages(datum, "geodetic_datum", authName, code, allowedAuthorities, sqlStatements); return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); // Check if the object is already known under that code std::string datumAuthName; std::string datumCode; identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble, datumAuthName, datumCode); if (datumAuthName == authName && datumCode == code) { return {}; } std::vector sqlStatements; const auto &members = ensemble->datums(); assert(!members.empty()); int counter = 1; std::vector> membersId; for (const auto &member : members) { std::string memberAuthName; std::string memberCode; identifyFromNameOrCode(self, allowedAuthorities, authName, member, memberAuthName, memberCode); if (memberAuthName.empty()) { memberAuthName = authName; if (numericCode) { memberCode = self->suggestsCodeFor(member, memberAuthName, true); } else { memberCode = "MEMBER_" + toString(counter) + "_OF_" + code; } const auto sqlStatementsTmp = self->getInsertStatementsFor(member, memberAuthName, memberCode, numericCode, allowedAuthorities); sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(), sqlStatementsTmp.end()); } membersId.emplace_back( std::pair(memberAuthName, memberCode)); ++counter; } const bool isGeodetic = util::nn_dynamic_pointer_cast( members.front()) != nullptr; // Insert new record in geodetic_datum/vertical_datum table const double accuracy = c_locale_stod(ensemble->positionalAccuracy()->value()); if (isGeodetic) { const auto firstDatum = AuthorityFactory::create(self, membersId.front().first) ->createGeodeticDatum(membersId.front().second); const auto &ellipsoid = firstDatum->ellipsoid(); const auto &ellipsoidIds = ellipsoid->identifiers(); assert(!ellipsoidIds.empty()); const std::string &ellipsoidAuthName = *(ellipsoidIds.front()->codeSpace()); const std::string &ellipsoidCode = ellipsoidIds.front()->code(); const auto &pm = firstDatum->primeMeridian(); const auto &pmIds = pm->identifiers(); assert(!pmIds.empty()); const std::string &pmAuthName = *(pmIds.front()->codeSpace()); const std::string &pmCode = pmIds.front()->code(); const std::string anchor(*(firstDatum->anchorDefinition())); const util::optional &anchorEpoch = firstDatum->anchorEpoch(); const auto sql = formatStatement( "INSERT INTO geodetic_datum VALUES(" "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);", authName.c_str(), code.c_str(), ensemble->nameStr().c_str(), "", // description ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(), pmCode.c_str(), accuracy, anchor.empty() ? nullptr : anchor.c_str(), anchorEpoch.has_value() ? anchorEpochToStr( anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR)) .c_str() : "NULL"); appendSql(sqlStatements, sql); } else { const auto firstDatum = AuthorityFactory::create(self, membersId.front().first) ->createVerticalDatum(membersId.front().second); const std::string anchor(*(firstDatum->anchorDefinition())); const util::optional &anchorEpoch = firstDatum->anchorEpoch(); const auto sql = formatStatement( "INSERT INTO vertical_datum VALUES(" "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);", authName.c_str(), code.c_str(), ensemble->nameStr().c_str(), "", // description accuracy, anchor.empty() ? nullptr : anchor.c_str(), anchorEpoch.has_value() ? anchorEpochToStr( anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR)) .c_str() : "NULL"); appendSql(sqlStatements, sql); } identifyOrInsertUsages(ensemble, isGeodetic ? "geodetic_datum" : "vertical_datum", authName, code, allowedAuthorities, sqlStatements); const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member" : "vertical_datum_ensemble_member"; counter = 1; for (const auto &authCodePair : membersId) { const auto sql = formatStatement( "INSERT INTO %s VALUES(" "'%q','%q','%q','%q',%d);", tableName, authName.c_str(), code.c_str(), authCodePair.first.c_str(), authCodePair.second.c_str(), counter); appendSql(sqlStatements, sql); ++counter; } return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const crs::GeodeticCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); std::vector sqlStatements; // Find or insert datum/datum ensemble std::string datumAuthName; std::string datumCode; const auto &ensemble = crs->datumEnsemble(); if (ensemble) { const auto ensembleNN = NN_NO_CHECK(ensemble); identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN, datumAuthName, datumCode); if (datumAuthName.empty()) { datumAuthName = authName; if (numericCode) { datumCode = self->suggestsCodeFor(ensembleNN, datumAuthName, true); } else { datumCode = "GEODETIC_DATUM_" + code; } sqlStatements = self->getInsertStatementsFor( ensembleNN, datumAuthName, datumCode, numericCode, allowedAuthorities); } } else { const auto &datum = crs->datum(); assert(datum); const auto datumNN = NN_NO_CHECK(datum); identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN, datumAuthName, datumCode); if (datumAuthName.empty()) { datumAuthName = authName; if (numericCode) { datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true); } else { datumCode = "GEODETIC_DATUM_" + code; } sqlStatements = self->getInsertStatementsFor(datumNN, datumAuthName, datumCode, numericCode, allowedAuthorities); } } // Find or insert coordinate system const auto &coordinateSystem = crs->coordinateSystem(); std::string csAuthName; std::string csCode; identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code, csAuthName, csCode, sqlStatements); const char *type = GEOG_2D; if (coordinateSystem->axisList().size() == 3) { if (dynamic_cast(crs.get())) { type = GEOG_3D; } else { type = GEOCENTRIC; } } // Insert new record in geodetic_crs table const auto sql = formatStatement("INSERT INTO geodetic_crs VALUES(" "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);", authName.c_str(), code.c_str(), crs->nameStr().c_str(), "", // description type, csAuthName.c_str(), csCode.c_str(), datumAuthName.c_str(), datumCode.c_str()); appendSql(sqlStatements, sql); identifyOrInsertUsages(crs, "geodetic_crs", authName, code, allowedAuthorities, sqlStatements); return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const crs::ProjectedCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); std::vector sqlStatements; // Find or insert base geodetic CRS const auto &baseCRS = crs->baseCRS(); std::string geodAuthName; std::string geodCode; auto allowedAuthoritiesTmp(allowedAuthorities); allowedAuthoritiesTmp.emplace_back(authName); for (const auto &allowedAuthority : allowedAuthoritiesTmp) { const auto factory = AuthorityFactory::create(self, allowedAuthority); const auto candidates = baseCRS->identify(factory); for (const auto &candidate : candidates) { if (candidate.second == 100) { const auto &ids = candidate.first->identifiers(); if (!ids.empty()) { const auto &id = ids.front(); geodAuthName = *(id->codeSpace()); geodCode = id->code(); break; } } if (!geodAuthName.empty()) { break; } } } if (geodAuthName.empty()) { geodAuthName = authName; geodCode = "GEODETIC_CRS_" + code; sqlStatements = self->getInsertStatementsFor( baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities); } // Insert new record in conversion table const auto &conversion = crs->derivingConversionRef(); std::string convAuthName(authName); std::string convCode("CONVERSION_" + code); if (numericCode) { convCode = self->suggestsCodeFor(conversion, convAuthName, true); } { const auto &method = conversion->method(); const auto &methodIds = method->identifiers(); std::string methodAuthName; std::string methodCode; const operation::MethodMapping *methodMapping = nullptr; if (methodIds.empty()) { const int epsgCode = method->getEPSGCode(); if (epsgCode > 0) { methodAuthName = metadata::Identifier::EPSG; methodCode = toString(epsgCode); } else { const auto &methodName = method->nameStr(); size_t nProjectionMethodMappings = 0; const auto projectionMethodMappings = operation::getProjectionMethodMappings( nProjectionMethodMappings); for (size_t i = 0; i < nProjectionMethodMappings; ++i) { const auto &mapping = projectionMethodMappings[i]; if (metadata::Identifier::isEquivalentName( mapping.wkt2_name, methodName.c_str())) { methodMapping = &mapping; } } if (methodMapping == nullptr || methodMapping->proj_name_main == nullptr) { throw FactoryException("Cannot insert projection with " "method without identifier"); } methodAuthName = "PROJ"; methodCode = methodMapping->proj_name_main; if (methodMapping->proj_name_aux) { methodCode += ' '; methodCode += methodMapping->proj_name_aux; } } } else { const auto &methodId = methodIds.front(); methodAuthName = *(methodId->codeSpace()); methodCode = methodId->code(); } auto sql = formatStatement("INSERT INTO conversion VALUES(" "'%q','%q','%q','','%q','%q','%q'", convAuthName.c_str(), convCode.c_str(), conversion->nameStr().c_str(), methodAuthName.c_str(), methodCode.c_str(), method->nameStr().c_str()); const auto &srcValues = conversion->parameterValues(); if (srcValues.size() > N_MAX_PARAMS) { throw FactoryException("Cannot insert projection with more than " + toString(static_cast(N_MAX_PARAMS)) + " parameters"); } std::vector values; if (methodMapping == nullptr) { if (methodAuthName == metadata::Identifier::EPSG) { methodMapping = operation::getMapping(atoi(methodCode.c_str())); } else { methodMapping = operation::getMapping(method->nameStr().c_str()); } } if (methodMapping != nullptr) { // Re-order projection parameters in their reference order for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) { for (size_t i = 0; i < srcValues.size(); ++i) { auto opParamValue = dynamic_cast< const operation::OperationParameterValue *>( srcValues[i].get()); if (!opParamValue) { throw FactoryException("Cannot insert projection with " "non-OperationParameterValue"); } if (methodMapping->params[j]->wkt2_name && opParamValue->parameter()->nameStr() == methodMapping->params[j]->wkt2_name) { values.emplace_back(srcValues[i]); } } } } if (values.size() != srcValues.size()) { values = srcValues; } for (const auto &genOpParamvalue : values) { auto opParamValue = dynamic_cast( genOpParamvalue.get()); if (!opParamValue) { throw FactoryException("Cannot insert projection with " "non-OperationParameterValue"); } const auto ¶m = opParamValue->parameter(); const auto ¶mIds = param->identifiers(); std::string paramAuthName; std::string paramCode; if (paramIds.empty()) { const int paramEPSGCode = param->getEPSGCode(); if (paramEPSGCode == 0) { throw FactoryException( "Cannot insert projection with method parameter " "without identifier"); } paramAuthName = metadata::Identifier::EPSG; paramCode = toString(paramEPSGCode); } else { const auto ¶mId = paramIds.front(); paramAuthName = *(paramId->codeSpace()); paramCode = paramId->code(); } const auto &value = opParamValue->parameterValue()->value(); const auto &unit = value.unit(); std::string uomAuthName; std::string uomCode; identifyOrInsert(self, unit, authName, uomAuthName, uomCode, sqlStatements); sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'", paramAuthName.c_str(), paramCode.c_str(), param->nameStr().c_str(), value.value(), uomAuthName.c_str(), uomCode.c_str()); } for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) { sql += ",NULL,NULL,NULL,NULL,NULL,NULL"; } sql += ",0);"; appendSql(sqlStatements, sql); identifyOrInsertUsages(crs, "conversion", convAuthName, convCode, allowedAuthorities, sqlStatements); } // Find or insert coordinate system const auto &coordinateSystem = crs->coordinateSystem(); std::string csAuthName; std::string csCode; identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code, csAuthName, csCode, sqlStatements); // Insert new record in projected_crs table const auto sql = formatStatement( "INSERT INTO projected_crs VALUES(" "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);", authName.c_str(), code.c_str(), crs->nameStr().c_str(), "", // description csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(), geodCode.c_str(), convAuthName.c_str(), convCode.c_str()); appendSql(sqlStatements, sql); identifyOrInsertUsages(crs, "projected_crs", authName, code, allowedAuthorities, sqlStatements); return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const datum::VerticalReferenceFrameNNPtr &datum, const std::string &authName, const std::string &code, bool /* numericCode */, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); std::vector sqlStatements; // Check if the object is already known under that code std::string datumAuthName; std::string datumCode; identifyFromNameOrCode(self, allowedAuthorities, authName, datum, datumAuthName, datumCode); if (datumAuthName == authName && datumCode == code) { return {}; } // Insert new record in vertical_datum table std::string publicationDate("NULL"); if (datum->publicationDate().has_value()) { publicationDate = '\''; publicationDate += replaceAll(datum->publicationDate()->toString(), "'", "''"); publicationDate += '\''; } std::string frameReferenceEpoch("NULL"); const auto dynamicDatum = dynamic_cast(datum.get()); if (dynamicDatum) { frameReferenceEpoch = toString(dynamicDatum->frameReferenceEpoch().value()); } const std::string anchor(*(datum->anchorDefinition())); const util::optional &anchorEpoch = datum->anchorEpoch(); const auto sql = formatStatement( "INSERT INTO vertical_datum VALUES(" "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);", authName.c_str(), code.c_str(), datum->nameStr().c_str(), "", // description publicationDate.c_str(), frameReferenceEpoch.c_str(), anchor.empty() ? nullptr : anchor.c_str(), anchorEpoch.has_value() ? anchorEpochToStr( anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR)) .c_str() : "NULL"); appendSql(sqlStatements, sql); identifyOrInsertUsages(datum, "vertical_datum", authName, code, allowedAuthorities, sqlStatements); return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const crs::VerticalCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); std::vector sqlStatements; // Find or insert datum/datum ensemble std::string datumAuthName; std::string datumCode; const auto &ensemble = crs->datumEnsemble(); if (ensemble) { const auto ensembleNN = NN_NO_CHECK(ensemble); identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN, datumAuthName, datumCode); if (datumAuthName.empty()) { datumAuthName = authName; if (numericCode) { datumCode = self->suggestsCodeFor(ensembleNN, datumAuthName, true); } else { datumCode = "VERTICAL_DATUM_" + code; } sqlStatements = self->getInsertStatementsFor( ensembleNN, datumAuthName, datumCode, numericCode, allowedAuthorities); } } else { const auto &datum = crs->datum(); assert(datum); const auto datumNN = NN_NO_CHECK(datum); identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN, datumAuthName, datumCode); if (datumAuthName.empty()) { datumAuthName = authName; if (numericCode) { datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true); } else { datumCode = "VERTICAL_DATUM_" + code; } sqlStatements = self->getInsertStatementsFor(datumNN, datumAuthName, datumCode, numericCode, allowedAuthorities); } } // Find or insert coordinate system const auto &coordinateSystem = crs->coordinateSystem(); std::string csAuthName; std::string csCode; identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code, csAuthName, csCode, sqlStatements); // Insert new record in vertical_crs table const auto sql = formatStatement("INSERT INTO vertical_crs VALUES(" "'%q','%q','%q','%q','%q','%q','%q','%q',0);", authName.c_str(), code.c_str(), crs->nameStr().c_str(), "", // description csAuthName.c_str(), csCode.c_str(), datumAuthName.c_str(), datumCode.c_str()); appendSql(sqlStatements, sql); identifyOrInsertUsages(crs, "vertical_crs", authName, code, allowedAuthorities, sqlStatements); return sqlStatements; } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getInsertStatementsFor( const crs::CompoundCRSNNPtr &crs, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { const auto self = NN_NO_CHECK(self_.lock()); std::vector sqlStatements; int counter = 1; std::vector> componentsId; const auto &components = crs->componentReferenceSystems(); if (components.size() != 2) { throw FactoryException( "Cannot insert compound CRS with number of components != 2"); } auto allowedAuthoritiesTmp(allowedAuthorities); allowedAuthoritiesTmp.emplace_back(authName); for (const auto &component : components) { std::string compAuthName; std::string compCode; for (const auto &allowedAuthority : allowedAuthoritiesTmp) { const auto factory = AuthorityFactory::create(self, allowedAuthority); const auto candidates = component->identify(factory); for (const auto &candidate : candidates) { if (candidate.second == 100) { const auto &ids = candidate.first->identifiers(); if (!ids.empty()) { const auto &id = ids.front(); compAuthName = *(id->codeSpace()); compCode = id->code(); break; } } if (!compAuthName.empty()) { break; } } } if (compAuthName.empty()) { compAuthName = authName; if (numericCode) { compCode = self->suggestsCodeFor(component, compAuthName, true); } else { compCode = "COMPONENT_" + code + '_' + toString(counter); } const auto sqlStatementsTmp = self->getInsertStatementsFor(component, compAuthName, compCode, numericCode, allowedAuthorities); sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(), sqlStatementsTmp.end()); } componentsId.emplace_back( std::pair(compAuthName, compCode)); ++counter; } // Insert new record in compound_crs table const auto sql = formatStatement( "INSERT INTO compound_crs VALUES(" "'%q','%q','%q','%q','%q','%q','%q','%q',0);", authName.c_str(), code.c_str(), crs->nameStr().c_str(), "", // description componentsId[0].first.c_str(), componentsId[0].second.c_str(), componentsId[1].first.c_str(), componentsId[1].second.c_str()); appendSql(sqlStatements, sql); identifyOrInsertUsages(crs, "compound_crs", authName, code, allowedAuthorities, sqlStatements); return sqlStatements; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DatabaseContext::~DatabaseContext() { try { stopInsertStatementsSession(); } catch (const std::exception &) { } } //! @endcond // --------------------------------------------------------------------------- DatabaseContext::DatabaseContext() : d(std::make_unique()) {} // --------------------------------------------------------------------------- /** \brief Instantiate a database context. * * This database context should be used only by one thread at a time. * * @param databasePath Path and filename of the database. Might be empty * string for the default rules to locate the default proj.db * @param auxiliaryDatabasePaths Path and filename of auxiliary databases. * Might be empty. * Starting with PROJ 8.1, if this parameter is an empty array, * the PROJ_AUX_DB environment variable will be used, if set. * It must contain one or several paths. If several paths are * provided, they must be separated by the colon (:) character on Unix, and * on Windows, by the semi-colon (;) character. * @param ctx Context used for file search. * @throw FactoryException if the database cannot be opened. */ DatabaseContextNNPtr DatabaseContext::create(const std::string &databasePath, const std::vector &auxiliaryDatabasePaths, PJ_CONTEXT *ctx) { auto dbCtx = DatabaseContext::nn_make_shared(); auto dbCtxPrivate = dbCtx->getPrivate(); dbCtxPrivate->open(databasePath, ctx); auto auxDbs(auxiliaryDatabasePaths); if (auxDbs.empty()) { const char *auxDbStr = getenv("PROJ_AUX_DB"); if (auxDbStr) { #ifdef _WIN32 const char *delim = ";"; #else const char *delim = ":"; #endif auxDbs = split(auxDbStr, delim); } } if (!auxDbs.empty()) { dbCtxPrivate->attachExtraDatabases(auxDbs); dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs); } dbCtxPrivate->self_ = dbCtx.as_nullable(); return dbCtx; } // --------------------------------------------------------------------------- /** \brief Return the list of authorities used in the database. */ std::set DatabaseContext::getAuthorities() const { auto res = d->run("SELECT auth_name FROM authority_list"); std::set list; for (const auto &row : res) { list.insert(row[0]); } return list; } // --------------------------------------------------------------------------- /** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER, * CREATE VIEW) needed to initialize a new database. */ std::vector DatabaseContext::getDatabaseStructure() const { return d->getDatabaseStructure(); } // --------------------------------------------------------------------------- /** \brief Return the path to the database. */ const std::string &DatabaseContext::getPath() const { return d->getPath(); } // --------------------------------------------------------------------------- /** \brief Return a metadata item. * * Value remains valid while this is alive and to the next call to getMetadata */ const char *DatabaseContext::getMetadata(const char *key) const { auto res = d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)}); if (res.empty()) { return nullptr; } d->lastMetadataValue_ = res.front()[0]; return d->lastMetadataValue_.c_str(); } // --------------------------------------------------------------------------- /** \brief Starts a session for getInsertStatementsFor() * * Starts a new session for one or several calls to getInsertStatementsFor(). * An insertion session guarantees that the inserted objects will not create * conflicting intermediate objects. * * The session must be stopped with stopInsertStatementsSession(). * * Only one session may be active at a time for a given database context. * * @throw FactoryException in case of error. * @since 8.1 */ void DatabaseContext::startInsertStatementsSession() { if (d->memoryDbHandle_) { throw FactoryException( "startInsertStatementsSession() cannot be invoked until " "stopInsertStatementsSession() is."); } d->memoryDbForInsertPath_.clear(); const auto sqlStatements = getDatabaseStructure(); // Create a in-memory temporary sqlite3 database std::ostringstream buffer; buffer << "file:temp_db_for_insert_statements_"; buffer << this; buffer << ".db?mode=memory&cache=shared"; d->memoryDbForInsertPath_ = buffer.str(); sqlite3 *memoryDbHandle = nullptr; sqlite3_open_v2( d->memoryDbForInsertPath_.c_str(), &memoryDbHandle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr); if (memoryDbHandle == nullptr) { throw FactoryException("Cannot create in-memory database"); } d->memoryDbHandle_ = SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true); // Fill the structure of this database for (const auto &sql : sqlStatements) { char *errmsg = nullptr; if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr, &errmsg) != SQLITE_OK) { const auto sErrMsg = "Cannot execute " + sql + ": " + (errmsg ? errmsg : ""); sqlite3_free(errmsg); throw FactoryException(sErrMsg); } sqlite3_free(errmsg); } // Attach this database to the current one(s) auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_); auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_); d->attachExtraDatabases(auxiliaryDatabasePaths); } // --------------------------------------------------------------------------- /** \brief Suggests a database code for the passed object. * * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble, * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion. * * @param object Object for which to suggest a code. * @param authName Authority name into which the object will be inserted. * @param numericCode Whether the code should be numeric, or derived from the * object name. * @return the suggested code, that is guaranteed to not conflict with an * existing one. * * @throw FactoryException in case of error. * @since 8.1 */ std::string DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object, const std::string &authName, bool numericCode) { const char *tableName = "prime_meridian"; if (dynamic_cast(object.get())) { // tableName = "prime_meridian"; } else if (dynamic_cast(object.get())) { tableName = "ellipsoid"; } else if (dynamic_cast( object.get())) { tableName = "geodetic_datum"; } else if (dynamic_cast( object.get())) { tableName = "vertical_datum"; } else if (const auto ensemble = dynamic_cast(object.get())) { const auto &datums = ensemble->datums(); if (!datums.empty() && dynamic_cast( datums[0].get())) { tableName = "geodetic_datum"; } else { tableName = "vertical_datum"; } } else if (const auto boundCRS = dynamic_cast(object.get())) { return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode); } else if (dynamic_cast(object.get())) { tableName = "crs_view"; } else if (dynamic_cast(object.get())) { tableName = "conversion"; } else { throw FactoryException("suggestsCodeFor(): unhandled type of object"); } if (numericCode) { std::string sql("SELECT MAX(code) FROM "); sql += tableName; sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' " "AND upper(code) = lower(code)"; const auto res = d->run(sql, {authName}); if (res.empty()) { return "1"; } return toString(atoi(res.front()[0].c_str()) + 1); } std::string code; code.reserve(object->nameStr().size()); bool insertUnderscore = false; for (const auto ch : toupper(object->nameStr())) { if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) { if (insertUnderscore && code.back() != '_') code += '_'; code += ch; insertUnderscore = false; } else { insertUnderscore = true; } } return d->findFreeCode(tableName, authName, code); } // --------------------------------------------------------------------------- /** \brief Returns SQL statements needed to insert the passed object into the * database. * * startInsertStatementsSession() must have been called previously. * * @param object The object to insert into the database. Currently only * PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS, * VerticalCRS, CompoundCRS or BoundCRS are supported. * @param authName Authority name into which the object will be inserted. * @param code Code with which the object will be inserted. * @param numericCode Whether intermediate objects that can be created should * use numeric codes (true), or may be alphanumeric (false) * @param allowedAuthorities Authorities to which intermediate objects are * allowed to refer to. authName will be implicitly * added to it. Note that unit, coordinate * systems, projection methods and parameters will in * any case be allowed to refer to EPSG. * @throw FactoryException in case of error. * @since 8.1 */ std::vector DatabaseContext::getInsertStatementsFor( const common::IdentifiedObjectNNPtr &object, const std::string &authName, const std::string &code, bool numericCode, const std::vector &allowedAuthorities) { if (d->memoryDbHandle_ == nullptr) { throw FactoryException( "startInsertStatementsSession() should be invoked first"); } const auto crs = util::nn_dynamic_pointer_cast(object); if (crs) { // Check if the object is already known under that code const auto self = NN_NO_CHECK(d->self_.lock()); auto allowedAuthoritiesTmp(allowedAuthorities); allowedAuthoritiesTmp.emplace_back(authName); for (const auto &allowedAuthority : allowedAuthoritiesTmp) { const auto factory = AuthorityFactory::create(self, allowedAuthority); const auto candidates = crs->identify(factory); for (const auto &candidate : candidates) { if (candidate.second == 100) { const auto &ids = candidate.first->identifiers(); for (const auto &id : ids) { if (*(id->codeSpace()) == authName && id->code() == code) { return {}; } } } } } } if (const auto pm = util::nn_dynamic_pointer_cast(object)) { return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code, numericCode, allowedAuthorities); } else if (const auto ellipsoid = util::nn_dynamic_pointer_cast(object)) { return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code, numericCode, allowedAuthorities); } else if (const auto geodeticDatum = util::nn_dynamic_pointer_cast( object)) { return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName, code, numericCode, allowedAuthorities); } else if (const auto ensemble = util::nn_dynamic_pointer_cast(object)) { return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code, numericCode, allowedAuthorities); } else if (const auto geodCRS = std::dynamic_pointer_cast(crs)) { return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code, numericCode, allowedAuthorities); } else if (const auto projCRS = std::dynamic_pointer_cast(crs)) { return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code, numericCode, allowedAuthorities); } else if (const auto verticalDatum = util::nn_dynamic_pointer_cast( object)) { return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName, code, numericCode, allowedAuthorities); } else if (const auto vertCRS = std::dynamic_pointer_cast(crs)) { return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code, numericCode, allowedAuthorities); } else if (const auto compoundCRS = std::dynamic_pointer_cast(crs)) { return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName, code, numericCode, allowedAuthorities); } else if (const auto boundCRS = std::dynamic_pointer_cast(crs)) { return getInsertStatementsFor(boundCRS->baseCRS(), authName, code, numericCode, allowedAuthorities); } else { throw FactoryException( "getInsertStatementsFor(): unhandled type of object"); } } // --------------------------------------------------------------------------- /** \brief Stops an insertion session started with * startInsertStatementsSession() * * @since 8.1 */ void DatabaseContext::stopInsertStatementsSession() { if (d->memoryDbHandle_) { d->clearCaches(); d->attachExtraDatabases(d->auxiliaryDatabasePaths_); d->memoryDbHandle_.reset(); d->memoryDbForInsertPath_.clear(); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) { auto ctxt = DatabaseContext::nn_make_shared(); ctxt->getPrivate()->setHandle(static_cast(sqlite_handle)); return ctxt; } // --------------------------------------------------------------------------- void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); } // --------------------------------------------------------------------------- bool DatabaseContext::lookForGridAlternative(const std::string &officialName, std::string &projFilename, std::string &projFormat, bool &inverse) const { auto res = d->run( "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM " "grid_alternatives WHERE original_grid_name = ? AND " "proj_grid_name <> ''", {officialName}); if (res.empty()) { return false; } const auto &row = res.front(); projFilename = row[0]; projFormat = row[1]; inverse = row[2] == "1"; return true; } // --------------------------------------------------------------------------- static std::string makeCachedGridKey(const std::string &projFilename, bool networkEnabled, bool considerKnownGridsAsAvailable) { std::string key(projFilename); key += networkEnabled ? "true" : "false"; key += considerKnownGridsAsAvailable ? "true" : "false"; return key; } // --------------------------------------------------------------------------- /** Invalidates information related to projFilename that might have been * previously cached by lookForGridInfo(). * * This is useful when downloading a new grid during a session. */ void DatabaseContext::invalidateGridInfo(const std::string &projFilename) { d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, false)); d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, true)); d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, false)); d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, true)); } // --------------------------------------------------------------------------- bool DatabaseContext::lookForGridInfo( const std::string &projFilename, bool considerKnownGridsAsAvailable, std::string &fullFilename, std::string &packageName, std::string &url, bool &directDownload, bool &openLicense, bool &gridAvailable) const { Private::GridInfoCache info; if (projFilename == "null") { // Special case for implicit "null" grid. fullFilename.clear(); packageName.clear(); url.clear(); directDownload = false; openLicense = true; gridAvailable = true; return true; } auto ctxt = d->pjCtxt(); if (ctxt == nullptr) { ctxt = pj_get_default_ctx(); d->setPjCtxt(ctxt); } const std::string key(makeCachedGridKey( projFilename, proj_context_is_network_enabled(ctxt) != false, considerKnownGridsAsAvailable)); if (d->getGridInfoFromCache(key, info)) { fullFilename = info.fullFilename; packageName = info.packageName; url = info.url; directDownload = info.directDownload; openLicense = info.openLicense; gridAvailable = info.gridAvailable; return info.found; } fullFilename.clear(); packageName.clear(); url.clear(); openLicense = false; directDownload = false; gridAvailable = false; const auto resolveFullFilename = [ctxt, &fullFilename, &projFilename]() { fullFilename.resize(2048); const int errno_before = proj_context_errno(ctxt); bool lGridAvailable = NS_PROJ::FileManager::open_resource_file( ctxt, projFilename.c_str(), &fullFilename[0], fullFilename.size() - 1) != nullptr; proj_context_errno_set(ctxt, errno_before); fullFilename.resize(strlen(fullFilename.c_str())); return lGridAvailable; }; auto res = d->run("SELECT " "grid_packages.package_name, " "grid_alternatives.url, " "grid_packages.url AS package_url, " "grid_alternatives.open_license, " "grid_packages.open_license AS package_open_license, " "grid_alternatives.direct_download, " "grid_packages.direct_download AS package_direct_download, " "grid_alternatives.proj_grid_name, " "grid_alternatives.old_proj_grid_name " "FROM grid_alternatives " "LEFT JOIN grid_packages ON " "grid_alternatives.package_name = grid_packages.package_name " "WHERE proj_grid_name = ? OR old_proj_grid_name = ?", {projFilename, projFilename}); bool ret = !res.empty(); if (ret) { const auto &row = res.front(); packageName = std::move(row[0]); url = row[1].empty() ? std::move(row[2]) : std::move(row[1]); openLicense = (row[3].empty() ? row[4] : row[3]) == "1"; directDownload = (row[5].empty() ? row[6] : row[5]) == "1"; const auto &proj_grid_name = row[7]; const auto &old_proj_grid_name = row[8]; if (proj_grid_name != old_proj_grid_name && old_proj_grid_name == projFilename) { std::string fullFilenameNewName; fullFilenameNewName.resize(2048); const int errno_before = proj_context_errno(ctxt); bool gridAvailableWithNewName = pj_find_file(ctxt, proj_grid_name.c_str(), &fullFilenameNewName[0], fullFilenameNewName.size() - 1) != 0; proj_context_errno_set(ctxt, errno_before); fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str())); if (gridAvailableWithNewName) { gridAvailable = true; fullFilename = std::move(fullFilenameNewName); } } if (!gridAvailable && considerKnownGridsAsAvailable && (!packageName.empty() || (!url.empty() && openLicense))) { // In considerKnownGridsAsAvailable mode, try to fetch the local // file name if it exists, but do not attempt network access. const auto network_was_enabled = proj_context_is_network_enabled(ctxt); proj_context_set_enable_network(ctxt, false); (void)resolveFullFilename(); proj_context_set_enable_network(ctxt, network_was_enabled); gridAvailable = true; } info.packageName = packageName; std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt())); if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) { if (endpoint.back() != '/') { endpoint += '/'; } url = endpoint + url.substr(strlen("https://cdn.proj.org/")); } info.directDownload = directDownload; info.openLicense = openLicense; if (!gridAvailable) { gridAvailable = resolveFullFilename(); } } else { gridAvailable = resolveFullFilename(); if (starts_with(fullFilename, "http://") || starts_with(fullFilename, "https://")) { url = fullFilename; fullFilename.clear(); } } info.fullFilename = fullFilename; info.url = url; info.gridAvailable = gridAvailable; info.found = ret; d->cache(key, info); return ret; } // --------------------------------------------------------------------------- /** Returns the number of queries to the database since the creation of this * instance. */ unsigned int DatabaseContext::getQueryCounter() const { return d->queryCounter_; } // --------------------------------------------------------------------------- bool DatabaseContext::isKnownName(const std::string &name, const std::string &tableName) const { std::string sql("SELECT 1 FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE name = ? LIMIT 1"; return !d->run(sql, {name}).empty(); } // --------------------------------------------------------------------------- std::string DatabaseContext::getProjGridName(const std::string &oldProjGridName) { auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE " "old_proj_grid_name = ?", {oldProjGridName}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- std::string DatabaseContext::getOldProjGridName(const std::string &gridName) { auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE " "proj_grid_name = ?", {gridName}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- // scripts/build_db_from_esri.py adds a second alias for // names that have '[' in them. See get_old_esri_name() // in scripts/build_db_from_esri.py // So if we only have two aliases detect that situation to get the official // new name static std::string getUniqueEsriAlias(const std::list &l) { std::string first = l.front(); std::string second = *(std::next(l.begin())); if (second.find('[') != std::string::npos) std::swap(first, second); if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") == second) { return first; } return std::string(); } // --------------------------------------------------------------------------- /** \brief Gets the alias name from an official name. * * @param officialName Official name. Mandatory * @param tableName Table name/category. Mandatory. * "geographic_2D_crs" and "geographic_3D_crs" are also * accepted as special names to add a constraint on the "type" * column of the "geodetic_crs" table. * @param source Source of the alias. Mandatory * @return Alias name (or empty if not found). * @throw FactoryException in case of error. */ std::string DatabaseContext::getAliasFromOfficialName(const std::string &officialName, const std::string &tableName, const std::string &source) const { std::string sql("SELECT auth_name, code FROM \""); const auto genuineTableName = tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs" ? "geodetic_crs" : tableName; sql += replaceAll(genuineTableName, "\"", "\"\""); sql += "\" WHERE name = ?"; if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") { sql += " AND type = " GEOG_2D_SINGLE_QUOTED; } else if (tableName == "geographic_3D_crs") { sql += " AND type = " GEOG_3D_SINGLE_QUOTED; } sql += " ORDER BY deprecated"; auto res = d->run(sql, {officialName}); // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be // NAD83(HARN), and that's definitely not desirable. if (res.empty() && !(officialName == "NAD83" && tableName == "geographic_3D_crs")) { res = d->run( "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND " "alt_name = ? AND source IN ('EPSG', 'PROJ')", {genuineTableName, officialName}); if (res.size() != 1) { return std::string(); } } for (const auto &row : res) { auto res2 = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND " "auth_name = ? AND code = ? AND source = ?", {genuineTableName, row[0], row[1], source}); if (!res2.empty()) { if (res2.size() == 2 && source == "ESRI") { std::list l; l.emplace_back(res2.front()[0]); l.emplace_back((*(std::next(res2.begin())))[0]); std::string uniqueEsriAlias = getUniqueEsriAlias(l); if (!uniqueEsriAlias.empty()) return uniqueEsriAlias; } return res2.front()[0]; } } return std::string(); } // --------------------------------------------------------------------------- /** \brief Gets the alias names for an object. * * Either authName + code or officialName must be non empty. * * @param authName Authority. * @param code Code. * @param officialName Official name. * @param tableName Table name/category. Mandatory. * "geographic_2D_crs" and "geographic_3D_crs" are also * accepted as special names to add a constraint on the "type" * column of the "geodetic_crs" table. * @param source Source of the alias. May be empty. * @return Aliases */ std::list DatabaseContext::getAliases( const std::string &authName, const std::string &code, const std::string &officialName, const std::string &tableName, const std::string &source) const { std::list res; const auto key(authName + code + officialName + tableName + source); if (d->cacheAliasNames_.tryGet(key, res)) { return res; } std::string resolvedAuthName(authName); std::string resolvedCode(code); const auto genuineTableName = tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs" ? "geodetic_crs" : tableName; if (authName.empty() || code.empty()) { std::string sql("SELECT auth_name, code FROM \""); sql += replaceAll(genuineTableName, "\"", "\"\""); sql += "\" WHERE name = ?"; if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") { sql += " AND type = " GEOG_2D_SINGLE_QUOTED; } else if (tableName == "geographic_3D_crs") { sql += " AND type = " GEOG_3D_SINGLE_QUOTED; } sql += " ORDER BY deprecated"; auto resSql = d->run(sql, {officialName}); if (resSql.empty()) { resSql = d->run("SELECT auth_name, code FROM alias_name WHERE " "table_name = ? AND " "alt_name = ? AND source IN ('EPSG', 'PROJ')", {genuineTableName, officialName}); if (resSql.size() != 1) { d->cacheAliasNames_.insert(key, res); return res; } } const auto &row = resSql.front(); resolvedAuthName = row[0]; resolvedCode = row[1]; } std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND " "auth_name = ? AND code = ?"); ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode}; if (source == "not EPSG_OLD") { sql += " AND source != 'EPSG_OLD'"; } else if (!source.empty()) { sql += " AND source = ?"; params.emplace_back(source); } auto resSql = d->run(sql, params); for (const auto &row : resSql) { res.emplace_back(row[0]); } if (res.size() == 2 && source == "ESRI") { const auto uniqueEsriAlias = getUniqueEsriAlias(res); if (!uniqueEsriAlias.empty()) { res.clear(); res.emplace_back(uniqueEsriAlias); } } d->cacheAliasNames_.insert(key, res); return res; } // --------------------------------------------------------------------------- /** \brief Return the 'name' column of a table for an object * * @param tableName Table name/category. * @param authName Authority name of the object. * @param code Code of the object * @return Name (or empty) * @throw FactoryException in case of error. */ std::string DatabaseContext::getName(const std::string &tableName, const std::string &authName, const std::string &code) const { std::string res; const auto key(tableName + authName + code); if (d->cacheNames_.tryGet(key, res)) { return res; } std::string sql("SELECT name FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; auto sqlRes = d->run(sql, {authName, code}); if (sqlRes.empty()) { res.clear(); } else { res = sqlRes.front()[0]; } d->cacheNames_.insert(key, res); return res; } // --------------------------------------------------------------------------- /** \brief Return the 'text_definition' column of a table for an object * * @param tableName Table name/category. * @param authName Authority name of the object. * @param code Code of the object * @return Text definition (or empty) * @throw FactoryException in case of error. */ std::string DatabaseContext::getTextDefinition(const std::string &tableName, const std::string &authName, const std::string &code) const { std::string sql("SELECT text_definition FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; auto res = d->run(sql, {authName, code}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- /** \brief Return the allowed authorities when researching transformations * between different authorities. * * @throw FactoryException in case of error. */ std::vector DatabaseContext::getAllowedAuthorities( const std::string &sourceAuthName, const std::string &targetAuthName) const { const auto key(sourceAuthName + targetAuthName); auto hit = d->cacheAllowedAuthorities_.find(key); if (hit != d->cacheAllowedAuthorities_.end()) { return hit->second; } auto sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = ? AND target_auth_name = ?", {sourceAuthName, targetAuthName}); if (sqlRes.empty()) { sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = ? AND target_auth_name = 'any'", {sourceAuthName}); } if (sqlRes.empty()) { sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = 'any' AND target_auth_name = ?", {targetAuthName}); } if (sqlRes.empty()) { sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = 'any' AND target_auth_name = 'any'", {}); } if (sqlRes.empty()) { d->cacheAllowedAuthorities_[key] = std::vector(); return std::vector(); } auto res = split(sqlRes.front()[0], ','); d->cacheAllowedAuthorities_[key] = res; return res; } // --------------------------------------------------------------------------- std::list> DatabaseContext::getNonDeprecated(const std::string &tableName, const std::string &authName, const std::string &code) const { auto sqlRes = d->run("SELECT replacement_auth_name, replacement_code, source " "FROM deprecation " "WHERE table_name = ? AND deprecated_auth_name = ? " "AND deprecated_code = ?", {tableName, authName, code}); std::list> res; for (const auto &row : sqlRes) { const auto &source = row[2]; if (source == "PROJ") { const auto &replacement_auth_name = row[0]; const auto &replacement_code = row[1]; res.emplace_back(replacement_auth_name, replacement_code); } } if (!res.empty()) { return res; } for (const auto &row : sqlRes) { const auto &replacement_auth_name = row[0]; const auto &replacement_code = row[1]; res.emplace_back(replacement_auth_name, replacement_code); } return res; } // --------------------------------------------------------------------------- const std::vector & DatabaseContext::Private::getCacheAuthNameWithVersion() { if (cacheAuthNameWithVersion_.empty()) { const auto sqlRes = run("SELECT versioned_auth_name, auth_name, version, priority " "FROM versioned_auth_name_mapping"); for (const auto &row : sqlRes) { VersionedAuthName van; van.versionedAuthName = row[0]; van.authName = row[1]; van.version = row[2]; van.priority = atoi(row[3].c_str()); cacheAuthNameWithVersion_.emplace_back(std::move(van)); } } return cacheAuthNameWithVersion_; } // --------------------------------------------------------------------------- // From IAU_2015 returns (IAU,2015) bool DatabaseContext::getAuthorityAndVersion( const std::string &versionedAuthName, std::string &authNameOut, std::string &versionOut) { for (const auto &van : d->getCacheAuthNameWithVersion()) { if (van.versionedAuthName == versionedAuthName) { authNameOut = van.authName; versionOut = van.version; return true; } } return false; } // --------------------------------------------------------------------------- // From IAU and 2015, returns IAU_2015 bool DatabaseContext::getVersionedAuthority(const std::string &authName, const std::string &version, std::string &versionedAuthNameOut) { for (const auto &van : d->getCacheAuthNameWithVersion()) { if (van.authName == authName && van.version == version) { versionedAuthNameOut = van.versionedAuthName; return true; } } return false; } // --------------------------------------------------------------------------- // From IAU returns IAU_latest, ... IAU_2015 std::vector DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) { typedef std::pair VersionedAuthNamePriority; std::vector tmp; for (const auto &van : d->getCacheAuthNameWithVersion()) { if (van.authName == authName) { tmp.emplace_back( VersionedAuthNamePriority(van.versionedAuthName, van.priority)); } } std::vector res; if (!tmp.empty()) { // Sort by decreasing priority std::sort(tmp.begin(), tmp.end(), [](const VersionedAuthNamePriority &a, const VersionedAuthNamePriority &b) { return b.second > a.second; }); for (const auto &pair : tmp) res.emplace_back(pair.first); } return res; } // --------------------------------------------------------------------------- std::vector DatabaseContext::getTransformationsForGridName( const DatabaseContextNNPtr &databaseContext, const std::string &gridName) { auto sqlRes = databaseContext->d->run( "SELECT auth_name, code FROM grid_transformation " "WHERE grid_name = ? OR grid_name IN " "(SELECT original_grid_name FROM grid_alternatives " "WHERE proj_grid_name = ?) ORDER BY auth_name, code", {gridName, gridName}); std::vector res; for (const auto &row : sqlRes) { res.emplace_back(AuthorityFactory::create(databaseContext, row[0]) ->createCoordinateOperation(row[1], true)); } return res; } // --------------------------------------------------------------------------- // Fixes wrong towgs84 values returned by epsg.io when using a Coordinate Frame // transformation, where they neglect to reverse the sign of the rotation terms. // Cf https://github.com/OSGeo/PROJ/issues/4170 and // https://github.com/maptiler/epsg.io/issues/194 // We do that only when we found a valid Coordinate Frame rotation that // has the same numeric values (and no corresponding Position Vector // transformation with same values, or Coordinate Frame transformation with // opposite sign for rotation terms, both are highly unlikely) bool DatabaseContext::toWGS84AutocorrectWrongValues( double &tx, double &ty, double &tz, double &rx, double &ry, double &rz, double &scale_difference) const { if (rx == 0 && ry == 0 && rz == 0) return false; // 9606: Position Vector transformation (geog2D domain) // 9607: Coordinate Frame rotation (geog2D domain) std::string sql( "SELECT DISTINCT method_code " "FROM helmert_transformation_table WHERE " "abs(tx - ?) <= 1e-8 * abs(tx) AND " "abs(ty - ?) <= 1e-8 * abs(ty) AND " "abs(tz - ?) <= 1e-8 * abs(tz) AND " "abs(rx - ?) <= 1e-8 * abs(rx) AND " "abs(ry - ?) <= 1e-8 * abs(ry) AND " "abs(rz - ?) <= 1e-8 * abs(rz) AND " "abs(scale_difference - ?) <= 1e-8 * abs(scale_difference) AND " "method_auth_name = 'EPSG' AND " "method_code IN (9606, 9607) AND " "translation_uom_auth_name = 'EPSG' AND " "translation_uom_code = 9001 AND " // metre "rotation_uom_auth_name = 'EPSG' AND " "rotation_uom_code = 9104 AND " // arc-second "scale_difference_uom_auth_name = 'EPSG' AND " "scale_difference_uom_code = 9202 AND " // parts per million "deprecated = 0"); ListOfParams params; params.emplace_back(tx); params.emplace_back(ty); params.emplace_back(tz); params.emplace_back(rx); params.emplace_back(ry); params.emplace_back(rz); params.emplace_back(scale_difference); bool bFound9606 = false; bool bFound9607 = false; for (const auto &row : d->run(sql, params)) { if (row[0] == "9606") { bFound9606 = true; } else if (row[0] == "9607") { bFound9607 = true; } } if (bFound9607 && !bFound9606) { params.clear(); params.emplace_back(tx); params.emplace_back(ty); params.emplace_back(tz); params.emplace_back(-rx); params.emplace_back(-ry); params.emplace_back(-rz); params.emplace_back(scale_difference); if (d->run(sql, params).empty()) { if (d->pjCtxt()) { pj_log(d->pjCtxt(), PJ_LOG_ERROR, "Auto-correcting wrong sign of rotation terms of " "TOWGS84 clause from %s,%s,%s,%s,%s,%s,%s to " "%s,%s,%s,%s,%s,%s,%s", internal::toString(tx).c_str(), internal::toString(ty).c_str(), internal::toString(tz).c_str(), internal::toString(rx).c_str(), internal::toString(ry).c_str(), internal::toString(rz).c_str(), internal::toString(scale_difference).c_str(), internal::toString(tx).c_str(), internal::toString(ty).c_str(), internal::toString(tz).c_str(), internal::toString(-rx).c_str(), internal::toString(-ry).c_str(), internal::toString(-rz).c_str(), internal::toString(scale_difference).c_str()); } rx = -rx; ry = -ry; rz = -rz; return true; } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct AuthorityFactory::Private { Private(const DatabaseContextNNPtr &contextIn, const std::string &authorityName) : context_(contextIn), authority_(authorityName) {} inline const std::string &authority() PROJ_PURE_DEFN { return authority_; } inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN { return context_; } // cppcheck-suppress functionStatic void setThis(AuthorityFactoryNNPtr factory) { thisFactory_ = factory.as_nullable(); } // cppcheck-suppress functionStatic AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); } inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) { if (auth_name == authority_) { return NN_NO_CHECK(thisFactory_.lock()); } return AuthorityFactory::create(context_, auth_name); } bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op, bool considerKnownGridsAsAvailable); UnitOfMeasure createUnitOfMeasure(const std::string &auth_name, const std::string &code); util::PropertyMap createProperties(const std::string &code, const std::string &name, bool deprecated, const std::vector &usages); util::PropertyMap createPropertiesSearchUsages(const std::string &table_name, const std::string &code, const std::string &name, bool deprecated); util::PropertyMap createPropertiesSearchUsages( const std::string &table_name, const std::string &code, const std::string &name, bool deprecated, const std::string &remarks); SQLResultSet run(const std::string &sql, const ListOfParams ¶meters = ListOfParams()); SQLResultSet runWithCodeParam(const std::string &sql, const std::string &code); SQLResultSet runWithCodeParam(const char *sql, const std::string &code); bool hasAuthorityRestriction() const { return !authority_.empty() && authority_ != "any"; } SQLResultSet createProjectedCRSBegin(const std::string &code); crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code, const SQLResultSet &res); private: DatabaseContextNNPtr context_; std::string authority_; std::weak_ptr thisFactory_{}; }; // --------------------------------------------------------------------------- SQLResultSet AuthorityFactory::Private::run(const std::string &sql, const ListOfParams ¶meters) { return context()->getPrivate()->run(sql, parameters); } // --------------------------------------------------------------------------- SQLResultSet AuthorityFactory::Private::runWithCodeParam(const std::string &sql, const std::string &code) { return run(sql, {authority(), code}); } // --------------------------------------------------------------------------- SQLResultSet AuthorityFactory::Private::runWithCodeParam(const char *sql, const std::string &code) { return runWithCodeParam(std::string(sql), code); } // --------------------------------------------------------------------------- UnitOfMeasure AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name, const std::string &code) { return *(createFactory(auth_name)->createUnitOfMeasure(code)); } // --------------------------------------------------------------------------- util::PropertyMap AuthorityFactory::Private::createProperties( const std::string &code, const std::string &name, bool deprecated, const std::vector &usages) { auto props = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, authority()) .set(metadata::Identifier::CODE_KEY, code) .set(common::IdentifiedObject::NAME_KEY, name); if (deprecated) { props.set(common::IdentifiedObject::DEPRECATED_KEY, true); } if (!usages.empty()) { auto array(util::ArrayOfBaseObject::create()); for (const auto &usage : usages) { array->add(usage); } props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, util::nn_static_pointer_cast(array)); } return props; } // --------------------------------------------------------------------------- util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( const std::string &table_name, const std::string &code, const std::string &name, bool deprecated) { SQLResultSet res; if (table_name == "geodetic_crs" && code == "4326" && authority() == "EPSG") { // EPSG v10.077 has changed the extent from 1262 to 2830, whose // description is super verbose. // Cf https://epsg.org/closed-change-request/browse/id/2022.086 // To avoid churn in our WKT2 output, hot patch to the usage of // 10.076 and earlier res = run("SELECT extent.description, extent.south_lat, " "extent.north_lat, extent.west_lon, extent.east_lon, " "scope.scope, 0 AS score FROM extent, scope WHERE " "extent.code = 1262 and scope.code = 1183"); } else { const std::string sql( "SELECT extent.description, extent.south_lat, " "extent.north_lat, extent.west_lon, extent.east_lon, " "scope.scope, " "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) " "AS score " "FROM usage " "JOIN extent ON usage.extent_auth_name = extent.auth_name AND " "usage.extent_code = extent.code " "JOIN scope ON usage.scope_auth_name = scope.auth_name AND " "usage.scope_code = scope.code " "WHERE object_table_name = ? AND object_auth_name = ? AND " "object_code = ? AND " // We voluntary exclude extent and scope with a specific code "NOT (usage.extent_auth_name = 'PROJ' AND " "usage.extent_code = 'EXTENT_UNKNOWN') AND " "NOT (usage.scope_auth_name = 'PROJ' AND " "usage.scope_code = 'SCOPE_UNKNOWN') " "ORDER BY score, usage.auth_name, usage.code"); res = run(sql, {table_name, authority(), code}); } std::vector usages; for (const auto &row : res) { try { size_t idx = 0; const auto &extent_description = row[idx++]; const auto &south_lat_str = row[idx++]; const auto &north_lat_str = row[idx++]; const auto &west_lon_str = row[idx++]; const auto &east_lon_str = row[idx++]; const auto &scope = row[idx]; util::optional scopeOpt; if (!scope.empty()) { scopeOpt = scope; } metadata::ExtentPtr extent; if (south_lat_str.empty()) { extent = metadata::Extent::create( util::optional(extent_description), {}, {}, {}) .as_nullable(); } else { double south_lat = c_locale_stod(south_lat_str); double north_lat = c_locale_stod(north_lat_str); double west_lon = c_locale_stod(west_lon_str); double east_lon = c_locale_stod(east_lon_str); auto bbox = metadata::GeographicBoundingBox::create( west_lon, south_lat, east_lon, north_lat); extent = metadata::Extent::create( util::optional(extent_description), std::vector{bbox}, std::vector(), std::vector()) .as_nullable(); } usages.emplace_back(ObjectDomain::create(scopeOpt, extent)); } catch (const std::exception &) { } } return createProperties(code, name, deprecated, std::move(usages)); } // --------------------------------------------------------------------------- util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( const std::string &table_name, const std::string &code, const std::string &name, bool deprecated, const std::string &remarks) { auto props = createPropertiesSearchUsages(table_name, code, name, deprecated); if (!remarks.empty()) props.set(common::IdentifiedObject::REMARKS_KEY, remarks); return props; } // --------------------------------------------------------------------------- bool AuthorityFactory::Private::rejectOpDueToMissingGrid( const operation::CoordinateOperationNNPtr &op, bool considerKnownGridsAsAvailable) { struct DisableNetwork { const DatabaseContextNNPtr &m_dbContext; bool m_old_network_enabled = false; explicit DisableNetwork(const DatabaseContextNNPtr &l_context) : m_dbContext(l_context) { auto ctxt = m_dbContext->d->pjCtxt(); if (ctxt == nullptr) { ctxt = pj_get_default_ctx(); m_dbContext->d->setPjCtxt(ctxt); } m_old_network_enabled = proj_context_is_network_enabled(ctxt) != FALSE; if (m_old_network_enabled) proj_context_set_enable_network(ctxt, false); } ~DisableNetwork() { if (m_old_network_enabled) { auto ctxt = m_dbContext->d->pjCtxt(); proj_context_set_enable_network(ctxt, true); } } }; auto &l_context = context(); // Temporarily disable networking as we are only interested in known grids DisableNetwork disabler(l_context); for (const auto &gridDesc : op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) { if (!gridDesc.available) { return true; } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::~AuthorityFactory() = default; //! @endcond // --------------------------------------------------------------------------- AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context, const std::string &authorityName) : d(std::make_unique(context, authorityName)) {} // --------------------------------------------------------------------------- // clang-format off /** \brief Instantiate a AuthorityFactory. * * The authority name might be set to the empty string in the particular case * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const * is called. * * @param context Context. * @param authorityName Authority name. * @return new AuthorityFactory. */ // clang-format on AuthorityFactoryNNPtr AuthorityFactory::create(const DatabaseContextNNPtr &context, const std::string &authorityName) { const auto getFactory = [&context, &authorityName]() { for (const auto &knownName : {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) { if (ci_equal(authorityName, knownName)) { return AuthorityFactory::nn_make_shared( context, knownName); } } return AuthorityFactory::nn_make_shared( context, authorityName); }; auto factory = getFactory(); factory->d->setThis(factory); return factory; } // --------------------------------------------------------------------------- /** \brief Returns the database context. */ const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const { return d->context(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::CRSInfo::CRSInfo() : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{}, bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{}, north_lat_degree{}, areaName{}, projectionMethodName{}, celestialBodyName{} {} //! @endcond // --------------------------------------------------------------------------- /** \brief Returns an arbitrary object from a code. * * The returned object will typically be an instance of Datum, * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of * the object is know at compile time, it is recommended to invoke the most * precise method instead of this one (for example * createCoordinateReferenceSystem(code) instead of createObject(code) * if the caller know he is asking for a coordinate reference system). * * If there are several objects with the same code, a FactoryException is * thrown. * * @param code Object code allocated by authority. (e.g. "4326") * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ util::BaseObjectNNPtr AuthorityFactory::createObject(const std::string &code) const { auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view " "WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("not found", d->authority(), code); } if (res.size() != 1) { std::string msg( "More than one object matching specified code. Objects found in "); bool first = true; for (const auto &row : res) { if (!first) msg += ", "; msg += row[0]; first = false; } throw FactoryException(msg); } const auto &first_row = res.front(); const auto &table_name = first_row[0]; const auto &type = first_row[1]; if (table_name == "extent") { return util::nn_static_pointer_cast( createExtent(code)); } if (table_name == "unit_of_measure") { return util::nn_static_pointer_cast( createUnitOfMeasure(code)); } if (table_name == "prime_meridian") { return util::nn_static_pointer_cast( createPrimeMeridian(code)); } if (table_name == "ellipsoid") { return util::nn_static_pointer_cast( createEllipsoid(code)); } if (table_name == "geodetic_datum") { if (type == "ensemble") { return util::nn_static_pointer_cast( createDatumEnsemble(code, table_name)); } return util::nn_static_pointer_cast( createGeodeticDatum(code)); } if (table_name == "vertical_datum") { if (type == "ensemble") { return util::nn_static_pointer_cast( createDatumEnsemble(code, table_name)); } return util::nn_static_pointer_cast( createVerticalDatum(code)); } if (table_name == "engineering_datum") { return util::nn_static_pointer_cast( createEngineeringDatum(code)); } if (table_name == "geodetic_crs") { return util::nn_static_pointer_cast( createGeodeticCRS(code)); } if (table_name == "vertical_crs") { return util::nn_static_pointer_cast( createVerticalCRS(code)); } if (table_name == "projected_crs") { return util::nn_static_pointer_cast( createProjectedCRS(code)); } if (table_name == "compound_crs") { return util::nn_static_pointer_cast( createCompoundCRS(code)); } if (table_name == "engineering_crs") { return util::nn_static_pointer_cast( createEngineeringCRS(code)); } if (table_name == "conversion") { return util::nn_static_pointer_cast( createConversion(code)); } if (table_name == "helmert_transformation" || table_name == "grid_transformation" || table_name == "other_transformation" || table_name == "concatenated_operation") { return util::nn_static_pointer_cast( createCoordinateOperation(code, false)); } throw FactoryException("unimplemented factory for " + res.front()[0]); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static FactoryException buildFactoryException(const char *type, const std::string &authName, const std::string &code, const std::exception &ex) { return FactoryException(std::string("cannot build ") + type + " " + authName + ":" + code + ": " + ex.what()); } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a metadata::Extent from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ metadata::ExtentNNPtr AuthorityFactory::createExtent(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto extent = d->context()->d->getExtentFromCache(cacheKey); if (extent) { return NN_NO_CHECK(extent); } } auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, " "deprecated FROM extent WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(sql, code); if (res.empty()) { throw NoSuchAuthorityCodeException("extent not found", d->authority(), code); } try { const auto &row = res.front(); const auto &description = row[0]; if (row[1].empty()) { auto extent = metadata::Extent::create( util::optional(description), {}, {}, {}); d->context()->d->cache(cacheKey, extent); return extent; } double south_lat = c_locale_stod(row[1]); double north_lat = c_locale_stod(row[2]); double west_lon = c_locale_stod(row[3]); double east_lon = c_locale_stod(row[4]); auto bbox = metadata::GeographicBoundingBox::create( west_lon, south_lat, east_lon, north_lat); auto extent = metadata::Extent::create( util::optional(description), std::vector{bbox}, std::vector(), std::vector()); d->context()->d->cache(cacheKey, extent); return extent; } catch (const std::exception &ex) { throw buildFactoryException("extent", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a common::UnitOfMeasure from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ UnitOfMeasureNNPtr AuthorityFactory::createUnitOfMeasure(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto uom = d->context()->d->getUOMFromCache(cacheKey); if (uom) { return NN_NO_CHECK(uom); } } auto res = d->context()->d->run( "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE " "auth_name = ? AND code = ?", {d->authority(), code}, true); if (res.empty()) { throw NoSuchAuthorityCodeException("unit of measure not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = (row[0] == "degree (supplier to define representation)") ? UnitOfMeasure::DEGREE.name() : row[0]; double conv_factor = (code == "9107" || code == "9108") ? UnitOfMeasure::DEGREE.conversionToSI() : c_locale_stod(row[1]); constexpr double EPS = 1e-10; if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) < EPS * UnitOfMeasure::DEGREE.conversionToSI()) { conv_factor = UnitOfMeasure::DEGREE.conversionToSI(); } if (std::fabs(conv_factor - UnitOfMeasure::ARC_SECOND.conversionToSI()) < EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) { conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI(); } const auto &type_str = row[2]; UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN; if (type_str == "length") unitType = UnitOfMeasure::Type::LINEAR; else if (type_str == "angle") unitType = UnitOfMeasure::Type::ANGULAR; else if (type_str == "scale") unitType = UnitOfMeasure::Type::SCALE; else if (type_str == "time") unitType = UnitOfMeasure::Type::TIME; auto uom = util::nn_make_shared( name, conv_factor, unitType, d->authority(), code); d->context()->d->cache(cacheKey, uom); return uom; } catch (const std::exception &ex) { throw buildFactoryException("unit of measure", d->authority(), code, ex); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static double normalizeMeasure(const std::string &uom_code, const std::string &value, std::string &normalized_uom_code) { if (uom_code == "9110") // DDD.MMSSsss..... { double normalized_value = c_locale_stod(value); std::ostringstream buffer; buffer.imbue(std::locale::classic()); constexpr size_t precision = 12; buffer << std::fixed << std::setprecision(precision) << normalized_value; auto formatted = buffer.str(); size_t dotPos = formatted.find('.'); assert(dotPos + 1 + precision == formatted.size()); auto minutes = formatted.substr(dotPos + 1, 2); auto seconds = formatted.substr(dotPos + 3); assert(seconds.size() == precision - 2); normalized_value = (normalized_value < 0 ? -1.0 : 1.0) * (std::floor(std::fabs(normalized_value)) + c_locale_stod(minutes) / 60. + (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) / 3600.); normalized_uom_code = common::UnitOfMeasure::DEGREE.code(); /* coverity[overflow_sink] */ return normalized_value; } else { normalized_uom_code = uom_code; return c_locale_stod(value); } } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a datum::PrimeMeridian from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ datum::PrimeMeridianNNPtr AuthorityFactory::createPrimeMeridian(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey); if (pm) { return NN_NO_CHECK(pm); } } auto res = d->runWithCodeParam( "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM " "prime_meridian WHERE " "auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("prime meridian not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &longitude = row[1]; const auto &uom_auth_name = row[2]; const auto &uom_code = row[3]; const bool deprecated = row[4] == "1"; std::string normalized_uom_code(uom_code); const double normalized_value = normalizeMeasure(uom_code, longitude, normalized_uom_code); auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code); auto props = d->createProperties(code, name, deprecated, {}); auto pm = datum::PrimeMeridian::create( props, common::Angle(normalized_value, uom)); d->context()->d->cache(cacheKey, pm); return pm; } catch (const std::exception &ex) { throw buildFactoryException("prime meridian", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Identify a celestial body from an approximate radius. * * @param semi_major_axis Approximate semi-major axis. * @param tolerance Relative error allowed. * @return celestial body name if one single match found. * @throw FactoryException in case of error. */ std::string AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis, double tolerance) const { auto res = d->run("SELECT DISTINCT name, " "(ABS(semi_major_axis - ?) / semi_major_axis ) AS rel_error " "FROM celestial_body WHERE rel_error <= ? " "ORDER BY rel_error, name", {semi_major_axis, tolerance}); if (res.empty()) { throw FactoryException("no match found"); } constexpr int IDX_NAME = 0; if (res.size() > 1) { constexpr int IDX_REL_ERROR = 1; // If the first object has a relative error of 0 and the next one // a non-zero error, then use the first one. if (res.front()[IDX_REL_ERROR] == "0" && (*std::next(res.begin()))[IDX_REL_ERROR] != "0") { return res.front()[IDX_NAME]; } for (const auto &row : res) { if (row[IDX_NAME] != res.front()[IDX_NAME]) { throw FactoryException("more than one match found"); } } } return res.front()[IDX_NAME]; } // --------------------------------------------------------------------------- /** \brief Returns a datum::Ellipsoid from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ datum::EllipsoidNNPtr AuthorityFactory::createEllipsoid(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey); if (ellps) { return NN_NO_CHECK(ellps); } } auto res = d->runWithCodeParam( "SELECT ellipsoid.name, ellipsoid.semi_major_axis, " "ellipsoid.uom_auth_name, ellipsoid.uom_code, " "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, " "celestial_body.name AS body_name, ellipsoid.deprecated FROM " "ellipsoid JOIN celestial_body " "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND " "ellipsoid.celestial_body_code = celestial_body.code WHERE " "ellipsoid.auth_name = ? AND ellipsoid.code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("ellipsoid not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &semi_major_axis_str = row[1]; double semi_major_axis = c_locale_stod(semi_major_axis_str); const auto &uom_auth_name = row[2]; const auto &uom_code = row[3]; const auto &inv_flattening_str = row[4]; const auto &semi_minor_axis_str = row[5]; const auto &body = row[6]; const bool deprecated = row[7] == "1"; auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); auto props = d->createProperties(code, name, deprecated, {}); if (!inv_flattening_str.empty()) { auto ellps = datum::Ellipsoid::createFlattenedSphere( props, common::Length(semi_major_axis, uom), common::Scale(c_locale_stod(inv_flattening_str)), body); d->context()->d->cache(cacheKey, ellps); return ellps; } else if (semi_major_axis_str == semi_minor_axis_str) { auto ellps = datum::Ellipsoid::createSphere( props, common::Length(semi_major_axis, uom), body); d->context()->d->cache(cacheKey, ellps); return ellps; } else { auto ellps = datum::Ellipsoid::createTwoAxis( props, common::Length(semi_major_axis, uom), common::Length(c_locale_stod(semi_minor_axis_str), uom), body); d->context()->d->cache(cacheKey, ellps); return ellps; } } catch (const std::exception &ex) { throw buildFactoryException("ellipsoid", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::GeodeticReferenceFrame from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ datum::GeodeticReferenceFrameNNPtr AuthorityFactory::createGeodeticDatum(const std::string &code) const { datum::GeodeticReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = true; createGeodeticDatumOrEnsemble(code, datum, datumEnsemble, turnEnsembleAsDatum); return NN_NO_CHECK(datum); } // --------------------------------------------------------------------------- void AuthorityFactory::createGeodeticDatumOrEnsemble( const std::string &code, datum::GeodeticReferenceFramePtr &outDatum, datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const { const auto cacheKey(d->authority() + code); { outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey); if (outDatumEnsemble) { if (!turnEnsembleAsDatum) return; outDatumEnsemble = nullptr; } outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey); if (outDatum) { return; } } auto res = d->runWithCodeParam( "SELECT name, ellipsoid_auth_name, ellipsoid_code, " "prime_meridian_auth_name, prime_meridian_code, " "publication_date, frame_reference_epoch, " "ensemble_accuracy, anchor, anchor_epoch, deprecated " "FROM geodetic_datum " "WHERE " "auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("geodetic datum not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &ellipsoid_auth_name = row[1]; const auto &ellipsoid_code = row[2]; const auto &prime_meridian_auth_name = row[3]; const auto &prime_meridian_code = row[4]; const auto &publication_date = row[5]; const auto &frame_reference_epoch = row[6]; const auto &ensemble_accuracy = row[7]; const auto &anchor = row[8]; const auto &anchor_epoch = row[9]; const bool deprecated = row[10] == "1"; std::string massagedName; if (turnEnsembleAsDatum) { massagedName = datum::DatumEnsemble::ensembleNameToNonEnsembleName(name); } if (massagedName.empty()) { massagedName = name; } auto props = d->createPropertiesSearchUsages("geodetic_datum", code, massagedName, deprecated); if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) { auto resMembers = d->run("SELECT member_auth_name, member_code FROM " "geodetic_datum_ensemble_member WHERE " "ensemble_auth_name = ? AND ensemble_code = ? " "ORDER BY sequence", {d->authority(), code}); std::vector members; for (const auto &memberRow : resMembers) { members.push_back( d->createFactory(memberRow[0])->createDatum(memberRow[1])); } auto datumEnsemble = datum::DatumEnsemble::create( props, std::move(members), metadata::PositionalAccuracy::create(ensemble_accuracy)); d->context()->d->cache(cacheKey, datumEnsemble); outDatumEnsemble = datumEnsemble.as_nullable(); } else { auto ellipsoid = d->createFactory(ellipsoid_auth_name) ->createEllipsoid(ellipsoid_code); auto pm = d->createFactory(prime_meridian_auth_name) ->createPrimeMeridian(prime_meridian_code); auto anchorOpt = util::optional(); if (!anchor.empty()) anchorOpt = anchor; if (!publication_date.empty()) { props.set("PUBLICATION_DATE", publication_date); } if (!anchor_epoch.empty()) { props.set("ANCHOR_EPOCH", anchor_epoch); } auto datum = frame_reference_epoch.empty() ? datum::GeodeticReferenceFrame::create( props, ellipsoid, anchorOpt, pm) : util::nn_static_pointer_cast< datum::GeodeticReferenceFrame>( datum::DynamicGeodeticReferenceFrame::create( props, ellipsoid, anchorOpt, pm, common::Measure( c_locale_stod(frame_reference_epoch), common::UnitOfMeasure::YEAR), util::optional())); d->context()->d->cache(cacheKey, datum); outDatum = datum.as_nullable(); } } catch (const std::exception &ex) { throw buildFactoryException("geodetic reference frame", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::VerticalReferenceFrame from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ datum::VerticalReferenceFrameNNPtr AuthorityFactory::createVerticalDatum(const std::string &code) const { datum::VerticalReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = true; createVerticalDatumOrEnsemble(code, datum, datumEnsemble, turnEnsembleAsDatum); return NN_NO_CHECK(datum); } // --------------------------------------------------------------------------- void AuthorityFactory::createVerticalDatumOrEnsemble( const std::string &code, datum::VerticalReferenceFramePtr &outDatum, datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const { auto res = d->runWithCodeParam("SELECT name, publication_date, " "frame_reference_epoch, ensemble_accuracy, anchor, " "anchor_epoch, deprecated FROM " "vertical_datum WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("vertical datum not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &publication_date = row[1]; const auto &frame_reference_epoch = row[2]; const auto &ensemble_accuracy = row[3]; const auto &anchor = row[4]; const auto &anchor_epoch = row[5]; const bool deprecated = row[6] == "1"; auto props = d->createPropertiesSearchUsages("vertical_datum", code, name, deprecated); if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) { auto resMembers = d->run("SELECT member_auth_name, member_code FROM " "vertical_datum_ensemble_member WHERE " "ensemble_auth_name = ? AND ensemble_code = ? " "ORDER BY sequence", {d->authority(), code}); std::vector members; for (const auto &memberRow : resMembers) { members.push_back( d->createFactory(memberRow[0])->createDatum(memberRow[1])); } auto datumEnsemble = datum::DatumEnsemble::create( props, std::move(members), metadata::PositionalAccuracy::create(ensemble_accuracy)); outDatumEnsemble = datumEnsemble.as_nullable(); } else { if (!publication_date.empty()) { props.set("PUBLICATION_DATE", publication_date); } if (!anchor_epoch.empty()) { props.set("ANCHOR_EPOCH", anchor_epoch); } if (d->authority() == "ESRI" && starts_with(code, "from_geogdatum_")) { props.set("VERT_DATUM_TYPE", "2002"); } auto anchorOpt = util::optional(); if (!anchor.empty()) anchorOpt = anchor; if (frame_reference_epoch.empty()) { outDatum = datum::VerticalReferenceFrame::create(props, anchorOpt) .as_nullable(); } else { outDatum = datum::DynamicVerticalReferenceFrame::create( props, anchorOpt, util::optional(), common::Measure(c_locale_stod(frame_reference_epoch), common::UnitOfMeasure::YEAR), util::optional()) .as_nullable(); } } } catch (const std::exception &ex) { throw buildFactoryException("vertical reference frame", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::EngineeringDatum from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. * @since 9.6 */ datum::EngineeringDatumNNPtr AuthorityFactory::createEngineeringDatum(const std::string &code) const { auto res = d->runWithCodeParam( "SELECT name, publication_date, " "anchor, anchor_epoch, deprecated FROM " "engineering_datum WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("engineering datum not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &publication_date = row[1]; const auto &anchor = row[2]; const auto &anchor_epoch = row[3]; const bool deprecated = row[4] == "1"; auto props = d->createPropertiesSearchUsages("engineering_datum", code, name, deprecated); if (!publication_date.empty()) { props.set("PUBLICATION_DATE", publication_date); } if (!anchor_epoch.empty()) { props.set("ANCHOR_EPOCH", anchor_epoch); } auto anchorOpt = util::optional(); if (!anchor.empty()) anchorOpt = anchor; return datum::EngineeringDatum::create(props, anchorOpt); } catch (const std::exception &ex) { throw buildFactoryException("engineering datum", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::DatumEnsemble from the specified code. * * @param code Object code allocated by authority. * @param type "geodetic_datum", "vertical_datum" or empty string if unknown * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ datum::DatumEnsembleNNPtr AuthorityFactory::createDatumEnsemble(const std::string &code, const std::string &type) const { auto res = d->run( "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM " "geodetic_datum WHERE " "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL " "UNION ALL " "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM " "vertical_datum WHERE " "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL", {d->authority(), code, d->authority(), code}); if (res.empty()) { throw NoSuchAuthorityCodeException("datum ensemble not found", d->authority(), code); } for (const auto &row : res) { const std::string &gotType = row[0]; const std::string &name = row[1]; const std::string &ensembleAccuracy = row[2]; const bool deprecated = row[3] == "1"; if (type.empty() || type == gotType) { auto resMembers = d->run("SELECT member_auth_name, member_code FROM " + gotType + "_ensemble_member WHERE " "ensemble_auth_name = ? AND ensemble_code = ? " "ORDER BY sequence", {d->authority(), code}); std::vector members; for (const auto &memberRow : resMembers) { members.push_back( d->createFactory(memberRow[0])->createDatum(memberRow[1])); } auto props = d->createPropertiesSearchUsages(gotType, code, name, deprecated); return datum::DatumEnsemble::create( props, std::move(members), metadata::PositionalAccuracy::create(ensembleAccuracy)); } } throw NoSuchAuthorityCodeException("datum ensemble not found", d->authority(), code); } // --------------------------------------------------------------------------- /** \brief Returns a datum::Datum from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const { auto res = d->run( "SELECT 'geodetic_datum' FROM geodetic_datum WHERE " "auth_name = ? AND code = ? " "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE " "auth_name = ? AND code = ? " "UNION ALL SELECT 'engineering_datum' FROM engineering_datum " "WHERE " "auth_name = ? AND code = ?", {d->authority(), code, d->authority(), code, d->authority(), code}); if (res.empty()) { throw NoSuchAuthorityCodeException("datum not found", d->authority(), code); } const auto &type = res.front()[0]; if (type == "geodetic_datum") { return createGeodeticDatum(code); } if (type == "vertical_datum") { return createVerticalDatum(code); } return createEngineeringDatum(code); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static cs::MeridianPtr createMeridian(const std::string &val) { try { const std::string degW(std::string("\xC2\xB0") + "W"); if (ends_with(val, degW)) { return cs::Meridian::create(common::Angle( -c_locale_stod(val.substr(0, val.size() - degW.size())))); } const std::string degE(std::string("\xC2\xB0") + "E"); if (ends_with(val, degE)) { return cs::Meridian::create(common::Angle( c_locale_stod(val.substr(0, val.size() - degE.size())))); } } catch (const std::exception &) { } return nullptr; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a cs::CoordinateSystem from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ cs::CoordinateSystemNNPtr AuthorityFactory::createCoordinateSystem(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey); if (cs) { return NN_NO_CHECK(cs); } } auto res = d->runWithCodeParam( "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, " "cs.type FROM " "axis LEFT JOIN coordinate_system cs ON " "axis.coordinate_system_auth_name = cs.auth_name AND " "axis.coordinate_system_code = cs.code WHERE " "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER " "BY coordinate_system_order", code); if (res.empty()) { throw NoSuchAuthorityCodeException("coordinate system not found", d->authority(), code); } const auto &csType = res.front()[5]; std::vector axisList; for (const auto &row : res) { const auto &name = row[0]; const auto &abbrev = row[1]; const auto &orientation = row[2]; const auto &uom_auth_name = row[3]; const auto &uom_code = row[4]; if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) { throw FactoryException("no unit of measure for an axis is only " "supported for ordinatal CS"); } auto uom = uom_auth_name.empty() ? common::UnitOfMeasure::NONE : d->createUnitOfMeasure(uom_auth_name, uom_code); auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name); const cs::AxisDirection *direction = cs::AxisDirection::valueOf(orientation); cs::MeridianPtr meridian; if (direction == nullptr) { if (orientation == "Geocentre > equator/0" "\xC2\xB0" "E") { direction = &(cs::AxisDirection::GEOCENTRIC_X); } else if (orientation == "Geocentre > equator/90" "\xC2\xB0" "E") { direction = &(cs::AxisDirection::GEOCENTRIC_Y); } else if (orientation == "Geocentre > north pole") { direction = &(cs::AxisDirection::GEOCENTRIC_Z); } else if (starts_with(orientation, "North along ")) { direction = &(cs::AxisDirection::NORTH); meridian = createMeridian(orientation.substr(strlen("North along "))); } else if (starts_with(orientation, "South along ")) { direction = &(cs::AxisDirection::SOUTH); meridian = createMeridian(orientation.substr(strlen("South along "))); } else { throw FactoryException("unknown axis direction: " + orientation); } } axisList.emplace_back(cs::CoordinateSystemAxis::create( props, abbrev, *direction, uom, meridian)); } const auto cacheAndRet = [this, &cacheKey](const cs::CoordinateSystemNNPtr &cs) { d->context()->d->cache(cacheKey, cs); return cs; }; auto props = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, d->authority()) .set(metadata::Identifier::CODE_KEY, code); if (csType == CS_TYPE_ELLIPSOIDAL) { if (axisList.size() == 2) { return cacheAndRet( cs::EllipsoidalCS::create(props, axisList[0], axisList[1])); } if (axisList.size() == 3) { return cacheAndRet(cs::EllipsoidalCS::create( props, axisList[0], axisList[1], axisList[2])); } throw FactoryException("invalid number of axis for EllipsoidalCS"); } if (csType == CS_TYPE_CARTESIAN) { if (axisList.size() == 2) { return cacheAndRet( cs::CartesianCS::create(props, axisList[0], axisList[1])); } if (axisList.size() == 3) { return cacheAndRet(cs::CartesianCS::create( props, axisList[0], axisList[1], axisList[2])); } throw FactoryException("invalid number of axis for CartesianCS"); } if (csType == CS_TYPE_SPHERICAL) { if (axisList.size() == 2) { return cacheAndRet( cs::SphericalCS::create(props, axisList[0], axisList[1])); } if (axisList.size() == 3) { return cacheAndRet(cs::SphericalCS::create( props, axisList[0], axisList[1], axisList[2])); } throw FactoryException("invalid number of axis for SphericalCS"); } if (csType == CS_TYPE_VERTICAL) { if (axisList.size() == 1) { return cacheAndRet(cs::VerticalCS::create(props, axisList[0])); } throw FactoryException("invalid number of axis for VerticalCS"); } if (csType == CS_TYPE_ORDINAL) { return cacheAndRet(cs::OrdinalCS::create(props, axisList)); } throw FactoryException("unhandled coordinate system type: " + csType); } // --------------------------------------------------------------------------- /** \brief Returns a crs::GeodeticCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ crs::GeodeticCRSNNPtr AuthorityFactory::createGeodeticCRS(const std::string &code) const { return createGeodeticCRS(code, false); } // --------------------------------------------------------------------------- /** \brief Returns a crs::GeographicCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ crs::GeographicCRSNNPtr AuthorityFactory::createGeographicCRS(const std::string &code) const { auto crs(util::nn_dynamic_pointer_cast( createGeodeticCRS(code, true))); if (!crs) { throw NoSuchAuthorityCodeException("geographicCRS not found", d->authority(), code); } return NN_NO_CHECK(crs); } // --------------------------------------------------------------------------- static crs::GeodeticCRSNNPtr cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS, const util::PropertyMap &props) { auto cs = geodCRS->coordinateSystem(); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(cs); if (ellipsoidalCS) { return crs::GeographicCRS::create(props, geodCRS->datum(), geodCRS->datumEnsemble(), NN_NO_CHECK(ellipsoidalCS)); } auto geocentricCS = util::nn_dynamic_pointer_cast(cs); if (geocentricCS) { return crs::GeodeticCRS::create(props, geodCRS->datum(), geodCRS->datumEnsemble(), NN_NO_CHECK(geocentricCS)); } return geodCRS; } // --------------------------------------------------------------------------- crs::GeodeticCRSNNPtr AuthorityFactory::createGeodeticCRS(const std::string &code, bool geographicOnly) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto geogCRS = std::dynamic_pointer_cast(crs); if (geogCRS) { return NN_NO_CHECK(geogCRS); } throw NoSuchAuthorityCodeException("geodeticCRS not found", d->authority(), code); } std::string sql("SELECT name, type, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " "text_definition, deprecated, description FROM " "geodetic_crs WHERE auth_name = ? AND code = ?"); if (geographicOnly) { sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ")"; } auto res = d->runWithCodeParam(sql, code); if (res.empty()) { throw NoSuchAuthorityCodeException("geodeticCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &type = row[1]; const auto &cs_auth_name = row[2]; const auto &cs_code = row[3]; const auto &datum_auth_name = row[4]; const auto &datum_code = row[5]; const auto &text_definition = row[6]; const bool deprecated = row[7] == "1"; const auto &remarks = row[8]; auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name, deprecated, remarks); if (!text_definition.empty()) { DatabaseContext::Private::RecursionDetector detector(d->context()); auto obj = createFromUserInput( pj_add_type_crs_if_needed(text_definition), d->context()); auto geodCRS = util::nn_dynamic_pointer_cast(obj); if (geodCRS) { auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props); d->context()->d->cache(cacheKey, crsRet); return crsRet; } auto boundCRS = dynamic_cast(obj.get()); if (boundCRS) { geodCRS = util::nn_dynamic_pointer_cast( boundCRS->baseCRS()); if (geodCRS) { auto newBoundCRS = crs::BoundCRS::create( cloneWithProps(NN_NO_CHECK(geodCRS), props), boundCRS->hubCRS(), boundCRS->transformation()); return NN_NO_CHECK( util::nn_dynamic_pointer_cast( newBoundCRS->baseCRSWithCanonicalBoundCRS())); } } throw FactoryException( "text_definition does not define a GeodeticCRS"); } auto cs = d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); datum::GeodeticReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; d->createFactory(datum_auth_name) ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble, turnEnsembleAsDatum); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(cs); if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) { auto crsRet = crs::GeographicCRS::create( props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } auto geocentricCS = util::nn_dynamic_pointer_cast(cs); if (type == GEOCENTRIC && geocentricCS) { auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(geocentricCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } auto sphericalCS = util::nn_dynamic_pointer_cast(cs); if (type == OTHER && sphericalCS) { auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(sphericalCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } throw FactoryException("unsupported (type, CS type) for geodeticCRS: " + type + ", " + cs->getWKT2Type(true)); } catch (const std::exception &ex) { throw buildFactoryException("geodeticCRS", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::VerticalCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ crs::VerticalCRSNNPtr AuthorityFactory::createVerticalCRS(const std::string &code) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto projCRS = std::dynamic_pointer_cast(crs); if (projCRS) { return NN_NO_CHECK(projCRS); } throw NoSuchAuthorityCodeException("verticalCRS not found", d->authority(), code); } auto res = d->runWithCodeParam( "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " "deprecated FROM " "vertical_crs WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("verticalCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &cs_auth_name = row[1]; const auto &cs_code = row[2]; const auto &datum_auth_name = row[3]; const auto &datum_code = row[4]; const bool deprecated = row[5] == "1"; auto cs = d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); datum::VerticalReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; d->createFactory(datum_auth_name) ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble, turnEnsembleAsDatum); auto props = d->createPropertiesSearchUsages("vertical_crs", code, name, deprecated); auto verticalCS = util::nn_dynamic_pointer_cast(cs); if (verticalCS) { auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(verticalCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } throw FactoryException("unsupported CS type for verticalCRS: " + cs->getWKT2Type(true)); } catch (const std::exception &ex) { throw buildFactoryException("verticalCRS", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::EngineeringCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. * @since 9.6 */ crs::EngineeringCRSNNPtr AuthorityFactory::createEngineeringCRS(const std::string &code) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto engCRS = std::dynamic_pointer_cast(crs); if (engCRS) { return NN_NO_CHECK(engCRS); } throw NoSuchAuthorityCodeException("engineeringCRS not found", d->authority(), code); } auto res = d->runWithCodeParam( "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " "deprecated FROM " "engineering_crs WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("engineeringCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &cs_auth_name = row[1]; const auto &cs_code = row[2]; const auto &datum_auth_name = row[3]; const auto &datum_code = row[4]; const bool deprecated = row[5] == "1"; auto cs = d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); auto datum = d->createFactory(datum_auth_name) ->createEngineeringDatum(datum_code); auto props = d->createPropertiesSearchUsages("engineering_crs", code, name, deprecated); auto crsRet = crs::EngineeringCRS::create(props, datum, cs); d->context()->d->cache(cacheKey, crsRet); return crsRet; } catch (const std::exception &ex) { throw buildFactoryException("engineeringCRS", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a operation::Conversion from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ operation::ConversionNNPtr AuthorityFactory::createConversion(const std::string &code) const { static const char *sql = "SELECT name, description, " "method_auth_name, method_code, method_name, " "param1_auth_name, param1_code, param1_name, param1_value, " "param1_uom_auth_name, param1_uom_code, " "param2_auth_name, param2_code, param2_name, param2_value, " "param2_uom_auth_name, param2_uom_code, " "param3_auth_name, param3_code, param3_name, param3_value, " "param3_uom_auth_name, param3_uom_code, " "param4_auth_name, param4_code, param4_name, param4_value, " "param4_uom_auth_name, param4_uom_code, " "param5_auth_name, param5_code, param5_name, param5_value, " "param5_uom_auth_name, param5_uom_code, " "param6_auth_name, param6_code, param6_name, param6_value, " "param6_uom_auth_name, param6_uom_code, " "param7_auth_name, param7_code, param7_name, param7_value, " "param7_uom_auth_name, param7_uom_code, " "deprecated FROM conversion WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(sql, code); if (res.empty()) { try { // Conversions using methods Change of Vertical Unit or // Height Depth Reversal are stored in other_transformation auto op = createCoordinateOperation( code, false /* allowConcatenated */, false /* usePROJAlternativeGridNames */, "other_transformation"); auto conv = util::nn_dynamic_pointer_cast(op); if (conv) { return NN_NO_CHECK(conv); } } catch (const std::exception &) { } throw NoSuchAuthorityCodeException("conversion not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const size_t base_param_idx = idx; std::vector parameters; std::vector values; for (size_t i = 0; i < N_MAX_PARAMS; ++i) { const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; if (param_auth_name.empty()) { break; } const auto ¶m_code = row[base_param_idx + i * 6 + 1]; const auto ¶m_name = row[base_param_idx + i * 6 + 2]; const auto ¶m_value = row[base_param_idx + i * 6 + 3]; const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) .set(metadata::Identifier::CODE_KEY, param_code) .set(common::IdentifiedObject::NAME_KEY, param_name))); std::string normalized_uom_code(param_uom_code); const double normalized_value = normalizeMeasure( param_uom_code, param_value, normalized_uom_code); auto uom = d->createUnitOfMeasure(param_uom_auth_name, normalized_uom_code); values.emplace_back(operation::ParameterValue::create( common::Measure(normalized_value, uom))); } const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1"; auto propConversion = d->createPropertiesSearchUsages( "conversion", code, name, deprecated); if (!description.empty()) propConversion.set(common::IdentifiedObject::REMARKS_KEY, description); auto propMethod = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, method_name); if (!method_auth_name.empty()) { propMethod .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code); } return operation::Conversion::create(propConversion, propMethod, parameters, values); } catch (const std::exception &ex) { throw buildFactoryException("conversion", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::ProjectedCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ crs::ProjectedCRSNNPtr AuthorityFactory::createProjectedCRS(const std::string &code) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto projCRS = std::dynamic_pointer_cast(crs); if (projCRS) { return NN_NO_CHECK(projCRS); } throw NoSuchAuthorityCodeException("projectedCRS not found", d->authority(), code); } return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** Returns the result of the SQL query needed by createProjectedCRSEnd * * The split in two functions is for createFromCoordinateReferenceSystemCodes() * convenience, to avoid throwing exceptions. */ SQLResultSet AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) { return runWithCodeParam( "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, " "conversion_auth_name, conversion_code, " "text_definition, " "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?", code); } // --------------------------------------------------------------------------- /** Build a ProjectedCRS from the result of createProjectedCRSBegin() */ crs::ProjectedCRSNNPtr AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code, const SQLResultSet &res) { const auto cacheKey(authority() + code); if (res.empty()) { throw NoSuchAuthorityCodeException("projectedCRS not found", authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &cs_auth_name = row[1]; const auto &cs_code = row[2]; const auto &geodetic_crs_auth_name = row[3]; const auto &geodetic_crs_code = row[4]; const auto &conversion_auth_name = row[5]; const auto &conversion_code = row[6]; const auto &text_definition = row[7]; const bool deprecated = row[8] == "1"; auto props = createPropertiesSearchUsages("projected_crs", code, name, deprecated); if (!text_definition.empty()) { DatabaseContext::Private::RecursionDetector detector(context()); auto obj = createFromUserInput( pj_add_type_crs_if_needed(text_definition), context()); auto projCRS = dynamic_cast(obj.get()); if (projCRS) { auto conv = projCRS->derivingConversion(); auto newConv = (conv->nameStr() == "unnamed") ? operation::Conversion::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, name), conv->method(), conv->parameterValues()) : std::move(conv); auto crsRet = crs::ProjectedCRS::create( props, projCRS->baseCRS(), newConv, projCRS->coordinateSystem()); context()->d->cache(cacheKey, crsRet); return crsRet; } auto boundCRS = dynamic_cast(obj.get()); if (boundCRS) { projCRS = dynamic_cast( boundCRS->baseCRS().get()); if (projCRS) { auto newBoundCRS = crs::BoundCRS::create( crs::ProjectedCRS::create(props, projCRS->baseCRS(), projCRS->derivingConversion(), projCRS->coordinateSystem()), boundCRS->hubCRS(), boundCRS->transformation()); return NN_NO_CHECK( util::nn_dynamic_pointer_cast( newBoundCRS->baseCRSWithCanonicalBoundCRS())); } } throw FactoryException( "text_definition does not define a ProjectedCRS"); } auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code); auto baseCRS = createFactory(geodetic_crs_auth_name) ->createGeodeticCRS(geodetic_crs_code); auto conv = createFactory(conversion_auth_name) ->createConversion(conversion_code); if (conv->nameStr() == "unnamed") { conv = conv->shallowClone(); conv->setProperties(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, name)); } auto cartesianCS = util::nn_dynamic_pointer_cast(cs); if (cartesianCS) { auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv, NN_NO_CHECK(cartesianCS)); context()->d->cache(cacheKey, crsRet); return crsRet; } throw FactoryException("unsupported CS type for projectedCRS: " + cs->getWKT2Type(true)); } catch (const std::exception &ex) { throw buildFactoryException("projectedCRS", authority(), code, ex); } } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a crs::CompoundCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ crs::CompoundCRSNNPtr AuthorityFactory::createCompoundCRS(const std::string &code) const { auto res = d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, " "vertical_crs_auth_name, vertical_crs_code, " "deprecated FROM " "compound_crs WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("compoundCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &horiz_crs_auth_name = row[1]; const auto &horiz_crs_code = row[2]; const auto &vertical_crs_auth_name = row[3]; const auto &vertical_crs_code = row[4]; const bool deprecated = row[5] == "1"; auto horizCRS = d->createFactory(horiz_crs_auth_name) ->createCoordinateReferenceSystem(horiz_crs_code, false); auto vertCRS = d->createFactory(vertical_crs_auth_name) ->createVerticalCRS(vertical_crs_code); auto props = d->createPropertiesSearchUsages("compound_crs", code, name, deprecated); return crs::CompoundCRS::create( props, std::vector{std::move(horizCRS), std::move(vertCRS)}); } catch (const std::exception &ex) { throw buildFactoryException("compoundCRS", d->authority(), code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::CRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem( const std::string &code) const { return createCoordinateReferenceSystem(code, true); } //! @cond Doxygen_Suppress crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(const std::string &code, bool allowCompound) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { return NN_NO_CHECK(crs); } if (d->authority() == metadata::Identifier::OGC) { if (code == "AnsiDate") { // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate return crs::TemporalCRS::create( util::PropertyMap() // above URL indicates Julian Date" as name... likely wrong .set(common::IdentifiedObject::NAME_KEY, "Ansi Date") .set(metadata::Identifier::CODESPACE_KEY, d->authority()) .set(metadata::Identifier::CODE_KEY, code), datum::TemporalDatum::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) " "as day 1."), common::DateTime::create("1600-12-31T00:00:00Z"), datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN), cs::TemporalCountCS::create( util::PropertyMap(), cs::CoordinateSystemAxis::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "Time"), "T", cs::AxisDirection::FUTURE, common::UnitOfMeasure("day", 0, UnitOfMeasure::Type::TIME)))); } if (code == "JulianDate") { // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate return crs::TemporalCRS::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, "Julian Date") .set(metadata::Identifier::CODESPACE_KEY, d->authority()) .set(metadata::Identifier::CODE_KEY, code), datum::TemporalDatum::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "The beginning of the Julian period."), common::DateTime::create("-4714-11-24T12:00:00Z"), datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN), cs::TemporalCountCS::create( util::PropertyMap(), cs::CoordinateSystemAxis::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "Time"), "T", cs::AxisDirection::FUTURE, common::UnitOfMeasure("day", 0, UnitOfMeasure::Type::TIME)))); } if (code == "UnixTime") { // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime return crs::TemporalCRS::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, "Unix Time") .set(metadata::Identifier::CODESPACE_KEY, d->authority()) .set(metadata::Identifier::CODE_KEY, code), datum::TemporalDatum::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, "Unix epoch"), common::DateTime::create("1970-01-01T00:00:00Z"), datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN), cs::TemporalCountCS::create( util::PropertyMap(), cs::CoordinateSystemAxis::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, "Time"), "T", cs::AxisDirection::FUTURE, common::UnitOfMeasure::SECOND))); } if (code == "84") { return createCoordinateReferenceSystem("CRS84", false); } } auto res = d->runWithCodeParam( "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("crs not found", d->authority(), code); } const auto &type = res.front()[0]; if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC || type == OTHER) { return createGeodeticCRS(code); } if (type == VERTICAL) { return createVerticalCRS(code); } if (type == PROJECTED) { return createProjectedCRS(code); } if (type == ENGINEERING) { return createEngineeringCRS(code); } if (allowCompound && type == COMPOUND) { return createCompoundCRS(code); } throw FactoryException("unhandled CRS type: " + type); } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a coordinates::CoordinateMetadata from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. * @since 9.4 */ coordinates::CoordinateMetadataNNPtr AuthorityFactory::createCoordinateMetadata(const std::string &code) const { auto res = d->runWithCodeParam( "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch " "FROM coordinate_metadata WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("coordinate_metadata not found", d->authority(), code); } try { const auto &row = res.front(); const auto &crs_auth_name = row[0]; const auto &crs_code = row[1]; const auto &crs_text_definition = row[2]; const auto &coordinate_epoch = row[3]; auto l_context = d->context(); DatabaseContext::Private::RecursionDetector detector(l_context); auto crs = !crs_auth_name.empty() ? d->createFactory(crs_auth_name) ->createCoordinateReferenceSystem(crs_code) .as_nullable() : util::nn_dynamic_pointer_cast( createFromUserInput(crs_text_definition, l_context)); if (!crs) { throw FactoryException( std::string("cannot build CoordinateMetadata ") + d->authority() + ":" + code + ": cannot build CRS"); } if (coordinate_epoch.empty()) { return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs)); } else { return coordinates::CoordinateMetadata::create( NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch), l_context.as_nullable()); } } catch (const std::exception &ex) { throw buildFactoryException("CoordinateMetadata", d->authority(), code, ex); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { return util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, code); } // --------------------------------------------------------------------------- static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) { const char *name = operation::OperationParameter::getNameForEPSGCode(code); assert(name); return operation::OperationParameter::create( createMapNameEPSGCode(name, code)); } static operation::ParameterValueNNPtr createLength(const std::string &value, const UnitOfMeasure &uom) { return operation::ParameterValue::create( common::Length(c_locale_stod(value), uom)); } static operation::ParameterValueNNPtr createAngle(const std::string &value, const UnitOfMeasure &uom) { return operation::ParameterValue::create( common::Angle(c_locale_stod(value), uom)); } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a operation::CoordinateOperation from the specified code. * * @param code Object code allocated by authority. * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. * @return object. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const std::string &code, bool usePROJAlternativeGridNames) const { return createCoordinateOperation(code, true, usePROJAlternativeGridNames, std::string()); } operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const std::string &code, bool allowConcatenated, bool usePROJAlternativeGridNames, const std::string &typeIn) const { std::string type(typeIn); if (type.empty()) { auto res = d->runWithCodeParam( "SELECT type FROM coordinate_operation_with_conversion_view " "WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("coordinate operation not found", d->authority(), code); } type = res.front()[0]; } if (type == "conversion") { return createConversion(code); } if (type == "helmert_transformation") { auto res = d->runWithCodeParam( "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " "target_crs_code, " "accuracy, tx, ty, tz, translation_uom_auth_name, " "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, " "rotation_uom_code, scale_difference, " "scale_difference_uom_auth_name, scale_difference_uom_code, " "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, " "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, " "rate_rotation_uom_auth_name, rate_rotation_uom_code, " "rate_scale_difference, rate_scale_difference_uom_auth_name, " "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, " "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, " "operation_version, deprecated FROM " "helmert_transformation WHERE auth_name = ? AND code = ?", code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException( "helmert_transformation not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &tx = row[idx++]; const auto &ty = row[idx++]; const auto &tz = row[idx++]; const auto &translation_uom_auth_name = row[idx++]; const auto &translation_uom_code = row[idx++]; const auto &rx = row[idx++]; const auto &ry = row[idx++]; const auto &rz = row[idx++]; const auto &rotation_uom_auth_name = row[idx++]; const auto &rotation_uom_code = row[idx++]; const auto &scale_difference = row[idx++]; const auto &scale_difference_uom_auth_name = row[idx++]; const auto &scale_difference_uom_code = row[idx++]; const auto &rate_tx = row[idx++]; const auto &rate_ty = row[idx++]; const auto &rate_tz = row[idx++]; const auto &rate_translation_uom_auth_name = row[idx++]; const auto &rate_translation_uom_code = row[idx++]; const auto &rate_rx = row[idx++]; const auto &rate_ry = row[idx++]; const auto &rate_rz = row[idx++]; const auto &rate_rotation_uom_auth_name = row[idx++]; const auto &rate_rotation_uom_code = row[idx++]; const auto &rate_scale_difference = row[idx++]; const auto &rate_scale_difference_uom_auth_name = row[idx++]; const auto &rate_scale_difference_uom_code = row[idx++]; const auto &epoch = row[idx++]; const auto &epoch_uom_auth_name = row[idx++]; const auto &epoch_uom_code = row[idx++]; const auto &px = row[idx++]; const auto &py = row[idx++]; const auto &pz = row[idx++]; const auto &pivot_uom_auth_name = row[idx++]; const auto &pivot_uom_code = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; assert(idx == row.size()); auto uom_translation = d->createUnitOfMeasure( translation_uom_auth_name, translation_uom_code); auto uom_epoch = epoch_uom_auth_name.empty() ? common::UnitOfMeasure::NONE : d->createUnitOfMeasure(epoch_uom_auth_name, epoch_uom_code); auto sourceCRS = d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code); auto targetCRS = d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); std::vector parameters; std::vector values; parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); values.emplace_back(createLength(tx, uom_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); values.emplace_back(createLength(ty, uom_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); values.emplace_back(createLength(tz, uom_translation)); if (!rx.empty()) { // Helmert 7-, 8-, 10- or 15- parameter cases auto uom_rotation = d->createUnitOfMeasure( rotation_uom_auth_name, rotation_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_X_AXIS_ROTATION)); values.emplace_back(createAngle(rx, uom_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Y_AXIS_ROTATION)); values.emplace_back(createAngle(ry, uom_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Z_AXIS_ROTATION)); values.emplace_back(createAngle(rz, uom_rotation)); auto uom_scale_difference = scale_difference_uom_auth_name.empty() ? common::UnitOfMeasure::NONE : d->createUnitOfMeasure(scale_difference_uom_auth_name, scale_difference_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_SCALE_DIFFERENCE)); values.emplace_back(operation::ParameterValue::create( common::Scale(c_locale_stod(scale_difference), uom_scale_difference))); } if (!rate_tx.empty()) { // Helmert 15-parameter auto uom_rate_translation = d->createUnitOfMeasure( rate_translation_uom_auth_name, rate_translation_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION)); values.emplace_back( createLength(rate_tx, uom_rate_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION)); values.emplace_back( createLength(rate_ty, uom_rate_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION)); values.emplace_back( createLength(rate_tz, uom_rate_translation)); auto uom_rate_rotation = d->createUnitOfMeasure( rate_rotation_uom_auth_name, rate_rotation_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION)); values.emplace_back(createAngle(rate_rx, uom_rate_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION)); values.emplace_back(createAngle(rate_ry, uom_rate_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION)); values.emplace_back(createAngle(rate_rz, uom_rate_rotation)); auto uom_rate_scale_difference = d->createUnitOfMeasure(rate_scale_difference_uom_auth_name, rate_scale_difference_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE)); values.emplace_back(operation::ParameterValue::create( common::Scale(c_locale_stod(rate_scale_difference), uom_rate_scale_difference))); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_REFERENCE_EPOCH)); values.emplace_back(operation::ParameterValue::create( common::Measure(c_locale_stod(epoch), uom_epoch))); } else if (uom_epoch != common::UnitOfMeasure::NONE) { // Helmert 8-parameter parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH)); values.emplace_back(operation::ParameterValue::create( common::Measure(c_locale_stod(epoch), uom_epoch))); } else if (!px.empty()) { // Molodensky-Badekas case auto uom_pivot = d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT)); values.emplace_back(createLength(px, uom_pivot)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT)); values.emplace_back(createLength(py, uom_pivot)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT)); values.emplace_back(createLength(pz, uom_pivot)); } auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } auto propsMethod = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code) .set(common::IdentifiedObject::NAME_KEY, method_name); std::vector accuracies; if (!accuracy.empty() && accuracy != "999.0") { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } return operation::Transformation::create( props, sourceCRS, targetCRS, nullptr, propsMethod, parameters, values, accuracies); } catch (const std::exception &ex) { throw buildFactoryException("transformation", d->authority(), code, ex); } } if (type == "grid_transformation") { auto res = d->runWithCodeParam( "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " "target_crs_code, " "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, " "grid_name, " "grid2_param_auth_name, grid2_param_code, grid2_param_name, " "grid2_name, " "param1_auth_name, param1_code, param1_name, param1_value, " "param1_uom_auth_name, param1_uom_code, " "param2_auth_name, param2_code, param2_name, param2_value, " "param2_uom_auth_name, param2_uom_code, " "interpolation_crs_auth_name, interpolation_crs_code, " "operation_version, deprecated FROM " "grid_transformation WHERE auth_name = ? AND code = ?", code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException("grid_transformation not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &grid_param_auth_name = row[idx++]; const auto &grid_param_code = row[idx++]; const auto &grid_param_name = row[idx++]; const auto &grid_name = row[idx++]; const auto &grid2_param_auth_name = row[idx++]; const auto &grid2_param_code = row[idx++]; const auto &grid2_param_name = row[idx++]; const auto &grid2_name = row[idx++]; std::vector parameters; std::vector values; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, grid_param_name) .set(metadata::Identifier::CODESPACE_KEY, grid_param_auth_name) .set(metadata::Identifier::CODE_KEY, grid_param_code))); values.emplace_back( operation::ParameterValue::createFilename(grid_name)); if (!grid2_name.empty()) { parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, grid2_param_name) .set(metadata::Identifier::CODESPACE_KEY, grid2_param_auth_name) .set(metadata::Identifier::CODE_KEY, grid2_param_code))); values.emplace_back( operation::ParameterValue::createFilename(grid2_name)); } const size_t base_param_idx = idx; constexpr size_t N_MAX_PARAMS_GRID_TRANSFORMATION = 2; for (size_t i = 0; i < N_MAX_PARAMS_GRID_TRANSFORMATION; ++i) { const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; if (param_auth_name.empty()) { break; } const auto ¶m_code = row[base_param_idx + i * 6 + 1]; const auto ¶m_name = row[base_param_idx + i * 6 + 2]; const auto ¶m_value = row[base_param_idx + i * 6 + 3]; const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) .set(metadata::Identifier::CODE_KEY, param_code) .set(common::IdentifiedObject::NAME_KEY, param_name))); std::string normalized_uom_code(param_uom_code); const double normalized_value = normalizeMeasure( param_uom_code, param_value, normalized_uom_code); auto uom = d->createUnitOfMeasure(param_uom_auth_name, normalized_uom_code); values.emplace_back(operation::ParameterValue::create( common::Measure(normalized_value, uom))); } idx = base_param_idx + 6 * N_MAX_PARAMS_GRID_TRANSFORMATION; const auto &interpolation_crs_auth_name = row[idx++]; const auto &interpolation_crs_code = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; assert(idx == row.size()); auto sourceCRS = d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code); auto targetCRS = d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); auto interpolationCRS = interpolation_crs_auth_name.empty() ? nullptr : d->createFactory(interpolation_crs_auth_name) ->createCoordinateReferenceSystem( interpolation_crs_code) .as_nullable(); auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } auto propsMethod = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code) .set(common::IdentifiedObject::NAME_KEY, method_name); std::vector accuracies; if (!accuracy.empty() && accuracy != "999.0") { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } // A bit fragile to detect the operation type with the method name, // but not worth changing the database model if (starts_with(method_name, "Point motion")) { if (!sourceCRS->isEquivalentTo(targetCRS.get())) { throw operation::InvalidOperation( "source_crs and target_crs should be the same for a " "PointMotionOperation"); } auto pmo = operation::PointMotionOperation::create( props, sourceCRS, propsMethod, parameters, values, accuracies); if (usePROJAlternativeGridNames) { return pmo->substitutePROJAlternativeGridNames( d->context()); } return pmo; } auto transf = operation::Transformation::create( props, sourceCRS, targetCRS, interpolationCRS, propsMethod, parameters, values, accuracies); if (usePROJAlternativeGridNames) { return transf->substitutePROJAlternativeGridNames(d->context()); } return transf; } catch (const std::exception &ex) { throw buildFactoryException("transformation", d->authority(), code, ex); } } if (type == "other_transformation") { std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " "target_crs_code, " "grid_param_auth_name, grid_param_code, grid_param_name, " "grid_name, " "interpolation_crs_auth_name, interpolation_crs_code, " "operation_version, accuracy, deprecated"; constexpr int N_MAX_PARAMS_OTHER_TRANSFORMATION = 9; for (size_t i = 1; i <= N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) { buffer << ", param" << i << "_auth_name"; buffer << ", param" << i << "_code"; buffer << ", param" << i << "_name"; buffer << ", param" << i << "_value"; buffer << ", param" << i << "_uom_auth_name"; buffer << ", param" << i << "_uom_code"; } buffer << " FROM other_transformation " "WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(buffer.str(), code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException("other_transformation not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &grid_param_auth_name = row[idx++]; const auto &grid_param_code = row[idx++]; const auto &grid_param_name = row[idx++]; const auto &grid_name = row[idx++]; const auto &interpolation_crs_auth_name = row[idx++]; const auto &interpolation_crs_code = row[idx++]; const auto &operation_version = row[idx++]; const auto &accuracy = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; const size_t base_param_idx = idx; std::vector parameters; std::vector values; for (size_t i = 0; i < N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) { const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; if (param_auth_name.empty()) { break; } const auto ¶m_code = row[base_param_idx + i * 6 + 1]; const auto ¶m_name = row[base_param_idx + i * 6 + 2]; const auto ¶m_value = row[base_param_idx + i * 6 + 3]; const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) .set(metadata::Identifier::CODE_KEY, param_code) .set(common::IdentifiedObject::NAME_KEY, param_name))); std::string normalized_uom_code(param_uom_code); const double normalized_value = normalizeMeasure( param_uom_code, param_value, normalized_uom_code); auto uom = d->createUnitOfMeasure(param_uom_auth_name, normalized_uom_code); values.emplace_back(operation::ParameterValue::create( common::Measure(normalized_value, uom))); } idx = base_param_idx + 6 * N_MAX_PARAMS_OTHER_TRANSFORMATION; (void)idx; assert(idx == row.size()); if (!grid_name.empty()) { parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, grid_param_name) .set(metadata::Identifier::CODESPACE_KEY, grid_param_auth_name) .set(metadata::Identifier::CODE_KEY, grid_param_code))); values.emplace_back( operation::ParameterValue::createFilename(grid_name)); } auto sourceCRS = d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code); auto targetCRS = d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); auto interpolationCRS = interpolation_crs_auth_name.empty() ? nullptr : d->createFactory(interpolation_crs_auth_name) ->createCoordinateReferenceSystem( interpolation_crs_code) .as_nullable(); auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } std::vector accuracies; if (!accuracy.empty() && accuracy != "999.0") { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } if (method_auth_name == "PROJ") { if (method_code == "PROJString") { auto op = operation::SingleOperation::createPROJBased( props, method_name, sourceCRS, targetCRS, accuracies); op->setCRSs(sourceCRS, targetCRS, interpolationCRS); return op; } else if (method_code == "WKT") { auto op = util::nn_dynamic_pointer_cast< operation::CoordinateOperation>( WKTParser().createFromWKT(method_name)); if (!op) { throw FactoryException("WKT string does not express a " "coordinate operation"); } op->setCRSs(sourceCRS, targetCRS, interpolationCRS); return NN_NO_CHECK(op); } } auto propsMethod = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code) .set(common::IdentifiedObject::NAME_KEY, method_name); if (method_auth_name == metadata::Identifier::EPSG) { int method_code_int = std::atoi(method_code.c_str()); if (operation::isAxisOrderReversal(method_code_int) || method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT || method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR || method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { auto op = operation::Conversion::create(props, propsMethod, parameters, values); op->setCRSs(sourceCRS, targetCRS, interpolationCRS); return op; } } auto transf = operation::Transformation::create( props, sourceCRS, targetCRS, interpolationCRS, propsMethod, parameters, values, accuracies); if (usePROJAlternativeGridNames) { return transf->substitutePROJAlternativeGridNames(d->context()); } return transf; } catch (const std::exception &ex) { throw buildFactoryException("transformation", d->authority(), code, ex); } } if (allowConcatenated && type == "concatenated_operation") { auto res = d->runWithCodeParam( "SELECT name, description, " "source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "accuracy, " "operation_version, deprecated FROM " "concatenated_operation WHERE auth_name = ? AND code = ?", code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException( "concatenated_operation not found", d->authority(), code); } auto resSteps = d->runWithCodeParam( "SELECT step_auth_name, step_code, step_direction FROM " "concatenated_operation_step WHERE operation_auth_name = ? " "AND operation_code = ? ORDER BY step_number", code); try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; std::vector operations; size_t countExplicitDirection = 0; for (const auto &rowStep : resSteps) { const auto &step_auth_name = rowStep[0]; const auto &step_code = rowStep[1]; const auto &step_direction = rowStep[2]; auto stepOp = d->createFactory(step_auth_name) ->createCoordinateOperation(step_code, false, usePROJAlternativeGridNames, std::string()); if (step_direction == "forward") { ++countExplicitDirection; operations.push_back(std::move(stepOp)); } else if (step_direction == "reverse") { ++countExplicitDirection; operations.push_back(stepOp->inverse()); } else { operations.push_back(std::move(stepOp)); } } if (countExplicitDirection > 0 && countExplicitDirection != resSteps.size()) { throw FactoryException("not all steps have a defined direction " "for concatenated operation " + code); } const bool fixDirectionAllowed = (countExplicitDirection == 0); operation::ConcatenatedOperation::fixSteps( d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code), d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code), operations, d->context(), fixDirectionAllowed); auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } std::vector accuracies; if (!accuracy.empty()) { if (accuracy != "999.0") { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } } else { // Try to compute a reasonable accuracy from the members double totalAcc = -1; try { for (const auto &op : operations) { auto accs = op->coordinateOperationAccuracies(); if (accs.size() == 1) { double acc = c_locale_stod(accs[0]->value()); if (totalAcc < 0) { totalAcc = acc; } else { totalAcc += acc; } } else if (dynamic_cast( op.get())) { // A conversion is perfectly accurate. if (totalAcc < 0) { totalAcc = 0; } } else { totalAcc = -1; break; } } if (totalAcc >= 0) { accuracies.emplace_back( metadata::PositionalAccuracy::create( toString(totalAcc))); } } catch (const std::exception &) { } } return operation::ConcatenatedOperation::create(props, operations, accuracies); } catch (const std::exception &ex) { throw buildFactoryException("transformation", d->authority(), code, ex); } } throw FactoryException("unhandled coordinate operation type: " + type); } // --------------------------------------------------------------------------- /** \brief Returns a list operation::CoordinateOperation between two CRS. * * The list is ordered with preferred operations first. No attempt is made * at inferring operations that are not explicitly in the database. * * Deprecated operations are rejected. * * @param sourceCRSCode Source CRS code allocated by authority. * @param targetCRSCode Source CRS code allocated by authority. * @return list of coordinate operations * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ std::vector AuthorityFactory::createFromCoordinateReferenceSystemCodes( const std::string &sourceCRSCode, const std::string &targetCRSCode) const { return createFromCoordinateReferenceSystemCodes( d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false, false, false, false); } // --------------------------------------------------------------------------- /** \brief Returns a list of geoid models available for that crs * * The list includes the geoid models connected directly with the crs, * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations * * @param code crs code allocated by authority. * @return list of geoid model names * @throw FactoryException in case of error. */ std::list AuthorityFactory::getGeoidModels(const std::string &code) const { ListOfParams params; std::string sql; sql += "SELECT DISTINCT GM0.name " " FROM geoid_model GM0 " "INNER JOIN grid_transformation GT0 " " ON GT0.code = GM0.operation_code " " AND GT0.auth_name = GM0.operation_auth_name " " AND GT0.deprecated = 0 " "INNER JOIN vertical_crs VC0 " " ON VC0.code = GT0.target_crs_code " " AND VC0.auth_name = GT0.target_crs_auth_name " "INNER JOIN vertical_crs VC1 " " ON VC1.datum_code = VC0.datum_code " " AND VC1.datum_auth_name = VC0.datum_auth_name " " AND VC1.code = ? "; params.emplace_back(code); if (d->hasAuthorityRestriction()) { sql += " AND GT0.target_crs_auth_name = ? "; params.emplace_back(d->authority()); } sql += " ORDER BY 1 "; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { res.push_back(row[0]); } return res; } // --------------------------------------------------------------------------- /** \brief Returns a list operation::CoordinateOperation between two CRS. * * The list is ordered with preferred operations first. No attempt is made * at inferring operations that are not explicitly in the database (see * createFromCRSCodesWithIntermediates() for that), and only * source -> target operations are searched (i.e. if target -> source is * present, you need to call this method with the arguments reversed, and apply * the reverse transformations). * * Deprecated operations are rejected. * * If getAuthority() returns empty, then coordinate operations from all * authorities are considered. * * @param sourceCRSAuthName Authority name of sourceCRSCode * @param sourceCRSCode Source CRS code allocated by authority * sourceCRSAuthName. * @param targetCRSAuthName Authority name of targetCRSCode * @param targetCRSCode Source CRS code allocated by authority * targetCRSAuthName. * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. * @param discardIfMissingGrid Whether coordinate operations that reference * missing grids should be removed from the result set. * @param considerKnownGridsAsAvailable Whether known grids should be considered * as available (typically when network is enabled). * @param discardSuperseded Whether coordinate operations that are superseded * (but not deprecated) should be removed from the result set. * @param tryReverseOrder whether to search in the reverse order too (and thus * inverse results found that way) * @param reportOnlyIntersectingTransformations if intersectingExtent1 and * intersectingExtent2 should be honored in a strict way. * @param intersectingExtent1 Optional extent that the resulting operations * must intersect. * @param intersectingExtent2 Optional extent that the resulting operations * must intersect. * @return list of coordinate operations * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ std::vector AuthorityFactory::createFromCoordinateReferenceSystemCodes( const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, bool tryReverseOrder, bool reportOnlyIntersectingTransformations, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const { auto cacheKey(d->authority()); cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName; cacheKey += sourceCRSCode; cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName; cacheKey += targetCRSCode; cacheKey += (usePROJAlternativeGridNames ? '1' : '0'); cacheKey += (discardIfMissingGrid ? '1' : '0'); cacheKey += (considerKnownGridsAsAvailable ? '1' : '0'); cacheKey += (discardSuperseded ? '1' : '0'); cacheKey += (tryReverseOrder ? '1' : '0'); cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0'); for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { const auto &geogExtent = extent->geographicElements(); if (geogExtent.size() == 1) { auto bbox = dynamic_cast( geogExtent[0].get()); if (bbox) { cacheKey += toString(bbox->southBoundLatitude()); cacheKey += toString(bbox->westBoundLongitude()); cacheKey += toString(bbox->northBoundLatitude()); cacheKey += toString(bbox->eastBoundLongitude()); } } } } std::vector list; if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) { return list; } // Check if sourceCRS would be the base of a ProjectedCRS targetCRS // In which case use the conversion of the ProjectedCRS if (!targetCRSAuthName.empty()) { auto targetFactory = d->createFactory(targetCRSAuthName); const auto cacheKeyProjectedCRS(targetFactory->d->authority() + targetCRSCode); auto crs = targetFactory->d->context()->d->getCRSFromCache( cacheKeyProjectedCRS); crs::ProjectedCRSPtr targetProjCRS; if (crs) { targetProjCRS = std::dynamic_pointer_cast(crs); } else { const auto sqlRes = targetFactory->d->createProjectedCRSBegin(targetCRSCode); if (!sqlRes.empty()) { try { targetProjCRS = targetFactory->d ->createProjectedCRSEnd(targetCRSCode, sqlRes) .as_nullable(); } catch (const std::exception &) { } } } if (targetProjCRS) { const auto &baseIds = targetProjCRS->baseCRS()->identifiers(); if (sourceCRSAuthName.empty() || (!baseIds.empty() && *(baseIds.front()->codeSpace()) == sourceCRSAuthName && baseIds.front()->code() == sourceCRSCode)) { bool ok = true; auto conv = targetProjCRS->derivingConversion(); if (d->hasAuthorityRestriction()) { ok = *(conv->identifiers().front()->codeSpace()) == d->authority(); } if (ok) { list.emplace_back(conv); d->context()->d->cache(cacheKey, list); return list; } } } } std::string sql; if (discardSuperseded) { sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, " "cov.target_crs_auth_name, cov.target_crs_code, " "cov.auth_name, cov.code, cov.table_name, " "extent.south_lat, extent.west_lon, extent.north_lat, " "extent.east_lon, " "ss.replacement_auth_name, ss.replacement_code, " "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, " "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid " "FROM " "coordinate_operation_view cov " "JOIN usage ON " "usage.object_table_name = cov.table_name AND " "usage.object_auth_name = cov.auth_name AND " "usage.object_code = cov.code " "JOIN extent " "ON extent.auth_name = usage.extent_auth_name AND " "extent.code = usage.extent_code " "LEFT JOIN supersession ss ON " "ss.superseded_table_name = cov.table_name AND " "ss.superseded_auth_name = cov.auth_name AND " "ss.superseded_code = cov.code AND " "ss.superseded_table_name = ss.replacement_table_name AND " "ss.same_source_target_crs = 1 " "LEFT JOIN grid_transformation gt ON " "gt.auth_name = ss.replacement_auth_name AND " "gt.code = ss.replacement_code " "LEFT JOIN grid_alternatives ga ON " "ga.original_grid_name = gt.grid_name " "WHERE "; } else { sql = "SELECT source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "cov.auth_name, cov.code, cov.table_name, " "extent.south_lat, extent.west_lon, extent.north_lat, " "extent.east_lon " "FROM " "coordinate_operation_view cov " "JOIN usage ON " "usage.object_table_name = cov.table_name AND " "usage.object_auth_name = cov.auth_name AND " "usage.object_code = cov.code " "JOIN extent " "ON extent.auth_name = usage.extent_auth_name AND " "extent.code = usage.extent_code " "WHERE "; } ListOfParams params; if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) { if (tryReverseOrder) { sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? " "AND " "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) " "OR " "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? " "AND " "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) " "AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); } else { sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? " "AND " "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? " "AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); } } else if (!sourceCRSAuthName.empty()) { if (tryReverseOrder) { sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? " ")OR " "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))" " AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); } else { sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? " "AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); } } else if (!targetCRSAuthName.empty()) { if (tryReverseOrder) { sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)" " OR " "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))" " AND "; params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); } else { sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? " "AND "; params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); } } sql += "cov.deprecated = 0"; if (d->hasAuthorityRestriction()) { sql += " AND cov.auth_name = ?"; params.emplace_back(d->authority()); } sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, " "east_lon) DESC, " "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy"; auto res = d->run(sql, params); std::set> setTransf; if (discardSuperseded) { for (const auto &row : res) { const auto &auth_name = row[4]; const auto &code = row[5]; setTransf.insert( std::pair(auth_name, code)); } } // Do a pass to determine if there are transformations that intersect // intersectingExtent1 & intersectingExtent2 std::vector intersectingTransformations; intersectingTransformations.resize(res.size()); bool hasIntersectingTransformations = false; size_t i = 0; for (const auto &row : res) { size_t thisI = i; ++i; if (discardSuperseded) { const auto &replacement_auth_name = row[11]; const auto &replacement_code = row[12]; const bool replacement_is_grid_transform = row[13] == "1"; const bool replacement_is_known_grid = row[14] == "1"; if (!replacement_auth_name.empty() && // Ignore supersession if the replacement uses a unknown grid !(replacement_is_grid_transform && !replacement_is_known_grid) && setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } } bool intersecting = true; try { double south_lat = c_locale_stod(row[7]); double west_lon = c_locale_stod(row[8]); double north_lat = c_locale_stod(row[9]); double east_lon = c_locale_stod(row[10]); auto transf_extent = metadata::Extent::createFromBBOX( west_lon, south_lat, east_lon, north_lat); for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { if (!transf_extent->intersects(NN_NO_CHECK(extent))) { intersecting = false; break; } } } } catch (const std::exception &) { } intersectingTransformations[thisI] = intersecting; if (intersecting) hasIntersectingTransformations = true; } // If there are intersecting transformations, then only report those ones // If there are no intersecting transformations, report all of them // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we // still want to be able to use the Pulkovo datum shift if EPSG:32631 // coordinates are used i = 0; for (const auto &row : res) { size_t thisI = i; ++i; if ((hasIntersectingTransformations || reportOnlyIntersectingTransformations) && !intersectingTransformations[thisI]) { continue; } if (discardSuperseded) { const auto &replacement_auth_name = row[11]; const auto &replacement_code = row[12]; const bool replacement_is_grid_transform = row[13] == "1"; const bool replacement_is_known_grid = row[14] == "1"; if (!replacement_auth_name.empty() && // Ignore supersession if the replacement uses a unknown grid !(replacement_is_grid_transform && !replacement_is_known_grid) && setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } } const auto &source_crs_auth_name = row[0]; const auto &source_crs_code = row[1]; const auto &target_crs_auth_name = row[2]; const auto &target_crs_code = row[3]; const auto &auth_name = row[4]; const auto &code = row[5]; const auto &table_name = row[6]; try { auto op = d->createFactory(auth_name)->createCoordinateOperation( code, true, usePROJAlternativeGridNames, table_name); if (tryReverseOrder && (!sourceCRSAuthName.empty() ? (source_crs_auth_name != sourceCRSAuthName || source_crs_code != sourceCRSCode) : (target_crs_auth_name != targetCRSAuthName || target_crs_code != targetCRSCode))) { op = op->inverse(); } if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) { list.emplace_back(op); } } catch (const std::exception &e) { // Mostly for debugging purposes when using an inconsistent // database if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) { fprintf(stderr, "Ignoring invalid operation: %s\n", e.what()); } else { throw; } } } d->context()->d->cache(cacheKey, list); return list; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op, const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode) { auto concat = dynamic_cast(op.get()); if (!concat) { return false; } auto ops = concat->operations(); for (size_t i = 0; i + 1 < ops.size(); i++) { auto targetCRS = ops[i]->targetCRS(); if (targetCRS) { const auto &ids = targetCRS->identifiers(); if (ids.size() == 1 && ((*ids[0]->codeSpace() == sourceCRSAuthName && ids[0]->code() == sourceCRSCode) || (*ids[0]->codeSpace() == targetCRSAuthName && ids[0]->code() == targetCRSCode))) { return true; } } } return false; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a list operation::CoordinateOperation between two CRS, * using intermediate codes. * * The list is ordered with preferred operations first. * * Deprecated operations are rejected. * * The method will take care of considering all potential combinations (i.e. * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to * call it with sourceCRS and targetCRS switched) * * If getAuthority() returns empty, then coordinate operations from all * authorities are considered. * * @param sourceCRSAuthName Authority name of sourceCRSCode * @param sourceCRSCode Source CRS code allocated by authority * sourceCRSAuthName. * @param targetCRSAuthName Authority name of targetCRSCode * @param targetCRSCode Source CRS code allocated by authority * targetCRSAuthName. * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. * @param discardIfMissingGrid Whether coordinate operations that reference * missing grids should be removed from the result set. * @param considerKnownGridsAsAvailable Whether known grids should be considered * as available (typically when network is enabled). * @param discardSuperseded Whether coordinate operations that are superseded * (but not deprecated) should be removed from the result set. * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be * used as potential intermediate CRS. If the list is empty, the database will * be used to find common CRS in operations involving both the source and * target CRS. * @param allowedIntermediateObjectType Restrict the type of the intermediate * object considered. * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently * @param allowedAuthorities One or several authority name allowed for the two * coordinate operations that are going to be searched. When this vector is * no empty, it overrides the authority of this object. This is useful for * example when the coordinate operations to chain belong to two different * allowed authorities. * @param intersectingExtent1 Optional extent that the resulting operations * must intersect. * @param intersectingExtent2 Optional extent that the resulting operations * must intersect. * @return list of coordinate operations * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ std::vector AuthorityFactory::createFromCRSCodesWithIntermediates( const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, const std::vector> &intermediateCRSAuthCodes, ObjectType allowedIntermediateObjectType, const std::vector &allowedAuthorities, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const { std::vector listTmp; if (sourceCRSAuthName == targetCRSAuthName && sourceCRSCode == targetCRSCode) { return listTmp; } const auto CheckIfHasOperations = [this](const std::string &auth_name, const std::string &code) { return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE " "(source_crs_auth_name = ? AND source_crs_code = ?) OR " "(target_crs_auth_name = ? AND target_crs_code = ?) " "LIMIT 1", {auth_name, code, auth_name, code}) .empty()); }; // If the source or target CRS are not the source or target of an operation, // do not run the next costly requests. if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) || !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) { return listTmp; } const std::string sqlProlog( discardSuperseded ? "SELECT v1.table_name as table1, " "v1.auth_name AS auth_name1, v1.code AS code1, " "v1.accuracy AS accuracy1, " "v2.table_name as table2, " "v2.auth_name AS auth_name2, v2.code AS code2, " "v2.accuracy as accuracy2, " "a1.south_lat AS south_lat1, " "a1.west_lon AS west_lon1, " "a1.north_lat AS north_lat1, " "a1.east_lon AS east_lon1, " "a2.south_lat AS south_lat2, " "a2.west_lon AS west_lon2, " "a2.north_lat AS north_lat2, " "a2.east_lon AS east_lon2, " "ss1.replacement_auth_name AS replacement_auth_name1, " "ss1.replacement_code AS replacement_code1, " "ss2.replacement_auth_name AS replacement_auth_name2, " "ss2.replacement_code AS replacement_code2 " "FROM coordinate_operation_view v1 " "JOIN coordinate_operation_view v2 " : "SELECT v1.table_name as table1, " "v1.auth_name AS auth_name1, v1.code AS code1, " "v1.accuracy AS accuracy1, " "v2.table_name as table2, " "v2.auth_name AS auth_name2, v2.code AS code2, " "v2.accuracy as accuracy2, " "a1.south_lat AS south_lat1, " "a1.west_lon AS west_lon1, " "a1.north_lat AS north_lat1, " "a1.east_lon AS east_lon1, " "a2.south_lat AS south_lat2, " "a2.west_lon AS west_lon2, " "a2.north_lat AS north_lat2, " "a2.east_lon AS east_lon2 " "FROM coordinate_operation_view v1 " "JOIN coordinate_operation_view v2 "); const char *joinSupersession = "LEFT JOIN supersession ss1 ON " "ss1.superseded_table_name = v1.table_name AND " "ss1.superseded_auth_name = v1.auth_name AND " "ss1.superseded_code = v1.code AND " "ss1.superseded_table_name = ss1.replacement_table_name AND " "ss1.same_source_target_crs = 1 " "LEFT JOIN supersession ss2 ON " "ss2.superseded_table_name = v2.table_name AND " "ss2.superseded_auth_name = v2.auth_name AND " "ss2.superseded_code = v2.code AND " "ss2.superseded_table_name = ss2.replacement_table_name AND " "ss2.same_source_target_crs = 1 "; const std::string joinArea( (discardSuperseded ? std::string(joinSupersession) : std::string()) .append("JOIN usage u1 ON " "u1.object_table_name = v1.table_name AND " "u1.object_auth_name = v1.auth_name AND " "u1.object_code = v1.code " "JOIN extent a1 " "ON a1.auth_name = u1.extent_auth_name AND " "a1.code = u1.extent_code " "JOIN usage u2 ON " "u2.object_table_name = v2.table_name AND " "u2.object_auth_name = v2.auth_name AND " "u2.object_code = v2.code " "JOIN extent a2 " "ON a2.auth_name = u2.extent_auth_name AND " "a2.code = u2.extent_code ")); const std::string orderBy( "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + " "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), " "accuracy1 + accuracy2"); // Case (source->intermediate) and (intermediate->target) std::string sql( sqlProlog + "ON v1.target_crs_auth_name = v2.source_crs_auth_name " "AND v1.target_crs_code = v2.source_crs_code " + joinArea + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "); std::string minDate; std::string criterionOnIntermediateCRS; const auto sourceCRS = d->createFactory(sourceCRSAuthName) ->createCoordinateReferenceSystem(sourceCRSCode); const auto targetCRS = d->createFactory(targetCRSAuthName) ->createCoordinateReferenceSystem(targetCRSCode); const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") && starts_with(targetCRS->nameStr(), "ETRF"); const bool NAD83_CSRS_to_NAD83_CSRS = starts_with(sourceCRS->nameStr(), "NAD83(CSRS)") && starts_with(targetCRS->nameStr(), "NAD83(CSRS)"); if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { const auto &sourceGeogCRS = dynamic_cast(sourceCRS.get()); const auto &targetGeogCRS = dynamic_cast(targetCRS.get()); if (sourceGeogCRS && targetGeogCRS) { const auto &sourceDatum = sourceGeogCRS->datum(); const auto &targetDatum = targetGeogCRS->datum(); if (sourceDatum && sourceDatum->publicationDate().has_value() && targetDatum && targetDatum->publicationDate().has_value()) { const auto sourceDate( sourceDatum->publicationDate()->toString()); const auto targetDate( targetDatum->publicationDate()->toString()); minDate = std::min(sourceDate, targetDate); // Check that the datum of the intermediateCRS has a publication // date most recent that the one of the source and the target // CRS Except when using the usual WGS84 pivot which happens to // have a NULL publication date. criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x " "JOIN geodetic_datum y " "ON " "y.auth_name = x.datum_auth_name AND " "y.code = x.datum_code " "WHERE " "x.auth_name = v1.target_crs_auth_name AND " "x.code = v1.target_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D') AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } } if (criterionOnIntermediateCRS.empty()) { criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE " "x.auth_name = v1.target_crs_auth_name AND " "x.code = v1.target_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D')) "; } sql += criterionOnIntermediateCRS; } auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode}; std::string additionalWhere( "AND v1.deprecated = 0 AND v2.deprecated = 0 " "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, " "south_lat2, west_lon2, north_lat2, east_lon2) = 1 "); if (!allowedAuthorities.empty()) { additionalWhere += "AND v1.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) additionalWhere += ','; additionalWhere += '?'; } additionalWhere += ") AND v2.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) additionalWhere += ','; additionalWhere += '?'; } additionalWhere += ')'; for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } } if (d->hasAuthorityRestriction()) { additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? "; params.emplace_back(d->authority()); params.emplace_back(d->authority()); } for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { const auto &geogExtent = extent->geographicElements(); if (geogExtent.size() == 1) { auto bbox = dynamic_cast( geogExtent[0].get()); if (bbox) { const double south_lat = bbox->southBoundLatitude(); const double west_lon = bbox->westBoundLongitude(); const double north_lat = bbox->northBoundLatitude(); const double east_lon = bbox->eastBoundLongitude(); if (south_lat != -90.0 || west_lon != -180.0 || north_lat != 90.0 || east_lon != 180.0) { additionalWhere += "AND intersects_bbox(south_lat1, " "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND " "intersects_bbox(south_lat2, west_lon2, " "north_lat2, east_lon2, ?, ?, ?, ?) "; params.emplace_back(south_lat); params.emplace_back(west_lon); params.emplace_back(north_lat); params.emplace_back(east_lon); params.emplace_back(south_lat); params.emplace_back(west_lon); params.emplace_back(north_lat); params.emplace_back(east_lon); } } } } } const auto buildIntermediateWhere = [&intermediateCRSAuthCodes](const std::string &first_field, const std::string &second_field) { if (intermediateCRSAuthCodes.empty()) { return std::string(); } std::string l_sql(" AND ("); for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) { if (i > 0) { l_sql += " OR"; } l_sql += "(v1." + first_field + "_crs_auth_name = ? AND "; l_sql += "v1." + first_field + "_crs_code = ? AND "; l_sql += "v2." + second_field + "_crs_auth_name = ? AND "; l_sql += "v2." + second_field + "_crs_code = ?) "; } l_sql += ')'; return l_sql; }; std::string intermediateWhere = buildIntermediateWhere("target", "source"); for (const auto &pair : intermediateCRSAuthCodes) { params.emplace_back(pair.first); params.emplace_back(pair.second); params.emplace_back(pair.first); params.emplace_back(pair.second); } auto res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); const auto filterOutSuperseded = [](SQLResultSet &&resultSet) { std::set> setTransf1; std::set> setTransf2; for (const auto &row : resultSet) { // table1 const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // accuracy1 // table2 const auto &auth_name2 = row[5]; const auto &code2 = row[6]; setTransf1.insert( std::pair(auth_name1, code1)); setTransf2.insert( std::pair(auth_name2, code2)); } SQLResultSet filteredResultSet; for (const auto &row : resultSet) { const auto &replacement_auth_name1 = row[16]; const auto &replacement_code1 = row[17]; const auto &replacement_auth_name2 = row[18]; const auto &replacement_code2 = row[19]; if (!replacement_auth_name1.empty() && setTransf1.find(std::pair( replacement_auth_name1, replacement_code1)) != setTransf1.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } if (!replacement_auth_name2.empty() && setTransf2.find(std::pair( replacement_auth_name2, replacement_code2)) != setTransf2.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } filteredResultSet.emplace_back(row); } return filteredResultSet; }; if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } const auto checkPivot = [ETRFtoETRF, NAD83_CSRS_to_NAD83_CSRS, &sourceCRS, &targetCRS](const crs::CRSPtr &intermediateCRS) { // Make sure that ETRF2000 to ETRF2014 doesn't go through ITRF9x or // ITRF>2014 if (ETRFtoETRF && intermediateCRS && starts_with(intermediateCRS->nameStr(), "ITRF")) { const auto normalizeDate = [](int v) { return (v >= 80 && v <= 99) ? v + 1900 : v; }; const int srcDate = normalizeDate( atoi(sourceCRS->nameStr().c_str() + strlen("ETRF"))); const int tgtDate = normalizeDate( atoi(targetCRS->nameStr().c_str() + strlen("ETRF"))); const int intermDate = normalizeDate( atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF"))); if (srcDate > 0 && tgtDate > 0 && intermDate > 0) { if (intermDate < std::min(srcDate, tgtDate) || intermDate > std::max(srcDate, tgtDate)) { return false; } } } // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go through // NAD83 generic. Cf https://github.com/OSGeo/PROJ/issues/4464 if (NAD83_CSRS_to_NAD83_CSRS && intermediateCRS && (intermediateCRS->nameStr() == "NAD83" || intermediateCRS->nameStr() == "WGS 84")) { return false; } return true; }; for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; try { auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } if (!checkPivot(op1->targetCRS())) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {std::move(op1), std::move(op2)}, false)); } catch (const std::exception &e) { // Mostly for debugging purposes when using an inconsistent // database if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) { fprintf(stderr, "Ignoring invalid operation: %s\n", e.what()); } else { throw; } } } // Case (source->intermediate) and (target->intermediate) sql = sqlProlog + "ON v1.target_crs_auth_name = v2.target_crs_auth_name " "AND v1.target_crs_code = v2.target_crs_code " + joinArea + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { sql += criterionOnIntermediateCRS; } intermediateWhere = buildIntermediateWhere("target", "target"); res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; try { auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } if (!checkPivot(op1->targetCRS())) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {std::move(op1), op2->inverse()}, false)); } catch (const std::exception &e) { // Mostly for debugging purposes when using an inconsistent // database if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) { fprintf(stderr, "Ignoring invalid operation: %s\n", e.what()); } else { throw; } } } // Case (intermediate->source) and (intermediate->target) sql = sqlProlog + "ON v1.source_crs_auth_name = v2.source_crs_auth_name " "AND v1.source_crs_code = v2.source_crs_code " + joinArea + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { if (!minDate.empty()) { criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x " "JOIN geodetic_datum y " "ON " "y.auth_name = x.datum_auth_name AND " "y.code = x.datum_code " "WHERE " "x.auth_name = v1.source_crs_auth_name AND " "x.code = v1.source_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D') AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } else { criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE " "x.auth_name = v1.source_crs_auth_name AND " "x.code = v1.source_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D')) "; } sql += criterionOnIntermediateCRS; } intermediateWhere = buildIntermediateWhere("source", "source"); res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; try { auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } if (!checkPivot(op1->sourceCRS())) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {op1->inverse(), std::move(op2)}, false)); } catch (const std::exception &e) { // Mostly for debugging purposes when using an inconsistent // database if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) { fprintf(stderr, "Ignoring invalid operation: %s\n", e.what()); } else { throw; } } } // Case (intermediate->source) and (target->intermediate) sql = sqlProlog + "ON v1.source_crs_auth_name = v2.target_crs_auth_name " "AND v1.source_crs_code = v2.target_crs_code " + joinArea + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { sql += criterionOnIntermediateCRS; } intermediateWhere = buildIntermediateWhere("source", "target"); res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; try { auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } if (!checkPivot(op1->sourceCRS())) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {op1->inverse(), op2->inverse()}, false)); } catch (const std::exception &e) { // Mostly for debugging purposes when using an inconsistent // database if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) { fprintf(stderr, "Ignoring invalid operation: %s\n", e.what()); } else { throw; } } } std::vector list; for (const auto &op : listTmp) { if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) { list.emplace_back(op); } } return list; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct TrfmInfo { std::string situation{}; std::string table_name{}; std::string auth_name{}; std::string code{}; std::string name{}; double west = 0; double south = 0; double east = 0; double north = 0; }; // --------------------------------------------------------------------------- std::vector AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates( const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, const std::vector &allowedAuthorities, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const { std::vector listTmp; if (sourceCRSAuthName == targetCRSAuthName && sourceCRSCode == targetCRSCode) { return listTmp; } const auto sourceGeodCRS = dynamic_cast(sourceCRS.get()); const auto targetGeodCRS = dynamic_cast(targetCRS.get()); if (!sourceGeodCRS || !targetGeodCRS) { return listTmp; } const bool NAD83_CSRS_to_NAD83_CSRS = starts_with(sourceGeodCRS->nameStr(), "NAD83(CSRS)") && starts_with(targetGeodCRS->nameStr(), "NAD83(CSRS)"); const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs, const std::string &crsAuthName, const std::string &crsCode) { // Find all geodetic CRS that share the same datum as the CRS SQLResultSet listCRS; const common::IdentifiedObject *obj = crs->datum().get(); if (obj == nullptr) obj = crs->datumEnsemble().get(); assert(obj != nullptr); const auto &ids = obj->identifiers(); std::string datumAuthName; std::string datumCode; if (!ids.empty()) { const auto &id = ids.front(); datumAuthName = *(id->codeSpace()); datumCode = id->code(); } else { const auto res = d->run("SELECT datum_auth_name, datum_code FROM " "geodetic_crs WHERE auth_name = ? AND code = ?", {crsAuthName, crsCode}); if (res.size() != 1) { return listCRS; } const auto &row = res.front(); datumAuthName = row[0]; datumCode = row[1]; } listCRS = d->run("SELECT auth_name, code FROM geodetic_crs WHERE " "datum_auth_name = ? AND datum_code = ? AND deprecated = 0", {datumAuthName, datumCode}); if (listCRS.empty()) { // Can happen if the CRS is deprecated listCRS.emplace_back(SQLRow{crsAuthName, crsCode}); } return listCRS; }; const SQLResultSet listSourceCRS = GetListCRSWithSameDatum( sourceGeodCRS, sourceCRSAuthName, sourceCRSCode); const SQLResultSet listTargetCRS = GetListCRSWithSameDatum( targetGeodCRS, targetCRSAuthName, targetCRSCode); if (listSourceCRS.empty() || listTargetCRS.empty()) { // would happen only if we had CRS objects in the database without a // link to a datum. return listTmp; } ListOfParams params; const auto BuildSQLPart = [this, NAD83_CSRS_to_NAD83_CSRS, &allowedAuthorities, ¶ms, &listSourceCRS, &listTargetCRS](bool isSourceCRS, bool selectOnTarget) { std::string situation; if (isSourceCRS) situation = "src"; else situation = "tgt"; if (selectOnTarget) situation += "_is_tgt"; else situation += "_is_src"; const std::string prefix1(selectOnTarget ? "source" : "target"); const std::string prefix2(selectOnTarget ? "target" : "source"); std::string sql("SELECT '"); sql += situation; sql += "' as situation, v.table_name, v.auth_name, " "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat " "FROM coordinate_operation_view v " "JOIN geodetic_crs gcrs on gcrs.auth_name = "; sql += prefix1; sql += "_crs_auth_name AND gcrs.code = "; sql += prefix1; sql += "_crs_code " "LEFT JOIN usage u ON " "u.object_table_name = v.table_name AND " "u.object_auth_name = v.auth_name AND " "u.object_code = v.code " "LEFT JOIN extent a " "ON a.auth_name = u.extent_auth_name AND " "a.code = u.extent_code " "WHERE v.deprecated = 0 AND ("; std::string cond; const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS; for (const auto &row : list) { if (!cond.empty()) cond += " OR "; cond += '('; cond += prefix2; cond += "_crs_auth_name = ? AND "; cond += prefix2; cond += "_crs_code = ?)"; params.emplace_back(row[0]); params.emplace_back(row[1]); } sql += cond; sql += ") "; if (!allowedAuthorities.empty()) { sql += "AND v.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) sql += ','; sql += '?'; } sql += ") "; for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } } if (d->hasAuthorityRestriction()) { sql += "AND v.auth_name = ? "; params.emplace_back(d->authority()); } if (NAD83_CSRS_to_NAD83_CSRS) { // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go // through NAD83 generic. Cf // https://github.com/OSGeo/PROJ/issues/4464 sql += "AND gcrs.name NOT IN ('NAD83', 'WGS 84') "; } return sql; }; std::string sql(BuildSQLPart(true, true)); sql += "UNION ALL "; sql += BuildSQLPart(false, true); sql += "UNION ALL "; sql += BuildSQLPart(true, false); sql += "UNION ALL "; sql += BuildSQLPart(false, false); // fprintf(stderr, "sql : %s\n", sql.c_str()); // Find all operations that have as source/target CRS a CRS that // share the same datum as the source or targetCRS const auto res = d->run(sql, params); std::map> mapIntermDatumOfSource; std::map> mapIntermDatumOfTarget; for (const auto &row : res) { try { TrfmInfo trfm; trfm.situation = row[0]; trfm.table_name = row[1]; trfm.auth_name = row[2]; trfm.code = row[3]; trfm.name = row[4]; const auto &datum_auth_name = row[5]; const auto &datum_code = row[6]; trfm.west = c_locale_stod(row[7]); trfm.south = c_locale_stod(row[8]); trfm.east = c_locale_stod(row[9]); trfm.north = c_locale_stod(row[10]); const std::string key = std::string(datum_auth_name).append(":").append(datum_code); if (trfm.situation == "src_is_tgt" || trfm.situation == "src_is_src") mapIntermDatumOfSource[key].emplace_back(std::move(trfm)); else mapIntermDatumOfTarget[key].emplace_back(std::move(trfm)); } catch (const std::exception &) { } } std::vector extraBbox; for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { const auto &geogExtent = extent->geographicElements(); if (geogExtent.size() == 1) { auto bbox = dynamic_cast( geogExtent[0].get()); if (bbox) { const double south_lat = bbox->southBoundLatitude(); const double west_lon = bbox->westBoundLongitude(); const double north_lat = bbox->northBoundLatitude(); const double east_lon = bbox->eastBoundLongitude(); if (south_lat != -90.0 || west_lon != -180.0 || north_lat != 90.0 || east_lon != 180.0) { extraBbox.emplace_back(bbox); } } } } } std::map oMapTrfmKeyToOp; std::list> candidates; std::map setOfTransformations; const auto MakeKey = [](const TrfmInfo &trfm) { return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code; }; // Find transformations that share a pivot datum, and do bbox filtering for (const auto &kvSource : mapIntermDatumOfSource) { const auto &listTrmfSource = kvSource.second; auto iter = mapIntermDatumOfTarget.find(kvSource.first); if (iter == mapIntermDatumOfTarget.end()) continue; const auto &listTrfmTarget = iter->second; for (const auto &trfmSource : listTrmfSource) { auto bbox1 = metadata::GeographicBoundingBox::create( trfmSource.west, trfmSource.south, trfmSource.east, trfmSource.north); bool okBbox1 = true; for (const auto bbox : extraBbox) okBbox1 &= bbox->intersects(bbox1); if (!okBbox1) continue; const std::string key1 = MakeKey(trfmSource); for (const auto &trfmTarget : listTrfmTarget) { auto bbox2 = metadata::GeographicBoundingBox::create( trfmTarget.west, trfmTarget.south, trfmTarget.east, trfmTarget.north); if (!bbox1->intersects(bbox2)) continue; bool okBbox2 = true; for (const auto bbox : extraBbox) okBbox2 &= bbox->intersects(bbox2); if (!okBbox2) continue; operation::CoordinateOperationPtr op1; if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) { auto op1NN = d->createFactory(trfmSource.auth_name) ->createCoordinateOperation( trfmSource.code, true, usePROJAlternativeGridNames, trfmSource.table_name); op1 = op1NN.as_nullable(); if (useIrrelevantPivot(op1NN, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { op1.reset(); } oMapTrfmKeyToOp[key1] = op1; } else { op1 = oMapTrfmKeyToOp[key1]; } if (op1 == nullptr) continue; const std::string key2 = MakeKey(trfmTarget); operation::CoordinateOperationPtr op2; if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) { auto op2NN = d->createFactory(trfmTarget.auth_name) ->createCoordinateOperation( trfmTarget.code, true, usePROJAlternativeGridNames, trfmTarget.table_name); op2 = op2NN.as_nullable(); if (useIrrelevantPivot(op2NN, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { op2.reset(); } oMapTrfmKeyToOp[key2] = op2; } else { op2 = oMapTrfmKeyToOp[key2]; } if (op2 == nullptr) continue; candidates.emplace_back( std::pair(trfmSource, trfmTarget)); setOfTransformations[key1] = trfmSource; setOfTransformations[key2] = trfmTarget; } } } std::set setSuperseded; if (discardSuperseded && !setOfTransformations.empty()) { std::string findSupersededSql( "SELECT superseded_table_name, " "superseded_auth_name, superseded_code, " "replacement_auth_name, replacement_code " "FROM supersession WHERE same_source_target_crs = 1 AND ("); bool findSupersededFirstWhere = true; ListOfParams findSupersededParams; const auto keyMapSupersession = [](const std::string &table_name, const std::string &auth_name, const std::string &code) { return table_name + auth_name + code; }; std::set> setTransf; for (const auto &kv : setOfTransformations) { const auto &table = kv.second.table_name; const auto &auth_name = kv.second.auth_name; const auto &code = kv.second.code; if (!findSupersededFirstWhere) findSupersededSql += " OR "; findSupersededFirstWhere = false; findSupersededSql += "(superseded_table_name = ? AND replacement_table_name = " "superseded_table_name AND superseded_auth_name = ? AND " "superseded_code = ?)"; findSupersededParams.push_back(table); findSupersededParams.push_back(auth_name); findSupersededParams.push_back(code); setTransf.insert( std::pair(auth_name, code)); } findSupersededSql += ')'; std::map>> mapSupersession; const auto resSuperseded = d->run(findSupersededSql, findSupersededParams); for (const auto &row : resSuperseded) { const auto &superseded_table_name = row[0]; const auto &superseded_auth_name = row[1]; const auto &superseded_code = row[2]; const auto &replacement_auth_name = row[3]; const auto &replacement_code = row[4]; mapSupersession[keyMapSupersession(superseded_table_name, superseded_auth_name, superseded_code)] .push_back(std::pair( replacement_auth_name, replacement_code)); } for (const auto &kv : setOfTransformations) { const auto &table = kv.second.table_name; const auto &auth_name = kv.second.auth_name; const auto &code = kv.second.code; const auto iter = mapSupersession.find( keyMapSupersession(table, auth_name, code)); if (iter != mapSupersession.end()) { bool foundReplacement = false; for (const auto &replacement : iter->second) { const auto &replacement_auth_name = replacement.first; const auto &replacement_code = replacement.second; if (setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others // that got // returned in the result set. foundReplacement = true; break; } } if (foundReplacement) { setSuperseded.insert(kv.first); } } } } std::string sourceDatumPubDate; const auto sourceDatum = sourceGeodCRS->datumNonNull(d->context()); if (sourceDatum->publicationDate().has_value()) { sourceDatumPubDate = sourceDatum->publicationDate()->toString(); } std::string targetDatumPubDate; const auto targetDatum = targetGeodCRS->datumNonNull(d->context()); if (targetDatum->publicationDate().has_value()) { targetDatumPubDate = targetDatum->publicationDate()->toString(); } const std::string mostAncientDatumPubDate = (!targetDatumPubDate.empty() && (sourceDatumPubDate.empty() || targetDatumPubDate < sourceDatumPubDate)) ? targetDatumPubDate : sourceDatumPubDate; auto opFactory = operation::CoordinateOperationFactory::create(); for (const auto &pair : candidates) { const auto &trfmSource = pair.first; const auto &trfmTarget = pair.second; const std::string key1 = MakeKey(trfmSource); const std::string key2 = MakeKey(trfmTarget); if (setSuperseded.find(key1) != setSuperseded.end() || setSuperseded.find(key2) != setSuperseded.end()) { continue; } auto op1 = oMapTrfmKeyToOp[key1]; auto op2 = oMapTrfmKeyToOp[key2]; auto op1NN = NN_NO_CHECK(op1); auto op2NN = NN_NO_CHECK(op2); if (trfmSource.situation == "src_is_tgt") op1NN = op1NN->inverse(); if (trfmTarget.situation == "tgt_is_src") op2NN = op2NN->inverse(); const auto &op1Source = op1NN->sourceCRS(); const auto &op1Target = op1NN->targetCRS(); const auto &op2Source = op2NN->sourceCRS(); const auto &op2Target = op2NN->targetCRS(); if (!(op1Source && op1Target && op2Source && op2Target)) { continue; } // Skip operations using a datum that is older than the source or // target datum (e.g to avoid ED50 to WGS84 to go through NTF) if (!mostAncientDatumPubDate.empty()) { const auto isOlderCRS = [this, &mostAncientDatumPubDate]( const crs::CRSPtr &crs) { const auto geogCRS = dynamic_cast(crs.get()); if (geogCRS) { const auto datum = geogCRS->datumNonNull(d->context()); // Hum, theoretically we'd want to check // datum->publicationDate()->toString() < // mostAncientDatumPubDate but that would exclude doing // IG05/12 Intermediate CRS to ITRF2014 through ITRF2008, // since IG05/12 Intermediate CRS has been published later // than ITRF2008. So use a cut of date for ancient vs // "modern" era. constexpr const char *CUT_OFF_DATE = "1900-01-01"; if (datum->publicationDate().has_value() && datum->publicationDate()->toString() < CUT_OFF_DATE && mostAncientDatumPubDate > CUT_OFF_DATE) { return true; } } return false; }; if (isOlderCRS(op1Source) || isOlderCRS(op1Target) || isOlderCRS(op2Source) || isOlderCRS(op2Target)) continue; } std::vector steps; if (!sourceCRS->isEquivalentTo( op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opFirst = opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source)); assert(opFirst); steps.emplace_back(NN_NO_CHECK(opFirst)); } steps.emplace_back(op1NN); if (!op1Target->isEquivalentTo( op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target), NN_NO_CHECK(op2Source)); assert(opMiddle); steps.emplace_back(NN_NO_CHECK(opMiddle)); } steps.emplace_back(op2NN); if (!op2Target->isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS); assert(opLast); steps.emplace_back(NN_NO_CHECK(opLast)); } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata(steps, false)); } std::vector list; for (const auto &op : listTmp) { if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) { list.emplace_back(op); } } return list; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns the authority name associated to this factory. * @return name. */ const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN { return d->authority(); } // --------------------------------------------------------------------------- /** \brief Returns the set of authority codes of the given object type. * * @param type Object type. * @param allowDeprecated whether we should return deprecated objects as well. * @return the set of authority codes for spatial reference objects of the given * type * @throw FactoryException in case of error. */ std::set AuthorityFactory::getAuthorityCodes(const ObjectType &type, bool allowDeprecated) const { std::string sql; switch (type) { case ObjectType::PRIME_MERIDIAN: sql = "SELECT code FROM prime_meridian WHERE "; break; case ObjectType::ELLIPSOID: sql = "SELECT code FROM ellipsoid WHERE "; break; case ObjectType::DATUM: sql = "SELECT code FROM object_view WHERE table_name IN " "('geodetic_datum', 'vertical_datum', 'engineering_datum') AND "; break; case ObjectType::GEODETIC_REFERENCE_FRAME: sql = "SELECT code FROM geodetic_datum WHERE "; break; case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME: sql = "SELECT code FROM geodetic_datum WHERE " "frame_reference_epoch IS NOT NULL AND "; break; case ObjectType::VERTICAL_REFERENCE_FRAME: sql = "SELECT code FROM vertical_datum WHERE "; break; case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME: sql = "SELECT code FROM vertical_datum WHERE " "frame_reference_epoch IS NOT NULL AND "; break; case ObjectType::ENGINEERING_DATUM: sql = "SELECT code FROM engineering_datum WHERE "; break; case ObjectType::CRS: sql = "SELECT code FROM crs_view WHERE "; break; case ObjectType::GEODETIC_CRS: sql = "SELECT code FROM geodetic_crs WHERE "; break; case ObjectType::GEOCENTRIC_CRS: sql = "SELECT code FROM geodetic_crs WHERE type " "= " GEOCENTRIC_SINGLE_QUOTED " AND "; break; case ObjectType::GEOGRAPHIC_CRS: sql = "SELECT code FROM geodetic_crs WHERE type IN " "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND "; break; case ObjectType::GEOGRAPHIC_2D_CRS: sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED " AND "; break; case ObjectType::GEOGRAPHIC_3D_CRS: sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED " AND "; break; case ObjectType::VERTICAL_CRS: sql = "SELECT code FROM vertical_crs WHERE "; break; case ObjectType::PROJECTED_CRS: sql = "SELECT code FROM projected_crs WHERE "; break; case ObjectType::COMPOUND_CRS: sql = "SELECT code FROM compound_crs WHERE "; break; case ObjectType::ENGINEERING_CRS: sql = "SELECT code FROM engineering_crs WHERE "; break; case ObjectType::COORDINATE_OPERATION: sql = "SELECT code FROM coordinate_operation_with_conversion_view WHERE "; break; case ObjectType::CONVERSION: sql = "SELECT code FROM conversion WHERE "; break; case ObjectType::TRANSFORMATION: sql = "SELECT code FROM coordinate_operation_view WHERE table_name != " "'concatenated_operation' AND "; break; case ObjectType::CONCATENATED_OPERATION: sql = "SELECT code FROM concatenated_operation WHERE "; break; case ObjectType::DATUM_ENSEMBLE: sql = "SELECT code FROM object_view WHERE table_name IN " "('geodetic_datum', 'vertical_datum') AND " "type = 'ensemble' AND "; break; } sql += "auth_name = ?"; if (!allowDeprecated) { sql += " AND deprecated = 0"; } auto res = d->run(sql, {d->authority()}); std::set set; for (const auto &row : res) { set.insert(row[0]); } return set; } // --------------------------------------------------------------------------- /** \brief Gets a description of the object corresponding to a code. * * \note In case of several objects of different types with the same code, * one of them will be arbitrarily selected. But if a CRS object is found, it * will be selected. * * @param code Object code allocated by authority. (e.g. "4326") * @return description. * @throw NoSuchAuthorityCodeException if there is no matching object. * @throw FactoryException in case of other errors. */ std::string AuthorityFactory::getDescriptionText(const std::string &code) const { auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? " "AND code = ? ORDER BY table_name"; auto sqlRes = d->runWithCodeParam(sql, code); if (sqlRes.empty()) { throw NoSuchAuthorityCodeException("object not found", d->authority(), code); } std::string text; for (const auto &row : sqlRes) { const auto &tableName = row[1]; if (tableName == "geodetic_crs" || tableName == "projected_crs" || tableName == "vertical_crs" || tableName == "compound_crs" || tableName == "engineering_crs") { return row[0]; } else if (text.empty()) { text = row[0]; } } return text; } // --------------------------------------------------------------------------- /** \brief Return a list of information on CRS objects * * This is functionally equivalent of listing the codes from an authority, * instantiating * a CRS object for each of them and getting the information from this CRS * object, but this implementation has much less overhead. * * @throw FactoryException in case of error. */ std::list AuthorityFactory::getCRSInfoList() const { const auto getSqlArea = [](const char *table_name) { std::string sql("LEFT JOIN usage u ON u.object_table_name = '"); sql += table_name; sql += "' AND " "u.object_auth_name = c.auth_name AND " "u.object_code = c.code " "LEFT JOIN extent a " "ON a.auth_name = u.extent_auth_name AND " "a.code = u.extent_code "; return sql; }; const auto getJoinCelestialBody = [](const char *crs_alias) { std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = "); sql += crs_alias; sql += ".datum_auth_name AND gd.code = "; sql += crs_alias; sql += ".datum_code " "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name " "AND e.code = gd.ellipsoid_code " "LEFT JOIN celestial_body cb ON " "cb.auth_name = e.celestial_body_auth_name " "AND cb.code = e.celestial_body_code "; return sql; }; std::string sql = "SELECT * FROM (" "SELECT c.auth_name, c.code, c.name, c.type, " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL, cb.name FROM geodetic_crs c "; sql += getSqlArea("geodetic_crs"); sql += getJoinCelestialBody("c"); ListOfParams params; if (d->hasAuthorityRestriction()) { sql += "WHERE c.auth_name = ? "; params.emplace_back(d->authority()); } sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, cm.name, cb.name AS conversion_method_name FROM " "projected_crs c " "LEFT JOIN conversion_table conv ON " "c.conversion_auth_name = conv.auth_name AND " "c.conversion_code = conv.code " "LEFT JOIN conversion_method cm ON " "conv.method_auth_name = cm.auth_name AND " "conv.method_code = cm.code " "LEFT JOIN geodetic_crs gcrs ON " "gcrs.auth_name = c.geodetic_crs_auth_name " "AND gcrs.code = c.geodetic_crs_code "; sql += getSqlArea("projected_crs"); sql += getJoinCelestialBody("gcrs"); if (d->hasAuthorityRestriction()) { sql += "WHERE c.auth_name = ? "; params.emplace_back(d->authority()); } // FIXME: we can't handle non-EARTH vertical CRS for now sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL, 'Earth' FROM vertical_crs c "; sql += getSqlArea("vertical_crs"); if (d->hasAuthorityRestriction()) { sql += "WHERE c.auth_name = ? "; params.emplace_back(d->authority()); } // FIXME: we can't handle non-EARTH compound CRS for now sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL, 'Earth' FROM compound_crs c "; sql += getSqlArea("compound_crs"); if (d->hasAuthorityRestriction()) { sql += "WHERE c.auth_name = ? "; params.emplace_back(d->authority()); } // FIXME: we can't handle non-EARTH compound CRS for now sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'engineering', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL, 'Earth' FROM engineering_crs c "; sql += getSqlArea("engineering_crs"); if (d->hasAuthorityRestriction()) { sql += "WHERE c.auth_name = ? "; params.emplace_back(d->authority()); } sql += ") r ORDER BY auth_name, code"; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { AuthorityFactory::CRSInfo info; info.authName = row[0]; info.code = row[1]; info.name = row[2]; const auto &type = row[3]; if (type == GEOG_2D) { info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS; } else if (type == GEOG_3D) { info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS; } else if (type == GEOCENTRIC) { info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS; } else if (type == OTHER) { info.type = AuthorityFactory::ObjectType::GEODETIC_CRS; } else if (type == PROJECTED) { info.type = AuthorityFactory::ObjectType::PROJECTED_CRS; } else if (type == VERTICAL) { info.type = AuthorityFactory::ObjectType::VERTICAL_CRS; } else if (type == COMPOUND) { info.type = AuthorityFactory::ObjectType::COMPOUND_CRS; } else if (type == ENGINEERING) { info.type = AuthorityFactory::ObjectType::ENGINEERING_CRS; } info.deprecated = row[4] == "1"; if (row[5].empty()) { info.bbox_valid = false; } else { info.bbox_valid = true; info.west_lon_degree = c_locale_stod(row[5]); info.south_lat_degree = c_locale_stod(row[6]); info.east_lon_degree = c_locale_stod(row[7]); info.north_lat_degree = c_locale_stod(row[8]); } info.areaName = row[9]; info.projectionMethodName = row[10]; info.celestialBodyName = row[11]; res.emplace_back(info); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::UnitInfo::UnitInfo() : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{}, deprecated{} {} //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {} //! @endcond // --------------------------------------------------------------------------- /** \brief Return the list of units. * @throw FactoryException in case of error. * * @since 7.1 */ std::list AuthorityFactory::getUnitList() const { std::string sql = "SELECT auth_name, code, name, type, conv_factor, " "proj_short_name, deprecated FROM unit_of_measure"; ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " WHERE auth_name = ?"; params.emplace_back(d->authority()); } sql += " ORDER BY auth_name, code"; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { AuthorityFactory::UnitInfo info; info.authName = row[0]; info.code = row[1]; info.name = row[2]; const std::string &raw_category(row[3]); if (raw_category == "length") { info.category = info.name.find(" per ") != std::string::npos ? "linear_per_time" : "linear"; } else if (raw_category == "angle") { info.category = info.name.find(" per ") != std::string::npos ? "angular_per_time" : "angular"; } else if (raw_category == "scale") { info.category = info.name.find(" per year") != std::string::npos || info.name.find(" per second") != std::string::npos ? "scale_per_time" : "scale"; } else { info.category = raw_category; } info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]); info.projShortName = row[5]; info.deprecated = row[6] == "1"; res.emplace_back(info); } return res; } // --------------------------------------------------------------------------- /** \brief Return the list of celestial bodies. * @throw FactoryException in case of error. * * @since 8.1 */ std::list AuthorityFactory::getCelestialBodyList() const { std::string sql = "SELECT auth_name, name FROM celestial_body"; ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " WHERE auth_name = ?"; params.emplace_back(d->authority()); } sql += " ORDER BY auth_name, name"; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { AuthorityFactory::CelestialBodyInfo info; info.authName = row[0]; info.name = row[1]; res.emplace_back(info); } return res; } // --------------------------------------------------------------------------- /** \brief Gets the official name from a possibly alias name. * * @param aliasedName Alias name. * @param tableName Table name/category. Can help in case of ambiguities. * Or empty otherwise. * @param source Source of the alias. Can help in case of ambiguities. * Or empty otherwise. * @param tryEquivalentNameSpelling whether the comparison of aliasedName with * the alt_name column of the alias_name table should be done with using * metadata::Identifier::isEquivalentName() rather than strict string * comparison; * @param outTableName Table name in which the official name has been found. * @param outAuthName Authority name of the official name that has been found. * @param outCode Code of the official name that has been found. * @return official name (or empty if not found). * @throw FactoryException in case of error. */ std::string AuthorityFactory::getOfficialNameFromAlias( const std::string &aliasedName, const std::string &tableName, const std::string &source, bool tryEquivalentNameSpelling, std::string &outTableName, std::string &outAuthName, std::string &outCode) const { if (tryEquivalentNameSpelling) { std::string sql( "SELECT table_name, auth_name, code, alt_name FROM alias_name"); ListOfParams params; if (!tableName.empty()) { sql += " WHERE table_name = ?"; params.push_back(tableName); } if (!source.empty()) { if (!tableName.empty()) { sql += " AND "; } else { sql += " WHERE "; } sql += "source = ?"; params.push_back(source); } auto res = d->run(sql, params); if (res.empty()) { return std::string(); } for (const auto &row : res) { const auto &alt_name = row[3]; if (metadata::Identifier::isEquivalentName(alt_name.c_str(), aliasedName.c_str())) { outTableName = row[0]; outAuthName = row[1]; outCode = row[2]; sql = "SELECT name FROM \""; sql += replaceAll(outTableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; res = d->run(sql, {outAuthName, outCode}); if (res.empty()) { // shouldn't happen normally return std::string(); } return res.front()[0]; } } return std::string(); } else { std::string sql( "SELECT table_name, auth_name, code FROM alias_name WHERE " "alt_name = ?"); ListOfParams params{aliasedName}; if (!tableName.empty()) { sql += " AND table_name = ?"; params.push_back(tableName); } if (!source.empty()) { if (source == "ESRI") { sql += " AND source IN ('ESRI', 'ESRI_OLD')"; } else { sql += " AND source = ?"; params.push_back(source); } } auto res = d->run(sql, params); if (res.empty()) { return std::string(); } params.clear(); sql.clear(); bool first = true; for (const auto &row : res) { if (!first) sql += " UNION ALL "; first = false; outTableName = row[0]; outAuthName = row[1]; outCode = row[2]; sql += "SELECT name, ? AS table_name, auth_name, code, deprecated " "FROM \""; sql += replaceAll(outTableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; params.emplace_back(outTableName); params.emplace_back(outAuthName); params.emplace_back(outCode); } sql = "SELECT name, table_name, auth_name, code FROM (" + sql + ") x ORDER BY deprecated LIMIT 1"; res = d->run(sql, params); if (res.empty()) { // shouldn't happen normally return std::string(); } const auto &row = res.front(); outTableName = row[1]; outAuthName = row[2]; outCode = row[3]; return row[0]; } } // --------------------------------------------------------------------------- /** \brief Return a list of objects, identified by their name * * @param searchedName Searched name. Must be at least 2 character long. * @param allowedObjectTypes List of object types into which to search. If * empty, all object types will be searched. * @param approximateMatch Whether approximate name identification is allowed. * @param limitResultCount Maximum number of results to return. * Or 0 for unlimited. * @return list of matched objects. * @throw FactoryException in case of error. */ std::list AuthorityFactory::createObjectsFromName( const std::string &searchedName, const std::vector &allowedObjectTypes, bool approximateMatch, size_t limitResultCount) const { std::list res; const auto resTmp(createObjectsFromNameEx( searchedName, allowedObjectTypes, approximateMatch, limitResultCount)); for (const auto &pair : resTmp) { res.emplace_back(pair.first); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return a list of objects, identifier by their name, with the name * on which the match occurred. * * The name on which the match occurred might be different from the object name, * if the match has been done on an alias name of that object. * * @param searchedName Searched name. Must be at least 2 character long. * @param allowedObjectTypes List of object types into which to search. If * empty, all object types will be searched. * @param approximateMatch Whether approximate name identification is allowed. * @param limitResultCount Maximum number of results to return. * Or 0 for unlimited. * @param useAliases Whether querying the alias_name table is allowed * @return list of matched objects. * @throw FactoryException in case of error. */ std::list AuthorityFactory::createObjectsFromNameEx( const std::string &searchedName, const std::vector &allowedObjectTypes, bool approximateMatch, size_t limitResultCount, bool useAliases) const { std::string searchedNameWithoutDeprecated(searchedName); bool deprecated = false; if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) { deprecated = true; searchedNameWithoutDeprecated.resize( searchedNameWithoutDeprecated.size() - strlen(" (deprecated)")); } const std::string canonicalizedSearchedName( metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated)); if (canonicalizedSearchedName.size() <= 1) { return {}; } std::string sql( "SELECT table_name, auth_name, code, name, deprecated, is_alias " "FROM ("); const auto getTableAndTypeConstraints = [&allowedObjectTypes, &searchedName]() { typedef std::pair TableType; std::list res; // Hide ESRI D_ vertical datums const bool startsWithDUnderscore = starts_with(searchedName, "D_"); if (allowedObjectTypes.empty()) { for (const auto &tableName : {"prime_meridian", "ellipsoid", "geodetic_datum", "vertical_datum", "engineering_datum", "geodetic_crs", "projected_crs", "vertical_crs", "compound_crs", "engineering_crs", "conversion", "helmert_transformation", "grid_transformation", "other_transformation", "concatenated_operation"}) { if (!(startsWithDUnderscore && strcmp(tableName, "vertical_datum") == 0)) { res.emplace_back(TableType(tableName, std::string())); } } } else { for (const auto type : allowedObjectTypes) { switch (type) { case ObjectType::PRIME_MERIDIAN: res.emplace_back( TableType("prime_meridian", std::string())); break; case ObjectType::ELLIPSOID: res.emplace_back(TableType("ellipsoid", std::string())); break; case ObjectType::DATUM: res.emplace_back( TableType("geodetic_datum", std::string())); if (!startsWithDUnderscore) { res.emplace_back( TableType("vertical_datum", std::string())); res.emplace_back( TableType("engineering_datum", std::string())); } break; case ObjectType::GEODETIC_REFERENCE_FRAME: res.emplace_back( TableType("geodetic_datum", std::string())); break; case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME: res.emplace_back( TableType("geodetic_datum", "frame_reference_epoch")); break; case ObjectType::VERTICAL_REFERENCE_FRAME: res.emplace_back( TableType("vertical_datum", std::string())); break; case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME: res.emplace_back( TableType("vertical_datum", "frame_reference_epoch")); break; case ObjectType::ENGINEERING_DATUM: res.emplace_back( TableType("engineering_datum", std::string())); break; case ObjectType::CRS: res.emplace_back(TableType("geodetic_crs", std::string())); res.emplace_back(TableType("projected_crs", std::string())); res.emplace_back(TableType("vertical_crs", std::string())); res.emplace_back(TableType("compound_crs", std::string())); res.emplace_back( TableType("engineering_crs", std::string())); break; case ObjectType::GEODETIC_CRS: res.emplace_back(TableType("geodetic_crs", std::string())); break; case ObjectType::GEOCENTRIC_CRS: res.emplace_back(TableType("geodetic_crs", GEOCENTRIC)); break; case ObjectType::GEOGRAPHIC_CRS: res.emplace_back(TableType("geodetic_crs", GEOG_2D)); res.emplace_back(TableType("geodetic_crs", GEOG_3D)); break; case ObjectType::GEOGRAPHIC_2D_CRS: res.emplace_back(TableType("geodetic_crs", GEOG_2D)); break; case ObjectType::GEOGRAPHIC_3D_CRS: res.emplace_back(TableType("geodetic_crs", GEOG_3D)); break; case ObjectType::PROJECTED_CRS: res.emplace_back(TableType("projected_crs", std::string())); break; case ObjectType::VERTICAL_CRS: res.emplace_back(TableType("vertical_crs", std::string())); break; case ObjectType::COMPOUND_CRS: res.emplace_back(TableType("compound_crs", std::string())); break; case ObjectType::ENGINEERING_CRS: res.emplace_back( TableType("engineering_crs", std::string())); break; case ObjectType::COORDINATE_OPERATION: res.emplace_back(TableType("conversion", std::string())); res.emplace_back( TableType("helmert_transformation", std::string())); res.emplace_back( TableType("grid_transformation", std::string())); res.emplace_back( TableType("other_transformation", std::string())); res.emplace_back( TableType("concatenated_operation", std::string())); break; case ObjectType::CONVERSION: res.emplace_back(TableType("conversion", std::string())); break; case ObjectType::TRANSFORMATION: res.emplace_back( TableType("helmert_transformation", std::string())); res.emplace_back( TableType("grid_transformation", std::string())); res.emplace_back( TableType("other_transformation", std::string())); break; case ObjectType::CONCATENATED_OPERATION: res.emplace_back( TableType("concatenated_operation", std::string())); break; case ObjectType::DATUM_ENSEMBLE: res.emplace_back(TableType("geodetic_datum", "ensemble")); res.emplace_back(TableType("vertical_datum", "ensemble")); break; } } } return res; }; bool datumEnsembleAllowed = false; if (allowedObjectTypes.empty()) { datumEnsembleAllowed = true; } else { for (const auto type : allowedObjectTypes) { if (type == ObjectType::DATUM_ENSEMBLE) { datumEnsembleAllowed = true; break; } } } const auto listTableNameType = getTableAndTypeConstraints(); bool first = true; ListOfParams params; for (const auto &tableNameTypePair : listTableNameType) { if (!first) { sql += " UNION "; } first = false; sql += "SELECT '"; sql += tableNameTypePair.first; sql += "' AS table_name, auth_name, code, name, deprecated, " "0 AS is_alias FROM "; sql += tableNameTypePair.first; sql += " WHERE 1 = 1 "; if (!tableNameTypePair.second.empty()) { if (tableNameTypePair.second == "frame_reference_epoch") { sql += "AND frame_reference_epoch IS NOT NULL "; } else if (tableNameTypePair.second == "ensemble") { sql += "AND ensemble_accuracy IS NOT NULL "; } else { sql += "AND type = '"; sql += tableNameTypePair.second; sql += "' "; } } if (deprecated) { sql += "AND deprecated = 1 "; } if (!approximateMatch) { sql += "AND name = ? COLLATE NOCASE "; params.push_back(searchedNameWithoutDeprecated); } if (d->hasAuthorityRestriction()) { sql += "AND auth_name = ? "; params.emplace_back(d->authority()); } if (useAliases) { sql += " UNION SELECT '"; sql += tableNameTypePair.first; sql += "' AS table_name, " "ov.auth_name AS auth_name, " "ov.code AS code, a.alt_name AS name, " "ov.deprecated AS deprecated, 1 as is_alias FROM "; sql += tableNameTypePair.first; sql += " ov " "JOIN alias_name a ON " "ov.auth_name = a.auth_name AND ov.code = a.code WHERE " "a.source != 'EPSG_OLD' AND a.table_name = '"; sql += tableNameTypePair.first; sql += "' "; if (!tableNameTypePair.second.empty()) { if (tableNameTypePair.second == "frame_reference_epoch") { sql += "AND ov.frame_reference_epoch IS NOT NULL "; } else if (tableNameTypePair.second == "ensemble") { sql += "AND ov.ensemble_accuracy IS NOT NULL "; } else { sql += "AND ov.type = '"; sql += tableNameTypePair.second; sql += "' "; } } if (deprecated) { sql += "AND ov.deprecated = 1 "; } if (!approximateMatch) { sql += "AND a.alt_name = ? COLLATE NOCASE "; params.push_back(searchedNameWithoutDeprecated); } if (d->hasAuthorityRestriction()) { sql += "AND ov.auth_name = ? "; params.emplace_back(d->authority()); } } } sql += ") ORDER BY deprecated, is_alias, length(name), name"; if (limitResultCount > 0 && limitResultCount < static_cast(std::numeric_limits::max()) && !approximateMatch) { sql += " LIMIT "; sql += toString(static_cast(limitResultCount)); } std::list res; std::set> setIdentified; // Querying geodetic datum is a super hot path when importing from WKT1 // so cache results. if (allowedObjectTypes.size() == 1 && allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME && approximateMatch && d->authority().empty()) { auto &mapCanonicalizeGRFName = d->context()->getPrivate()->getMapCanonicalizeGRFName(); if (mapCanonicalizeGRFName.empty()) { auto sqlRes = d->run(sql, params); for (const auto &row : sqlRes) { const auto &name = row[3]; const auto &deprecatedStr = row[4]; const auto canonicalizedName( metadata::Identifier::canonicalizeName(name)); auto &v = mapCanonicalizeGRFName[canonicalizedName]; if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") { v.push_back(row); } } } auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName); if (iter != mapCanonicalizeGRFName.end()) { const auto &listOfRow = iter->second; for (const auto &row : listOfRow) { const auto &auth_name = row[1]; const auto &code = row[2]; auto key = std::pair(auth_name, code); if (setIdentified.find(key) != setIdentified.end()) { continue; } setIdentified.insert(std::move(key)); auto factory = d->createFactory(auth_name); const auto &name = row[3]; res.emplace_back( PairObjectName(factory->createGeodeticDatum(code), name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } } else { for (const auto &pair : mapCanonicalizeGRFName) { const auto &listOfRow = pair.second; for (const auto &row : listOfRow) { const auto &name = row[3]; bool match = ci_find(name, searchedNameWithoutDeprecated) != std::string::npos; if (!match) { const auto &canonicalizedName(pair.first); match = ci_find(canonicalizedName, canonicalizedSearchedName) != std::string::npos; } if (!match) { continue; } const auto &auth_name = row[1]; const auto &code = row[2]; auto key = std::pair(auth_name, code); if (setIdentified.find(key) != setIdentified.end()) { continue; } setIdentified.insert(std::move(key)); auto factory = d->createFactory(auth_name); res.emplace_back(PairObjectName( factory->createGeodeticDatum(code), name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } } } else { auto sqlRes = d->run(sql, params); bool isFirst = true; bool firstIsDeprecated = false; size_t countExactMatch = 0; size_t countExactMatchOnAlias = 0; std::size_t hashCodeFirstMatch = 0; for (const auto &row : sqlRes) { const auto &name = row[3]; if (approximateMatch) { bool match = ci_find(name, searchedNameWithoutDeprecated) != std::string::npos; if (!match) { const auto canonicalizedName( metadata::Identifier::canonicalizeName(name)); match = ci_find(canonicalizedName, canonicalizedSearchedName) != std::string::npos; } if (!match) { continue; } } const auto &table_name = row[0]; const auto &auth_name = row[1]; const auto &code = row[2]; auto key = std::pair(auth_name, code); if (setIdentified.find(key) != setIdentified.end()) { continue; } setIdentified.insert(std::move(key)); const auto &deprecatedStr = row[4]; if (isFirst) { firstIsDeprecated = deprecatedStr == "1"; isFirst = false; } if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) { break; } auto factory = d->createFactory(auth_name); auto getObject = [&factory, datumEnsembleAllowed]( const std::string &l_table_name, const std::string &l_code) -> common::IdentifiedObjectNNPtr { if (l_table_name == "prime_meridian") { return factory->createPrimeMeridian(l_code); } else if (l_table_name == "ellipsoid") { return factory->createEllipsoid(l_code); } else if (l_table_name == "geodetic_datum") { if (datumEnsembleAllowed) { datum::GeodeticReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; factory->createGeodeticDatumOrEnsemble( l_code, datum, datumEnsemble, turnEnsembleAsDatum); if (datum) { return NN_NO_CHECK(datum); } assert(datumEnsemble); return NN_NO_CHECK(datumEnsemble); } return factory->createGeodeticDatum(l_code); } else if (l_table_name == "vertical_datum") { if (datumEnsembleAllowed) { datum::VerticalReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; factory->createVerticalDatumOrEnsemble( l_code, datum, datumEnsemble, turnEnsembleAsDatum); if (datum) { return NN_NO_CHECK(datum); } assert(datumEnsemble); return NN_NO_CHECK(datumEnsemble); } return factory->createVerticalDatum(l_code); } else if (l_table_name == "engineering_datum") { return factory->createEngineeringDatum(l_code); } else if (l_table_name == "geodetic_crs") { return factory->createGeodeticCRS(l_code); } else if (l_table_name == "projected_crs") { return factory->createProjectedCRS(l_code); } else if (l_table_name == "vertical_crs") { return factory->createVerticalCRS(l_code); } else if (l_table_name == "compound_crs") { return factory->createCompoundCRS(l_code); } else if (l_table_name == "engineering_crs") { return factory->createEngineeringCRS(l_code); } else if (l_table_name == "conversion") { return factory->createConversion(l_code); } else if (l_table_name == "grid_transformation" || l_table_name == "helmert_transformation" || l_table_name == "other_transformation" || l_table_name == "concatenated_operation") { return factory->createCoordinateOperation(l_code, true); } throw std::runtime_error("Unsupported table_name"); }; const auto obj = getObject(table_name, code); if (metadata::Identifier::isEquivalentName( obj->nameStr().c_str(), searchedName.c_str(), false)) { countExactMatch++; } else if (metadata::Identifier::isEquivalentName( name.c_str(), searchedName.c_str(), false)) { countExactMatchOnAlias++; } const auto objPtr = obj.get(); if (res.empty()) { hashCodeFirstMatch = typeid(*objPtr).hash_code(); } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) { hashCodeFirstMatch = 0; } res.emplace_back(PairObjectName(obj, name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } // If we found several objects that are an exact match, and all objects // have the same type, and we are not in approximate mode, only keep the // objects with the exact name match. if ((countExactMatch + countExactMatchOnAlias) >= 1 && hashCodeFirstMatch != 0 && !approximateMatch) { std::list resTmp; bool biggerDifferencesAllowed = (countExactMatch == 0); for (const auto &pair : res) { if (metadata::Identifier::isEquivalentName( pair.first->nameStr().c_str(), searchedName.c_str(), biggerDifferencesAllowed) || (countExactMatch == 0 && metadata::Identifier::isEquivalentName( pair.second.c_str(), searchedName.c_str(), biggerDifferencesAllowed))) { resTmp.emplace_back(pair); } } res = std::move(resTmp); } } auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) { const auto &aName = a.first->nameStr(); const auto &bName = b.first->nameStr(); if (aName.size() < bName.size()) { return true; } if (aName.size() > bName.size()) { return false; } const auto &aIds = a.first->identifiers(); const auto &bIds = b.first->identifiers(); if (aIds.size() < bIds.size()) { return true; } if (aIds.size() > bIds.size()) { return false; } for (size_t idx = 0; idx < aIds.size(); idx++) { const auto &aCodeSpace = *aIds[idx]->codeSpace(); const auto &bCodeSpace = *bIds[idx]->codeSpace(); const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace); if (codeSpaceComparison < 0) { return true; } if (codeSpaceComparison > 0) { return false; } const auto &aCode = aIds[idx]->code(); const auto &bCode = bIds[idx]->code(); const auto codeComparison = aCode.compare(bCode); if (codeComparison < 0) { return true; } if (codeComparison > 0) { return false; } } return strcmp(typeid(a.first.get()).name(), typeid(b.first.get()).name()) < 0; }; res.sort(sortLambda); return res; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a list of area of use from their name * * @param name Searched name. * @param approximateMatch Whether approximate name identification is allowed. * @return list of (auth_name, code) of matched objects. * @throw FactoryException in case of error. */ std::list> AuthorityFactory::listAreaOfUseFromName(const std::string &name, bool approximateMatch) const { std::string sql( "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND "); ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " auth_name = ? AND "; params.emplace_back(d->authority()); } sql += "name LIKE ?"; if (!approximateMatch) { params.push_back(name); } else { params.push_back('%' + name + '%'); } auto sqlRes = d->run(sql, params); std::list> res; for (const auto &row : sqlRes) { res.emplace_back(row[0], row[1]); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createEllipsoidFromExisting( const datum::EllipsoidNNPtr &ellipsoid) const { std::string sql( "SELECT auth_name, code FROM ellipsoid WHERE " "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND " "((semi_minor_axis IS NOT NULL AND " "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR " "((inv_flattening IS NOT NULL AND " "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))"); ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(), ellipsoid->computeSemiMinorAxis().getSIValue(), ellipsoid->computedInverseFlattening()}; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createGeodeticCRSFromDatum( const std::string &datum_auth_name, const std::string &datum_code, const std::string &geodetic_crs_type) const { std::string sql( "SELECT auth_name, code FROM geodetic_crs WHERE " "datum_auth_name = ? AND datum_code = ? AND deprecated = 0"); ListOfParams params{datum_auth_name, datum_code}; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } if (!geodetic_crs_type.empty()) { sql += " AND type = ?"; params.emplace_back(geodetic_crs_type); } sql += " ORDER BY auth_name, code"; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createGeodeticCRSFromDatum( const datum::GeodeticReferenceFrameNNPtr &datum, const std::string &preferredAuthName, const std::string &geodetic_crs_type) const { std::list candidates; const auto &ids = datum->identifiers(); const auto &datumName = datum->nameStr(); if (!ids.empty()) { for (const auto &id : ids) { const auto &authName = *(id->codeSpace()); const auto &code = id->code(); if (!authName.empty()) { const auto tmpFactory = (preferredAuthName == authName) ? create(databaseContext(), authName) : NN_NO_CHECK(d->getSharedFromThis()); auto l_candidates = tmpFactory->createGeodeticCRSFromDatum( authName, code, geodetic_crs_type); for (const auto &candidate : l_candidates) { candidates.emplace_back(candidate); } } } } else if (datumName != "unknown" && datumName != "unnamed") { auto matches = createObjectsFromName( datumName, {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false, 2); if (matches.size() == 1) { const auto &match = matches.front(); if (datum->_isEquivalentTo(match.get(), util::IComparable::Criterion::EQUIVALENT, databaseContext().as_nullable()) && !match->identifiers().empty()) { return createGeodeticCRSFromDatum( util::nn_static_pointer_cast( match), preferredAuthName, geodetic_crs_type); } } } return candidates; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createVerticalCRSFromDatum( const std::string &datum_auth_name, const std::string &datum_code) const { std::string sql( "SELECT auth_name, code FROM vertical_crs WHERE " "datum_auth_name = ? AND datum_code = ? AND deprecated = 0"); ListOfParams params{datum_auth_name, datum_code}; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createGeodeticCRSFromEllipsoid( const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code, const std::string &geodetic_crs_type) const { std::string sql( "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs " "JOIN geodetic_datum ON " "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND " "geodetic_crs.datum_code = geodetic_datum.code WHERE " "geodetic_datum.ellipsoid_auth_name = ? AND " "geodetic_datum.ellipsoid_code = ? AND " "geodetic_datum.deprecated = 0 AND " "geodetic_crs.deprecated = 0"); ListOfParams params{ellipsoid_auth_name, ellipsoid_code}; if (d->hasAuthorityRestriction()) { sql += " AND geodetic_crs.auth_name = ?"; params.emplace_back(d->authority()); } if (!geodetic_crs_type.empty()) { sql += " AND geodetic_crs.type = ?"; params.emplace_back(geodetic_crs_type); } auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static std::string buildSqlLookForAuthNameCode( const std::list> &list, ListOfParams ¶ms, const char *prefixField) { std::string sql("("); std::set authorities; for (const auto &crs : list) { auto boundCRS = dynamic_cast(crs.first.get()); const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers() : crs.first->identifiers(); if (!ids.empty()) { authorities.insert(*(ids[0]->codeSpace())); } } bool firstAuth = true; for (const auto &auth_name : authorities) { if (!firstAuth) { sql += " OR "; } firstAuth = false; sql += "( "; sql += prefixField; sql += "auth_name = ? AND "; sql += prefixField; sql += "code IN ("; params.emplace_back(auth_name); bool firstGeodCRSForAuth = true; for (const auto &crs : list) { auto boundCRS = dynamic_cast(crs.first.get()); const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers() : crs.first->identifiers(); if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) { if (!firstGeodCRSForAuth) { sql += ','; } firstGeodCRSForAuth = false; sql += '?'; params.emplace_back(ids[0]->code()); } } sql += "))"; } sql += ')'; return sql; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createProjectedCRSFromExisting( const crs::ProjectedCRSNNPtr &crs) const { std::list res; const auto &conv = crs->derivingConversionRef(); const auto &method = conv->method(); const auto methodEPSGCode = method->getEPSGCode(); if (methodEPSGCode == 0) { return res; } auto lockedThisFactory(d->getSharedFromThis()); assert(lockedThisFactory); const auto &baseCRS(crs->baseCRS()); auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory); auto geogCRS = dynamic_cast(baseCRS.get()); if (geogCRS) { const auto axisOrder = geogCRS->coordinateSystem()->axisOrder(); if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { const auto &unit = geogCRS->coordinateSystem()->axisList()[0]->unit(); auto otherOrderGeogCRS = crs::GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, geogCRS->nameStr()), geogCRS->datum(), geogCRS->datumEnsemble(), axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ? cs::EllipsoidalCS::createLatitudeLongitude(unit) : cs::EllipsoidalCS::createLongitudeLatitude(unit)); auto otherCandidatesGeodCRS = otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory); candidatesGeodCRS.insert(candidatesGeodCRS.end(), otherCandidatesGeodCRS.begin(), otherCandidatesGeodCRS.end()); } } std::string sql( "SELECT projected_crs.auth_name, projected_crs.code, " "projected_crs.name FROM projected_crs " "JOIN conversion_table conv ON " "projected_crs.conversion_auth_name = conv.auth_name AND " "projected_crs.conversion_code = conv.code " "JOIN geodetic_crs gcrs ON " "gcrs.auth_name = projected_crs.geodetic_crs_auth_name AND " "gcrs.code = projected_crs.geodetic_crs_code " "JOIN geodetic_datum datum ON " "datum.auth_name = gcrs.datum_auth_name AND " "datum.code = gcrs.datum_code " "JOIN ellipsoid ellps ON " "ellps.auth_name = datum.ellipsoid_auth_name AND " "ellps.code = datum.ellipsoid_code " "WHERE " "abs(ellps.semi_major_axis - ?) <= 1e-4 * ellps.semi_major_axis AND " "projected_crs.deprecated = 0 AND "); ListOfParams params; params.emplace_back( toString(crs->baseCRS()->ellipsoid()->semiMajorAxis().value())); if (!candidatesGeodCRS.empty()) { sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, "projected_crs.geodetic_crs_"); sql += " AND "; } sql += "conv.method_auth_name = 'EPSG' AND " "conv.method_code = ?"; params.emplace_back(toString(methodEPSGCode)); if (d->hasAuthorityRestriction()) { sql += " AND projected_crs.auth_name = ?"; params.emplace_back(d->authority()); } int iParam = 0; bool hasLat1stStd = false; double lat1stStd = 0; int iParamLat1stStd = 0; bool hasLat2ndStd = false; double lat2ndStd = 0; int iParamLat2ndStd = 0; for (const auto &genOpParamvalue : conv->parameterValues()) { iParam++; auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (!opParamvalue) { break; } const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); const auto ¶meterValue = opParamvalue->parameterValue(); if (!(paramEPSGCode > 0 && parameterValue->type() == operation::ParameterValue::Type::MEASURE)) { break; } const auto &measure = parameterValue->value(); const auto &unit = measure.unit(); if (unit == common::UnitOfMeasure::DEGREE && baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) { if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { // Special case for standard parallels of LCC_2SP. See below if (paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) { hasLat1stStd = true; lat1stStd = measure.value(); iParamLat1stStd = iParam; continue; } else if (paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { hasLat2ndStd = true; lat2ndStd = measure.value(); iParamLat2ndStd = iParam; continue; } } const auto iParamAsStr(toString(iParam)); sql += " AND conv.param"; sql += iParamAsStr; sql += "_code = ? AND conv.param"; sql += iParamAsStr; sql += "_auth_name = 'EPSG' AND conv.param"; sql += iParamAsStr; sql += "_value BETWEEN ? AND ?"; // As angles might be expressed with the odd unit EPSG:9110 // "sexagesimal DMS", we have to provide a broad range params.emplace_back(toString(paramEPSGCode)); params.emplace_back(measure.value() - 1); params.emplace_back(measure.value() + 1); } } // Special case for standard parallels of LCC_2SP: they can be switched if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && hasLat1stStd && hasLat2ndStd) { const auto iParam1AsStr(toString(iParamLat1stStd)); const auto iParam2AsStr(toString(iParamLat2ndStd)); sql += " AND conv.param"; sql += iParam1AsStr; sql += "_code = ? AND conv.param"; sql += iParam1AsStr; sql += "_auth_name = 'EPSG' AND conv.param"; sql += iParam2AsStr; sql += "_code = ? AND conv.param"; sql += iParam2AsStr; sql += "_auth_name = 'EPSG' AND (("; params.emplace_back( toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)); params.emplace_back( toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)); double val1 = lat1stStd; double val2 = lat2ndStd; for (int i = 0; i < 2; i++) { if (i == 1) { sql += ") OR ("; std::swap(val1, val2); } sql += "conv.param"; sql += iParam1AsStr; sql += "_value BETWEEN ? AND ? AND conv.param"; sql += iParam2AsStr; sql += "_value BETWEEN ? AND ?"; params.emplace_back(val1 - 1); params.emplace_back(val1 + 1); params.emplace_back(val2 - 1); params.emplace_back(val2 + 1); } sql += "))"; } auto sqlRes = d->run(sql, params); for (const auto &row : sqlRes) { const auto &name = row[2]; if (metadata::Identifier::isEquivalentName(crs->nameStr().c_str(), name.c_str())) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back( d->createFactory(auth_name)->createProjectedCRS(code)); } } if (!res.empty()) { return res; } params.clear(); sql = "SELECT auth_name, code FROM projected_crs WHERE " "deprecated = 0 AND conversion_auth_name IS NULL AND "; if (!candidatesGeodCRS.empty()) { sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, "geodetic_crs_"); sql += " AND "; } const auto escapeLikeStr = [](const std::string &str) { return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"), "%", "\\%"); }; const auto ellpsSemiMajorStr = toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10); sql += "(text_definition LIKE ? ESCAPE '\\'"; // WKT2 definition { std::string patternVal("%"); patternVal += ','; patternVal += ellpsSemiMajorStr; patternVal += '%'; patternVal += escapeLikeStr(method->nameStr()); patternVal += '%'; params.emplace_back(patternVal); } const auto *mapping = getMapping(method.get()); if (mapping && mapping->proj_name_main) { sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?"; std::string patternVal("%"); patternVal += "proj="; patternVal += mapping->proj_name_main; patternVal += '%'; params.emplace_back(patternVal); // could be a= or R= patternVal = "%="; patternVal += ellpsSemiMajorStr; patternVal += '%'; params.emplace_back(patternVal); std::string projEllpsName; std::string ellpsName; if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName, ellpsName)) { sql += " OR text_definition LIKE ?"; // Could be ellps= or datum= patternVal = "%="; patternVal += projEllpsName; patternVal += '%'; params.emplace_back(patternVal); } sql += "))"; } // WKT1_GDAL definition const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName(); if (wkt1GDALMethodName) { sql += " OR text_definition LIKE ? ESCAPE '\\'"; std::string patternVal("%"); patternVal += ','; patternVal += ellpsSemiMajorStr; patternVal += '%'; patternVal += escapeLikeStr(wkt1GDALMethodName); patternVal += '%'; params.emplace_back(patternVal); } // WKT1_ESRI definition const char *esriMethodName = conv->getESRIMethodName(); if (esriMethodName) { sql += " OR text_definition LIKE ? ESCAPE '\\'"; std::string patternVal("%"); patternVal += ','; patternVal += ellpsSemiMajorStr; patternVal += '%'; patternVal += escapeLikeStr(esriMethodName); patternVal += '%'; auto fe = &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING); if (*fe == Measure()) { fe = &conv->parameterValueMeasure( EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN); } if (!(*fe == Measure())) { patternVal += "PARAMETER[\"False\\_Easting\","; patternVal += toString(fe->convertToUnit( crs->coordinateSystem()->axisList()[0]->unit()), 10); patternVal += '%'; } auto lat = &conv->parameterValueMeasure( EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); if (*lat == Measure()) { lat = &conv->parameterValueMeasure( EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN); } if (!(*lat == Measure())) { patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\","; const auto &angularUnit = dynamic_cast(crs->baseCRS().get()) ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit() : UnitOfMeasure::DEGREE; patternVal += toString(lat->convertToUnit(angularUnit), 10); patternVal += '%'; } params.emplace_back(patternVal); } sql += ")"; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes2 = d->run(sql, params); if (sqlRes.size() <= 200) { for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back( d->createFactory(auth_name)->createProjectedCRS(code)); } } if (sqlRes2.size() <= 200) { for (const auto &row : sqlRes2) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back( d->createFactory(auth_name)->createProjectedCRS(code)); } } return res; } // --------------------------------------------------------------------------- std::list AuthorityFactory::createCompoundCRSFromExisting( const crs::CompoundCRSNNPtr &crs) const { std::list res; auto lockedThisFactory(d->getSharedFromThis()); assert(lockedThisFactory); const auto &components = crs->componentReferenceSystems(); if (components.size() != 2) { return res; } auto candidatesHorizCRS = components[0]->identify(lockedThisFactory); auto candidatesVertCRS = components[1]->identify(lockedThisFactory); if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) { return res; } std::string sql("SELECT auth_name, code FROM compound_crs WHERE " "deprecated = 0 AND "); ListOfParams params; bool addAnd = false; if (!candidatesHorizCRS.empty()) { sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params, "horiz_crs_"); addAnd = true; } if (!candidatesVertCRS.empty()) { if (addAnd) { sql += " AND "; } sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params, "vertical_crs_"); addAnd = true; } if (d->hasAuthorityRestriction()) { if (addAnd) { sql += " AND "; } sql += "auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes = d->run(sql, params); for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code)); } return res; } // --------------------------------------------------------------------------- std::vector AuthorityFactory::getTransformationsForGeoid( const std::string &geoidName, bool usePROJAlternativeGridNames) const { std::vector res; const std::string sql("SELECT operation_auth_name, operation_code FROM " "geoid_model WHERE name = ?"); auto sqlRes = d->run(sql, {geoidName}); for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation( code, usePROJAlternativeGridNames)); } return res; } // --------------------------------------------------------------------------- std::vector AuthorityFactory::getPointMotionOperationsFor( const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const { std::vector res; const auto crsList = createGeodeticCRSFromDatum(crs->datumNonNull(d->context()), /* preferredAuthName = */ std::string(), /* geodetic_crs_type = */ std::string()); if (crsList.empty()) return res; std::string sql("SELECT auth_name, code FROM coordinate_operation_view " "WHERE source_crs_auth_name = target_crs_auth_name AND " "source_crs_code = target_crs_code AND deprecated = 0 AND " "("); bool addOr = false; ListOfParams params; for (const auto &candidateCrs : crsList) { if (addOr) sql += " OR "; addOr = true; sql += "(source_crs_auth_name = ? AND source_crs_code = ?)"; const auto &ids = candidateCrs->identifiers(); params.emplace_back(*(ids[0]->codeSpace())); params.emplace_back(ids[0]->code()); } sql += ")"; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes = d->run(sql, params); for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; auto pmo = util::nn_dynamic_pointer_cast( d->createFactory(auth_name)->createCoordinateOperation( code, usePROJAlternativeGridNames)); if (pmo) { res.emplace_back(NN_NO_CHECK(pmo)); } } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress FactoryException::FactoryException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- FactoryException::FactoryException(const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- FactoryException::~FactoryException() = default; // --------------------------------------------------------------------------- FactoryException::FactoryException(const FactoryException &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct NoSuchAuthorityCodeException::Private { std::string authority_; std::string code_; Private(const std::string &authority, const std::string &code) : authority_(authority), code_(code) {} }; // --------------------------------------------------------------------------- NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( const std::string &message, const std::string &authority, const std::string &code) : FactoryException(message), d(std::make_unique(authority, code)) { } // --------------------------------------------------------------------------- NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default; // --------------------------------------------------------------------------- NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( const NoSuchAuthorityCodeException &other) : FactoryException(other), d(std::make_unique(*(other.d))) {} //! @endcond // --------------------------------------------------------------------------- /** \brief Returns authority name. */ const std::string &NoSuchAuthorityCodeException::getAuthority() const { return d->authority_; } // --------------------------------------------------------------------------- /** \brief Returns authority code. */ const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const { return d->code_; } // --------------------------------------------------------------------------- } // namespace io NS_PROJ_END // --------------------------------------------------------------------------- void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); } proj-9.8.1/src/iso19111/coordinatesystem.cpp000664 001750 001750 00000157126 15166171715 020545 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/coordinatesystem.hpp" #include "proj/common.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/coordinatesystem_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include using namespace NS_PROJ::internal; #if 0 namespace dropbox{ namespace oxygen { template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; }} #endif NS_PROJ_START namespace cs { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct Meridian::Private { common::Angle longitude_{}; explicit Private(const common::Angle &longitude) : longitude_(longitude) {} }; //! @endcond // --------------------------------------------------------------------------- Meridian::Meridian(const common::Angle &longitudeIn) : d(std::make_unique(longitudeIn)) {} // --------------------------------------------------------------------------- #ifdef notdef Meridian::Meridian(const Meridian &other) : IdentifiedObject(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress Meridian::~Meridian() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the longitude of the meridian that the axis follows from the * pole. * * @return the longitude. */ const common::Angle &Meridian::longitude() PROJ_PURE_DEFN { return d->longitude_; } // --------------------------------------------------------------------------- /** \brief Instantiate a Meridian. * * @param longitudeIn longitude of the meridian that the axis follows from the * pole. * @return new Meridian. */ MeridianNNPtr Meridian::create(const common::Angle &longitudeIn) { return Meridian::nn_make_shared(longitudeIn); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Meridian::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { formatter->startNode(io::WKTConstants::MERIDIAN, !identifiers().empty()); formatter->add(longitude().value()); longitude().unit()._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT); if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void Meridian::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("Meridian", !identifiers().empty())); const auto &l_long = longitude(); writer->AddObjKey("longitude"); const auto &unit = l_long.unit(); if (unit == common::UnitOfMeasure::DEGREE) { writer->Add(l_long.value(), 15); } else { auto longitudeContext(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("value"); writer->Add(l_long.value(), 15); writer->AddObjKey("unit"); unit._exportToJSON(formatter); } if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateSystemAxis::Private { std::string abbreviation{}; const AxisDirection *direction = &(AxisDirection::UNSPECIFIED); common::UnitOfMeasure unit{}; util::optional rangeMeaning = util::optional(); util::optional minimumValue{}; util::optional maximumValue{}; MeridianPtr meridian{}; }; //! @endcond // --------------------------------------------------------------------------- CoordinateSystemAxis::CoordinateSystemAxis() : d(std::make_unique()) {} // --------------------------------------------------------------------------- #ifdef notdef CoordinateSystemAxis::CoordinateSystemAxis(const CoordinateSystemAxis &other) : IdentifiedObject(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateSystemAxis::~CoordinateSystemAxis() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the axis abbreviation. * * The abbreviation used for this coordinate system axis; this abbreviation * is also used to identify the coordinates in the coordinate tuple. * Examples are X and Y. * * @return the abbreviation. */ const std::string &CoordinateSystemAxis::abbreviation() PROJ_PURE_DEFN { return d->abbreviation; } // --------------------------------------------------------------------------- /** \brief Return the axis direction. * * The direction of this coordinate system axis (or in the case of Cartesian * projected coordinates, the direction of this coordinate system axis locally) * Examples: north or south, east or west, up or down. Within any set of * coordinate system axes, only one of each pair of terms can be used. For * Earth-fixed CRSs, this direction is often approximate and intended to * provide a human interpretable meaning to the axis. When a geodetic reference * frame is used, the precise directions of the axes may therefore vary * slightly from this approximate direction. Note that an EngineeringCRS often * requires specific descriptions of the directions of its coordinate system * axes. * * @return the direction. */ const AxisDirection &CoordinateSystemAxis::direction() PROJ_PURE_DEFN { return *(d->direction); } // --------------------------------------------------------------------------- /** \brief Return the axis unit. * * This is the spatial unit or temporal quantity used for this coordinate * system axis. The value of a coordinate in a coordinate tuple shall be * recorded using this unit. * * @return the axis unit. */ const common::UnitOfMeasure &CoordinateSystemAxis::unit() PROJ_PURE_DEFN { return d->unit; } // --------------------------------------------------------------------------- /** \brief Return the minimum value normally allowed for this axis, in the unit * for the axis. * * @return the minimum value, or empty. */ const util::optional & CoordinateSystemAxis::minimumValue() PROJ_PURE_DEFN { return d->minimumValue; } // --------------------------------------------------------------------------- /** \brief Return the maximum value normally allowed for this axis, in the unit * for the axis. * * @return the maximum value, or empty. */ const util::optional & CoordinateSystemAxis::maximumValue() PROJ_PURE_DEFN { return d->maximumValue; } // --------------------------------------------------------------------------- /** \brief Return the range meaning * * @return the range meaning, or empty. * @since 9.2 */ const util::optional & CoordinateSystemAxis::rangeMeaning() PROJ_PURE_DEFN { return d->rangeMeaning; } // --------------------------------------------------------------------------- /** \brief Return the meridian that the axis follows from the pole, for a * coordinate * reference system centered on a pole. * * @return the meridian, or null. */ const MeridianPtr &CoordinateSystemAxis::meridian() PROJ_PURE_DEFN { return d->meridian; } // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateSystemAxis. * * @param properties See \ref general_properties. The name should generally be * defined. * @param abbreviationIn Axis abbreviation (might be empty) * @param directionIn Axis direction * @param unitIn Axis unit * @param meridianIn The meridian that the axis follows from the pole, for a * coordinate * reference system centered on a pole, or nullptr * @return a new CoordinateSystemAxis. */ CoordinateSystemAxisNNPtr CoordinateSystemAxis::create( const util::PropertyMap &properties, const std::string &abbreviationIn, const AxisDirection &directionIn, const common::UnitOfMeasure &unitIn, const MeridianPtr &meridianIn) { auto csa(CoordinateSystemAxis::nn_make_shared()); csa->setProperties(properties); csa->d->abbreviation = abbreviationIn; csa->d->direction = &directionIn; csa->d->unit = unitIn; csa->d->meridian = meridianIn; return csa; } // --------------------------------------------------------------------------- /** \brief Instantiate a CoordinateSystemAxis. * * @param properties See \ref general_properties. The name should generally be * defined. * @param abbreviationIn Axis abbreviation (might be empty) * @param directionIn Axis direction * @param unitIn Axis unit * @param minimumValueIn Minimum value along axis * @param maximumValueIn Maximum value along axis * @param rangeMeaningIn Range Meaning * @param meridianIn The meridian that the axis follows from the pole, for a * coordinate * reference system centered on a pole, or nullptr * @return a new CoordinateSystemAxis. * @since 9.2 */ CoordinateSystemAxisNNPtr CoordinateSystemAxis::create( const util::PropertyMap &properties, const std::string &abbreviationIn, const AxisDirection &directionIn, const common::UnitOfMeasure &unitIn, const util::optional &minimumValueIn, const util::optional &maximumValueIn, const util::optional &rangeMeaningIn, const MeridianPtr &meridianIn) { auto csa(CoordinateSystemAxis::nn_make_shared()); csa->setProperties(properties); csa->d->abbreviation = abbreviationIn; csa->d->direction = &directionIn; csa->d->unit = unitIn; csa->d->minimumValue = minimumValueIn; csa->d->maximumValue = maximumValueIn; csa->d->rangeMeaning = rangeMeaningIn; csa->d->meridian = meridianIn; return csa; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateSystemAxis::_exportToWKT( // cppcheck-suppress passedByValue io::WKTFormatter *formatter) const // throw(FormattingException) { _exportToWKT(formatter, 0, false); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::string CoordinateSystemAxis::normalizeAxisName(const std::string &str) { if (str.empty()) { return str; } // on import, transform from WKT2 "longitude" to "Longitude", as in the // EPSG database. return toupper(str.substr(0, 1)) + str.substr(1); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateSystemAxis::_exportToWKT(io::WKTFormatter *formatter, int order, bool disableAbbrev) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(io::WKTConstants::AXIS, !identifiers().empty()); const std::string &axisName = nameStr(); const std::string &abbrev = abbreviation(); std::string parenthesizedAbbrev = std::string("(").append(abbrev).append(")"); std::string dir = direction().toString(); std::string axisDesignation; // It seems that the convention in WKT2 for axis name is first letter in // lower case. Whereas in WKT1 GDAL, it is in upper case (as in the EPSG // database) if (!axisName.empty()) { if (isWKT2) { axisDesignation = tolower(axisName.substr(0, 1)) + axisName.substr(1); } else { if (axisName == "Geodetic latitude") { axisDesignation = "Latitude"; } else if (axisName == "Geodetic longitude") { axisDesignation = "Longitude"; } else { axisDesignation = axisName; } } } if (!disableAbbrev && isWKT2 && // For geodetic CS, export the axis name without abbreviation !(axisName == AxisName::Latitude || axisName == AxisName::Longitude)) { if (!axisDesignation.empty() && !abbrev.empty()) { axisDesignation += " "; } if (!abbrev.empty()) { axisDesignation += parenthesizedAbbrev; } } if (!isWKT2) { dir = toupper(dir); if (direction() == AxisDirection::GEOCENTRIC_Z) { dir = AxisDirectionWKT1::NORTH; } else if (AxisDirectionWKT1::valueOf(dir) == nullptr) { dir = AxisDirectionWKT1::OTHER; } } else if (!abbrev.empty()) { // For geocentric CS, just put the abbreviation if (direction() == AxisDirection::GEOCENTRIC_X || direction() == AxisDirection::GEOCENTRIC_Y || direction() == AxisDirection::GEOCENTRIC_Z) { axisDesignation = std::move(parenthesizedAbbrev); } // For cartesian CS with Easting/Northing, export only the abbreviation else if ((order == 1 && axisName == AxisName::Easting && abbrev == AxisAbbreviation::E) || (order == 2 && axisName == AxisName::Northing && abbrev == AxisAbbreviation::N)) { axisDesignation = std::move(parenthesizedAbbrev); } } formatter->addQuotedString(axisDesignation); formatter->add(dir); const auto &l_meridian = meridian(); if (isWKT2 && l_meridian) { l_meridian->_exportToWKT(formatter); } if (formatter->outputAxisOrder() && order > 0) { formatter->startNode(io::WKTConstants::ORDER, false); formatter->add(order); formatter->endNode(); } if (formatter->outputUnit() && unit().type() != common::UnitOfMeasure::Type::NONE) { unit()._exportToWKT(formatter); } if (isWKT2 && formatter->use2019Keywords()) { if (d->minimumValue.has_value()) { formatter->startNode(io::WKTConstants::AXISMINVALUE, false); formatter->add(*(d->minimumValue)); formatter->endNode(); } if (d->maximumValue.has_value()) { formatter->startNode(io::WKTConstants::AXISMAXVALUE, false); formatter->add(*(d->maximumValue)); formatter->endNode(); } if (d->minimumValue.has_value() && d->maximumValue.has_value() && d->rangeMeaning.has_value()) { formatter->startNode(io::WKTConstants::RANGEMEANING, false); formatter->add(d->rangeMeaning->toString()); formatter->endNode(); } } if (formatter->outputId()) { formatID(formatter); } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateSystemAxis::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("Axis", !identifiers().empty())); writer->AddObjKey("name"); writer->Add(nameStr()); writer->AddObjKey("abbreviation"); writer->Add(abbreviation()); writer->AddObjKey("direction"); writer->Add(direction().toString()); const auto &l_meridian = meridian(); if (l_meridian) { writer->AddObjKey("meridian"); formatter->setOmitTypeInImmediateChild(); l_meridian->_exportToJSON(formatter); } const auto &l_unit(unit()); if (l_unit == common::UnitOfMeasure::METRE || l_unit == common::UnitOfMeasure::DEGREE) { writer->AddObjKey("unit"); writer->Add(l_unit.name()); } else if (l_unit.type() != common::UnitOfMeasure::Type::NONE) { writer->AddObjKey("unit"); l_unit._exportToJSON(formatter); } if (d->minimumValue.has_value()) { writer->AddObjKey("minimum_value"); writer->Add(*(d->minimumValue)); } if (d->maximumValue.has_value()) { writer->AddObjKey("maximum_value"); writer->Add(*(d->maximumValue)); } if (d->minimumValue.has_value() && d->maximumValue.has_value() && d->rangeMeaning.has_value()) { writer->AddObjKey("range_meaning"); writer->Add(d->rangeMeaning->toString()); } if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool CoordinateSystemAxis::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherCSA = dynamic_cast(other); if (otherCSA == nullptr) { return false; } // For approximate comparison, only care about axis direction and unit. if (!(*(d->direction) == *(otherCSA->d->direction) && d->unit._isEquivalentTo(otherCSA->d->unit, criterion))) { return false; } if (criterion == util::IComparable::Criterion::STRICT) { if (!IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } if (abbreviation() != otherCSA->abbreviation()) { return false; } // TODO other metadata } return true; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateSystemAxisNNPtr CoordinateSystemAxis::alterUnit(const common::UnitOfMeasure &newUnit) const { return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, name()), abbreviation(), direction(), newUnit, meridian()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CoordinateSystem::Private { std::vector axisList{}; explicit Private(const std::vector &axisListIn) : axisList(axisListIn) {} }; //! @endcond // --------------------------------------------------------------------------- CoordinateSystem::CoordinateSystem( const std::vector &axisIn) : d(std::make_unique(axisIn)) {} // --------------------------------------------------------------------------- #ifdef notdef CoordinateSystem::CoordinateSystem(const CoordinateSystem &other) : IdentifiedObject(other), d(std::make_unique(*other.d)) {} #endif // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateSystem::~CoordinateSystem() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the list of axes of this coordinate system. * * @return the axes. */ const std::vector & CoordinateSystem::axisList() PROJ_PURE_DEFN { return d->axisList; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateSystem::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { if (formatter->outputAxis() != io::WKTFormatter::OutputAxisRule::YES) { return; } const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const auto &l_axisList = axisList(); if (isWKT2) { formatter->startNode(io::WKTConstants::CS_, !identifiers().empty()); formatter->add(getWKT2Type(formatter->use2019Keywords())); formatter->add(static_cast(l_axisList.size())); formatter->endNode(); formatter->startNode(std::string(), false); // anonymous indentation level } common::UnitOfMeasure unit = common::UnitOfMeasure::NONE; bool bAllSameUnit = true; bool bFirstUnit = true; for (const auto &axis : l_axisList) { const auto &l_unit = axis->unit(); if (bFirstUnit) { unit = l_unit; bFirstUnit = false; } else if (unit != l_unit) { bAllSameUnit = false; } } formatter->pushOutputUnit( isWKT2 && (!bAllSameUnit || !formatter->outputCSUnitOnlyOnceIfSame())); int order = 1; const bool disableAbbrev = (l_axisList.size() == 3 && l_axisList[0]->nameStr() == AxisName::Latitude && l_axisList[1]->nameStr() == AxisName::Longitude && l_axisList[2]->nameStr() == AxisName::Ellipsoidal_height); for (auto &axis : l_axisList) { int axisOrder = (isWKT2 && l_axisList.size() > 1) ? order : 0; axis->_exportToWKT(formatter, axisOrder, disableAbbrev); order++; } if (isWKT2 && !l_axisList.empty() && bAllSameUnit && formatter->outputCSUnitOnlyOnceIfSame()) { unit._exportToWKT(formatter); } formatter->popOutputUnit(); if (isWKT2) { formatter->endNode(); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CoordinateSystem::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto writer = formatter->writer(); auto objectContext(formatter->MakeObjectContext("CoordinateSystem", !identifiers().empty())); writer->AddObjKey("subtype"); writer->Add(getWKT2Type(true)); writer->AddObjKey("axis"); { auto axisContext(writer->MakeArrayContext(false)); const auto &l_axisList = axisList(); for (auto &axis : l_axisList) { formatter->setOmitTypeInImmediateChild(); axis->_exportToJSON(formatter); } } if (formatter->outputId()) { formatID(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool CoordinateSystem::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherCS = dynamic_cast(other); if (otherCS == nullptr || !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { return false; } const auto &list = axisList(); const auto &otherList = otherCS->axisList(); if (list.size() != otherList.size()) { return false; } if (getWKT2Type(true) != otherCS->getWKT2Type(true)) { return false; } for (size_t i = 0; i < list.size(); i++) { if (!list[i]->_isEquivalentTo(otherList[i].get(), criterion, dbContext)) { return false; } } return true; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress SphericalCS::~SphericalCS() = default; //! @endcond // --------------------------------------------------------------------------- SphericalCS::SphericalCS(const std::vector &axisIn) : CoordinateSystem(axisIn) {} // --------------------------------------------------------------------------- #ifdef notdef SphericalCS::SphericalCS(const SphericalCS &) = default; #endif // --------------------------------------------------------------------------- /** \brief Instantiate a SphericalCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @param axis3 The third axis. * @return a new SphericalCS. */ SphericalCSNNPtr SphericalCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3) { std::vector axis{axis1, axis2, axis3}; auto cs(SphericalCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- /** \brief Instantiate a SphericalCS with 2 axis. * * This is an extension to ISO19111 to support (planet)-ocentric CS with * geocentric latitude. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @return a new SphericalCS. */ SphericalCSNNPtr SphericalCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2) { std::vector axis{axis1, axis2}; auto cs(SphericalCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress EllipsoidalCS::~EllipsoidalCS() = default; //! @endcond // --------------------------------------------------------------------------- EllipsoidalCS::EllipsoidalCS( const std::vector &axisIn) : CoordinateSystem(axisIn) {} // --------------------------------------------------------------------------- #ifdef notdef EllipsoidalCS::EllipsoidalCS(const EllipsoidalCS &) = default; #endif // --------------------------------------------------------------------------- /** \brief Instantiate a EllipsoidalCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @return a new EllipsoidalCS. */ EllipsoidalCSNNPtr EllipsoidalCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2) { std::vector axis{axis1, axis2}; auto cs(EllipsoidalCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- /** \brief Instantiate a EllipsoidalCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @param axis3 The third axis. * @return a new EllipsoidalCS. */ EllipsoidalCSNNPtr EllipsoidalCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3) { std::vector axis{axis1, axis2, axis3}; auto cs(EllipsoidalCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CoordinateSystemAxisNNPtr CoordinateSystemAxis::createLAT_NORTH(const common::UnitOfMeasure &unit) { return create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Latitude), AxisAbbreviation::lat, AxisDirection::NORTH, unit); } CoordinateSystemAxisNNPtr CoordinateSystemAxis::createLONG_EAST(const common::UnitOfMeasure &unit) { return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Longitude), AxisAbbreviation::lon, AxisDirection::EAST, unit); } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a EllipsoidalCS with a Latitude (first) and Longitude * (second) axis. * * @param unit Angular unit of the axes. * @return a new EllipsoidalCS. */ EllipsoidalCSNNPtr EllipsoidalCS::createLatitudeLongitude(const common::UnitOfMeasure &unit) { return EllipsoidalCS::create(util::PropertyMap(), CoordinateSystemAxis::createLAT_NORTH(unit), CoordinateSystemAxis::createLONG_EAST(unit)); } // --------------------------------------------------------------------------- /** \brief Instantiate a EllipsoidalCS with a Latitude (first), Longitude * (second) axis and ellipsoidal height (third) axis. * * @param angularUnit Angular unit of the latitude and longitude axes. * @param linearUnit Linear unit of the ellipsoidal height axis. * @return a new EllipsoidalCS. */ EllipsoidalCSNNPtr EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( const common::UnitOfMeasure &angularUnit, const common::UnitOfMeasure &linearUnit) { return EllipsoidalCS::create( util::PropertyMap(), CoordinateSystemAxis::createLAT_NORTH(angularUnit), CoordinateSystemAxis::createLONG_EAST(angularUnit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Ellipsoidal_height), AxisAbbreviation::h, AxisDirection::UP, linearUnit)); } // --------------------------------------------------------------------------- /** \brief Instantiate a EllipsoidalCS with a Longitude (first) and Latitude * (second) axis. * * @param unit Angular unit of the axes. * @return a new EllipsoidalCS. */ EllipsoidalCSNNPtr EllipsoidalCS::createLongitudeLatitude(const common::UnitOfMeasure &unit) { return EllipsoidalCS::create(util::PropertyMap(), CoordinateSystemAxis::createLONG_EAST(unit), CoordinateSystemAxis::createLAT_NORTH(unit)); } // --------------------------------------------------------------------------- /** \brief Instantiate a EllipsoidalCS with a Longitude (first), Latitude * (second) axis and ellipsoidal height (third) axis. * * @param angularUnit Angular unit of the latitude and longitude axes. * @param linearUnit Linear unit of the ellipsoidal height axis. * @return a new EllipsoidalCS. * @since 7.0 */ EllipsoidalCSNNPtr EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight( const common::UnitOfMeasure &angularUnit, const common::UnitOfMeasure &linearUnit) { return EllipsoidalCS::create( util::PropertyMap(), CoordinateSystemAxis::createLONG_EAST(angularUnit), CoordinateSystemAxis::createLAT_NORTH(angularUnit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Ellipsoidal_height), AxisAbbreviation::h, AxisDirection::UP, linearUnit)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return the axis order in an enumerated way. */ EllipsoidalCS::AxisOrder EllipsoidalCS::axisOrder() const { const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; const auto &dir0 = l_axisList[0]->direction(); const auto &dir1 = l_axisList[1]->direction(); if (&dir0 == &AxisDirection::NORTH && &dir1 == &AxisDirection::EAST) { if (l_axisList.size() == 2) { return AxisOrder::LAT_NORTH_LONG_EAST; } else if (&l_axisList[2]->direction() == &AxisDirection::UP) { return AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP; } } else if (&dir0 == &AxisDirection::EAST && &dir1 == &AxisDirection::NORTH) { if (l_axisList.size() == 2) { return AxisOrder::LONG_EAST_LAT_NORTH; } else if (&l_axisList[2]->direction() == &AxisDirection::UP) { return AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP; } } return AxisOrder::OTHER; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress EllipsoidalCSNNPtr EllipsoidalCS::alterAngularUnit( const common::UnitOfMeasure &angularUnit) const { const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; if (l_axisList.size() == 2) { return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0]->alterUnit(angularUnit), l_axisList[1]->alterUnit(angularUnit)); } else { assert(l_axisList.size() == 3); return EllipsoidalCS::create( util::PropertyMap(), l_axisList[0]->alterUnit(angularUnit), l_axisList[1]->alterUnit(angularUnit), l_axisList[2]); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress EllipsoidalCSNNPtr EllipsoidalCS::alterLinearUnit(const common::UnitOfMeasure &linearUnit) const { const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; if (l_axisList.size() == 2) { return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0], l_axisList[1]); } else { assert(l_axisList.size() == 3); return EllipsoidalCS::create(util::PropertyMap(), l_axisList[0], l_axisList[1], l_axisList[2]->alterUnit(linearUnit)); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress VerticalCS::~VerticalCS() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress VerticalCS::VerticalCS(const CoordinateSystemAxisNNPtr &axisIn) : CoordinateSystem(std::vector{axisIn}) {} //! @endcond // --------------------------------------------------------------------------- #ifdef notdef VerticalCS::VerticalCS(const VerticalCS &) = default; #endif // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalCS. * * @param properties See \ref general_properties. * @param axis The axis. * @return a new VerticalCS. */ VerticalCSNNPtr VerticalCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis) { auto cs(VerticalCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalCS with a Gravity-related height axis * * @param unit linear unit. * @return a new VerticalCS. */ VerticalCSNNPtr VerticalCS::createGravityRelatedHeight(const common::UnitOfMeasure &unit) { auto cs(VerticalCS::nn_make_shared(CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, "Gravity-related height"), "H", AxisDirection::UP, unit))); return cs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress VerticalCSNNPtr VerticalCS::alterUnit(const common::UnitOfMeasure &unit) const { const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; return VerticalCS::nn_make_shared( l_axisList[0]->alterUnit(unit)); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CartesianCS::~CartesianCS() = default; //! @endcond // --------------------------------------------------------------------------- CartesianCS::CartesianCS(const std::vector &axisIn) : CoordinateSystem(axisIn) {} // --------------------------------------------------------------------------- #ifdef notdef CartesianCS::CartesianCS(const CartesianCS &) = default; #endif // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2) { std::vector axis{axis1, axis2}; auto cs(CartesianCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @param axis3 The third axis. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3) { std::vector axis{axis1, axis2, axis3}; auto cs(CartesianCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS with a Easting (first) and Northing * (second) axis. * * @param unit Linear unit of the axes. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::createEastingNorthing(const common::UnitOfMeasure &unit) { return create(util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Easting), AxisAbbreviation::E, AxisDirection::EAST, unit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Northing), AxisAbbreviation::N, AxisDirection::NORTH, unit)); } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS with a Northing (first) and Easting * (second) axis. * * @param unit Linear unit of the axes. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::createNorthingEasting(const common::UnitOfMeasure &unit) { return create(util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Northing), AxisAbbreviation::N, AxisDirection::NORTH, unit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Easting), AxisAbbreviation::E, AxisDirection::EAST, unit)); } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS with a Westing (first) and Southing * (second) axis. * * @param unit Linear unit of the axes. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::createWestingSouthing(const common::UnitOfMeasure &unit) { return create(util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Easting), AxisAbbreviation::Y, AxisDirection::WEST, unit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Northing), AxisAbbreviation::X, AxisDirection::SOUTH, unit)); } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS, north-pole centered, * with a Easting (first) South-Oriented and * Northing (second) South-Oriented axis. * * @param unit Linear unit of the axes. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::createNorthPoleEastingSouthNorthingSouth( const common::UnitOfMeasure &unit) { return create(util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Easting), AxisAbbreviation::E, AxisDirection::SOUTH, unit, Meridian::create(common::Angle(90))), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Northing), AxisAbbreviation::N, AxisDirection::SOUTH, unit, Meridian::create(common::Angle(180)))); } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS, south-pole centered, * with a Easting (first) North-Oriented and * Northing (second) North-Oriented axis. * * @param unit Linear unit of the axes. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::createSouthPoleEastingNorthNorthingNorth( const common::UnitOfMeasure &unit) { return create(util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Easting), AxisAbbreviation::E, AxisDirection::NORTH, unit, Meridian::create(common::Angle(90))), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Northing), AxisAbbreviation::N, AxisDirection::NORTH, unit, Meridian::create(common::Angle(0)))); } // --------------------------------------------------------------------------- /** \brief Instantiate a CartesianCS with the three geocentric axes. * * @param unit Linear unit of the axes. * @return a new CartesianCS. */ CartesianCSNNPtr CartesianCS::createGeocentric(const common::UnitOfMeasure &unit) { return create(util::PropertyMap(), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Geocentric_X), AxisAbbreviation::X, AxisDirection::GEOCENTRIC_X, unit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Geocentric_Y), AxisAbbreviation::Y, AxisDirection::GEOCENTRIC_Y, unit), CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, AxisName::Geocentric_Z), AxisAbbreviation::Z, AxisDirection::GEOCENTRIC_Z, unit)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CartesianCSNNPtr CartesianCS::alterUnit(const common::UnitOfMeasure &unit) const { const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; if (l_axisList.size() == 2) { return CartesianCS::create(util::PropertyMap(), l_axisList[0]->alterUnit(unit), l_axisList[1]->alterUnit(unit)); } else { assert(l_axisList.size() == 3); return CartesianCS::create( util::PropertyMap(), l_axisList[0]->alterUnit(unit), l_axisList[1]->alterUnit(unit), l_axisList[2]->alterUnit(unit)); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AffineCS::~AffineCS() = default; //! @endcond // --------------------------------------------------------------------------- AffineCS::AffineCS(const std::vector &axisIn) : CoordinateSystem(axisIn) {} // --------------------------------------------------------------------------- /** \brief Instantiate a AffineCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @return a new AffineCS. */ AffineCSNNPtr AffineCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2) { std::vector axis{axis1, axis2}; auto cs(AffineCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- /** \brief Instantiate a AffineCS. * * @param properties See \ref general_properties. * @param axis1 The first axis. * @param axis2 The second axis. * @param axis3 The third axis. * @return a new AffineCS. */ AffineCSNNPtr AffineCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axis1, const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3) { std::vector axis{axis1, axis2, axis3}; auto cs(AffineCS::nn_make_shared(axis)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AffineCSNNPtr AffineCS::alterUnit(const common::UnitOfMeasure &unit) const { const auto &l_axisList = CoordinateSystem::getPrivate()->axisList; if (l_axisList.size() == 2) { return AffineCS::create(util::PropertyMap(), l_axisList[0]->alterUnit(unit), l_axisList[1]->alterUnit(unit)); } else { assert(l_axisList.size() == 3); return AffineCS::create( util::PropertyMap(), l_axisList[0]->alterUnit(unit), l_axisList[1]->alterUnit(unit), l_axisList[2]->alterUnit(unit)); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress OrdinalCS::~OrdinalCS() = default; //! @endcond // --------------------------------------------------------------------------- OrdinalCS::OrdinalCS(const std::vector &axisIn) : CoordinateSystem(axisIn) {} // --------------------------------------------------------------------------- #ifdef notdef OrdinalCS::OrdinalCS(const OrdinalCS &) = default; #endif // --------------------------------------------------------------------------- /** \brief Instantiate a OrdinalCS. * * @param properties See \ref general_properties. * @param axisIn List of axis. * @return a new OrdinalCS. */ OrdinalCSNNPtr OrdinalCS::create(const util::PropertyMap &properties, const std::vector &axisIn) { auto cs(OrdinalCS::nn_make_shared(axisIn)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ParametricCS::~ParametricCS() = default; //! @endcond // --------------------------------------------------------------------------- ParametricCS::ParametricCS(const std::vector &axisIn) : CoordinateSystem(axisIn) {} // --------------------------------------------------------------------------- #ifdef notdef ParametricCS::ParametricCS(const ParametricCS &) = default; #endif // --------------------------------------------------------------------------- /** \brief Instantiate a ParametricCS. * * @param properties See \ref general_properties. * @param axisIn Axis. * @return a new ParametricCS. */ ParametricCSNNPtr ParametricCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axisIn) { auto cs(ParametricCS::nn_make_shared( std::vector{axisIn})); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- AxisDirection::AxisDirection(const std::string &nameIn) : CodeList(nameIn) { auto lowerName = tolower(nameIn); assert(registry.find(lowerName) == registry.end()); registry[lowerName] = this; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const AxisDirection * AxisDirection::valueOf(const std::string &nameIn) noexcept { auto iter = registry.find(tolower(nameIn)); if (iter == registry.end()) return nullptr; return iter->second; } //! @endcond // --------------------------------------------------------------------------- RangeMeaning::RangeMeaning(const std::string &nameIn) : CodeList(nameIn) { auto lowerName = tolower(nameIn); assert(registry.find(lowerName) == registry.end()); registry[lowerName] = this; } // --------------------------------------------------------------------------- RangeMeaning::RangeMeaning() : CodeList(std::string()) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const RangeMeaning *RangeMeaning::valueOf(const std::string &nameIn) noexcept { auto iter = registry.find(tolower(nameIn)); if (iter == registry.end()) return nullptr; return iter->second; } //! @endcond //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- AxisDirectionWKT1::AxisDirectionWKT1(const std::string &nameIn) : CodeList(nameIn) { auto lowerName = tolower(nameIn); assert(registry.find(lowerName) == registry.end()); registry[lowerName] = this; } // --------------------------------------------------------------------------- const AxisDirectionWKT1 *AxisDirectionWKT1::valueOf(const std::string &nameIn) { auto iter = registry.find(tolower(nameIn)); if (iter == registry.end()) return nullptr; return iter->second; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalCS::~TemporalCS() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalCS::TemporalCS(const CoordinateSystemAxisNNPtr &axisIn) : CoordinateSystem(std::vector{axisIn}) {} //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DateTimeTemporalCS::~DateTimeTemporalCS() = default; //! @endcond // --------------------------------------------------------------------------- DateTimeTemporalCS::DateTimeTemporalCS(const CoordinateSystemAxisNNPtr &axisIn) : TemporalCS(axisIn) {} // --------------------------------------------------------------------------- /** \brief Instantiate a DateTimeTemporalCS. * * @param properties See \ref general_properties. * @param axisIn The axis. * @return a new DateTimeTemporalCS. */ DateTimeTemporalCSNNPtr DateTimeTemporalCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axisIn) { auto cs(DateTimeTemporalCS::nn_make_shared(axisIn)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- std::string DateTimeTemporalCS::getWKT2Type(bool use2019Keywords) const { return use2019Keywords ? WKT2_2019_TYPE : WKT2_2015_TYPE; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalCountCS::~TemporalCountCS() = default; //! @endcond // --------------------------------------------------------------------------- TemporalCountCS::TemporalCountCS(const CoordinateSystemAxisNNPtr &axisIn) : TemporalCS(axisIn) {} // --------------------------------------------------------------------------- /** \brief Instantiate a TemporalCountCS. * * @param properties See \ref general_properties. * @param axisIn The axis. * @return a new TemporalCountCS. */ TemporalCountCSNNPtr TemporalCountCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axisIn) { auto cs(TemporalCountCS::nn_make_shared(axisIn)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- std::string TemporalCountCS::getWKT2Type(bool use2019Keywords) const { return use2019Keywords ? WKT2_2019_TYPE : WKT2_2015_TYPE; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalMeasureCS::~TemporalMeasureCS() = default; //! @endcond // --------------------------------------------------------------------------- TemporalMeasureCS::TemporalMeasureCS(const CoordinateSystemAxisNNPtr &axisIn) : TemporalCS(axisIn) {} // --------------------------------------------------------------------------- /** \brief Instantiate a TemporalMeasureCS. * * @param properties See \ref general_properties. * @param axisIn The axis. * @return a new TemporalMeasureCS. */ TemporalMeasureCSNNPtr TemporalMeasureCS::create(const util::PropertyMap &properties, const CoordinateSystemAxisNNPtr &axisIn) { auto cs(TemporalMeasureCS::nn_make_shared(axisIn)); cs->setProperties(properties); return cs; } // --------------------------------------------------------------------------- std::string TemporalMeasureCS::getWKT2Type(bool use2019Keywords) const { return use2019Keywords ? WKT2_2019_TYPE : WKT2_2015_TYPE; } } // namespace cs NS_PROJ_END proj-9.8.1/src/iso19111/crs.cpp000664 001750 001750 00001112306 15166171715 015730 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif //! @cond Doxygen_Suppress #define DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE //! @endcond #include "proj/crs.hpp" #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinatesystem.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/coordinatesystem_internal.hpp" #include "proj/internal/crs_internal.hpp" #include "proj/internal/datum_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "operation/oputils.hpp" #include "proj_constants.h" #include "proj_json_streaming_writer.hpp" #include #include #include #include #include #include #include #include using namespace NS_PROJ::internal; #if 0 namespace dropbox{ namespace oxygen { template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; template<> nn::~nn() = default; }} #endif NS_PROJ_START namespace crs { //! @cond Doxygen_Suppress constexpr const char *PROMOTED_TO_3D_PRELUDE = "Promoted to 3D from "; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CRS::Private { BoundCRSPtr canonicalBoundCRS_{}; std::string extensionProj4_{}; bool implicitCS_ = false; bool over_ = false; bool allowNonConformantWKT1Export_ = false; // for what was initially a COMPD_CS with a VERT_CS with a datum type == // ellipsoidal height / 2002 CompoundCRSPtr originalCompoundCRS_{}; void setNonStandardProperties(const util::PropertyMap &properties) { { const auto pVal = properties.get("IMPLICIT_CS"); if (pVal) { if (const auto genVal = dynamic_cast(pVal->get())) { if (genVal->type() == util::BoxedValue::Type::BOOLEAN && genVal->booleanValue()) { implicitCS_ = true; } } } } { const auto pVal = properties.get("OVER"); if (pVal) { if (const auto genVal = dynamic_cast(pVal->get())) { if (genVal->type() == util::BoxedValue::Type::BOOLEAN && genVal->booleanValue()) { over_ = true; } } } } } }; //! @endcond // --------------------------------------------------------------------------- CRS::CRS() : d(std::make_unique()) {} // --------------------------------------------------------------------------- CRS::CRS(const CRS &other) : ObjectUsage(other), d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRS::~CRS() = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return whether the CRS has an implicit coordinate system * (e.g from ESRI WKT) */ bool CRS::hasImplicitCS() const { return d->implicitCS_; } /** \brief Return whether the CRS has a +over flag */ bool CRS::hasOver() const { return d->over_; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the BoundCRS potentially attached to this CRS. * * In the case this method is called on a object returned by * BoundCRS::baseCRSWithCanonicalBoundCRS(), this method will return this * BoundCRS * * @return a BoundCRSPtr, that might be null. */ const BoundCRSPtr &CRS::canonicalBoundCRS() PROJ_PURE_DEFN { return d->canonicalBoundCRS_; } // --------------------------------------------------------------------------- /** \brief Return whether a CRS is a dynamic CRS. * * A dynamic CRS is a CRS that contains a geodetic CRS whose geodetic reference * frame is dynamic, or a vertical CRS whose vertical reference frame is * dynamic. * @param considerWGS84AsDynamic set to true to consider the WGS 84 / EPSG:6326 * datum ensemble as dynamic. * @since 9.2 */ bool CRS::isDynamic(bool considerWGS84AsDynamic) const { if (auto raw = extractGeodeticCRSRaw()) { const auto &l_datum = raw->datum(); if (l_datum) { if (dynamic_cast( l_datum.get())) { return true; } if (considerWGS84AsDynamic && l_datum->nameStr() == "World Geodetic System 1984") { return true; } } if (considerWGS84AsDynamic) { const auto &l_datumEnsemble = raw->datumEnsemble(); if (l_datumEnsemble && l_datumEnsemble->nameStr() == "World Geodetic System 1984 ensemble") { return true; } } } if (auto vertCRS = extractVerticalCRS()) { const auto &l_datum = vertCRS->datum(); if (l_datum && dynamic_cast( l_datum.get())) { return true; } } return false; } // --------------------------------------------------------------------------- /** \brief Return the GeodeticCRS of the CRS. * * Returns the GeodeticCRS contained in a CRS. This works currently with * input parameters of type GeodeticCRS or derived, ProjectedCRS, * CompoundCRS or BoundCRS. * * @return a GeodeticCRSPtr, that might be null. */ GeodeticCRSPtr CRS::extractGeodeticCRS() const { auto raw = extractGeodeticCRSRaw(); if (raw) { return std::dynamic_pointer_cast( raw->shared_from_this().as_nullable()); } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const GeodeticCRS *CRS::extractGeodeticCRSRaw() const { auto geodCRS = dynamic_cast(this); if (geodCRS) { return geodCRS; } auto projCRS = dynamic_cast(this); if (projCRS) { return projCRS->baseCRS()->extractGeodeticCRSRaw(); } auto compoundCRS = dynamic_cast(this); if (compoundCRS) { for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { auto retGeogCRS = subCrs->extractGeodeticCRSRaw(); if (retGeogCRS) { return retGeogCRS; } } } auto boundCRS = dynamic_cast(this); if (boundCRS) { return boundCRS->baseCRS()->extractGeodeticCRSRaw(); } auto derivedProjectedCRS = dynamic_cast(this); if (derivedProjectedCRS) { return derivedProjectedCRS->baseCRS()->extractGeodeticCRSRaw(); } return nullptr; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const std::string &CRS::getExtensionProj4() const noexcept { return d->extensionProj4_; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the GeographicCRS of the CRS. * * Returns the GeographicCRS contained in a CRS. This works currently with * input parameters of type GeographicCRS or derived, ProjectedCRS, * CompoundCRS or BoundCRS. * * @return a GeographicCRSPtr, that might be null. */ GeographicCRSPtr CRS::extractGeographicCRS() const { auto raw = extractGeodeticCRSRaw(); if (raw) { return std::dynamic_pointer_cast( raw->shared_from_this().as_nullable()); } return nullptr; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createPropertyMap(const common::IdentifiedObject *obj) { auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, obj->nameStr()); if (obj->isDeprecated()) { props.set(common::IdentifiedObject::DEPRECATED_KEY, true); } return props; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::alterGeodeticCRS(const GeodeticCRSNNPtr &newGeodCRS) const { if (dynamic_cast(this)) { return newGeodCRS; } if (auto projCRS = dynamic_cast(this)) { return ProjectedCRS::create(createPropertyMap(this), newGeodCRS, projCRS->derivingConversion(), projCRS->coordinateSystem()); } if (auto derivedProjCRS = dynamic_cast(this)) { auto newProjCRS = NN_CHECK_ASSERT(util::nn_dynamic_pointer_cast( derivedProjCRS->baseCRS()->alterGeodeticCRS(newGeodCRS))); return DerivedProjectedCRS::create(createPropertyMap(this), newProjCRS, derivedProjCRS->derivingConversion(), derivedProjCRS->coordinateSystem()); } if (auto compoundCRS = dynamic_cast(this)) { std::vector components; for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { components.emplace_back(subCrs->alterGeodeticCRS(newGeodCRS)); } return CompoundCRS::create(createPropertyMap(this), components); } return NN_NO_CHECK( std::dynamic_pointer_cast(shared_from_this().as_nullable())); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::alterCSLinearUnit(const common::UnitOfMeasure &unit) const { { auto projCRS = dynamic_cast(this); if (projCRS) { return ProjectedCRS::create( createPropertyMap(this), projCRS->baseCRS(), projCRS->derivingConversion(), projCRS->coordinateSystem()->alterUnit(unit)); } } { auto geodCRS = dynamic_cast(this); if (geodCRS && geodCRS->isGeocentric()) { auto cs = dynamic_cast( geodCRS->coordinateSystem().get()); assert(cs); return GeodeticCRS::create( createPropertyMap(this), geodCRS->datum(), geodCRS->datumEnsemble(), cs->alterUnit(unit)); } } { auto geogCRS = dynamic_cast(this); if (geogCRS && geogCRS->coordinateSystem()->axisList().size() == 3) { return GeographicCRS::create( createPropertyMap(this), geogCRS->datum(), geogCRS->datumEnsemble(), geogCRS->coordinateSystem()->alterLinearUnit(unit)); } } { auto vertCRS = dynamic_cast(this); if (vertCRS) { return VerticalCRS::create( createPropertyMap(this), vertCRS->datum(), vertCRS->datumEnsemble(), vertCRS->coordinateSystem()->alterUnit(unit)); } } { auto engCRS = dynamic_cast(this); if (engCRS) { auto cartCS = util::nn_dynamic_pointer_cast( engCRS->coordinateSystem()); if (cartCS) { return EngineeringCRS::create(createPropertyMap(this), engCRS->datum(), cartCS->alterUnit(unit)); } else { auto vertCS = util::nn_dynamic_pointer_cast( engCRS->coordinateSystem()); if (vertCS) { return EngineeringCRS::create(createPropertyMap(this), engCRS->datum(), vertCS->alterUnit(unit)); } } } } { auto derivedProjCRS = dynamic_cast(this); if (derivedProjCRS) { auto cs = derivedProjCRS->coordinateSystem(); auto cartCS = util::nn_dynamic_pointer_cast(cs); if (cartCS) { cs = cartCS->alterUnit(unit); } return DerivedProjectedCRS::create( createPropertyMap(this), derivedProjCRS->baseCRS(), derivedProjCRS->derivingConversion(), cs); } } { auto compoundCRS = dynamic_cast(this); if (compoundCRS) { std::vector components; for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { components.push_back(subCrs->alterCSLinearUnit(unit)); } return CompoundCRS::create(createPropertyMap(this), components); } } { auto boundCRS = dynamic_cast(this); if (boundCRS) { return BoundCRS::create( createPropertyMap(this), boundCRS->baseCRS()->alterCSLinearUnit(unit), boundCRS->hubCRS(), boundCRS->transformation()); } } return NN_NO_CHECK( std::dynamic_pointer_cast(shared_from_this().as_nullable())); } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the VerticalCRS of the CRS. * * Returns the VerticalCRS contained in a CRS. This works currently with * input parameters of type VerticalCRS or derived, CompoundCRS or BoundCRS. * * @return a VerticalCRSPtr, that might be null. */ VerticalCRSPtr CRS::extractVerticalCRS() const { auto vertCRS = dynamic_cast(this); if (vertCRS) { return std::dynamic_pointer_cast( shared_from_this().as_nullable()); } auto compoundCRS = dynamic_cast(this); if (compoundCRS) { for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { auto retVertCRS = subCrs->extractVerticalCRS(); if (retVertCRS) { return retVertCRS; } } } auto boundCRS = dynamic_cast(this); if (boundCRS) { return boundCRS->baseCRS()->extractVerticalCRS(); } return nullptr; } // --------------------------------------------------------------------------- /** \brief Returns potentially * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS * * If no such BoundCRS is possible, the object will be returned. * * The purpose of this method is to be able to format a PROJ.4 string with * a +towgs84 parameter or a WKT1:GDAL string with a TOWGS node. * * This method will fetch the GeographicCRS of this CRS and find a * transformation to EPSG:4326 using the domain of the validity of the main CRS, * and there's only one Helmert transformation. * * @return a CRS. */ CRSNNPtr CRS::createBoundCRSToWGS84IfPossible( const io::DatabaseContextPtr &dbContext, operation::CoordinateOperationContext::IntermediateCRSUse allowIntermediateCRSUse) const { auto thisAsCRS = NN_NO_CHECK( std::static_pointer_cast(shared_from_this().as_nullable())); auto boundCRS = util::nn_dynamic_pointer_cast(thisAsCRS); if (!boundCRS) { boundCRS = canonicalBoundCRS(); } if (boundCRS) { if (boundCRS->hubCRS()->_isEquivalentTo( GeographicCRS::EPSG_4326.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { return NN_NO_CHECK(boundCRS); } } auto compoundCRS = dynamic_cast(this); if (compoundCRS) { const auto &comps = compoundCRS->componentReferenceSystems(); if (comps.size() == 2) { auto horiz = comps[0]->createBoundCRSToWGS84IfPossible( dbContext, allowIntermediateCRSUse); auto vert = comps[1]->createBoundCRSToWGS84IfPossible( dbContext, allowIntermediateCRSUse); if (horiz.get() != comps[0].get() || vert.get() != comps[1].get()) { return CompoundCRS::create(createPropertyMap(this), {std::move(horiz), std::move(vert)}); } } return thisAsCRS; } if (!dbContext) { return thisAsCRS; } const auto &l_domains = domains(); metadata::ExtentPtr extent; if (!l_domains.empty()) { if (l_domains.size() == 2) { // Special case for the UTM ETRS89 CRS that have 2 domains since // EPSG v11.009. The "Pan-European conformal mapping at scales // larger than 1:500,000" one includes a slightly smaller one // "Engineering survey, topographic mapping" valid for some // countries. Let's take the larger one to get a transformation // valid for all domains. auto extent0 = l_domains[0]->domainOfValidity(); auto extent1 = l_domains[1]->domainOfValidity(); if (extent0 && extent1) { if (extent0->contains(NN_NO_CHECK(extent1))) { extent = std::move(extent0); } else if (extent1->contains(NN_NO_CHECK(extent0))) { extent = std::move(extent1); } } } if (!extent) { if (l_domains.size() > 1) { // If there are several domains of validity, then it is // extremely unlikely, we could get a single transformation // valid for all. At least, in the current state of the code of // createOperations() which returns a single extent, this can't // happen. return thisAsCRS; } extent = l_domains[0]->domainOfValidity(); } } std::string crs_authority; const auto &l_identifiers = identifiers(); // If the object has an authority, restrict the transformations to // come from that codespace too. This avoids for example EPSG:4269 // (NAD83) to use a (dubious) ESRI transformation. if (!l_identifiers.empty()) { crs_authority = *(l_identifiers[0]->codeSpace()); } auto authorities = dbContext->getAllowedAuthorities( crs_authority, metadata::Identifier::EPSG); if (authorities.empty()) { authorities.emplace_back(); } // Vertical CRS ? auto vertCRS = dynamic_cast(this); if (vertCRS) { auto hubCRS = util::nn_static_pointer_cast(GeographicCRS::EPSG_4979); for (const auto &authority : authorities) { try { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), authority == "any" ? std::string() : authority); auto ctxt = operation::CoordinateOperationContext::create( authFactory, extent, 0.0); ctxt->setAllowUseIntermediateCRS(allowIntermediateCRSUse); // ctxt->setSpatialCriterion( // operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = operation::CoordinateOperationFactory::create() ->createOperations(hubCRS, thisAsCRS, ctxt); CRSPtr candidateBoundCRS; for (const auto &op : list) { auto transf = util::nn_dynamic_pointer_cast< operation::Transformation>(op); // Only keep transformations that use a known grid if (transf && !transf->hasBallparkTransformation()) { auto gridsNeeded = transf->gridsNeeded(dbContext, true); bool gridsKnown = !gridsNeeded.empty(); for (const auto &gridDesc : gridsNeeded) { if (gridDesc.packageName.empty() && !(!gridDesc.url.empty() && gridDesc.openLicense) && !gridDesc.available) { gridsKnown = false; break; } } if (gridsKnown) { if (candidateBoundCRS) { candidateBoundCRS = nullptr; break; } candidateBoundCRS = BoundCRS::create(thisAsCRS, hubCRS, NN_NO_CHECK(transf)) .as_nullable(); } } } if (candidateBoundCRS) { return NN_NO_CHECK(candidateBoundCRS); } } catch (const std::exception &) { } } return thisAsCRS; } // Geodetic/geographic CRS ? auto geodCRS = util::nn_dynamic_pointer_cast(thisAsCRS); auto geogCRS = extractGeographicCRS(); auto hubCRS = util::nn_static_pointer_cast(GeographicCRS::EPSG_4326); if (geodCRS && !geogCRS) { if (geodCRS->datumNonNull(dbContext)->nameStr() != "unknown" && geodCRS->_isEquivalentTo(GeographicCRS::EPSG_4978.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { return thisAsCRS; } hubCRS = util::nn_static_pointer_cast(GeodeticCRS::EPSG_4978); } else if (!geogCRS || (geogCRS->datumNonNull(dbContext)->nameStr() != "unknown" && geogCRS->_isEquivalentTo( GeographicCRS::EPSG_4326.get(), util::IComparable::Criterion::EQUIVALENT, dbContext))) { return thisAsCRS; } else { geodCRS = geogCRS; } for (const auto &authority : authorities) { try { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), authority == "any" ? std::string() : authority); metadata::ExtentPtr extentResolved(extent); if (!extent) { getResolvedCRS(thisAsCRS, authFactory, extentResolved); } auto ctxt = operation::CoordinateOperationContext::create( authFactory, extentResolved, 0.0); ctxt->setAllowUseIntermediateCRS(allowIntermediateCRSUse); // ctxt->setSpatialCriterion( // operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); auto list = operation::CoordinateOperationFactory::create() ->createOperations(NN_NO_CHECK(geodCRS), hubCRS, ctxt); CRSPtr candidateBoundCRS; int candidateCount = 0; const auto takeIntoAccountCandidate = [&](const operation::TransformationNNPtr &transf) { if (transf->getTOWGS84Parameters(false).empty()) { return; } candidateCount++; if (candidateBoundCRS == nullptr) { candidateCount = 1; candidateBoundCRS = BoundCRS::create(thisAsCRS, hubCRS, transf) .as_nullable(); } }; for (const auto &op : list) { auto transf = util::nn_dynamic_pointer_cast( op); if (transf && !starts_with(transf->nameStr(), "Ballpark geo")) { takeIntoAccountCandidate(NN_NO_CHECK(transf)); } else { auto concatenated = dynamic_cast( op.get()); if (concatenated) { // Case for EPSG:4807 / "NTF (Paris)" that is made of a // longitude rotation followed by a Helmert // The prime meridian shift will be accounted elsewhere const auto &subops = concatenated->operations(); if (subops.size() == 2) { auto firstOpIsTransformation = dynamic_cast( subops[0].get()); auto firstOpIsConversion = dynamic_cast( subops[0].get()); if ((firstOpIsTransformation && firstOpIsTransformation ->isLongitudeRotation()) || (dynamic_cast(thisAsCRS.get()) && firstOpIsConversion)) { transf = util::nn_dynamic_pointer_cast< operation::Transformation>(subops[1]); if (transf && !starts_with(transf->nameStr(), "Ballpark geo")) { takeIntoAccountCandidate( NN_NO_CHECK(transf)); } } } } } } if (candidateCount == 1 && candidateBoundCRS) { return NN_NO_CHECK(candidateBoundCRS); } } catch (const std::exception &) { } } return thisAsCRS; } // --------------------------------------------------------------------------- /** \brief Returns a CRS whose coordinate system does not contain a vertical * component. * * As of PROJ 9.5, this method is an alias of demoteTo2D(std::string(), * nullptr), which deals with all potential CRS types. * * demoteTo2D() is a preferred alternative, especially when invoked with a * non-null database context, to perform a look-up in the database for * already registered 2D CRS. * * @return a CRS. */ CRSNNPtr CRS::stripVerticalComponent() const { return demoteTo2D(std::string(), nullptr); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return a shallow clone of this object. */ CRSNNPtr CRS::shallowClone() const { return _shallowClone(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::allowNonConformantWKT1Export() const { const auto boundCRS = dynamic_cast(this); if (boundCRS) { return BoundCRS::create( boundCRS->baseCRS()->allowNonConformantWKT1Export(), boundCRS->hubCRS(), boundCRS->transformation()); } auto crs(shallowClone()); crs->d->allowNonConformantWKT1Export_ = true; return crs; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::attachOriginalCompoundCRS(const CompoundCRSNNPtr &compoundCRS) const { const auto boundCRS = dynamic_cast(this); if (boundCRS) { return BoundCRS::create( boundCRS->baseCRS()->attachOriginalCompoundCRS(compoundCRS), boundCRS->hubCRS(), boundCRS->transformation()); } auto crs(shallowClone()); crs->d->originalCompoundCRS_ = compoundCRS.as_nullable(); return crs; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::alterName(const std::string &newName) const { auto crs = shallowClone(); auto newNameMod(newName); auto props = util::PropertyMap(); if (ends_with(newNameMod, " (deprecated)")) { newNameMod.resize(newNameMod.size() - strlen(" (deprecated)")); props.set(common::IdentifiedObject::DEPRECATED_KEY, true); } props.set(common::IdentifiedObject::NAME_KEY, newNameMod); crs->setProperties(props); return crs; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::alterId(const std::string &authName, const std::string &code) const { auto crs = shallowClone(); auto props = util::PropertyMap(); props.set(metadata::Identifier::CODESPACE_KEY, authName) .set(metadata::Identifier::CODE_KEY, code); crs->setProperties(props); return crs; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool mustAxisOrderBeSwitchedForVisualizationInternal( const std::vector &axisList) { const auto &dir0 = axisList[0]->direction(); const auto &dir1 = axisList[1]->direction(); if (&dir0 == &cs::AxisDirection::NORTH && &dir1 == &cs::AxisDirection::EAST) { return true; } // Address EPSG:32661 "WGS 84 / UPS North (N,E)" if (&dir0 == &cs::AxisDirection::SOUTH && &dir1 == &cs::AxisDirection::SOUTH) { const auto &meridian0 = axisList[0]->meridian(); const auto &meridian1 = axisList[1]->meridian(); return meridian0 != nullptr && meridian1 != nullptr && std::abs(meridian0->longitude().convertToUnit( common::UnitOfMeasure::DEGREE) - 180.0) < 1e-10 && std::abs(meridian1->longitude().convertToUnit( common::UnitOfMeasure::DEGREE) - 90.0) < 1e-10; } if (&dir0 == &cs::AxisDirection::NORTH && &dir1 == &cs::AxisDirection::NORTH) { const auto &meridian0 = axisList[0]->meridian(); const auto &meridian1 = axisList[1]->meridian(); return meridian0 != nullptr && meridian1 != nullptr && (( // Address EPSG:32761 "WGS 84 / UPS South (N,E)" std::abs(meridian0->longitude().convertToUnit( common::UnitOfMeasure::DEGREE) - 0.0) < 1e-10 && std::abs(meridian1->longitude().convertToUnit( common::UnitOfMeasure::DEGREE) - 90.0) < 1e-10) || // Address EPSG:5482 "RSRGD2000 / RSPS2000" (std::abs(meridian0->longitude().convertToUnit( common::UnitOfMeasure::DEGREE) - 180) < 1e-10 && std::abs(meridian1->longitude().convertToUnit( common::UnitOfMeasure::DEGREE) - -90.0) < 1e-10)); } return false; } // --------------------------------------------------------------------------- bool CRS::mustAxisOrderBeSwitchedForVisualization() const { if (const CompoundCRS *compoundCRS = dynamic_cast(this)) { const auto &comps = compoundCRS->componentReferenceSystems(); if (!comps.empty()) { return comps[0]->mustAxisOrderBeSwitchedForVisualization(); } } if (const GeographicCRS *geogCRS = dynamic_cast(this)) { return mustAxisOrderBeSwitchedForVisualizationInternal( geogCRS->coordinateSystem()->axisList()); } if (const ProjectedCRS *projCRS = dynamic_cast(this)) { return mustAxisOrderBeSwitchedForVisualizationInternal( projCRS->coordinateSystem()->axisList()); } if (const DerivedProjectedCRS *derivedProjCRS = dynamic_cast(this)) { return mustAxisOrderBeSwitchedForVisualizationInternal( derivedProjCRS->coordinateSystem()->axisList()); } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CRS::setProperties( const util::PropertyMap &properties) // throw(InvalidValueTypeException) { std::string l_remarks; std::string extensionProj4; properties.getStringValue(IdentifiedObject::REMARKS_KEY, l_remarks); properties.getStringValue("EXTENSION_PROJ4", extensionProj4); const char *PROJ_CRS_STRING_PREFIX = "PROJ CRS string: "; const char *PROJ_CRS_STRING_SUFFIX = ". "; const auto beginOfProjStringPos = l_remarks.find(PROJ_CRS_STRING_PREFIX); if (beginOfProjStringPos == std::string::npos && extensionProj4.empty()) { ObjectUsage::setProperties(properties); return; } util::PropertyMap newProperties(properties); // Parse remarks and extract EXTENSION_PROJ4 from it if (extensionProj4.empty()) { if (beginOfProjStringPos != std::string::npos) { const auto endOfProjStringPos = l_remarks.find(PROJ_CRS_STRING_SUFFIX, beginOfProjStringPos); if (endOfProjStringPos == std::string::npos) { extensionProj4 = l_remarks.substr( beginOfProjStringPos + strlen(PROJ_CRS_STRING_PREFIX)); } else { extensionProj4 = l_remarks.substr( beginOfProjStringPos + strlen(PROJ_CRS_STRING_PREFIX), endOfProjStringPos - beginOfProjStringPos - strlen(PROJ_CRS_STRING_PREFIX)); } } } if (!extensionProj4.empty()) { if (beginOfProjStringPos == std::string::npos) { // Add EXTENSION_PROJ4 to remarks l_remarks = PROJ_CRS_STRING_PREFIX + extensionProj4 + (l_remarks.empty() ? std::string() : PROJ_CRS_STRING_SUFFIX + l_remarks); } } newProperties.set(IdentifiedObject::REMARKS_KEY, l_remarks); ObjectUsage::setProperties(newProperties); d->extensionProj4_ = std::move(extensionProj4); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- CRSNNPtr CRS::applyAxisOrderReversal(const char *nameSuffix) const { const auto createProperties = [this, nameSuffix](const std::string &newNameIn = std::string()) { std::string newName(newNameIn); if (newName.empty()) { newName = nameStr(); if (ends_with(newName, NORMALIZED_AXIS_ORDER_SUFFIX_STR)) { newName.resize(newName.size() - strlen(NORMALIZED_AXIS_ORDER_SUFFIX_STR)); } else if (ends_with(newName, AXIS_ORDER_REVERSED_SUFFIX_STR)) { newName.resize(newName.size() - strlen(AXIS_ORDER_REVERSED_SUFFIX_STR)); } else { newName += nameSuffix; } } auto props = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, newName); const auto &l_domains = domains(); if (!l_domains.empty()) { auto array = util::ArrayOfBaseObject::create(); for (const auto &domain : l_domains) { array->add(domain); } if (!array->empty()) { props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, array); } } const auto &l_identifiers = identifiers(); const auto &l_remarks = remarks(); if (l_identifiers.size() == 1) { std::string remarks("Axis order reversed compared to "); if (!starts_with(l_remarks, remarks)) { remarks += *(l_identifiers[0]->codeSpace()); remarks += ':'; remarks += l_identifiers[0]->code(); if (!l_remarks.empty()) { remarks += ". "; remarks += l_remarks; } props.set(common::IdentifiedObject::REMARKS_KEY, remarks); } } else if (!l_remarks.empty()) { props.set(common::IdentifiedObject::REMARKS_KEY, l_remarks); } return props; }; if (const CompoundCRS *compoundCRS = dynamic_cast(this)) { const auto &comps = compoundCRS->componentReferenceSystems(); if (!comps.empty()) { std::vector newComps; newComps.emplace_back(comps[0]->applyAxisOrderReversal(nameSuffix)); std::string l_name = newComps.back()->nameStr(); for (size_t i = 1; i < comps.size(); i++) { newComps.emplace_back(comps[i]); l_name += " + "; l_name += newComps.back()->nameStr(); } return util::nn_static_pointer_cast( CompoundCRS::create(createProperties(l_name), newComps)); } } if (const GeographicCRS *geogCRS = dynamic_cast(this)) { const auto &axisList = geogCRS->coordinateSystem()->axisList(); auto cs = axisList.size() == 2 ? cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1], axisList[0]) : cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1], axisList[0], axisList[2]); return util::nn_static_pointer_cast( GeographicCRS::create(createProperties(), geogCRS->datum(), geogCRS->datumEnsemble(), cs)); } if (const ProjectedCRS *projCRS = dynamic_cast(this)) { const auto &axisList = projCRS->coordinateSystem()->axisList(); auto cs = axisList.size() == 2 ? cs::CartesianCS::create(util::PropertyMap(), axisList[1], axisList[0]) : cs::CartesianCS::create(util::PropertyMap(), axisList[1], axisList[0], axisList[2]); return util::nn_static_pointer_cast( ProjectedCRS::create(createProperties(), projCRS->baseCRS(), projCRS->derivingConversion(), cs)); } if (const DerivedProjectedCRS *derivedProjCRS = dynamic_cast(this)) { const auto &axisList = derivedProjCRS->coordinateSystem()->axisList(); auto cs = axisList.size() == 2 ? cs::CartesianCS::create(util::PropertyMap(), axisList[1], axisList[0]) : cs::CartesianCS::create(util::PropertyMap(), axisList[1], axisList[0], axisList[2]); return util::nn_static_pointer_cast(DerivedProjectedCRS::create( createProperties(), derivedProjCRS->baseCRS(), derivedProjCRS->derivingConversion(), cs)); } throw util::UnsupportedOperationException( "axis order reversal not supported on this type of CRS"); } // --------------------------------------------------------------------------- CRSNNPtr CRS::normalizeForVisualization() const { if (const CompoundCRS *compoundCRS = dynamic_cast(this)) { const auto &comps = compoundCRS->componentReferenceSystems(); if (!comps.empty() && comps[0]->mustAxisOrderBeSwitchedForVisualization()) { return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); } } if (const GeographicCRS *geogCRS = dynamic_cast(this)) { const auto &axisList = geogCRS->coordinateSystem()->axisList(); if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); } } if (const ProjectedCRS *projCRS = dynamic_cast(this)) { const auto &axisList = projCRS->coordinateSystem()->axisList(); if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); } } if (const DerivedProjectedCRS *derivedProjCRS = dynamic_cast(this)) { const auto &axisList = derivedProjCRS->coordinateSystem()->axisList(); if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); } } if (const BoundCRS *boundCRS = dynamic_cast(this)) { auto baseNormCRS = boundCRS->baseCRS()->normalizeForVisualization(); return BoundCRS::create(baseNormCRS, boundCRS->hubCRS(), boundCRS->transformation()); } return NN_NO_CHECK( std::static_pointer_cast(shared_from_this().as_nullable())); } //! @endcond // --------------------------------------------------------------------------- /** \brief Identify the CRS with reference CRSs. * * The candidate CRSs are either hard-coded, or looked in the database when * authorityFactory is not null. * * Note that the implementation uses a set of heuristics to have a good * compromise of successful identifications over execution time. It might miss * legitimate matches in some circumstances. * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. *
    *
  • 100% means that the name of the reference entry * perfectly matches the CRS name, and both are equivalent. In which case a * single result is returned. * Note: in the case of a GeographicCRS whose axis * order is implicit in the input definition (for example ESRI WKT), then axis * order is ignored for the purpose of identification. That is the CRS built * from * GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]], * PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] * will be identified to EPSG:4326, but will not pass a * isEquivalentTo(EPSG_4326, util::IComparable::Criterion::EQUIVALENT) test, * but rather isEquivalentTo(EPSG_4326, * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) *
  • *
  • 90% means that CRS are equivalent, but the names are not exactly the * same.
  • *
  • 70% means that CRS are equivalent), but the names do not match at * all.
  • *
  • 25% means that the CRS are not equivalent, but there is some similarity * in * the names.
  • *
* Other confidence values may be returned by some specialized implementations. * * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and * CompoundCRS. * * @param authorityFactory Authority factory (or null, but degraded * functionality) * @return a list of matching reference CRS, and the percentage (0-100) of * confidence in the match. */ std::list> CRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { return _identify(authorityFactory); } // --------------------------------------------------------------------------- /** \brief Return CRSs that are non-deprecated substitutes for the current CRS. */ std::list CRS::getNonDeprecated(const io::DatabaseContextNNPtr &dbContext) const { std::list res; const auto &l_identifiers = identifiers(); if (l_identifiers.empty()) { return res; } const char *tableName = nullptr; if (dynamic_cast(this)) { tableName = "geodetic_crs"; } else if (dynamic_cast(this)) { tableName = "projected_crs"; } else if (dynamic_cast(this)) { tableName = "vertical_crs"; } else if (dynamic_cast(this)) { tableName = "compound_crs"; } if (!tableName) { return res; } const auto &id = l_identifiers[0]; auto tmpRes = dbContext->getNonDeprecated(tableName, *(id->codeSpace()), id->code()); for (const auto &pair : tmpRes) { res.emplace_back(io::AuthorityFactory::create(dbContext, pair.first) ->createCoordinateReferenceSystem(pair.second)); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return the authority name to which this object is registered, or * has an indirect provenance. * * Typically this method called on EPSG:4269 (NAD83) promoted to 3D will return * "EPSG". * * Returns empty string if more than an authority or no originating authority is * found. */ std::string CRS::getOriginatingAuthName() const { const auto &ids = identifiers(); if (ids.size() == 1) { return *(ids[0]->codeSpace()); } if (ids.size() > 1) { return std::string(); } const auto &l_remarks = remarks(); if (starts_with(l_remarks, PROMOTED_TO_3D_PRELUDE)) { const auto pos = l_remarks.find(':'); if (pos != std::string::npos) { return l_remarks.substr(strlen(PROMOTED_TO_3D_PRELUDE), pos - strlen(PROMOTED_TO_3D_PRELUDE)); } } return std::string(); } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a variant of this CRS "promoted" to a 3D one, if not already * the case. * * The new axis will be ellipsoidal height, oriented upwards, and with metre * units. * * @param newName Name of the new CRS. If empty, nameStr() will be used. * @param dbContext Database context to look for potentially already registered * 3D CRS. May be nullptr. * @return a new CRS promoted to 3D, or the current one if already 3D or not * applicable. * @since 6.3 */ CRSNNPtr CRS::promoteTo3D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const { auto upAxis = cs::CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, cs::AxisName::Ellipsoidal_height), cs::AxisAbbreviation::h, cs::AxisDirection::UP, common::UnitOfMeasure::METRE); return promoteTo3D(newName, dbContext, upAxis); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CRSNNPtr CRS::promoteTo3D(const std::string &newName, const io::DatabaseContextPtr &dbContext, const cs::CoordinateSystemAxisNNPtr &verticalAxisIfNotAlreadyPresent) const { const auto createProperties = [this, &newName]() { auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, !newName.empty() ? newName : nameStr()); const auto &l_domains = domains(); if (!l_domains.empty()) { auto array = util::ArrayOfBaseObject::create(); for (const auto &domain : l_domains) { auto extent = domain->domainOfValidity(); if (extent) { // Propagate only the extent, not the scope, as it might // imply more that we can guarantee with the promotion to // 3D. auto newDomain = common::ObjectDomain::create( util::optional(), extent); array->add(newDomain); } } if (!array->empty()) { props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, array); } } const auto &l_identifiers = identifiers(); const auto &l_remarks = remarks(); if (l_identifiers.size() == 1) { std::string remarks(PROMOTED_TO_3D_PRELUDE); remarks += *(l_identifiers[0]->codeSpace()); remarks += ':'; remarks += l_identifiers[0]->code(); if (!l_remarks.empty()) { remarks += ". "; remarks += l_remarks; } props.set(common::IdentifiedObject::REMARKS_KEY, remarks); } else if (!l_remarks.empty()) { props.set(common::IdentifiedObject::REMARKS_KEY, l_remarks); } return props; }; if (auto derivedGeogCRS = dynamic_cast(this)) { const auto &axisList = derivedGeogCRS->coordinateSystem()->axisList(); if (axisList.size() == 2) { auto cs = cs::EllipsoidalCS::create( util::PropertyMap(), axisList[0], axisList[1], verticalAxisIfNotAlreadyPresent); auto baseGeog3DCRS = util::nn_dynamic_pointer_cast( derivedGeogCRS->baseCRS()->promoteTo3D( std::string(), dbContext, verticalAxisIfNotAlreadyPresent)); return util::nn_static_pointer_cast( DerivedGeographicCRS::create( createProperties(), NN_CHECK_THROW(std::move(baseGeog3DCRS)), derivedGeogCRS->derivingConversion(), std::move(cs))); } } else if (auto derivedProjCRS = dynamic_cast(this)) { const auto &axisList = derivedProjCRS->coordinateSystem()->axisList(); if (axisList.size() == 2) { auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], axisList[1], verticalAxisIfNotAlreadyPresent); auto baseProj3DCRS = util::nn_dynamic_pointer_cast( derivedProjCRS->baseCRS()->promoteTo3D( std::string(), dbContext, verticalAxisIfNotAlreadyPresent)); return util::nn_static_pointer_cast( DerivedProjectedCRS::create( createProperties(), NN_CHECK_THROW(std::move(baseProj3DCRS)), derivedProjCRS->derivingConversion(), std::move(cs))); } } else if (auto geogCRS = dynamic_cast(this)) { const auto &axisList = geogCRS->coordinateSystem()->axisList(); if (axisList.size() == 2) { const auto &l_identifiers = identifiers(); // First check if there is a Geographic 3D CRS in the database // of the same name. // This is the common practice in the EPSG dataset. if (dbContext && l_identifiers.size() == 1) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), *(l_identifiers[0]->codeSpace())); auto res = authFactory->createObjectsFromName( nameStr(), {io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS}, false); if (!res.empty()) { const auto &firstRes = res.front(); const auto firstResGeog = dynamic_cast(firstRes.get()); const auto &firstResAxisList = firstResGeog->coordinateSystem()->axisList(); if (firstResAxisList[2]->_isEquivalentTo( verticalAxisIfNotAlreadyPresent.get(), util::IComparable::Criterion::EQUIVALENT) && geogCRS->is2DPartOf3D(NN_NO_CHECK(firstResGeog), dbContext)) { return NN_NO_CHECK( util::nn_dynamic_pointer_cast(firstRes)); } } } auto cs = cs::EllipsoidalCS::create( util::PropertyMap(), axisList[0], axisList[1], verticalAxisIfNotAlreadyPresent); return util::nn_static_pointer_cast( GeographicCRS::create(createProperties(), geogCRS->datum(), geogCRS->datumEnsemble(), std::move(cs))); } } else if (auto projCRS = dynamic_cast(this)) { const auto &axisList = projCRS->coordinateSystem()->axisList(); if (axisList.size() == 2) { auto base3DCRS = projCRS->baseCRS()->promoteTo3D(std::string(), dbContext); auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], axisList[1], verticalAxisIfNotAlreadyPresent); return util::nn_static_pointer_cast(ProjectedCRS::create( createProperties(), NN_NO_CHECK( util::nn_dynamic_pointer_cast(base3DCRS)), projCRS->derivingConversion(), std::move(cs))); } } else if (auto boundCRS = dynamic_cast(this)) { auto base3DCRS = boundCRS->baseCRS()->promoteTo3D( newName, dbContext, verticalAxisIfNotAlreadyPresent); auto transf = boundCRS->transformation(); if (!transf->getTOWGS84Parameters(false).empty()) { return BoundCRS::create( createProperties(), base3DCRS, boundCRS->hubCRS()->promoteTo3D(std::string(), dbContext), transf->promoteTo3D(std::string(), dbContext)); } else { return BoundCRS::create(base3DCRS, boundCRS->hubCRS(), std::move(transf)); } } return NN_NO_CHECK( std::static_pointer_cast(shared_from_this().as_nullable())); } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a variant of this CRS "demoted" to a 2D one, if not already * the case. * * * @param newName Name of the new CRS. If empty, nameStr() will be used. * @param dbContext Database context to look for potentially already registered * 2D CRS. May be nullptr. * @return a new CRS demoted to 2D, or the current one if already 2D or not * applicable. * @since 6.3 */ CRSNNPtr CRS::demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const { if (auto derivedGeogCRS = dynamic_cast(this)) { return derivedGeogCRS->demoteTo2D(newName, dbContext); } else if (auto derivedProjCRS = dynamic_cast(this)) { return derivedProjCRS->demoteTo2D(newName, dbContext); } else if (auto geogCRS = dynamic_cast(this)) { return geogCRS->demoteTo2D(newName, dbContext); } else if (auto projCRS = dynamic_cast(this)) { return projCRS->demoteTo2D(newName, dbContext); } else if (auto boundCRS = dynamic_cast(this)) { auto base2DCRS = boundCRS->baseCRS()->demoteTo2D(newName, dbContext); auto transf = boundCRS->transformation(); if (!transf->getTOWGS84Parameters(false).empty()) { return BoundCRS::create( base2DCRS, boundCRS->hubCRS()->demoteTo2D(std::string(), dbContext), transf->demoteTo2D(std::string(), dbContext)); } else { return BoundCRS::create(base2DCRS, boundCRS->hubCRS(), transf); } } else if (auto compoundCRS = dynamic_cast(this)) { const auto &components = compoundCRS->componentReferenceSystems(); if (components.size() >= 2) { return components[0]; } } return NN_NO_CHECK( std::static_pointer_cast(shared_from_this().as_nullable())); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> CRS::_identify(const io::AuthorityFactoryPtr &) const { return {}; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct SingleCRS::Private { datum::DatumPtr datum{}; datum::DatumEnsemblePtr datumEnsemble{}; cs::CoordinateSystemNNPtr coordinateSystem; Private(const datum::DatumPtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::CoordinateSystemNNPtr &csIn) : datum(datumIn), datumEnsemble(datumEnsembleIn), coordinateSystem(csIn) { if ((datum ? 1 : 0) + (datumEnsemble ? 1 : 0) != 1) { throw util::Exception("datum or datumEnsemble should be set"); } } }; //! @endcond // --------------------------------------------------------------------------- SingleCRS::SingleCRS(const datum::DatumPtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::CoordinateSystemNNPtr &csIn) : d(std::make_unique(datumIn, datumEnsembleIn, csIn)) {} // --------------------------------------------------------------------------- SingleCRS::SingleCRS(const SingleCRS &other) : CRS(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress SingleCRS::~SingleCRS() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the datum::Datum associated with the CRS. * * This might be null, in which case datumEnsemble() return will not be null. * * @return a Datum that might be null. */ const datum::DatumPtr &SingleCRS::datum() PROJ_PURE_DEFN { return d->datum; } // --------------------------------------------------------------------------- /** \brief Return the datum::DatumEnsemble associated with the CRS. * * This might be null, in which case datum() return will not be null. * * @return a DatumEnsemble that might be null. */ const datum::DatumEnsemblePtr &SingleCRS::datumEnsemble() PROJ_PURE_DEFN { return d->datumEnsemble; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return the real datum or a synthesized one if a datumEnsemble. */ const datum::DatumNNPtr SingleCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const { return d->datum ? NN_NO_CHECK(d->datum) : d->datumEnsemble->asDatum(dbContext); } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the cs::CoordinateSystem associated with the CRS. * * @return a CoordinateSystem. */ const cs::CoordinateSystemNNPtr &SingleCRS::coordinateSystem() PROJ_PURE_DEFN { return d->coordinateSystem; } // --------------------------------------------------------------------------- bool SingleCRS::baseIsEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherSingleCRS = dynamic_cast(other); if (otherSingleCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } // Check datum const auto &thisDatum = d->datum; const auto &otherDatum = otherSingleCRS->d->datum; const auto &thisDatumEnsemble = d->datumEnsemble; const auto &otherDatumEnsemble = otherSingleCRS->d->datumEnsemble; if (thisDatum && otherDatum) { if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion, dbContext)) { return false; } } else if (thisDatumEnsemble && otherDatumEnsemble) { if (!thisDatumEnsemble->_isEquivalentTo(otherDatumEnsemble.get(), criterion, dbContext)) { return false; } } if (criterion == util::IComparable::Criterion::STRICT) { if ((thisDatum != nullptr) ^ (otherDatum != nullptr)) { return false; } if ((thisDatumEnsemble != nullptr) ^ (otherDatumEnsemble != nullptr)) { return false; } } else { if (!datumNonNull(dbContext)->_isEquivalentTo( otherSingleCRS->datumNonNull(dbContext).get(), criterion, dbContext)) { return false; } } // Check coordinate system if (!(d->coordinateSystem->_isEquivalentTo( otherSingleCRS->d->coordinateSystem.get(), criterion, dbContext))) { return false; } // Now compare PROJ4 extensions const auto &thisProj4 = getExtensionProj4(); const auto &otherProj4 = otherSingleCRS->getExtensionProj4(); if (thisProj4.empty() && otherProj4.empty()) { return true; } if (!(thisProj4.empty() ^ otherProj4.empty())) { return true; } // Asks for a "normalized" output during toString(), aimed at comparing two // strings for equivalence. auto formatter1 = io::PROJStringFormatter::create(); formatter1->setNormalizeOutput(); formatter1->ingestPROJString(thisProj4); auto formatter2 = io::PROJStringFormatter::create(); formatter2->setNormalizeOutput(); formatter2->ingestPROJString(otherProj4); return formatter1->toString() == formatter2->toString(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void SingleCRS::exportDatumOrDatumEnsembleToWkt( io::WKTFormatter *formatter) const // throw(io::FormattingException) { const auto &l_datum = d->datum; if (l_datum) { l_datum->_exportToWKT(formatter); } else { const auto &l_datumEnsemble = d->datumEnsemble; assert(l_datumEnsemble); l_datumEnsemble->_exportToWKT(formatter); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeodeticCRS::Private { std::vector velocityModel{}; datum::GeodeticReferenceFramePtr datum_; explicit Private(const datum::GeodeticReferenceFramePtr &datumIn) : datum_(datumIn) {} }; // --------------------------------------------------------------------------- static const datum::DatumEnsemblePtr & checkEnsembleForGeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &ensemble) { const char *msg = "One of Datum or DatumEnsemble should be defined"; if (datumIn) { if (!ensemble) { return ensemble; } msg = "Datum and DatumEnsemble should not be defined"; } else if (ensemble) { const auto &datums = ensemble->datums(); assert(!datums.empty()); auto grfFirst = dynamic_cast(datums[0].get()); if (grfFirst) { return ensemble; } msg = "Ensemble should contain GeodeticReferenceFrame"; } throw util::Exception(msg); } //! @endcond // --------------------------------------------------------------------------- GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::EllipsoidalCSNNPtr &csIn) : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), d(std::make_unique(datumIn)) {} // --------------------------------------------------------------------------- GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::SphericalCSNNPtr &csIn) : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), d(std::make_unique(datumIn)) {} // --------------------------------------------------------------------------- GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::CartesianCSNNPtr &csIn) : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), d(std::make_unique(datumIn)) {} // --------------------------------------------------------------------------- GeodeticCRS::GeodeticCRS(const GeodeticCRS &other) : SingleCRS(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeodeticCRS::~GeodeticCRS() = default; //! @endcond // --------------------------------------------------------------------------- CRSNNPtr GeodeticCRS::_shallowClone() const { auto crs(GeodeticCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the datum::GeodeticReferenceFrame associated with the CRS. * * @return a GeodeticReferenceFrame or null (in which case datumEnsemble() * should return a non-null pointer.) */ const datum::GeodeticReferenceFramePtr &GeodeticCRS::datum() PROJ_PURE_DEFN { return d->datum_; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return the real datum or a synthesized one if a datumEnsemble. */ const datum::GeodeticReferenceFrameNNPtr GeodeticCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const { return NN_NO_CHECK( d->datum_ ? d->datum_ : util::nn_dynamic_pointer_cast( SingleCRS::getPrivate()->datumEnsemble->asDatum(dbContext))); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static datum::GeodeticReferenceFrame *oneDatum(const GeodeticCRS *crs) { const auto &l_datumEnsemble = crs->datumEnsemble(); assert(l_datumEnsemble); const auto &l_datums = l_datumEnsemble->datums(); return static_cast(l_datums[0].get()); } //! @endcond // --------------------------------------------------------------------------- /** \brief Return the PrimeMeridian associated with the GeodeticReferenceFrame * or with one of the GeodeticReferenceFrame of the datumEnsemble(). * * @return the PrimeMeridian. */ const datum::PrimeMeridianNNPtr &GeodeticCRS::primeMeridian() PROJ_PURE_DEFN { if (d->datum_) { return d->datum_->primeMeridian(); } return oneDatum(this)->primeMeridian(); } // --------------------------------------------------------------------------- /** \brief Return the ellipsoid associated with the GeodeticReferenceFrame * or with one of the GeodeticReferenceFrame of the datumEnsemble(). * * @return the PrimeMeridian. */ const datum::EllipsoidNNPtr &GeodeticCRS::ellipsoid() PROJ_PURE_DEFN { if (d->datum_) { return d->datum_->ellipsoid(); } return oneDatum(this)->ellipsoid(); } // --------------------------------------------------------------------------- /** \brief Return the velocity model associated with the CRS. * * @return a velocity model. might be null. */ const std::vector & GeodeticCRS::velocityModel() PROJ_PURE_DEFN { return d->velocityModel; } // --------------------------------------------------------------------------- /** \brief Return whether the CRS is a Cartesian geocentric one. * * A geocentric CRS is a geodetic CRS that has a Cartesian coordinate system * with three axis, whose direction is respectively * cs::AxisDirection::GEOCENTRIC_X, * cs::AxisDirection::GEOCENTRIC_Y and cs::AxisDirection::GEOCENTRIC_Z. * * @return true if the CRS is a geocentric CRS. */ bool GeodeticCRS::isGeocentric() PROJ_PURE_DEFN { const auto &cs = coordinateSystem(); const auto &axisList = cs->axisList(); return axisList.size() == 3 && dynamic_cast(cs.get()) != nullptr && &axisList[0]->direction() == &cs::AxisDirection::GEOCENTRIC_X && &axisList[1]->direction() == &cs::AxisDirection::GEOCENTRIC_Y && &axisList[2]->direction() == &cs::AxisDirection::GEOCENTRIC_Z; } // --------------------------------------------------------------------------- /** \brief Return whether the CRS is a Spherical planetocentric one. * * A Spherical planetocentric CRS is a geodetic CRS that has a spherical * (angular) coordinate system with 2 axis, which represent geocentric latitude/ * longitude or longitude/geocentric latitude. * * Such CRS are typically used in use case that apply to non-Earth bodies. * * @return true if the CRS is a Spherical planetocentric CRS. * * @since 8.2 */ bool GeodeticCRS::isSphericalPlanetocentric() PROJ_PURE_DEFN { const auto &cs = coordinateSystem(); const auto &axisList = cs->axisList(); return axisList.size() == 2 && dynamic_cast(cs.get()) != nullptr && ((ci_equal(axisList[0]->nameStr(), "planetocentric latitude") && ci_equal(axisList[1]->nameStr(), "planetocentric longitude")) || (ci_equal(axisList[0]->nameStr(), "planetocentric longitude") && ci_equal(axisList[1]->nameStr(), "planetocentric latitude"))); } // --------------------------------------------------------------------------- /** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame and a * cs::SphericalCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datum The datum of the CRS. * @param cs a SphericalCS. * @return new GeodeticCRS. */ GeodeticCRSNNPtr GeodeticCRS::create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, const cs::SphericalCSNNPtr &cs) { return create(properties, datum.as_nullable(), nullptr, cs); } // --------------------------------------------------------------------------- /** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame or * datum::DatumEnsemble and a cs::SphericalCS. * * One and only one of datum or datumEnsemble should be set to a non-null value. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datum The datum of the CRS, or nullptr * @param datumEnsemble The datum ensemble of the CRS, or nullptr. * @param cs a SphericalCS. * @return new GeodeticCRS. */ GeodeticCRSNNPtr GeodeticCRS::create(const util::PropertyMap &properties, const datum::GeodeticReferenceFramePtr &datum, const datum::DatumEnsemblePtr &datumEnsemble, const cs::SphericalCSNNPtr &cs) { auto crs( GeodeticCRS::nn_make_shared(datum, datumEnsemble, cs)); crs->assignSelf(crs); crs->setProperties(properties); return crs; } // --------------------------------------------------------------------------- /** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame and a * cs::CartesianCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datum The datum of the CRS. * @param cs a CartesianCS. * @return new GeodeticCRS. */ GeodeticCRSNNPtr GeodeticCRS::create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, const cs::CartesianCSNNPtr &cs) { return create(properties, datum.as_nullable(), nullptr, cs); } // --------------------------------------------------------------------------- /** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame or * datum::DatumEnsemble and a cs::CartesianCS. * * One and only one of datum or datumEnsemble should be set to a non-null value. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datum The datum of the CRS, or nullptr * @param datumEnsemble The datum ensemble of the CRS, or nullptr. * @param cs a CartesianCS * @return new GeodeticCRS. */ GeodeticCRSNNPtr GeodeticCRS::create(const util::PropertyMap &properties, const datum::GeodeticReferenceFramePtr &datum, const datum::DatumEnsemblePtr &datumEnsemble, const cs::CartesianCSNNPtr &cs) { auto crs( GeodeticCRS::nn_make_shared(datum, datumEnsemble, cs)); crs->assignSelf(crs); crs->setProperties(properties); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // Try to format a Geographic/ProjectedCRS 3D CRS as a // GEOGCS[]/PROJCS[],VERTCS[...,DATUM[],...] if we find corresponding objects static bool exportAsESRIWktCompoundCRSWithEllipsoidalHeight( const CRS *self, const GeodeticCRS *geodCRS, io::WKTFormatter *formatter) { const auto &dbContext = formatter->databaseContext(); if (!dbContext) { return false; } const auto l_datum = geodCRS->datumNonNull(formatter->databaseContext()); auto l_esri_name = dbContext->getAliasFromOfficialName( l_datum->nameStr(), "geodetic_datum", "ESRI"); if (l_esri_name.empty()) { l_esri_name = l_datum->nameStr(); } auto authFactory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); auto list = authFactory->createObjectsFromName( l_esri_name, {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false /* approximate=false*/); if (list.empty()) { return false; } auto gdatum = util::nn_dynamic_pointer_cast(list.front()); if (gdatum == nullptr || gdatum->identifiers().empty()) { return false; } const auto &gdatum_ids = gdatum->identifiers(); auto vertCRSList = authFactory->createVerticalCRSFromDatum( "ESRI", "from_geogdatum_" + *gdatum_ids[0]->codeSpace() + '_' + gdatum_ids[0]->code()); self->demoteTo2D(std::string(), dbContext)->_exportToWKT(formatter); if (vertCRSList.size() == 1) { vertCRSList.front()->_exportToWKT(formatter); } else { // This will not be recognized properly by ESRI software // See https://github.com/OSGeo/PROJ/issues/2757 const auto &axisList = geodCRS->coordinateSystem()->axisList(); assert(axisList.size() == 3U); formatter->startNode(io::WKTConstants::VERTCS, false); auto vertcs_name = std::move(l_esri_name); if (starts_with(vertcs_name.c_str(), "GCS_")) vertcs_name = vertcs_name.substr(4); formatter->addQuotedString(vertcs_name); gdatum->_exportToWKT(formatter); // Seems to be a constant value... formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("Vertical_Shift"); formatter->add(0.0); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("Direction"); formatter->add( axisList[2]->direction() == cs::AxisDirection::UP ? 1.0 : -1.0); formatter->endNode(); axisList[2]->unit()._exportToWKT(formatter); formatter->endNode(); } return true; } // --------------------------------------------------------------------------- // Try to format a Geographic/ProjectedCRS 3D CRS as a // GEOGCS[]/PROJCS[],VERTCS["Ellipsoid (metre)",DATUM["Ellipsoid",2002],...] static void exportAsWKT1CompoundCRSWithEllipsoidalHeight( const CRSNNPtr &base2DCRS, const cs::CoordinateSystemAxisNNPtr &verticalAxis, io::WKTFormatter *formatter) { std::string verticalCRSName = "Ellipsoid ("; verticalCRSName += verticalAxis->unit().name(); verticalCRSName += ')'; auto vertDatum = datum::VerticalReferenceFrame::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, "Ellipsoid") .set("VERT_DATUM_TYPE", "2002")); auto vertCRS = VerticalCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, verticalCRSName), vertDatum.as_nullable(), nullptr, cs::VerticalCS::create(util::PropertyMap(), verticalAxis)); formatter->startNode(io::WKTConstants::COMPD_CS, false); formatter->addQuotedString(base2DCRS->nameStr() + " + " + verticalCRSName); base2DCRS->_exportToWKT(formatter); vertCRS->_exportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const bool isGeographic = dynamic_cast(this) != nullptr; const auto &cs = coordinateSystem(); const auto &axisList = cs->axisList(); const bool isGeographic3D = isGeographic && axisList.size() == 3; const auto oldAxisOutputRule = formatter->outputAxis(); std::string l_name = nameStr(); const auto &dbContext = formatter->databaseContext(); const bool isESRIExport = !isWKT2 && formatter->useESRIDialect(); const auto &l_identifiers = identifiers(); if (isESRIExport && axisList.size() == 3) { if (!isGeographic) { io::FormattingException::Throw( "Geocentric CRS not supported in WKT1_ESRI"); } if (!formatter->isAllowedLINUNITNode()) { // Try to format the Geographic 3D CRS as a // GEOGCS[],VERTCS[...,DATUM[]] if we find corresponding objects if (dbContext) { if (exportAsESRIWktCompoundCRSWithEllipsoidalHeight( this, this, formatter)) { return; } } io::FormattingException::Throw( "Cannot export this Geographic 3D CRS in WKT1_ESRI"); } } if (!isWKT2 && !isESRIExport && formatter->isStrict() && isGeographic && axisList.size() == 3 && oldAxisOutputRule != io::WKTFormatter::OutputAxisRule::NO) { auto geogCRS2D = demoteTo2D(std::string(), dbContext); if (dbContext) { const auto res = geogCRS2D->identify(io::AuthorityFactory::create( NN_NO_CHECK(dbContext), metadata::Identifier::EPSG)); if (res.size() == 1) { const auto &front = res.front(); if (front.second == 100) { geogCRS2D = front.first; } } } if (CRS::getPrivate()->allowNonConformantWKT1Export_) { formatter->startNode(io::WKTConstants::COMPD_CS, false); formatter->addQuotedString(l_name + " + " + l_name); geogCRS2D->_exportToWKT(formatter); const std::vector oldTOWGSParameters( formatter->getTOWGS84Parameters()); formatter->setTOWGS84Parameters({}); geogCRS2D->_exportToWKT(formatter); formatter->setTOWGS84Parameters(oldTOWGSParameters); formatter->endNode(); return; } auto &originalCompoundCRS = CRS::getPrivate()->originalCompoundCRS_; if (originalCompoundCRS) { originalCompoundCRS->_exportToWKT(formatter); return; } if (formatter->isAllowedEllipsoidalHeightAsVerticalCRS()) { exportAsWKT1CompoundCRSWithEllipsoidalHeight(geogCRS2D, axisList[2], formatter); return; } io::FormattingException::Throw( "WKT1 does not support Geographic 3D CRS."); } formatter->startNode(isWKT2 ? ((formatter->use2019Keywords() && isGeographic) ? io::WKTConstants::GEOGCRS : io::WKTConstants::GEODCRS) : isGeocentric() ? io::WKTConstants::GEOCCS : io::WKTConstants::GEOGCS, !l_identifiers.empty()); if (isESRIExport) { std::string l_esri_name; if (l_name == "WGS 84") { l_esri_name = isGeographic3D ? "WGS_1984_3D" : "GCS_WGS_1984"; } else { if (dbContext) { const auto tableName = isGeographic3D ? "geographic_3D_crs" : "geodetic_crs"; if (!l_identifiers.empty()) { // Try to find the ESRI alias from the CRS identified by its // id const auto aliases = dbContext->getAliases(*(l_identifiers[0]->codeSpace()), l_identifiers[0]->code(), std::string(), // officialName, tableName, "ESRI"); if (aliases.size() == 1) l_esri_name = aliases.front(); } if (l_esri_name.empty()) { // Then find the ESRI alias from the CRS name l_esri_name = dbContext->getAliasFromOfficialName( l_name, tableName, "ESRI"); } if (l_esri_name.empty()) { // Then try to build an ESRI CRS from the CRS name, and if // there's one, the ESRI name is the CRS name auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "ESRI"); const bool found = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory:: ObjectType::GEODETIC_CRS}, false // approximateMatch ) .size() == 1; if (found) l_esri_name = l_name; } if (l_esri_name.empty() && !l_identifiers.empty()) { // Case for example for ETRS89-NOR [EUREF89] that has no // ESRI alias. Fallback to ETRS89 const auto EPSGOldAliases = dbContext->getAliases(*(l_identifiers[0]->codeSpace()), l_identifiers[0]->code(), std::string(), // officialName, tableName, "EPSG_OLD"); if (EPSGOldAliases.size() == 1) { auto authFactoryEPSG = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), "EPSG"); auto objCandidates = authFactoryEPSG->createObjectsFromNameEx( EPSGOldAliases.front(), {io::AuthorityFactory::ObjectType:: GEODETIC_CRS}, false, // approximateMatch 0, // limitResultCount false // useAliases ); const auto thisNature = isGeographic3D ? "geographic_3D" : isGeocentric() ? "geocentric" : "geographic_2D"; for (const auto &[obj, name] : objCandidates) { (void)name; const auto objGeodetic = dynamic_cast(obj.get()); if (objGeodetic) { const auto objGeographic = dynamic_cast( obj.get()); const auto objNature = objGeographic && objGeographic->coordinateSystem() ->axisList() .size() == 3 ? "geographic_3D" : objGeodetic->isGeocentric() ? "geocentric" : "geographic_2D"; const auto &objIdentifiers = obj->identifiers(); if (!objIdentifiers.empty() && strcmp(thisNature, objNature) == 0) { const auto ESRIAliases = dbContext->getAliases( *(objIdentifiers[0]->codeSpace()), objIdentifiers[0]->code(), std::string(), // officialName, tableName, "ESRI"); if (ESRIAliases.size() == 1) { l_esri_name = ESRIAliases.front(); break; } } } } } } } if (l_esri_name.empty()) { l_esri_name = io::WKTFormatter::morphNameToESRI(l_name); if (!starts_with(l_esri_name, "GCS_")) { l_esri_name = "GCS_" + l_esri_name; } } } const std::string &l_esri_name_ref(l_esri_name); l_name = l_esri_name_ref; } else if (!isWKT2 && isDeprecated()) { l_name += " (deprecated)"; } formatter->addQuotedString(l_name); const auto &unit = axisList[0]->unit(); formatter->pushAxisAngularUnit(common::UnitOfMeasure::create(unit)); exportDatumOrDatumEnsembleToWkt(formatter); primeMeridian()->_exportToWKT(formatter); formatter->popAxisAngularUnit(); if (!isWKT2) { unit._exportToWKT(formatter); } if (isGeographic3D && isESRIExport) { axisList[2]->unit()._exportToWKT(formatter, io::WKTConstants::LINUNIT); } if (oldAxisOutputRule == io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE && isGeocentric()) { formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); } cs->_exportToWKT(formatter); formatter->setOutputAxis(oldAxisOutputRule); ObjectUsage::baseExportToWKT(formatter); if (!isWKT2 && !isESRIExport) { const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; if (!extensionProj4.empty()) { formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4"); formatter->addQuotedString(extensionProj4); formatter->endNode(); } } formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::addGeocentricUnitConversionIntoPROJString( io::PROJStringFormatter *formatter) const { const auto &axisList = coordinateSystem()->axisList(); const auto &unit = axisList[0]->unit(); if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT)) { if (formatter->getCRSExport()) { io::FormattingException::Throw( "GeodeticCRS::exportToPROJString() only " "supports metre unit"); } formatter->addStep("unitconvert"); formatter->addParam("xy_in", "m"); formatter->addParam("z_in", "m"); { auto projUnit = unit.exportToPROJString(); if (!projUnit.empty()) { formatter->addParam("xy_out", projUnit); formatter->addParam("z_out", projUnit); return; } } const auto &toSI = unit.conversionToSI(); formatter->addParam("xy_out", toSI); formatter->addParam("z_out", toSI); } else if (formatter->getCRSExport()) { formatter->addParam("units", "m"); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::addAxisSwap(io::PROJStringFormatter *formatter) const { const auto &axisList = coordinateSystem()->axisList(); const char *order[2] = {nullptr, nullptr}; const char *one = "1"; const char *two = "2"; for (int i = 0; i < 2; i++) { const auto &dir = axisList[i]->direction(); if (&dir == &cs::AxisDirection::WEST) { order[i] = "-1"; } else if (&dir == &cs::AxisDirection::EAST) { order[i] = one; } else if (&dir == &cs::AxisDirection::SOUTH) { order[i] = "-2"; } else if (&dir == &cs::AxisDirection::NORTH) { order[i] = two; } } if (order[0] && order[1] && (order[0] != one || order[1] != two)) { formatter->addStep("axisswap"); char orderStr[10]; snprintf(orderStr, sizeof(orderStr), "%.2s,%.2s", order[0], order[1]); formatter->addParam("order", orderStr); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::addAngularUnitConvertAndAxisSwap( io::PROJStringFormatter *formatter) const { const auto &axisList = coordinateSystem()->axisList(); formatter->addStep("unitconvert"); formatter->addParam("xy_in", "rad"); if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { formatter->addParam("z_in", "m"); } { const auto &unitHoriz = axisList[0]->unit(); const auto projUnit = unitHoriz.exportToPROJString(); if (projUnit.empty()) { formatter->addParam("xy_out", unitHoriz.conversionToSI()); } else { formatter->addParam("xy_out", projUnit); } } if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { const auto &unitZ = axisList[2]->unit(); auto projVUnit = unitZ.exportToPROJString(); if (projVUnit.empty()) { formatter->addParam("z_out", unitZ.conversionToSI()); } else { formatter->addParam("z_out", projVUnit); } } addAxisSwap(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; if (!extensionProj4.empty()) { formatter->ingestPROJString( replaceAll(extensionProj4, " +type=crs", "")); formatter->addNoDefs(false); return; } if (isGeocentric()) { if (!formatter->getCRSExport()) { formatter->addStep("cart"); } else { formatter->addStep("geocent"); } addDatumInfoToPROJString(formatter); addGeocentricUnitConversionIntoPROJString(formatter); } else if (isSphericalPlanetocentric()) { if (!formatter->getCRSExport()) { if (!formatter->omitProjLongLatIfPossible() || primeMeridian()->longitude().getSIValue() != 0.0 || !ellipsoid()->isSphere() || !formatter->getTOWGS84Parameters().empty() || !formatter->getHDatumExtension().empty()) { formatter->addStep("geoc"); addDatumInfoToPROJString(formatter); } addAngularUnitConvertAndAxisSwap(formatter); } else { io::FormattingException::Throw( "GeodeticCRS::exportToPROJString() not supported on spherical " "planetocentric coordinate systems"); // The below code now works as input to PROJ, but I'm not sure we // want to propagate this, given that we got cs2cs doing conversion // in the wrong direction in past versions. /*formatter->addStep("longlat"); formatter->addParam("geoc"); addDatumInfoToPROJString(formatter);*/ } } else { io::FormattingException::Throw( "GeodeticCRS::exportToPROJString() only " "supports geocentric or spherical planetocentric " "coordinate systems"); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::addDatumInfoToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); bool datumWritten = false; const auto &nadgrids = formatter->getHDatumExtension(); const auto l_datum = datumNonNull(formatter->databaseContext()); if (formatter->getCRSExport() && TOWGS84Params.empty() && nadgrids.empty() && l_datum->nameStr() != "unknown") { if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), util::IComparable::Criterion::EQUIVALENT)) { datumWritten = true; formatter->addParam("datum", "WGS84"); } else if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6267.get(), util::IComparable::Criterion::EQUIVALENT)) { datumWritten = true; formatter->addParam("datum", "NAD27"); } else if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6269.get(), util::IComparable::Criterion::EQUIVALENT)) { datumWritten = true; if (formatter->getLegacyCRSToCRSContext()) { // We do not want datum=NAD83 to cause a useless towgs84=0,0,0 formatter->addParam("ellps", "GRS80"); } else { formatter->addParam("datum", "NAD83"); } } } if (!datumWritten) { ellipsoid()->_exportToPROJString(formatter); primeMeridian()->_exportToPROJString(formatter); } if (TOWGS84Params.size() == 7) { formatter->addParam("towgs84", TOWGS84Params); } if (!nadgrids.empty()) { formatter->addParam("nadgrids", nadgrids); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeodeticCRS::_exportToJSONInternal( io::JSONFormatter *formatter, const char *objectName) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext(objectName, !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } const auto &l_datum(datum()); if (l_datum) { writer->AddObjKey("datum"); l_datum->_exportToJSON(formatter); } else { writer->AddObjKey("datum_ensemble"); formatter->setOmitTypeInImmediateChild(); datumEnsemble()->_exportToJSON(formatter); } writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); if (const auto dynamicGRF = dynamic_cast( l_datum.get())) { const auto &deformationModel = dynamicGRF->deformationModelName(); if (deformationModel.has_value()) { writer->AddObjKey("deformation_models"); auto arrayContext(writer->MakeArrayContext(false)); auto objectContext2(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("name"); writer->Add(*deformationModel); } } ObjectUsage::baseExportToJSON(formatter); } void GeodeticCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { _exportToJSONInternal(formatter, "GeodeticCRS"); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::IComparable::Criterion getStandardCriterion(util::IComparable::Criterion criterion) { return criterion == util::IComparable::Criterion:: EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS ? util::IComparable::Criterion::EQUIVALENT : criterion; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool GeodeticCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (other == nullptr || !util::isOfExactType(*other)) { return false; } return _isEquivalentToNoTypeCheck(other, criterion, dbContext); } bool GeodeticCRS::_isEquivalentToNoTypeCheck( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { const auto standardCriterion = getStandardCriterion(criterion); // TODO test velocityModel return SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { return util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, code); } //! @endcond // --------------------------------------------------------------------------- GeodeticCRSNNPtr GeodeticCRS::createEPSG_4978() { return create( createMapNameEPSGCode("WGS 84", 4978), datum::GeodeticReferenceFrame::EPSG_6326, cs::CartesianCS::createGeocentric(common::UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool hasCodeCompatibleOfAuthorityFactory( const common::IdentifiedObject *obj, const io::AuthorityFactoryPtr &authorityFactory) { const auto &ids = obj->identifiers(); if (!ids.empty() && authorityFactory->getAuthority().empty()) { return true; } for (const auto &id : ids) { if (*(id->codeSpace()) == authorityFactory->getAuthority()) { return true; } } return false; } static bool hasCodeCompatibleOfAuthorityFactory( const metadata::IdentifierNNPtr &id, const io::AuthorityFactoryPtr &authorityFactory) { if (authorityFactory->getAuthority().empty()) { return true; } return *(id->codeSpace()) == authorityFactory->getAuthority(); } //! @endcond // --------------------------------------------------------------------------- /** \brief Identify the CRS with reference CRSs. * * The candidate CRSs are either hard-coded, or looked in the database when * authorityFactory is not null. * * Note that the implementation uses a set of heuristics to have a good * compromise of successful identifications over execution time. It might miss * legitimate matches in some circumstances. * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match: *
    *
  • 100% means that the name of the reference entry * perfectly matches the CRS name, and both are equivalent. In which case a * single result is returned. * Note: in the case of a GeographicCRS whose axis * order is implicit in the input definition (for example ESRI WKT), then axis * order is ignored for the purpose of identification. That is the CRS built * from * GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]], * PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] * will be identified to EPSG:4326, but will not pass a * isEquivalentTo(EPSG_4326, util::IComparable::Criterion::EQUIVALENT) test, * but rather isEquivalentTo(EPSG_4326, * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) *
  • *
  • 90% means that CRS are equivalent, but the names are not exactly the * same. *
  • 70% means that CRS are equivalent (equivalent datum and coordinate * system), * but the names are not equivalent.
  • *
  • 60% means that ellipsoid, prime meridian and coordinate systems are * equivalent, but the CRS and datum names do not match.
  • *
  • 25% means that the CRS are not equivalent, but there is some similarity * in * the names.
  • *
* * @param authorityFactory Authority factory (or null, but degraded * functionality) * @return a list of matching reference CRS, and the percentage (0-100) of * confidence in the match. */ std::list> GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; const auto &thisName(nameStr()); io::DatabaseContextPtr dbContext = authorityFactory ? authorityFactory->databaseContext().as_nullable() : nullptr; const bool l_implicitCS = hasImplicitCS(); const auto crsCriterion = l_implicitCS ? util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS : util::IComparable::Criterion::EQUIVALENT; if (authorityFactory == nullptr || authorityFactory->getAuthority().empty() || authorityFactory->getAuthority() == metadata::Identifier::EPSG) { const GeographicCRSNNPtr candidatesCRS[] = {GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4267, GeographicCRS::EPSG_4269}; for (const auto &crs : candidatesCRS) { const bool nameEquivalent = metadata::Identifier::isEquivalentName( thisName.c_str(), crs->nameStr().c_str()); const bool nameEqual = thisName == crs->nameStr(); const bool isEq = _isEquivalentTo(crs.get(), crsCriterion, dbContext); if (nameEquivalent && isEq && (!authorityFactory || nameEqual)) { res.emplace_back(util::nn_static_pointer_cast(crs), nameEqual ? 100 : 90); return res; } else if (nameEqual && !isEq && !authorityFactory) { res.emplace_back(util::nn_static_pointer_cast(crs), 25); return res; } else if (isEq && !authorityFactory) { res.emplace_back(util::nn_static_pointer_cast(crs), 70); return res; } } } std::string geodetic_crs_type; if (isGeocentric()) { geodetic_crs_type = "geocentric"; } else { auto geogCRS = dynamic_cast(this); if (geogCRS) { if (coordinateSystem()->axisList().size() == 2) { geodetic_crs_type = "geographic 2D"; } else { geodetic_crs_type = "geographic 3D"; } } } if (authorityFactory) { const auto thisDatum(datumNonNull(dbContext)); auto searchByDatumCode = [this, &authorityFactory, &res, &geodetic_crs_type, crsCriterion, &dbContext](const common::IdentifiedObjectNNPtr &l_datum) { bool resModified = false; for (const auto &id : l_datum->identifiers()) { try { auto tempRes = authorityFactory->createGeodeticCRSFromDatum( *id->codeSpace(), id->code(), geodetic_crs_type); for (const auto &crs : tempRes) { if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) { res.emplace_back(crs, 70); resModified = true; } } } catch (const std::exception &) { } } return resModified; }; auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum, &geodetic_crs_type, l_implicitCS, &dbContext]() { const auto &thisEllipsoid = thisDatum->ellipsoid(); const std::list ellipsoids( thisEllipsoid->identifiers().empty() ? authorityFactory->createEllipsoidFromExisting( thisEllipsoid) : std::list{thisEllipsoid}); for (const auto &ellps : ellipsoids) { for (const auto &id : ellps->identifiers()) { try { auto tempRes = authorityFactory->createGeodeticCRSFromEllipsoid( *id->codeSpace(), id->code(), geodetic_crs_type); for (const auto &crs : tempRes) { const auto crsDatum(crs->datumNonNull(dbContext)); if (crsDatum->ellipsoid()->_isEquivalentTo( ellps.get(), util::IComparable::Criterion::EQUIVALENT, dbContext) && crsDatum->primeMeridian()->_isEquivalentTo( thisDatum->primeMeridian().get(), util::IComparable::Criterion::EQUIVALENT, dbContext) && (l_implicitCS || coordinateSystem()->_isEquivalentTo( crs->coordinateSystem().get(), util::IComparable::Criterion::EQUIVALENT, dbContext))) { res.emplace_back(crs, 60); } } } catch (const std::exception &) { } } } }; const auto searchByDatumOrEllipsoid = [&authorityFactory, &thisDatum, searchByDatumCode, searchByEllipsoid]() { if (!thisDatum->identifiers().empty()) { searchByDatumCode(thisDatum); } else { auto candidateDatums = authorityFactory->createObjectsFromName( thisDatum->nameStr(), {io::AuthorityFactory::ObjectType:: GEODETIC_REFERENCE_FRAME}, false); bool resModified = false; for (const auto &candidateDatum : candidateDatums) { if (searchByDatumCode(candidateDatum)) resModified = true; } if (!resModified) { searchByEllipsoid(); } } }; const bool insignificantName = thisName.empty() || ci_equal(thisName, "unknown") || ci_equal(thisName, "unnamed"); if (insignificantName) { searchByDatumOrEllipsoid(); } else if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) { // If the CRS has already an id, check in the database for the // official object, and verify that they are equivalent. for (const auto &id : identifiers()) { if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) { try { auto crs = io::AuthorityFactory::create( authorityFactory->databaseContext(), *id->codeSpace()) ->createGeodeticCRS(id->code()); bool match = _isEquivalentTo(crs.get(), crsCriterion, dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { } } } } else { bool gotAbove25Pct = false; for (int ipass = 0; ipass < 2; ipass++) { const bool approximateMatch = ipass == 1; auto objects = authorityFactory->createObjectsFromName( thisName, {io::AuthorityFactory::ObjectType::GEODETIC_CRS}, approximateMatch); for (const auto &obj : objects) { auto crs = util::nn_dynamic_pointer_cast(obj); assert(crs); auto crsNN = NN_NO_CHECK(crs); if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); return res; } const bool eqName = metadata::Identifier::isEquivalentName( thisName.c_str(), crs->nameStr().c_str()); res.emplace_back(crsNN, eqName ? 90 : 70); gotAbove25Pct = true; } else { res.emplace_back(crsNN, 25); } } if (!res.empty()) { break; } } if (!gotAbove25Pct) { searchByDatumOrEllipsoid(); } } const auto &thisCS(coordinateSystem()); // Sort results res.sort([&thisName, &thisDatum, &thisCS, &dbContext](const Pair &a, const Pair &b) { // First consider confidence if (a.second > b.second) { return true; } if (a.second < b.second) { return false; } // Then consider exact name matching const auto &aName(a.first->nameStr()); const auto &bName(b.first->nameStr()); if (aName == thisName && bName != thisName) { return true; } if (bName == thisName && aName != thisName) { return false; } // Then datum matching const auto aDatum(a.first->datumNonNull(dbContext)); const auto bDatum(b.first->datumNonNull(dbContext)); const auto thisEquivADatum(thisDatum->_isEquivalentTo( aDatum.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)); const auto thisEquivBDatum(thisDatum->_isEquivalentTo( bDatum.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)); if (thisEquivADatum && !thisEquivBDatum) { return true; } if (!thisEquivADatum && thisEquivBDatum) { return false; } // Then coordinate system matching const auto &aCS(a.first->coordinateSystem()); const auto &bCS(b.first->coordinateSystem()); const auto thisEquivACs(thisCS->_isEquivalentTo( aCS.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)); const auto thisEquivBCs(thisCS->_isEquivalentTo( bCS.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)); if (thisEquivACs && !thisEquivBCs) { return true; } if (!thisEquivACs && thisEquivBCs) { return false; } // Then dimension of the coordinate system matching const auto thisCSAxisListSize = thisCS->axisList().size(); const auto aCSAxistListSize = aCS->axisList().size(); const auto bCSAxistListSize = bCS->axisList().size(); if (thisCSAxisListSize == aCSAxistListSize && thisCSAxisListSize != bCSAxistListSize) { return true; } if (thisCSAxisListSize != aCSAxistListSize && thisCSAxisListSize == bCSAxistListSize) { return false; } // Favor the CRS whole ellipsoid names matches the ellipsoid // name (WGS84...) const bool aEllpsNameEqCRSName = metadata::Identifier::isEquivalentName( aDatum->ellipsoid()->nameStr().c_str(), a.first->nameStr().c_str()); const bool bEllpsNameEqCRSName = metadata::Identifier::isEquivalentName( bDatum->ellipsoid()->nameStr().c_str(), b.first->nameStr().c_str()); if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) { return true; } if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) { return false; } // Arbitrary final sorting criterion return aName < bName; }); // If there are results with 90% confidence, only keep those if (res.size() >= 2 && res.front().second == 90) { std::list newRes; for (const auto &pair : res) { if (pair.second == 90) { newRes.push_back(pair); } else { break; } } return newRes; } } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> GeodeticCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; auto resTemp = identify(authorityFactory); for (const auto &pair : resTemp) { res.emplace_back(pair.first, pair.second); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct GeographicCRS::Private { cs::EllipsoidalCSNNPtr coordinateSystem_; explicit Private(const cs::EllipsoidalCSNNPtr &csIn) : coordinateSystem_(csIn) {} }; //! @endcond // --------------------------------------------------------------------------- GeographicCRS::GeographicCRS(const datum::GeodeticReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::EllipsoidalCSNNPtr &csIn) : SingleCRS(datumIn, datumEnsembleIn, csIn), GeodeticCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), d(std::make_unique(csIn)) {} // --------------------------------------------------------------------------- GeographicCRS::GeographicCRS(const GeographicCRS &other) : SingleCRS(other), GeodeticCRS(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress GeographicCRS::~GeographicCRS() = default; //! @endcond // --------------------------------------------------------------------------- CRSNNPtr GeographicCRS::_shallowClone() const { auto crs(GeographicCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the cs::EllipsoidalCS associated with the CRS. * * @return a EllipsoidalCS. */ const cs::EllipsoidalCSNNPtr &GeographicCRS::coordinateSystem() PROJ_PURE_DEFN { return d->coordinateSystem_; } // --------------------------------------------------------------------------- /** \brief Instantiate a GeographicCRS from a datum::GeodeticReferenceFrameNNPtr * and a * cs::EllipsoidalCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datum The datum of the CRS. * @param cs a EllipsoidalCS. * @return new GeographicCRS. */ GeographicCRSNNPtr GeographicCRS::create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, const cs::EllipsoidalCSNNPtr &cs) { return create(properties, datum.as_nullable(), nullptr, cs); } // --------------------------------------------------------------------------- /** \brief Instantiate a GeographicCRS from a datum::GeodeticReferenceFramePtr * or * datum::DatumEnsemble and a * cs::EllipsoidalCS. * * One and only one of datum or datumEnsemble should be set to a non-null value. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datum The datum of the CRS, or nullptr * @param datumEnsemble The datum ensemble of the CRS, or nullptr. * @param cs a EllipsoidalCS. * @return new GeographicCRS. */ GeographicCRSNNPtr GeographicCRS::create(const util::PropertyMap &properties, const datum::GeodeticReferenceFramePtr &datum, const datum::DatumEnsemblePtr &datumEnsemble, const cs::EllipsoidalCSNNPtr &cs) { GeographicCRSNNPtr crs( GeographicCRS::nn_make_shared(datum, datumEnsemble, cs)); crs->assignSelf(crs); crs->setProperties(properties); crs->CRS::getPrivate()->setNonStandardProperties(properties); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return whether the current GeographicCRS is the 2D part of the * other 3D GeographicCRS. */ bool GeographicCRS::is2DPartOf3D(util::nn other, const io::DatabaseContextPtr &dbContext) PROJ_PURE_DEFN { const auto &axis = d->coordinateSystem_->axisList(); const auto &otherAxis = other->d->coordinateSystem_->axisList(); if (!(axis.size() == 2 && otherAxis.size() == 3)) { return false; } const auto &firstAxis = axis[0]; const auto &secondAxis = axis[1]; const auto &otherFirstAxis = otherAxis[0]; const auto &otherSecondAxis = otherAxis[1]; if (!(firstAxis->_isEquivalentTo( otherFirstAxis.get(), util::IComparable::Criterion::EQUIVALENT) && secondAxis->_isEquivalentTo( otherSecondAxis.get(), util::IComparable::Criterion::EQUIVALENT))) { return false; } try { const auto thisDatum = datumNonNull(dbContext); const auto otherDatum = other->datumNonNull(dbContext); return thisDatum->_isEquivalentTo( otherDatum.get(), util::IComparable::Criterion::EQUIVALENT); } catch (const util::InvalidValueTypeException &) { // should not happen really, but potentially thrown by // Identifier::Private::setProperties() assert(false); return false; } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool GeographicCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { if (other == nullptr || !util::isOfExactType(*other)) { return false; } const auto standardCriterion = getStandardCriterion(criterion); const auto otherGeogCRS = dynamic_cast(other); if (GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion, dbContext)) { // Make sure GeoPackage "Undefined geographic SRS" != EPSG:4326 if ((nameStr() == "Undefined geographic SRS" || otherGeogCRS->nameStr() == "Undefined geographic SRS") && otherGeogCRS->nameStr() != nameStr()) { return false; } return true; } if (criterion != util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) { return false; } const auto axisOrder = coordinateSystem()->axisOrder(); if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { const auto &unit = coordinateSystem()->axisList()[0]->unit(); return GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, nameStr()), datum(), datumEnsemble(), axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ? cs::EllipsoidalCS::createLatitudeLongitude(unit) : cs::EllipsoidalCS::createLongitudeLatitude(unit)) ->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion, dbContext); } if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP || axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP) { const auto &angularUnit = coordinateSystem()->axisList()[0]->unit(); const auto &linearUnit = coordinateSystem()->axisList()[2]->unit(); return GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, nameStr()), datum(), datumEnsemble(), axisOrder == cs::EllipsoidalCS::AxisOrder:: LONG_EAST_LAT_NORTH_HEIGHT_UP ? cs::EllipsoidalCS:: createLatitudeLongitudeEllipsoidalHeight( angularUnit, linearUnit) : cs::EllipsoidalCS:: createLongitudeLatitudeEllipsoidalHeight( angularUnit, linearUnit)) ->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion, dbContext); } return false; } //! @endcond // --------------------------------------------------------------------------- GeographicCRSNNPtr GeographicCRS::createEPSG_4267() { return create(createMapNameEPSGCode("NAD27", 4267), datum::GeodeticReferenceFrame::EPSG_6267, cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- GeographicCRSNNPtr GeographicCRS::createEPSG_4269() { return create(createMapNameEPSGCode("NAD83", 4269), datum::GeodeticReferenceFrame::EPSG_6269, cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- GeographicCRSNNPtr GeographicCRS::createEPSG_4326() { return create(createMapNameEPSGCode("WGS 84", 4326), datum::GeodeticReferenceFrame::EPSG_6326, cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- GeographicCRSNNPtr GeographicCRS::createOGC_CRS84() { util::PropertyMap propertiesCRS; propertiesCRS .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::OGC) .set(metadata::Identifier::CODE_KEY, "CRS84") .set(common::IdentifiedObject::NAME_KEY, "WGS 84 (CRS84)"); return create(propertiesCRS, datum::GeodeticReferenceFrame::EPSG_6326, cs::EllipsoidalCS::createLongitudeLatitude( // Long Lat ! common::UnitOfMeasure::DEGREE)); } // --------------------------------------------------------------------------- GeographicCRSNNPtr GeographicCRS::createEPSG_4979() { return create( createMapNameEPSGCode("WGS 84", 4979), datum::GeodeticReferenceFrame::EPSG_6326, cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE)); } // --------------------------------------------------------------------------- GeographicCRSNNPtr GeographicCRS::createEPSG_4807() { auto ellps(datum::Ellipsoid::createFlattenedSphere( createMapNameEPSGCode("Clarke 1880 (IGN)", 7011), common::Length(6378249.2), common::Scale(293.4660212936269))); auto cs(cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::GRAD)); auto datum(datum::GeodeticReferenceFrame::create( createMapNameEPSGCode("Nouvelle Triangulation Francaise (Paris)", 6807), ellps, util::optional(), datum::PrimeMeridian::PARIS)); return create(createMapNameEPSGCode("NTF (Paris)", 4807), datum, cs); } // --------------------------------------------------------------------------- /** \brief Return a variant of this CRS "demoted" to a 2D one, if not already * the case. * * * @param newName Name of the new CRS. If empty, nameStr() will be used. * @param dbContext Database context to look for potentially already registered * 2D CRS. May be nullptr. * @return a new CRS demoted to 2D, or the current one if already 2D or not * applicable. * @since 6.3 */ GeographicCRSNNPtr GeographicCRS::demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const { const auto &axisList = coordinateSystem()->axisList(); if (axisList.size() == 3) { const auto &l_identifiers = identifiers(); // First check if there is a Geographic 2D CRS in the database // of the same name. // This is the common practice in the EPSG dataset. if (dbContext && l_identifiers.size() == 1) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), *(l_identifiers[0]->codeSpace())); auto res = authFactory->createObjectsFromName( nameStr(), {io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS}, false); if (!res.empty()) { const auto &firstRes = res.front(); auto firstResAsGeogCRS = util::nn_dynamic_pointer_cast(firstRes); if (firstResAsGeogCRS && firstResAsGeogCRS->is2DPartOf3D( NN_NO_CHECK(this), dbContext)) { return NN_NO_CHECK(firstResAsGeogCRS); } } } auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), axisList[0], axisList[1]); return GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, !newName.empty() ? newName : nameStr()), datum(), datumEnsemble(), cs); } return NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeographicCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; if (!extensionProj4.empty()) { formatter->ingestPROJString( replaceAll(extensionProj4, " +type=crs", "")); formatter->addNoDefs(false); return; } if (!formatter->omitProjLongLatIfPossible() || primeMeridian()->longitude().getSIValue() != 0.0 || !formatter->getTOWGS84Parameters().empty() || !formatter->getHDatumExtension().empty()) { formatter->addStep("longlat"); bool done = false; if (formatter->getLegacyCRSToCRSContext() && formatter->getHDatumExtension().empty() && formatter->getTOWGS84Parameters().empty()) { const auto l_datum = datumNonNull(formatter->databaseContext()); if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), util::IComparable::Criterion::EQUIVALENT)) { done = true; formatter->addParam("ellps", "WGS84"); } else if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6269.get(), util::IComparable::Criterion::EQUIVALENT)) { done = true; // We do not want datum=NAD83 to cause a useless towgs84=0,0,0 formatter->addParam("ellps", "GRS80"); } } if (!done) { addDatumInfoToPROJString(formatter); } } if (!formatter->getCRSExport()) { addAngularUnitConvertAndAxisSwap(formatter); } if (hasOver()) { formatter->addParam("over"); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void GeographicCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { _exportToJSONInternal(formatter, "GeographicCRS"); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct VerticalCRS::Private { std::vector geoidModel{}; std::vector velocityModel{}; }; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const datum::DatumEnsemblePtr & checkEnsembleForVerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &ensemble) { const char *msg = "One of Datum or DatumEnsemble should be defined"; if (datumIn) { if (!ensemble) { return ensemble; } msg = "Datum and DatumEnsemble should not be defined"; } else if (ensemble) { const auto &datums = ensemble->datums(); assert(!datums.empty()); auto grfFirst = dynamic_cast(datums[0].get()); if (grfFirst) { return ensemble; } msg = "Ensemble should contain VerticalReferenceFrame"; } throw util::Exception(msg); } //! @endcond // --------------------------------------------------------------------------- VerticalCRS::VerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::VerticalCSNNPtr &csIn) : SingleCRS(datumIn, checkEnsembleForVerticalCRS(datumIn, datumEnsembleIn), csIn), d(std::make_unique()) {} // --------------------------------------------------------------------------- VerticalCRS::VerticalCRS(const VerticalCRS &other) : SingleCRS(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress VerticalCRS::~VerticalCRS() = default; //! @endcond // --------------------------------------------------------------------------- CRSNNPtr VerticalCRS::_shallowClone() const { auto crs(VerticalCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the datum::VerticalReferenceFrame associated with the CRS. * * @return a VerticalReferenceFrame. */ const datum::VerticalReferenceFramePtr VerticalCRS::datum() const { return std::static_pointer_cast( SingleCRS::getPrivate()->datum); } // --------------------------------------------------------------------------- /** \brief Return the geoid model associated with the CRS. * * Geoid height model or height correction model linked to a geoid-based * vertical CRS. * * @return a geoid model. might be null */ const std::vector & VerticalCRS::geoidModel() PROJ_PURE_DEFN { return d->geoidModel; } // --------------------------------------------------------------------------- /** \brief Return the velocity model associated with the CRS. * * @return a velocity model. might be null. */ const std::vector & VerticalCRS::velocityModel() PROJ_PURE_DEFN { return d->velocityModel; } // --------------------------------------------------------------------------- /** \brief Return the cs::VerticalCS associated with the CRS. * * @return a VerticalCS. */ const cs::VerticalCSNNPtr VerticalCRS::coordinateSystem() const { return util::nn_static_pointer_cast( SingleCRS::getPrivate()->coordinateSystem); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return the real datum or a synthesized one if a datumEnsemble. */ const datum::VerticalReferenceFrameNNPtr VerticalCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const { return NN_NO_CHECK( util::nn_dynamic_pointer_cast( SingleCRS::datumNonNull(dbContext))); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::VERTCRS : formatter->useESRIDialect() ? io::WKTConstants::VERTCS : io::WKTConstants::VERT_CS, !identifiers().empty()); std::string l_name(nameStr()); const auto &dbContext = formatter->databaseContext(); if (formatter->useESRIDialect()) { bool aliasFound = false; if (dbContext) { auto l_alias = dbContext->getAliasFromOfficialName( l_name, "vertical_crs", "ESRI"); if (!l_alias.empty()) { l_name = std::move(l_alias); aliasFound = true; } } if (!aliasFound && dbContext) { auto authFactory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI"); aliasFound = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, false // approximateMatch ) .size() == 1; } if (!aliasFound) { l_name = io::WKTFormatter::morphNameToESRI(l_name); } } formatter->addQuotedString(l_name); const auto l_datum = datum(); if (formatter->useESRIDialect() && l_datum && l_datum->getWKT1DatumType() == "2002") { bool foundMatch = false; if (dbContext) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string()); auto list = authFactory->createObjectsFromName( l_datum->nameStr(), {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false /* approximate=false*/); if (!list.empty()) { auto gdatum = util::nn_dynamic_pointer_cast(list.front()); if (gdatum) { gdatum->_exportToWKT(formatter); foundMatch = true; } } } if (!foundMatch) { // We should export a geodetic datum, but we cannot really do better l_datum->_exportToWKT(formatter); } } else { exportDatumOrDatumEnsembleToWkt(formatter); } const auto &cs = SingleCRS::getPrivate()->coordinateSystem; const auto &axisList = cs->axisList(); if (formatter->useESRIDialect()) { // Seems to be a constant value... formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("Vertical_Shift"); formatter->add(0.0); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("Direction"); formatter->add( axisList[0]->direction() == cs::AxisDirection::UP ? 1.0 : -1.0); formatter->endNode(); } if (!isWKT2) { axisList[0]->unit()._exportToWKT(formatter); } const auto oldAxisOutputRule = formatter->outputAxis(); if (oldAxisOutputRule == io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) { formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); } cs->_exportToWKT(formatter); formatter->setOutputAxis(oldAxisOutputRule); if (isWKT2 && formatter->use2019Keywords() && !d->geoidModel.empty()) { for (const auto &model : d->geoidModel) { formatter->startNode(io::WKTConstants::GEOIDMODEL, false); formatter->addQuotedString(model->nameStr()); model->formatID(formatter); formatter->endNode(); } } ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void VerticalCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &geoidgrids = formatter->getVDatumExtension(); if (!geoidgrids.empty()) { formatter->addParam("geoidgrids", geoidgrids); } const auto &geoidCRS = formatter->getGeoidCRSValue(); if (!geoidCRS.empty()) { formatter->addParam("geoid_crs", geoidCRS); } auto &axisList = coordinateSystem()->axisList(); if (!axisList.empty()) { auto projUnit = axisList[0]->unit().exportToPROJString(); if (projUnit.empty()) { formatter->addParam("vto_meter", axisList[0]->unit().conversionToSI()); } else { formatter->addParam("vunits", projUnit); } } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void VerticalCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("VerticalCRS", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } const auto &l_datum(datum()); if (l_datum) { writer->AddObjKey("datum"); l_datum->_exportToJSON(formatter); } else { writer->AddObjKey("datum_ensemble"); formatter->setOmitTypeInImmediateChild(); datumEnsemble()->_exportToJSON(formatter); } writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); const auto geoidModelExport = [&writer, &formatter](const operation::TransformationNNPtr &model) { auto objectContext2(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("name"); writer->Add(model->nameStr()); if (model->identifiers().empty()) { const auto &interpCRS = model->interpolationCRS(); if (interpCRS) { writer->AddObjKey("interpolation_crs"); interpCRS->_exportToJSON(formatter); } } model->formatID(formatter); }; if (d->geoidModel.size() == 1) { writer->AddObjKey("geoid_model"); geoidModelExport(d->geoidModel[0]); } else if (d->geoidModel.size() > 1) { writer->AddObjKey("geoid_models"); auto geoidModelsArrayContext(writer->MakeArrayContext(false)); for (const auto &model : d->geoidModel) { geoidModelExport(model); } } if (const auto dynamicVRF = dynamic_cast( l_datum.get())) { const auto &deformationModel = dynamicVRF->deformationModelName(); if (deformationModel.has_value()) { writer->AddObjKey("deformation_models"); auto arrayContext(writer->MakeArrayContext(false)); auto objectContext2(formatter->MakeObjectContext(nullptr, false)); writer->AddObjKey("name"); writer->Add(*deformationModel); } } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void VerticalCRS::addLinearUnitConvert( io::PROJStringFormatter *formatter) const { auto &axisList = coordinateSystem()->axisList(); if (!axisList.empty()) { if (axisList[0]->unit().conversionToSI() != 1.0) { formatter->addStep("unitconvert"); formatter->addParam("z_in", "m"); auto projVUnit = axisList[0]->unit().exportToPROJString(); if (projVUnit.empty()) { formatter->addParam("z_out", axisList[0]->unit().conversionToSI()); } else { formatter->addParam("z_out", projVUnit); } } } } //! @endcond // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalCRS from a datum::VerticalReferenceFrame and a * cs::VerticalCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. The GEOID_MODEL property can be set * to a TransformationNNPtr object. * @param datumIn The datum of the CRS. * @param csIn a VerticalCS. * @return new VerticalCRS. */ VerticalCRSNNPtr VerticalCRS::create(const util::PropertyMap &properties, const datum::VerticalReferenceFrameNNPtr &datumIn, const cs::VerticalCSNNPtr &csIn) { return create(properties, datumIn.as_nullable(), nullptr, csIn); } // --------------------------------------------------------------------------- /** \brief Instantiate a VerticalCRS from a datum::VerticalReferenceFrame or * datum::DatumEnsemble and a cs::VerticalCS. * * One and only one of datum or datumEnsemble should be set to a non-null value. * * @param properties See \ref general_properties. * At minimum the name should be defined. The GEOID_MODEL property can be set * to a TransformationNNPtr object. * @param datumIn The datum of the CRS, or nullptr * @param datumEnsembleIn The datum ensemble of the CRS, or nullptr. * @param csIn a VerticalCS. * @return new VerticalCRS. */ VerticalCRSNNPtr VerticalCRS::create(const util::PropertyMap &properties, const datum::VerticalReferenceFramePtr &datumIn, const datum::DatumEnsemblePtr &datumEnsembleIn, const cs::VerticalCSNNPtr &csIn) { auto crs(VerticalCRS::nn_make_shared(datumIn, datumEnsembleIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); const auto geoidModelPtr = properties.get("GEOID_MODEL"); if (geoidModelPtr) { if (auto array = util::nn_dynamic_pointer_cast( *geoidModelPtr)) { for (const auto &item : *array) { auto transf = util::nn_dynamic_pointer_cast( item); if (transf) { crs->d->geoidModel.emplace_back(NN_NO_CHECK(transf)); } } } else if (auto transf = util::nn_dynamic_pointer_cast( *geoidModelPtr)) { crs->d->geoidModel.emplace_back(NN_NO_CHECK(transf)); } } return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress bool VerticalCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherVertCRS = dynamic_cast(other); if (otherVertCRS == nullptr || !util::isOfExactType(*otherVertCRS)) { return false; } // TODO test geoidModel and velocityModel return SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- /** \brief Identify the CRS with reference CRSs. * * The candidate CRSs are looked in the database when * authorityFactory is not null. * * Note that the implementation uses a set of heuristics to have a good * compromise of successful identifications over execution time. It might miss * legitimate matches in some circumstances. * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. * 100% means that the name of the reference entry * perfectly matches the CRS name, and both are equivalent. In which case a * single result is returned. * 90% means that CRS are equivalent, but the names are not exactly the same. * 70% means that CRS are equivalent (equivalent datum and coordinate system), * but the names are not equivalent. * 25% means that the CRS are not equivalent, but there is some similarity in * the names. * * @param authorityFactory Authority factory (if null, will return an empty * list) * @return a list of matching reference CRS, and the percentage (0-100) of * confidence in the match. */ std::list> VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; const auto &thisName(nameStr()); if (authorityFactory) { const io::DatabaseContextNNPtr &dbContext = authorityFactory->databaseContext(); const bool insignificantName = thisName.empty() || ci_equal(thisName, "unknown") || ci_equal(thisName, "unnamed"); if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) { // If the CRS has already an id, check in the database for the // official object, and verify that they are equivalent. for (const auto &id : identifiers()) { if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) { try { auto crs = io::AuthorityFactory::create( dbContext, *id->codeSpace()) ->createVerticalCRS(id->code()); bool match = _isEquivalentTo( crs.get(), util::IComparable::Criterion::EQUIVALENT, dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { } } } } else if (!insignificantName) { for (int ipass = 0; ipass < 2; ipass++) { const bool approximateMatch = ipass == 1; auto objects = authorityFactory->createObjectsFromName( thisName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, approximateMatch); for (const auto &obj : objects) { auto crs = util::nn_dynamic_pointer_cast(obj); assert(crs); auto crsNN = NN_NO_CHECK(crs); if (_isEquivalentTo( crs.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); return res; } res.emplace_back(crsNN, 90); } else { res.emplace_back(crsNN, 25); } } if (!res.empty()) { break; } } } // Sort results res.sort([&thisName](const Pair &a, const Pair &b) { // First consider confidence if (a.second > b.second) { return true; } if (a.second < b.second) { return false; } // Then consider exact name matching const auto &aName(a.first->nameStr()); const auto &bName(b.first->nameStr()); if (aName == thisName && bName != thisName) { return true; } if (bName == thisName && aName != thisName) { return false; } // Arbitrary final sorting criterion return aName < bName; }); // Keep only results of the highest confidence if (res.size() >= 2) { const auto highestConfidence = res.front().second; std::list newRes; for (const auto &pair : res) { if (pair.second == highestConfidence) { newRes.push_back(pair); } else { break; } } return newRes; } } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> VerticalCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; auto resTemp = identify(authorityFactory); for (const auto &pair : resTemp) { res.emplace_back(pair.first, pair.second); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DerivedCRS::Private { SingleCRSNNPtr baseCRS_; operation::ConversionNNPtr derivingConversion_; Private(const SingleCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn) : baseCRS_(baseCRSIn), derivingConversion_(derivingConversionIn) {} // For the conversion make a _shallowClone(), so that we can later set // its targetCRS to this. Private(const Private &other) : baseCRS_(other.baseCRS_), derivingConversion_(other.derivingConversion_->shallowClone()) {} }; //! @endcond // --------------------------------------------------------------------------- // DerivedCRS is an abstract class, that virtually inherits from SingleCRS // Consequently the base constructor in SingleCRS will never be called by // that constructor. clang -Wabstract-vbase-init and VC++ underline this, but // other // compilers will complain if we don't call the base constructor. DerivedCRS::DerivedCRS(const SingleCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr & #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) cs #endif ) : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), cs), #endif d(std::make_unique(baseCRSIn, derivingConversionIn)) { } // --------------------------------------------------------------------------- DerivedCRS::DerivedCRS(const DerivedCRS &other) : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) SingleCRS(other), #endif d(std::make_unique(*other.d)) { } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DerivedCRS::~DerivedCRS() = default; //! @endcond // --------------------------------------------------------------------------- /** \brief Return the base CRS of a DerivedCRS. * * @return the base CRS. */ const SingleCRSNNPtr &DerivedCRS::baseCRS() PROJ_PURE_DEFN { return d->baseCRS_; } // --------------------------------------------------------------------------- /** \brief Return the deriving conversion from the base CRS to this CRS. * * @return the deriving conversion. */ const operation::ConversionNNPtr DerivedCRS::derivingConversion() const { return d->derivingConversion_->shallowClone(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress const operation::ConversionNNPtr & DerivedCRS::derivingConversionRef() PROJ_PURE_DEFN { return d->derivingConversion_; } //! @endcond // --------------------------------------------------------------------------- bool DerivedCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast(other); const auto standardCriterion = getStandardCriterion(criterion); if (otherDerivedCRS == nullptr || !SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext)) { return false; } return d->baseCRS_->_isEquivalentTo(otherDerivedCRS->d->baseCRS_.get(), criterion, dbContext) && d->derivingConversion_->_isEquivalentTo( otherDerivedCRS->d->derivingConversion_.get(), standardCriterion, dbContext); } // --------------------------------------------------------------------------- void DerivedCRS::setDerivingConversionCRS() { derivingConversionRef()->setWeakSourceTargetCRS( baseCRS().as_nullable(), std::static_pointer_cast(shared_from_this().as_nullable())); } // --------------------------------------------------------------------------- void DerivedCRS::baseExportToWKT(io::WKTFormatter *formatter, const std::string &keyword, const std::string &baseKeyword) const { formatter->startNode(keyword, !identifiers().empty()); formatter->addQuotedString(nameStr()); const auto &l_baseCRS = d->baseCRS_; formatter->startNode(baseKeyword, formatter->use2019Keywords() && !l_baseCRS->identifiers().empty()); formatter->addQuotedString(l_baseCRS->nameStr()); l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); if (formatter->use2019Keywords() && !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) { l_baseCRS->formatID(formatter); } formatter->endNode(); formatter->setUseDerivingConversion(true); derivingConversionRef()->_exportToWKT(formatter); formatter->setUseDerivingConversion(false); coordinateSystem()->_exportToWKT(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DerivedCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext(className(), !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("base_crs"); baseCRS()->_exportToJSON(formatter); writer->AddObjKey("conversion"); formatter->setOmitTypeInImmediateChild(); derivingConversionRef()->_exportToJSON(formatter); writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ProjectedCRS::Private { GeodeticCRSNNPtr baseCRS_; cs::CartesianCSNNPtr cs_; Private(const GeodeticCRSNNPtr &baseCRSIn, const cs::CartesianCSNNPtr &csIn) : baseCRS_(baseCRSIn), cs_(csIn) {} inline const GeodeticCRSNNPtr &baseCRS() const { return baseCRS_; } inline const cs::CartesianCSNNPtr &coordinateSystem() const { return cs_; } }; //! @endcond // --------------------------------------------------------------------------- ProjectedCRS::ProjectedCRS( const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn) : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(std::make_unique(baseCRSIn, csIn)) {} // --------------------------------------------------------------------------- ProjectedCRS::ProjectedCRS(const ProjectedCRS &other) : SingleCRS(other), DerivedCRS(other), d(std::make_unique(other.baseCRS(), other.coordinateSystem())) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ProjectedCRS::~ProjectedCRS() = default; //! @endcond // --------------------------------------------------------------------------- CRSNNPtr ProjectedCRS::_shallowClone() const { auto crs(ProjectedCRS::nn_make_shared(*this)); crs->assignSelf(crs); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Return the base CRS (a GeodeticCRS, which is generally a * GeographicCRS) of the ProjectedCRS. * * @return the base CRS. */ const GeodeticCRSNNPtr &ProjectedCRS::baseCRS() PROJ_PURE_DEFN { return d->baseCRS(); } // --------------------------------------------------------------------------- /** \brief Return the cs::CartesianCS associated with the CRS. * * @return a CartesianCS */ const cs::CartesianCSNNPtr &ProjectedCRS::coordinateSystem() PROJ_PURE_DEFN { return d->coordinateSystem(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const auto &l_identifiers = identifiers(); // Try to perfectly round-trip ESRI projectedCRS if the current object // perfectly matches the database definition const auto &dbContext = formatter->databaseContext(); std::string l_name(nameStr()); const auto &l_coordinateSystem = d->coordinateSystem(); const auto &axisList = l_coordinateSystem->axisList(); if (axisList.size() == 3 && !(isWKT2 && formatter->use2019Keywords())) { auto projCRS2D = demoteTo2D(std::string(), dbContext); if (dbContext) { const auto res = projCRS2D->identify(io::AuthorityFactory::create( NN_NO_CHECK(dbContext), metadata::Identifier::EPSG)); if (res.size() == 1) { const auto &front = res.front(); if (front.second == 100) { projCRS2D = front.first; } } } if (formatter->useESRIDialect() && dbContext) { // Try to format the ProjecteD 3D CRS as a // PROJCS[],VERTCS[...,DATUM[]] // if we find corresponding objects if (exportAsESRIWktCompoundCRSWithEllipsoidalHeight( this, baseCRS().as_nullable().get(), formatter)) { return; } } if (!formatter->useESRIDialect() && CRS::getPrivate()->allowNonConformantWKT1Export_) { formatter->startNode(io::WKTConstants::COMPD_CS, false); formatter->addQuotedString(l_name + " + " + baseCRS()->nameStr()); projCRS2D->_exportToWKT(formatter); baseCRS() ->demoteTo2D(std::string(), dbContext) ->_exportToWKT(formatter); formatter->endNode(); return; } auto &originalCompoundCRS = CRS::getPrivate()->originalCompoundCRS_; if (!formatter->useESRIDialect() && originalCompoundCRS) { originalCompoundCRS->_exportToWKT(formatter); return; } if (!formatter->useESRIDialect() && formatter->isAllowedEllipsoidalHeightAsVerticalCRS()) { exportAsWKT1CompoundCRSWithEllipsoidalHeight(projCRS2D, axisList[2], formatter); return; } io::FormattingException::Throw( "Projected 3D CRS can only be exported since WKT2:2019"); } std::string l_esri_name; if (formatter->useESRIDialect() && dbContext) { if (!l_identifiers.empty()) { // Try to find the ESRI alias from the CRS identified by its id const auto aliases = dbContext->getAliases( *(l_identifiers[0]->codeSpace()), l_identifiers[0]->code(), std::string(), // officialName, "projected_crs", "ESRI"); if (aliases.size() == 1) l_esri_name = aliases.front(); } if (l_esri_name.empty()) { // Then find the ESRI alias from the CRS name l_esri_name = dbContext->getAliasFromOfficialName( l_name, "projected_crs", "ESRI"); } if (l_esri_name.empty()) { // Then try to build an ESRI CRS from the CRS name, and if there's // one, the ESRI name is the CRS name auto authFactory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI"); const bool found = authFactory ->createObjectsFromName( l_name, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, false // approximateMatch ) .size() == 1; if (found) l_esri_name = l_name; } if (!isWKT2 && !l_identifiers.empty() && *(l_identifiers[0]->codeSpace()) == "ESRI") { try { // If the id of the object is in the ESRI namespace, then // try to find the full ESRI WKT from the database const auto definition = dbContext->getTextDefinition( "projected_crs", "ESRI", l_identifiers[0]->code()); if (starts_with(definition, "PROJCS")) { auto crsFromFromDef = io::WKTParser() .attachDatabaseContext(dbContext) .createFromWKT(definition); if (_isEquivalentTo( dynamic_cast(crsFromFromDef.get()), util::IComparable::Criterion::EQUIVALENT)) { formatter->ingestWKTNode( io::WKTNode::createFrom(definition)); return; } } } catch (const std::exception &) { } } else if (!isWKT2 && !l_esri_name.empty()) { try { auto res = io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI") ->createObjectsFromName( l_esri_name, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, false); if (res.size() == 1) { const auto definition = dbContext->getTextDefinition( "projected_crs", "ESRI", res.front()->identifiers()[0]->code()); if (starts_with(definition, "PROJCS")) { if (_isEquivalentTo( dynamic_cast(res.front().get()), util::IComparable::Criterion::EQUIVALENT)) { formatter->ingestWKTNode( io::WKTNode::createFrom(definition)); return; } } } } catch (const std::exception &) { } } } const auto exportAxis = [&l_coordinateSystem, &axisList, &formatter]() { const auto oldAxisOutputRule = formatter->outputAxis(); if (oldAxisOutputRule == io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) { if (&axisList[0]->direction() == &cs::AxisDirection::EAST && &axisList[1]->direction() == &cs::AxisDirection::NORTH) { formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); } } l_coordinateSystem->_exportToWKT(formatter); formatter->setOutputAxis(oldAxisOutputRule); }; if (!isWKT2 && !formatter->useESRIDialect() && starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) { formatter->startNode(io::WKTConstants::PROJCS, !l_identifiers.empty()); formatter->addQuotedString(nameStr()); formatter->setTOWGS84Parameters({0, 0, 0, 0, 0, 0, 0}); baseCRS()->_exportToWKT(formatter); formatter->setTOWGS84Parameters({}); formatter->startNode(io::WKTConstants::PROJECTION, false); formatter->addQuotedString("Mercator_1SP"); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("central_meridian"); formatter->add(0.0); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("scale_factor"); formatter->add(1.0); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("false_easting"); formatter->add(0.0); formatter->endNode(); formatter->startNode(io::WKTConstants::PARAMETER, false); formatter->addQuotedString("false_northing"); formatter->add(0.0); formatter->endNode(); axisList[0]->unit()._exportToWKT(formatter); exportAxis(); derivingConversionRef()->addWKTExtensionNode(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); return; } formatter->startNode(isWKT2 ? io::WKTConstants::PROJCRS : io::WKTConstants::PROJCS, !l_identifiers.empty()); if (formatter->useESRIDialect()) { if (l_esri_name.empty()) { l_name = io::WKTFormatter::morphNameToESRI(l_name); } else { const std::string &l_esri_name_ref(l_esri_name); l_name = l_esri_name_ref; } } if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { l_name += " (deprecated)"; } formatter->addQuotedString(l_name); const auto &l_baseCRS = d->baseCRS(); const auto &geodeticCRSAxisList = l_baseCRS->coordinateSystem()->axisList(); if (isWKT2) { formatter->startNode( (formatter->use2019Keywords() && dynamic_cast(l_baseCRS.get())) ? io::WKTConstants::BASEGEOGCRS : io::WKTConstants::BASEGEODCRS, formatter->use2019Keywords() && !l_baseCRS->identifiers().empty()); formatter->addQuotedString(l_baseCRS->nameStr()); l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); // insert ellipsoidal cs unit when the units of the map // projection angular parameters are not explicitly given within those // parameters. See // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis()) { geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); } l_baseCRS->primeMeridian()->_exportToWKT(formatter); if (formatter->use2019Keywords() && !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) { l_baseCRS->formatID(formatter); } formatter->endNode(); } else { const auto oldAxisOutputRule = formatter->outputAxis(); formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::NO); l_baseCRS->_exportToWKT(formatter); formatter->setOutputAxis(oldAxisOutputRule); } formatter->pushAxisLinearUnit( common::UnitOfMeasure::create(axisList[0]->unit())); formatter->pushAxisAngularUnit( common::UnitOfMeasure::create(geodeticCRSAxisList[0]->unit())); derivingConversionRef()->_exportToWKT(formatter); formatter->popAxisAngularUnit(); formatter->popAxisLinearUnit(); if (!isWKT2) { axisList[0]->unit()._exportToWKT(formatter); } exportAxis(); if (!isWKT2 && !formatter->useESRIDialect()) { const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; if (!extensionProj4.empty()) { formatter->startNode(io::WKTConstants::EXTENSION, false); formatter->addQuotedString("PROJ4"); formatter->addQuotedString(extensionProj4); formatter->endNode(); } else { derivingConversionRef()->addWKTExtensionNode(formatter); } } ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); return; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ProjectedCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("ProjectedCRS", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("base_crs"); formatter->setAllowIDInImmediateChild(); baseCRS()->_exportToJSON(formatter); writer->AddObjKey("conversion"); formatter->setOmitTypeInImmediateChild(); derivingConversionRef()->_exportToJSON(formatter); writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- void ProjectedCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_; if (!extensionProj4.empty()) { formatter->ingestPROJString( replaceAll(extensionProj4, " +type=crs", "")); formatter->addNoDefs(false); return; } derivingConversionRef()->_exportToPROJString(formatter); } // --------------------------------------------------------------------------- /** \brief Instantiate a ProjectedCRS from a base CRS, a deriving * operation::Conversion * and a coordinate system. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn The base CRS, a GeodeticCRS that is generally a * GeographicCRS. * @param derivingConversionIn The deriving operation::Conversion (typically * using a map * projection method) * @param csIn The coordniate system. * @return new ProjectedCRS. */ ProjectedCRSNNPtr ProjectedCRS::create(const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn) { auto crs = ProjectedCRS::nn_make_shared( baseCRSIn, derivingConversionIn, csIn); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); crs->CRS::getPrivate()->setNonStandardProperties(properties); return crs; } // --------------------------------------------------------------------------- bool ProjectedCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherProjCRS = dynamic_cast(other); if (otherProjCRS != nullptr && criterion == util::IComparable::Criterion::EQUIVALENT && (d->baseCRS_->hasImplicitCS() || otherProjCRS->d->baseCRS_->hasImplicitCS())) { // If one of the 2 base CRS has implicit coordinate system, then // relax the check. The axis order of the base CRS doesn't matter // for most purposes. criterion = util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS; } return other != nullptr && util::isOfExactType(*other) && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ProjectedCRSNNPtr ProjectedCRS::alterParametersLinearUnit(const common::UnitOfMeasure &unit, bool convertToNewUnit) const { return create( createPropertyMap(this), baseCRS(), derivingConversion()->alterParametersLinearUnit(unit, convertToNewUnit), coordinateSystem()); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, bool axisSpecFound) const { ProjectedCRS::addUnitConvertAndAxisSwap(d->coordinateSystem()->axisList(), formatter, axisSpecFound); } void ProjectedCRS::addUnitConvertAndAxisSwap( const std::vector &axisListIn, io::PROJStringFormatter *formatter, bool axisSpecFound) { const auto &unit = axisListIn[0]->unit(); const auto *zUnit = axisListIn.size() == 3 ? &(axisListIn[2]->unit()) : nullptr; if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT) || (zUnit && !zUnit->_isEquivalentTo(common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT))) { auto projUnit = unit.exportToPROJString(); const double toSI = unit.conversionToSI(); if (!formatter->getCRSExport()) { formatter->addStep("unitconvert"); formatter->addParam("xy_in", "m"); if (zUnit) formatter->addParam("z_in", "m"); if (projUnit.empty()) { formatter->addParam("xy_out", toSI); } else { formatter->addParam("xy_out", projUnit); } if (zUnit) { auto projZUnit = zUnit->exportToPROJString(); const double zToSI = zUnit->conversionToSI(); if (projZUnit.empty()) { formatter->addParam("z_out", zToSI); } else { formatter->addParam("z_out", projZUnit); } } } else { if (projUnit.empty()) { formatter->addParam("to_meter", toSI); } else { formatter->addParam("units", projUnit); } } } else if (formatter->getCRSExport() && !formatter->getLegacyCRSToCRSContext()) { formatter->addParam("units", "m"); } if (!axisSpecFound && (!formatter->getCRSExport() || formatter->getLegacyCRSToCRSContext())) { const auto &dir0 = axisListIn[0]->direction(); const auto &dir1 = axisListIn[1]->direction(); if (!(&dir0 == &cs::AxisDirection::EAST && &dir1 == &cs::AxisDirection::NORTH) && // For polar projections, that have south+south direction, // we don't want to mess with axes. dir0 != dir1) { const char *order[2] = {nullptr, nullptr}; for (int i = 0; i < 2; i++) { const auto &dir = axisListIn[i]->direction(); if (&dir == &cs::AxisDirection::WEST) order[i] = "-1"; else if (&dir == &cs::AxisDirection::EAST) order[i] = "1"; else if (&dir == &cs::AxisDirection::SOUTH) order[i] = "-2"; else if (&dir == &cs::AxisDirection::NORTH) order[i] = "2"; } if (order[0] && order[1]) { formatter->addStep("axisswap"); char orderStr[10]; snprintf(orderStr, sizeof(orderStr), "%.2s,%.2s", order[0], order[1]); formatter->addParam("order", orderStr); } } else { const auto &name0 = axisListIn[0]->nameStr(); const auto &name1 = axisListIn[1]->nameStr(); const bool northingEasting = ci_starts_with(name0, "northing") && ci_starts_with(name1, "easting"); // case of EPSG:32661 ["WGS 84 / UPS North (N,E)]" // case of EPSG:32761 ["WGS 84 / UPS South (N,E)]" if (((&dir0 == &cs::AxisDirection::SOUTH && &dir1 == &cs::AxisDirection::SOUTH) || (&dir0 == &cs::AxisDirection::NORTH && &dir1 == &cs::AxisDirection::NORTH)) && northingEasting) { formatter->addStep("axisswap"); formatter->addParam("order", "2,1"); } } } } //! @endcond // --------------------------------------------------------------------------- /** \brief Identify the CRS with reference CRSs. * * The candidate CRSs are either hard-coded, or looked in the database when * authorityFactory is not null. * * Note that the implementation uses a set of heuristics to have a good * compromise of successful identifications over execution time. It might miss * legitimate matches in some circumstances. * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. * * 100% means that the name of the reference entry * perfectly matches the CRS name, and both are equivalent. In which case a * single result is returned. * 90% means that CRS are equivalent, but the names are not exactly the same. * 70% means that CRS are equivalent (equivalent base CRS, conversion and * coordinate system), but the names are not equivalent. * 60% means that CRS have strong similarity (equivalent base datum, conversion * and coordinate system), but the names are not equivalent. * 50% means that CRS have similarity (equivalent base ellipsoid and * conversion), * but the coordinate system do not match (e.g. different axis ordering or * axis unit). * 25% means that the CRS are not equivalent, but there is some similarity in * the names. * * For the purpose of this function, equivalence is tested with the * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, that is * to say that the axis order of the base GeographicCRS is ignored. * * @param authorityFactory Authority factory (or null, but degraded * functionality) * @return a list of matching reference CRS, and the percentage (0-100) of * confidence in the match. */ std::list> ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; const auto &thisName(nameStr()); io::DatabaseContextPtr dbContext = authorityFactory ? authorityFactory->databaseContext().as_nullable() : nullptr; std::list> baseRes; const auto &l_baseCRS(baseCRS()); const auto l_datum = l_baseCRS->datumNonNull(dbContext); const bool significantNameForDatum = !ci_starts_with(l_datum->nameStr(), "unknown") && l_datum->nameStr() != "unnamed"; const auto &ellipsoid = l_baseCRS->ellipsoid(); auto geogCRS = dynamic_cast(l_baseCRS.get()); if (geogCRS && geogCRS->coordinateSystem()->axisOrder() == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) { baseRes = GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, geogCRS->nameStr()), geogCRS->datum(), geogCRS->datumEnsemble(), cs::EllipsoidalCS::createLatitudeLongitude( geogCRS->coordinateSystem()->axisList()[0]->unit())) ->identify(authorityFactory); } else { baseRes = l_baseCRS->identify(authorityFactory); } int zone = 0; bool north = false; auto computeConfidence = [&thisName](const std::string &crsName) { return crsName == thisName ? 100 : metadata::Identifier::isEquivalentName(crsName.c_str(), thisName.c_str()) ? 90 : 70; }; const auto &conv = derivingConversionRef(); const auto &cs = coordinateSystem(); if (baseRes.size() == 1 && baseRes.front().second >= 70 && (authorityFactory == nullptr || authorityFactory->getAuthority().empty() || authorityFactory->getAuthority() == metadata::Identifier::EPSG) && conv->isUTM(zone, north) && cs->_isEquivalentTo( cs::CartesianCS::createEastingNorthing(common::UnitOfMeasure::METRE) .get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { auto computeUTMCRSName = [](const char *base, int l_zone, bool l_north) { return base + toString(l_zone) + (l_north ? "N" : "S"); }; if (baseRes.front().first->_isEquivalentTo( GeographicCRS::EPSG_4326.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { std::string crsName( computeUTMCRSName("WGS 84 / UTM zone ", zone, north)); res.emplace_back( ProjectedCRS::create( createMapNameEPSGCode(crsName.c_str(), (north ? 32600 : 32700) + zone), GeographicCRS::EPSG_4326, conv->identify(), cs), computeConfidence(crsName)); return res; } else if (((zone >= 1 && zone <= 22) || zone == 59 || zone == 60) && north && baseRes.front().first->_isEquivalentTo( GeographicCRS::EPSG_4267.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { std::string crsName( computeUTMCRSName("NAD27 / UTM zone ", zone, north)); res.emplace_back( ProjectedCRS::create( createMapNameEPSGCode(crsName.c_str(), (zone >= 59) ? 3370 + zone - 59 : 26700 + zone), GeographicCRS::EPSG_4267, conv->identify(), cs), computeConfidence(crsName)); return res; } else if (((zone >= 1 && zone <= 23) || zone == 59 || zone == 60) && north && baseRes.front().first->_isEquivalentTo( GeographicCRS::EPSG_4269.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { std::string crsName( computeUTMCRSName("NAD83 / UTM zone ", zone, north)); res.emplace_back( ProjectedCRS::create( createMapNameEPSGCode(crsName.c_str(), (zone >= 59) ? 3372 + zone - 59 : 26900 + zone), GeographicCRS::EPSG_4269, conv->identify(), cs), computeConfidence(crsName)); return res; } } const bool l_implicitCS = hasImplicitCS(); const auto addCRS = [&](const ProjectedCRSNNPtr &crs, const bool eqName, bool hasNonMatchingId) -> Pair { const auto &l_unit = cs->axisList()[0]->unit(); if ((_isEquivalentTo(crs.get(), util::IComparable::Criterion:: EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, dbContext) || (l_implicitCS && l_unit._isEquivalentTo( crs->coordinateSystem()->axisList()[0]->unit(), util::IComparable::Criterion::EQUIVALENT) && l_baseCRS->_isEquivalentTo( crs->baseCRS().get(), util::IComparable::Criterion:: EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, dbContext) && derivingConversionRef()->_isEquivalentTo( crs->derivingConversionRef().get(), util::IComparable::Criterion::EQUIVALENT, dbContext))) && !((baseCRS()->datumNonNull(dbContext)->nameStr() == "unknown" && crs->baseCRS()->datumNonNull(dbContext)->nameStr() != "unknown") || (baseCRS()->datumNonNull(dbContext)->nameStr() != "unknown" && crs->baseCRS()->datumNonNull(dbContext)->nameStr() == "unknown"))) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crs, hasNonMatchingId ? 70 : 100); } else { res.emplace_back(crs, eqName ? 90 : 70); } } else if (ellipsoid->_isEquivalentTo( crs->baseCRS()->ellipsoid().get(), util::IComparable::Criterion::EQUIVALENT, dbContext) && derivingConversionRef()->_isEquivalentTo( crs->derivingConversionRef().get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { if ((l_implicitCS && l_unit._isEquivalentTo( crs->coordinateSystem()->axisList()[0]->unit(), util::IComparable::Criterion::EQUIVALENT)) || cs->_isEquivalentTo(crs->coordinateSystem().get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { if (!significantNameForDatum || l_datum->_isEquivalentTo( crs->baseCRS()->datumNonNull(dbContext).get(), util::IComparable::Criterion::EQUIVALENT)) { res.emplace_back(crs, 70); } else { res.emplace_back(crs, 60); } } else { res.emplace_back(crs, 50); } } else { res.emplace_back(crs, 25); } return res.back(); }; if (authorityFactory) { const bool insignificantName = thisName.empty() || ci_equal(thisName, "unknown") || ci_equal(thisName, "unnamed"); bool foundEquivalentName = false; bool hasNonMatchingId = false; if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) { // If the CRS has already an id, check in the database for the // official object, and verify that they are equivalent. for (const auto &id : identifiers()) { if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) { try { auto crs = io::AuthorityFactory::create( authorityFactory->databaseContext(), *id->codeSpace()) ->createProjectedCRS(id->code()); bool match = _isEquivalentTo( crs.get(), util::IComparable::Criterion:: EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, dbContext); res.emplace_back(crs, match ? 100 : 25); if (match) { return res; } } catch (const std::exception &) { } } } hasNonMatchingId = true; } else if (!insignificantName) { const double semiMajorAxis = ellipsoid->semiMajorAxis().getSIValue(); for (int ipass = 0; ipass < 2; ipass++) { const bool approximateMatch = ipass == 1; auto objects = authorityFactory->createObjectsFromNameEx( thisName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, approximateMatch); for (const auto &pairObjName : objects) { auto crs = util::nn_dynamic_pointer_cast( pairObjName.first); assert(crs); auto crsNN = NN_NO_CHECK(crs); const bool eqName = metadata::Identifier::isEquivalentName( thisName.c_str(), pairObjName.second.c_str()); foundEquivalentName |= eqName; if (std::fabs(semiMajorAxis - crsNN->baseCRS() ->ellipsoid() ->semiMajorAxis() .getSIValue()) <= 1e-4 * semiMajorAxis) { if (addCRS(crsNN, eqName, false).second == 100) { return res; } } } if (!res.empty()) { break; } } } const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { // First consider confidence if (a.second > b.second) { return true; } if (a.second < b.second) { return false; } // Then consider exact name matching const auto &aName(a.first->nameStr()); const auto &bName(b.first->nameStr()); if (aName == thisName && bName != thisName) { return true; } if (bName == thisName && aName != thisName) { return false; } // Arbitrary final sorting criterion return aName < bName; }; // Sort results res.sort(lambdaSort); if (!foundEquivalentName && (res.empty() || res.front().second < 50)) { std::set> alreadyKnown; for (const auto &pair : res) { const auto &ids = pair.first->identifiers(); assert(!ids.empty()); alreadyKnown.insert(std::pair( *(ids[0]->codeSpace()), ids[0]->code())); } auto self = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); auto candidates = authorityFactory->createProjectedCRSFromExisting(self); for (const auto &crs : candidates) { const auto &ids = crs->identifiers(); assert(!ids.empty()); if (alreadyKnown.find(std::pair( *(ids[0]->codeSpace()), ids[0]->code())) != alreadyKnown.end()) { continue; } addCRS(crs, insignificantName, hasNonMatchingId); } res.sort(lambdaSort); } // Keep only results of the highest confidence if (res.size() >= 2) { const auto highestConfidence = res.front().second; std::list newRes; for (const auto &pair : res) { if (pair.second == highestConfidence) { newRes.push_back(pair); } else { break; } } return newRes; } } return res; } // --------------------------------------------------------------------------- /** \brief Return a variant of this CRS "demoted" to a 2D one, if not already * the case. * * * @param newName Name of the new CRS. If empty, nameStr() will be used. * @param dbContext Database context to look for potentially already registered * 2D CRS. May be nullptr. * @return a new CRS demoted to 2D, or the current one if already 2D or not * applicable. * @since 6.3 */ ProjectedCRSNNPtr ProjectedCRS::demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const { const auto &axisList = coordinateSystem()->axisList(); if (axisList.size() == 3) { auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], axisList[1]); const auto &l_baseCRS = baseCRS(); const auto geogCRS = dynamic_cast(l_baseCRS.get()); const auto newBaseCRS = geogCRS ? util::nn_static_pointer_cast( geogCRS->demoteTo2D(std::string(), dbContext)) : l_baseCRS; return ProjectedCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, !newName.empty() ? newName : nameStr()), newBaseCRS, derivingConversion(), cs); } return NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> ProjectedCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; auto resTemp = identify(authorityFactory); for (const auto &pair : resTemp) { res.emplace_back(pair.first, pair.second); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress InvalidCompoundCRSException::InvalidCompoundCRSException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- InvalidCompoundCRSException::InvalidCompoundCRSException( const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- InvalidCompoundCRSException::~InvalidCompoundCRSException() = default; // --------------------------------------------------------------------------- InvalidCompoundCRSException::InvalidCompoundCRSException( const InvalidCompoundCRSException &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct CompoundCRS::Private { std::vector components_{}; }; //! @endcond // --------------------------------------------------------------------------- CompoundCRS::CompoundCRS(const std::vector &components) : CRS(), d(std::make_unique()) { d->components_ = components; } // --------------------------------------------------------------------------- CompoundCRS::CompoundCRS(const CompoundCRS &other) : CRS(other), d(std::make_unique(*other.d)) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress CompoundCRS::~CompoundCRS() = default; //! @endcond // --------------------------------------------------------------------------- CRSNNPtr CompoundCRS::_shallowClone() const { auto crs(CompoundCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the components of a CompoundCRS. * * @return the components. */ const std::vector & CompoundCRS::componentReferenceSystems() PROJ_PURE_DEFN { return d->components_; } // --------------------------------------------------------------------------- /** \brief Instantiate a CompoundCRS from a vector of CRS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param components the component CRS of the CompoundCRS. * @return new CompoundCRS. * @throw InvalidCompoundCRSException if the object cannot be constructed. */ CompoundCRSNNPtr CompoundCRS::create(const util::PropertyMap &properties, const std::vector &components) { if (components.size() < 2) { throw InvalidCompoundCRSException( "compound CRS should have at least 2 components"); } auto comp0 = components[0].get(); auto comp0Bound = dynamic_cast(comp0); if (comp0Bound) { comp0 = comp0Bound->baseCRS().get(); } auto comp0Geog = dynamic_cast(comp0); auto comp0Proj = dynamic_cast(comp0); auto comp0DerPr = dynamic_cast(comp0); auto comp0Eng = dynamic_cast(comp0); auto comp1 = components[1].get(); auto comp1Bound = dynamic_cast(comp1); if (comp1Bound) { comp1 = comp1Bound->baseCRS().get(); } auto comp1Vert = dynamic_cast(comp1); auto comp1Eng = dynamic_cast(comp1); // Loose validation based on // http://docs.opengeospatial.org/as/18-005r5/18-005r5.html#34 bool ok = false; const bool comp1IsVertOrEng1 = comp1Vert || (comp1Eng && comp1Eng->coordinateSystem()->axisList().size() == 1); if ((comp0Geog && comp0Geog->coordinateSystem()->axisList().size() == 2 && comp1IsVertOrEng1) || (comp0Proj && comp0Proj->coordinateSystem()->axisList().size() == 2 && comp1IsVertOrEng1) || (comp0DerPr && comp0DerPr->coordinateSystem()->axisList().size() == 2 && comp1IsVertOrEng1) || (comp0Eng && comp0Eng->coordinateSystem()->axisList().size() <= 2 && comp1Vert)) { // Spatial compound coordinate reference system ok = true; } else { bool isComp0Spatial = comp0Geog || comp0Proj || comp0DerPr || comp0Eng || dynamic_cast(comp0) || dynamic_cast(comp0); if (isComp0Spatial && dynamic_cast(comp1)) { // Spatio-temporal compound coordinate reference system ok = true; } else if (isComp0Spatial && dynamic_cast(comp1)) { // Spatio-parametric compound coordinate reference system ok = true; } } if (!ok) { throw InvalidCompoundCRSException( "components of the compound CRS do not belong to one of the " "allowed combinations of " "http://docs.opengeospatial.org/as/18-005r5/18-005r5.html#34"); } auto compoundCRS(CompoundCRS::nn_make_shared(components)); compoundCRS->assignSelf(compoundCRS); compoundCRS->setProperties(properties); if (!properties.get(common::IdentifiedObject::NAME_KEY)) { std::string name; for (const auto &crs : components) { if (!name.empty()) { name += " + "; } const auto &l_name = crs->nameStr(); if (!l_name.empty()) { name += l_name; } else { name += "unnamed"; } } util::PropertyMap propertyName; propertyName.set(common::IdentifiedObject::NAME_KEY, name); compoundCRS->setProperties(propertyName); } return compoundCRS; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Instantiate a CompoundCRS, a Geographic 3D CRS or a Projected CRS * from a vector of CRS. * * Be a bit "lax", in allowing formulations like EPSG:4326+4326 or * EPSG:32631+4326 to express Geographic 3D CRS / Projected3D CRS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param components the component CRS of the CompoundCRS. * @return new CRS. * @throw InvalidCompoundCRSException if the object cannot be constructed. */ CRSNNPtr CompoundCRS::createLax(const util::PropertyMap &properties, const std::vector &components, const io::DatabaseContextPtr &dbContext) { if (components.size() == 2) { auto comp0 = components[0].get(); auto comp1 = components[1].get(); auto comp0Geog = dynamic_cast(comp0); auto comp0Proj = dynamic_cast(comp0); auto comp0Bound = dynamic_cast(comp0); if (comp0Geog == nullptr && comp0Proj == nullptr) { if (comp0Bound) { const auto *baseCRS = comp0Bound->baseCRS().get(); comp0Geog = dynamic_cast(baseCRS); comp0Proj = dynamic_cast(baseCRS); } } auto comp1Geog = dynamic_cast(comp1); if ((comp0Geog != nullptr || comp0Proj != nullptr) && comp1Geog != nullptr) { const auto horizGeog = (comp0Proj != nullptr) ? comp0Proj->baseCRS().as_nullable().get() : comp0Geog; if (horizGeog->_isEquivalentTo( comp1Geog->demoteTo2D(std::string(), dbContext).get())) { return components[0] ->promoteTo3D(std::string(), dbContext) ->allowNonConformantWKT1Export(); } throw InvalidCompoundCRSException( "The 'vertical' geographic CRS is not equivalent to the " "geographic CRS of the horizontal part"); } // Detect a COMPD_CS whose VERT_CS is for ellipoidal heights auto comp1Vert = util::nn_dynamic_pointer_cast(components[1]); if (comp1Vert != nullptr && comp1Vert->datum() && comp1Vert->datum()->getWKT1DatumType() == "2002") { const auto &axis = comp1Vert->coordinateSystem()->axisList()[0]; std::string name(components[0]->nameStr()); if (!(axis->unit()._isEquivalentTo( common::UnitOfMeasure::METRE, util::IComparable::Criterion::EQUIVALENT) && &(axis->direction()) == &(cs::AxisDirection::UP))) { name += " (" + comp1Vert->nameStr() + ')'; } auto newVertAxis = cs::CoordinateSystemAxis::create( util::PropertyMap().set(IdentifiedObject::NAME_KEY, cs::AxisName::Ellipsoidal_height), cs::AxisAbbreviation::h, axis->direction(), axis->unit()); return components[0] ->promoteTo3D(name, dbContext, newVertAxis) ->attachOriginalCompoundCRS(create( properties, comp0Bound ? std::vector{comp0Bound->baseCRS(), components[1]} : components)); } } return create(properties, components); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; const auto &l_components = componentReferenceSystems(); if (!isWKT2 && formatter->useESRIDialect() && l_components.size() == 2) { l_components[0]->_exportToWKT(formatter); l_components[1]->_exportToWKT(formatter); } else { formatter->startNode(isWKT2 ? io::WKTConstants::COMPOUNDCRS : io::WKTConstants::COMPD_CS, !identifiers().empty()); formatter->addQuotedString(nameStr()); if (!l_components.empty()) { formatter->setGeogCRSOfCompoundCRS( l_components[0]->extractGeographicCRS()); } for (const auto &crs : l_components) { crs->_exportToWKT(formatter); } formatter->setGeogCRSOfCompoundCRS(nullptr); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void CompoundCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("CompoundCRS", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("components"); { auto componentsContext(writer->MakeArrayContext(false)); for (const auto &crs : componentReferenceSystems()) { crs->_exportToJSON(formatter); } } ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- void CompoundCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &l_components = componentReferenceSystems(); if (!l_components.empty()) { formatter->setGeogCRSOfCompoundCRS( l_components[0]->extractGeographicCRS()); } for (const auto &crs : l_components) { auto crs_exportable = dynamic_cast(crs.get()); if (crs_exportable) { crs_exportable->_exportToPROJString(formatter); } } formatter->setGeogCRSOfCompoundCRS(nullptr); } // --------------------------------------------------------------------------- bool CompoundCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherCompoundCRS = dynamic_cast(other); if (otherCompoundCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto &components = componentReferenceSystems(); const auto &otherComponents = otherCompoundCRS->componentReferenceSystems(); if (components.size() != otherComponents.size()) { return false; } for (size_t i = 0; i < components.size(); i++) { if (!components[i]->_isEquivalentTo(otherComponents[i].get(), criterion, dbContext)) { return false; } } return true; } // --------------------------------------------------------------------------- /** \brief Identify the CRS with reference CRSs. * * The candidate CRSs are looked in the database when * authorityFactory is not null. * * Note that the implementation uses a set of heuristics to have a good * compromise of successful identifications over execution time. It might miss * legitimate matches in some circumstances. * * The method returns a list of matching reference CRS, and the percentage * (0-100) of confidence in the match. The list is sorted by decreasing * confidence. * * 100% means that the name of the reference entry * perfectly matches the CRS name, and both are equivalent. In which case a * single result is returned. * 90% means that CRS are equivalent, but the names are not exactly the same. * 70% means that CRS are equivalent (equivalent horizontal and vertical CRS), * but the names are not equivalent. * 25% means that the CRS are not equivalent, but there is some similarity in * the names. * * @param authorityFactory Authority factory (if null, will return an empty * list) * @return a list of matching reference CRS, and the percentage (0-100) of * confidence in the match. */ std::list> CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; const auto &thisName(nameStr()); const auto &components = componentReferenceSystems(); bool l_implicitCS = components[0]->hasImplicitCS(); if (!l_implicitCS) { const auto projCRS = dynamic_cast(components[0].get()); if (projCRS) { l_implicitCS = projCRS->baseCRS()->hasImplicitCS(); } } const auto crsCriterion = l_implicitCS ? util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS : util::IComparable::Criterion::EQUIVALENT; if (authorityFactory) { const io::DatabaseContextNNPtr &dbContext = authorityFactory->databaseContext(); const bool insignificantName = thisName.empty() || ci_equal(thisName, "unknown") || ci_equal(thisName, "unnamed"); bool foundEquivalentName = false; if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) { // If the CRS has already an id, check in the database for the // official object, and verify that they are equivalent. for (const auto &id : identifiers()) { if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) { try { auto crs = io::AuthorityFactory::create( dbContext, *id->codeSpace()) ->createCompoundCRS(id->code()); bool match = _isEquivalentTo(crs.get(), crsCriterion, dbContext); res.emplace_back(crs, match ? 100 : 25); return res; } catch (const std::exception &) { } } } } else if (!insignificantName) { for (int ipass = 0; ipass < 2; ipass++) { const bool approximateMatch = ipass == 1; auto objects = authorityFactory->createObjectsFromName( thisName, {io::AuthorityFactory::ObjectType::COMPOUND_CRS}, approximateMatch); for (const auto &obj : objects) { auto crs = util::nn_dynamic_pointer_cast(obj); assert(crs); auto crsNN = NN_NO_CHECK(crs); const bool eqName = metadata::Identifier::isEquivalentName( thisName.c_str(), crs->nameStr().c_str()); foundEquivalentName |= eqName; if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) { if (crs->nameStr() == thisName) { res.clear(); res.emplace_back(crsNN, 100); return res; } res.emplace_back(crsNN, eqName ? 90 : 70); } else { res.emplace_back(crsNN, 25); } } if (!res.empty()) { break; } } } const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { // First consider confidence if (a.second > b.second) { return true; } if (a.second < b.second) { return false; } // Then consider exact name matching const auto &aName(a.first->nameStr()); const auto &bName(b.first->nameStr()); if (aName == thisName && bName != thisName) { return true; } if (bName == thisName && aName != thisName) { return false; } // Arbitrary final sorting criterion return aName < bName; }; // Sort results res.sort(lambdaSort); if (identifiers().empty() && !foundEquivalentName && (res.empty() || res.front().second < 50)) { std::set> alreadyKnown; for (const auto &pair : res) { const auto &ids = pair.first->identifiers(); assert(!ids.empty()); alreadyKnown.insert(std::pair( *(ids[0]->codeSpace()), ids[0]->code())); } auto self = NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); auto candidates = authorityFactory->createCompoundCRSFromExisting(self); for (const auto &crs : candidates) { const auto &ids = crs->identifiers(); assert(!ids.empty()); if (alreadyKnown.find(std::pair( *(ids[0]->codeSpace()), ids[0]->code())) != alreadyKnown.end()) { continue; } if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) { res.emplace_back(crs, insignificantName ? 90 : 70); } else { res.emplace_back(crs, 25); } } res.sort(lambdaSort); // If there's a single candidate at 90% confidence with same name, // then promote it to 100% if (res.size() == 1 && res.front().second == 90 && thisName == res.front().first->nameStr()) { res.front().second = 100; } } // If we didn't find a match for the CompoundCRS, check if the // horizontal and vertical parts are not themselves well known. if (identifiers().empty() && res.empty() && components.size() == 2) { auto candidatesHorizCRS = components[0]->identify(authorityFactory); auto candidatesVertCRS = components[1]->identify(authorityFactory); if (candidatesHorizCRS.size() == 1 && candidatesVertCRS.size() == 1 && candidatesHorizCRS.front().second >= 70 && candidatesVertCRS.front().second >= 70) { auto newCRS = CompoundCRS::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, candidatesHorizCRS.front().first->nameStr() + " + " + candidatesVertCRS.front().first->nameStr()), {candidatesHorizCRS.front().first, candidatesVertCRS.front().first}); const bool eqName = metadata::Identifier::isEquivalentName( thisName.c_str(), newCRS->nameStr().c_str()); res.emplace_back( newCRS, std::min(thisName == newCRS->nameStr() ? 100 : eqName ? 90 : 70, std::min(candidatesHorizCRS.front().second, candidatesVertCRS.front().second))); } } // Keep only results of the highest confidence if (res.size() >= 2) { const auto highestConfidence = res.front().second; std::list newRes; for (const auto &pair : res) { if (pair.second == highestConfidence) { newRes.push_back(pair); } else { break; } } return newRes; } } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> CompoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; auto resTemp = identify(authorityFactory); for (const auto &pair : resTemp) { res.emplace_back(pair.first, pair.second); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct PROJ_INTERNAL BoundCRS::Private { CRSNNPtr baseCRS_; CRSNNPtr hubCRS_; operation::TransformationNNPtr transformation_; Private(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn); inline const CRSNNPtr &baseCRS() const { return baseCRS_; } inline const CRSNNPtr &hubCRS() const { return hubCRS_; } inline const operation::TransformationNNPtr &transformation() const { return transformation_; } }; BoundCRS::Private::Private( const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn) : baseCRS_(baseCRSIn), hubCRS_(hubCRSIn), transformation_(transformationIn) {} //! @endcond // --------------------------------------------------------------------------- BoundCRS::BoundCRS(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn) : d(std::make_unique(baseCRSIn, hubCRSIn, transformationIn)) {} // --------------------------------------------------------------------------- BoundCRS::BoundCRS(const BoundCRS &other) : CRS(other), d(std::make_unique(other.d->baseCRS(), other.d->hubCRS(), other.d->transformation())) {} // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress BoundCRS::~BoundCRS() = default; //! @endcond // --------------------------------------------------------------------------- BoundCRSNNPtr BoundCRS::shallowCloneAsBoundCRS() const { auto crs(BoundCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- CRSNNPtr BoundCRS::_shallowClone() const { return shallowCloneAsBoundCRS(); } // --------------------------------------------------------------------------- /** \brief Return the base CRS. * * This is the CRS into which coordinates of the BoundCRS are expressed. * * @return the base CRS. */ const CRSNNPtr &BoundCRS::baseCRS() PROJ_PURE_DEFN { return d->baseCRS_; } // --------------------------------------------------------------------------- // The only legit caller is BoundCRS::baseCRSWithCanonicalBoundCRS() void CRS::setCanonicalBoundCRS(const BoundCRSNNPtr &boundCRS) { d->canonicalBoundCRS_ = boundCRS; } // --------------------------------------------------------------------------- /** \brief Return a shallow clone of the base CRS that points to a * shallow clone of this BoundCRS. * * The base CRS is the CRS into which coordinates of the BoundCRS are expressed. * * The returned CRS will actually be a shallow clone of the actual base CRS, * with the extra property that CRS::canonicalBoundCRS() will point to a * shallow clone of this BoundCRS. Use this only if you want to work with * the base CRS object rather than the BoundCRS, but wanting to be able to * retrieve the BoundCRS later. * * @return the base CRS. */ CRSNNPtr BoundCRS::baseCRSWithCanonicalBoundCRS() const { auto baseCRSClone = baseCRS()->_shallowClone(); baseCRSClone->setCanonicalBoundCRS(shallowCloneAsBoundCRS()); return baseCRSClone; } // --------------------------------------------------------------------------- /** \brief Return the target / hub CRS. * * @return the hub CRS. */ const CRSNNPtr &BoundCRS::hubCRS() PROJ_PURE_DEFN { return d->hubCRS_; } // --------------------------------------------------------------------------- /** \brief Return the transformation to the hub RS. * * @return transformation. */ const operation::TransformationNNPtr & BoundCRS::transformation() PROJ_PURE_DEFN { return d->transformation_; } // --------------------------------------------------------------------------- /** \brief Instantiate a BoundCRS from a base CRS, a hub CRS and a * transformation. * * @param properties See \ref general_properties. * @param baseCRSIn base CRS. * @param hubCRSIn hub CRS. * @param transformationIn transformation from base CRS to hub CRS. * @return new BoundCRS. * @since PROJ 8.2 */ BoundCRSNNPtr BoundCRS::create(const util::PropertyMap &properties, const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn) { auto crs = BoundCRS::nn_make_shared(baseCRSIn, hubCRSIn, transformationIn); crs->assignSelf(crs); const auto &l_name = baseCRSIn->nameStr(); if (properties.get(common::IdentifiedObject::NAME_KEY) == nullptr && !l_name.empty()) { auto newProperties(properties); newProperties.set(common::IdentifiedObject::NAME_KEY, l_name); crs->setProperties(newProperties); } else { crs->setProperties(properties); } return crs; } // --------------------------------------------------------------------------- /** \brief Instantiate a BoundCRS from a base CRS, a hub CRS and a * transformation. * * @param baseCRSIn base CRS. * @param hubCRSIn hub CRS. * @param transformationIn transformation from base CRS to hub CRS. * @return new BoundCRS. */ BoundCRSNNPtr BoundCRS::create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, const operation::TransformationNNPtr &transformationIn) { return create(util::PropertyMap(), baseCRSIn, hubCRSIn, transformationIn); } // --------------------------------------------------------------------------- /** \brief Instantiate a BoundCRS from a base CRS and TOWGS84 parameters * * @param baseCRSIn base CRS. * @param TOWGS84Parameters a vector of 3 or 7 double values representing WKT1 * TOWGS84 parameter. * @return new BoundCRS. */ BoundCRSNNPtr BoundCRS::createFromTOWGS84(const CRSNNPtr &baseCRSIn, const std::vector &TOWGS84Parameters) { auto transf = operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters); return create(baseCRSIn, transf->targetCRS(), transf); } // --------------------------------------------------------------------------- /** \brief Instantiate a BoundCRS from a base CRS and nadgrids parameters * * @param baseCRSIn base CRS. * @param filename Horizontal grid filename * @return new BoundCRS. */ BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, const std::string &filename) { const auto sourceGeographicCRS = baseCRSIn->extractGeographicCRS(); auto transformationSourceCRS = sourceGeographicCRS ? NN_NO_CHECK(std::static_pointer_cast(sourceGeographicCRS)) : baseCRSIn; if (sourceGeographicCRS != nullptr && sourceGeographicCRS->primeMeridian()->longitude().getSIValue() != 0.0) { transformationSourceCRS = GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, sourceGeographicCRS->nameStr() + " (with Greenwich prime meridian)"), datum::GeodeticReferenceFrame::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, sourceGeographicCRS->datumNonNull(nullptr)->nameStr() + " (with Greenwich prime meridian)"), sourceGeographicCRS->datumNonNull(nullptr)->ellipsoid(), util::optional(), datum::PrimeMeridian::GREENWICH), cs::EllipsoidalCS::createLatitudeLongitude( common::UnitOfMeasure::DEGREE)); } std::string transformationName = transformationSourceCRS->nameStr(); transformationName += " to WGS84"; return create( baseCRSIn, GeographicCRS::EPSG_4326, operation::Transformation::createNTv2( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, transformationName), transformationSourceCRS, GeographicCRS::EPSG_4326, filename, std::vector())); } // --------------------------------------------------------------------------- bool BoundCRS::isTOWGS84Compatible() const { return dynamic_cast(d->hubCRS().get()) != nullptr && ci_equal(d->hubCRS()->nameStr(), "WGS 84"); } // --------------------------------------------------------------------------- std::string BoundCRS::getHDatumPROJ4GRIDS( const io::DatabaseContextPtr &databaseContext) const { if (ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { if (databaseContext) { return d->transformation() ->substitutePROJAlternativeGridNames( NN_NO_CHECK(databaseContext)) ->getPROJ4NadgridsCompatibleFilename(); } return d->transformation()->getPROJ4NadgridsCompatibleFilename(); } return std::string(); } // --------------------------------------------------------------------------- std::string BoundCRS::getVDatumPROJ4GRIDS(const crs::GeographicCRS *geogCRSOfCompoundCRS, const char **outGeoidCRSValue) const { // When importing from WKT1 PROJ4_GRIDS extension, we used to hardcode // "WGS 84" as the hub CRS, so let's test that for backward compatibility. if (dynamic_cast(d->baseCRS().get()) && ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { if (outGeoidCRSValue) *outGeoidCRSValue = "WGS84"; return d->transformation()->getHeightToGeographic3DFilename(); } else if (geogCRSOfCompoundCRS && dynamic_cast(d->baseCRS().get()) && ci_equal(d->hubCRS()->nameStr(), geogCRSOfCompoundCRS->nameStr())) { if (outGeoidCRSValue) *outGeoidCRSValue = "horizontal_crs"; return d->transformation()->getHeightToGeographic3DFilename(); } return std::string(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void BoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (isWKT2) { formatter->startNode(io::WKTConstants::BOUNDCRS, false); formatter->startNode(io::WKTConstants::SOURCECRS, false); d->baseCRS()->_exportToWKT(formatter); formatter->endNode(); formatter->startNode(io::WKTConstants::TARGETCRS, false); d->hubCRS()->_exportToWKT(formatter); formatter->endNode(); formatter->setAbridgedTransformation(true); d->transformation()->_exportToWKT(formatter); formatter->setAbridgedTransformation(false); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } else { auto vdatumProj4GridName = getVDatumPROJ4GRIDS( formatter->getGeogCRSOfCompoundCRS().get(), nullptr); if (!vdatumProj4GridName.empty()) { formatter->setVDatumExtension(vdatumProj4GridName); d->baseCRS()->_exportToWKT(formatter); formatter->setVDatumExtension(std::string()); return; } auto hdatumProj4GridName = getHDatumPROJ4GRIDS(formatter->databaseContext()); if (!hdatumProj4GridName.empty()) { formatter->setHDatumExtension(hdatumProj4GridName); d->baseCRS()->_exportToWKT(formatter); formatter->setHDatumExtension(std::string()); return; } if (!isTOWGS84Compatible()) { io::FormattingException::Throw( "Cannot export BoundCRS with non-WGS 84 hub CRS in WKT1"); } auto params = d->transformation()->getTOWGS84Parameters(true); if (!formatter->useESRIDialect()) { formatter->setTOWGS84Parameters(params); } d->baseCRS()->_exportToWKT(formatter); formatter->setTOWGS84Parameters(std::vector()); } } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void BoundCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); const auto &l_name = nameStr(); auto objectContext(formatter->MakeObjectContext("BoundCRS", false)); const auto &l_sourceCRS = d->baseCRS(); if (!l_name.empty() && l_name != l_sourceCRS->nameStr()) { writer->AddObjKey("name"); writer->Add(l_name); } writer->AddObjKey("source_crs"); l_sourceCRS->_exportToJSON(formatter); writer->AddObjKey("target_crs"); const auto &l_targetCRS = d->hubCRS(); l_targetCRS->_exportToJSON(formatter); writer->AddObjKey("transformation"); formatter->setOmitTypeInImmediateChild(); formatter->setAbridgedTransformation(true); // Only write the source_crs of the transformation if it is different from // the source_crs of the BoundCRS. But don't do it for projectedCRS if its // base CRS matches the source_crs of the transformation and the targetCRS // is geographic const auto sourceCRSAsProjectedCRS = dynamic_cast(l_sourceCRS.get()); if (!l_sourceCRS->_isEquivalentTo( d->transformation()->sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT) && (sourceCRSAsProjectedCRS == nullptr || (dynamic_cast(l_targetCRS.get()) && !sourceCRSAsProjectedCRS->baseCRS()->_isEquivalentTo( d->transformation()->sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT)))) { formatter->setAbridgedTransformationWriteSourceCRS(true); } d->transformation()->_exportToJSON(formatter); formatter->setAbridgedTransformation(false); formatter->setAbridgedTransformationWriteSourceCRS(false); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- void BoundCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { auto crs_exportable = dynamic_cast(d->baseCRS_.get()); if (!crs_exportable) { io::FormattingException::Throw( "baseCRS of BoundCRS cannot be exported as a PROJ string"); } const char *geoidCRSValue = ""; auto vdatumProj4GridName = getVDatumPROJ4GRIDS( formatter->getGeogCRSOfCompoundCRS().get(), &geoidCRSValue); if (!vdatumProj4GridName.empty()) { formatter->setVDatumExtension(vdatumProj4GridName, geoidCRSValue); crs_exportable->_exportToPROJString(formatter); formatter->setVDatumExtension(std::string(), std::string()); } else { auto hdatumProj4GridName = getHDatumPROJ4GRIDS(formatter->databaseContext()); if (!hdatumProj4GridName.empty()) { formatter->setHDatumExtension(hdatumProj4GridName); crs_exportable->_exportToPROJString(formatter); formatter->setHDatumExtension(std::string()); } else { if (isTOWGS84Compatible()) { auto params = transformation()->getTOWGS84Parameters(true); formatter->setTOWGS84Parameters(params); } crs_exportable->_exportToPROJString(formatter); formatter->setTOWGS84Parameters(std::vector()); } } } // --------------------------------------------------------------------------- bool BoundCRS::_isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherBoundCRS = dynamic_cast(other); if (otherBoundCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } const auto standardCriterion = getStandardCriterion(criterion); return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(), criterion, dbContext) && d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(), criterion, dbContext) && d->transformation_->_isEquivalentTo( otherBoundCRS->d->transformation_.get(), standardCriterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { typedef std::pair Pair; std::list res; if (!authorityFactory) return res; std::list resMatchOfTransfToWGS84; const io::DatabaseContextNNPtr &dbContext = authorityFactory->databaseContext(); if (d->hubCRS_->_isEquivalentTo(GeographicCRS::EPSG_4326.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { auto resTemp = d->baseCRS_->identify(authorityFactory); std::string refTransfPROJString; bool refTransfPROJStringValid = false; auto refTransf = d->transformation_->normalizeForVisualization(); try { refTransfPROJString = refTransf->exportToPROJString( io::PROJStringFormatter::create().get()); refTransfPROJString = replaceAll( refTransfPROJString, " +rx=0 +ry=0 +rz=0 +s=0 +convention=position_vector", ""); refTransfPROJStringValid = true; } catch (const std::exception &) { } bool refIsNullTransform = false; if (isTOWGS84Compatible()) { auto params = transformation()->getTOWGS84Parameters(true); if (params == std::vector{0, 0, 0, 0, 0, 0, 0}) { refIsNullTransform = true; } } for (const auto &pair : resTemp) { const auto &candidateBaseCRS = pair.first; auto projCRS = dynamic_cast(candidateBaseCRS.get()); auto geodCRS = projCRS ? projCRS->baseCRS().as_nullable() : util::nn_dynamic_pointer_cast( candidateBaseCRS); if (geodCRS) { auto context = operation::CoordinateOperationContext::create( authorityFactory, nullptr, 0.0); context->setSpatialCriterion( operation::CoordinateOperationContext::SpatialCriterion:: PARTIAL_INTERSECTION); auto ops = operation::CoordinateOperationFactory::create() ->createOperations(NN_NO_CHECK(geodCRS), GeographicCRS::EPSG_4326, context); bool foundOp = false; for (const auto &op : ops) { auto opNormalized = op->normalizeForVisualization(); std::string opTransfPROJString; bool opTransfPROJStringValid = false; const auto &opName = op->nameStr(); if (starts_with( opName, operation::BALLPARK_GEOCENTRIC_TRANSLATION) || starts_with(opName, operation::NULL_GEOGRAPHIC_OFFSET)) { if (refIsNullTransform) { res.emplace_back(create(candidateBaseCRS, d->hubCRS_, transformation()), pair.second); foundOp = true; break; } continue; } try { opTransfPROJString = opNormalized->exportToPROJString( io::PROJStringFormatter::create().get()); opTransfPROJStringValid = true; opTransfPROJString = replaceAll(opTransfPROJString, " +rx=0 +ry=0 +rz=0 +s=0 " "+convention=position_vector", ""); } catch (const std::exception &) { } if ((refTransfPROJStringValid && opTransfPROJStringValid && refTransfPROJString == opTransfPROJString) || opNormalized->_isEquivalentTo( refTransf.get(), util::IComparable::Criterion::EQUIVALENT, dbContext)) { auto transf = util::nn_dynamic_pointer_cast< operation::Transformation>(op); if (transf) { resMatchOfTransfToWGS84.emplace_back( create(candidateBaseCRS, d->hubCRS_, NN_NO_CHECK(transf)), pair.second); foundOp = true; break; } else { auto concatenated = dynamic_cast< const operation::ConcatenatedOperation *>( op.get()); if (concatenated) { // Case for EPSG:4807 / "NTF (Paris)" that is // made of a longitude rotation followed by a // Helmert The prime meridian shift will be // accounted elsewhere const auto &subops = concatenated->operations(); if (subops.size() == 2) { auto firstOpIsTransformation = dynamic_cast< const operation::Transformation *>( subops[0].get()); auto firstOpIsConversion = dynamic_cast< const operation::Conversion *>( subops[0].get()); if ((firstOpIsTransformation && firstOpIsTransformation ->isLongitudeRotation()) || (dynamic_cast( candidateBaseCRS.get()) && firstOpIsConversion)) { transf = util::nn_dynamic_pointer_cast< operation::Transformation>( subops[1]); if (transf && !starts_with(transf->nameStr(), "Ballpark geo")) { resMatchOfTransfToWGS84 .emplace_back( create(candidateBaseCRS, d->hubCRS_, NN_NO_CHECK(transf)), pair.second); foundOp = true; break; } } } } } } } if (!foundOp) { res.emplace_back( create(candidateBaseCRS, d->hubCRS_, transformation()), std::min(70, pair.second)); } } } } return !resMatchOfTransfToWGS84.empty() ? resMatchOfTransfToWGS84 : res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DerivedGeodeticCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DerivedGeodeticCRS::~DerivedGeodeticCRS() = default; //! @endcond // --------------------------------------------------------------------------- DerivedGeodeticCRS::DerivedGeodeticCRS( const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn) : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} // --------------------------------------------------------------------------- DerivedGeodeticCRS::DerivedGeodeticCRS( const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::SphericalCSNNPtr &csIn) : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} // --------------------------------------------------------------------------- DerivedGeodeticCRS::DerivedGeodeticCRS(const DerivedGeodeticCRS &other) : SingleCRS(other), GeodeticCRS(other), DerivedCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- CRSNNPtr DerivedGeodeticCRS::_shallowClone() const { auto crs(DerivedGeodeticCRS::nn_make_shared(*this)); crs->assignSelf(crs); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeodeticCRS. * * @return the base CRS. */ const GeodeticCRSNNPtr DerivedGeodeticCRS::baseCRS() const { return NN_NO_CHECK(util::nn_dynamic_pointer_cast( DerivedCRS::getPrivate()->baseCRS_)); } // --------------------------------------------------------------------------- /** \brief Instantiate a DerivedGeodeticCRS from a base CRS, a deriving * conversion and a cs::CartesianCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn base CRS. * @param derivingConversionIn the deriving conversion from the base CRS to this * CRS. * @param csIn the coordinate system. * @return new DerivedGeodeticCRS. */ DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CartesianCSNNPtr &csIn) { auto crs(DerivedGeodeticCRS::nn_make_shared( baseCRSIn, derivingConversionIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Instantiate a DerivedGeodeticCRS from a base CRS, a deriving * conversion and a cs::SphericalCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn base CRS. * @param derivingConversionIn the deriving conversion from the base CRS to this * CRS. * @param csIn the coordinate system. * @return new DerivedGeodeticCRS. */ DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::SphericalCSNNPtr &csIn) { auto crs(DerivedGeodeticCRS::nn_make_shared( baseCRSIn, derivingConversionIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { io::FormattingException::Throw( "DerivedGeodeticCRS can only be exported to WKT2"); } formatter->startNode(io::WKTConstants::GEODCRS, !identifiers().empty()); formatter->addQuotedString(nameStr()); auto l_baseCRS = baseCRS(); formatter->startNode((formatter->use2019Keywords() && dynamic_cast(l_baseCRS.get())) ? io::WKTConstants::BASEGEOGCRS : io::WKTConstants::BASEGEODCRS, !baseCRS()->identifiers().empty()); formatter->addQuotedString(l_baseCRS->nameStr()); auto l_datum = l_baseCRS->datum(); if (l_datum) { l_datum->_exportToWKT(formatter); } else { auto l_datumEnsemble = datumEnsemble(); assert(l_datumEnsemble); l_datumEnsemble->_exportToWKT(formatter); } l_baseCRS->primeMeridian()->_exportToWKT(formatter); if (formatter->use2019Keywords() && !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) { l_baseCRS->formatID(formatter); } formatter->endNode(); formatter->setUseDerivingConversion(true); derivingConversionRef()->_exportToWKT(formatter); formatter->setUseDerivingConversion(false); coordinateSystem()->_exportToWKT(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- void DerivedGeodeticCRS::_exportToPROJString( io::PROJStringFormatter *) const // throw(io::FormattingException) { throw io::FormattingException( "DerivedGeodeticCRS cannot be exported to PROJ string"); } // --------------------------------------------------------------------------- bool DerivedGeodeticCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast(other); return otherDerivedCRS != nullptr && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> DerivedGeodeticCRS::_identify(const io::AuthorityFactoryPtr &factory) const { return CRS::_identify(factory); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DerivedGeographicCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DerivedGeographicCRS::~DerivedGeographicCRS() = default; //! @endcond // --------------------------------------------------------------------------- DerivedGeographicCRS::DerivedGeographicCRS( const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::EllipsoidalCSNNPtr &csIn) : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), GeographicCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} // --------------------------------------------------------------------------- DerivedGeographicCRS::DerivedGeographicCRS(const DerivedGeographicCRS &other) : SingleCRS(other), GeographicCRS(other), DerivedCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- CRSNNPtr DerivedGeographicCRS::_shallowClone() const { auto crs(DerivedGeographicCRS::nn_make_shared(*this)); crs->assignSelf(crs); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeographicCRS. * * @return the base CRS. */ const GeodeticCRSNNPtr DerivedGeographicCRS::baseCRS() const { return NN_NO_CHECK(util::nn_dynamic_pointer_cast( DerivedCRS::getPrivate()->baseCRS_)); } // --------------------------------------------------------------------------- /** \brief Instantiate a DerivedGeographicCRS from a base CRS, a deriving * conversion and a cs::EllipsoidalCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn base CRS. * @param derivingConversionIn the deriving conversion from the base CRS to this * CRS. * @param csIn the coordinate system. * @return new DerivedGeographicCRS. */ DerivedGeographicCRSNNPtr DerivedGeographicCRS::create( const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::EllipsoidalCSNNPtr &csIn) { auto crs(DerivedGeographicCRS::nn_make_shared( baseCRSIn, derivingConversionIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { io::FormattingException::Throw( "DerivedGeographicCRS can only be exported to WKT2"); } formatter->startNode(formatter->use2019Keywords() ? io::WKTConstants::GEOGCRS : io::WKTConstants::GEODCRS, !identifiers().empty()); formatter->addQuotedString(nameStr()); auto l_baseCRS = baseCRS(); formatter->startNode((formatter->use2019Keywords() && dynamic_cast(l_baseCRS.get())) ? io::WKTConstants::BASEGEOGCRS : io::WKTConstants::BASEGEODCRS, !l_baseCRS->identifiers().empty()); formatter->addQuotedString(l_baseCRS->nameStr()); l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); l_baseCRS->primeMeridian()->_exportToWKT(formatter); if (formatter->use2019Keywords() && !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) { l_baseCRS->formatID(formatter); } formatter->endNode(); formatter->setUseDerivingConversion(true); derivingConversionRef()->_exportToWKT(formatter); formatter->setUseDerivingConversion(false); coordinateSystem()->_exportToWKT(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- void DerivedGeographicCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { const auto &l_conv = derivingConversionRef(); const auto &methodName = l_conv->method()->nameStr(); for (const char *substr : {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat", "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) { if (starts_with(methodName, substr)) { l_conv->_exportToPROJString(formatter); return; } } if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION) || ci_equal(methodName, PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) { l_conv->_exportToPROJString(formatter); return; } throw io::FormattingException( "DerivedGeographicCRS cannot be exported to PROJ string"); } // --------------------------------------------------------------------------- bool DerivedGeographicCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast(other); return otherDerivedCRS != nullptr && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- /** \brief Return a variant of this CRS "demoted" to a 2D one, if not already * the case. * * * @param newName Name of the new CRS. If empty, nameStr() will be used. * @param dbContext Database context to look for potentially already registered * 2D CRS. May be nullptr. * @return a new CRS demoted to 2D, or the current one if already 2D or not * applicable. * @since 8.1.1 */ DerivedGeographicCRSNNPtr DerivedGeographicCRS::demoteTo2D( const std::string &newName, const io::DatabaseContextPtr &dbContext) const { const auto &axisList = coordinateSystem()->axisList(); if (axisList.size() == 3) { auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), axisList[0], axisList[1]); auto baseGeog2DCRS = util::nn_dynamic_pointer_cast( baseCRS()->demoteTo2D(std::string(), dbContext)); return DerivedGeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, !newName.empty() ? newName : nameStr()), NN_CHECK_THROW(std::move(baseGeog2DCRS)), derivingConversion(), std::move(cs)); } return NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> DerivedGeographicCRS::_identify(const io::AuthorityFactoryPtr &factory) const { return CRS::_identify(factory); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DerivedProjectedCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DerivedProjectedCRS::~DerivedProjectedCRS() = default; //! @endcond // --------------------------------------------------------------------------- DerivedProjectedCRS::DerivedProjectedCRS( const ProjectedCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr &csIn) : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} // --------------------------------------------------------------------------- DerivedProjectedCRS::DerivedProjectedCRS(const DerivedProjectedCRS &other) : SingleCRS(other), DerivedCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- CRSNNPtr DerivedProjectedCRS::_shallowClone() const { auto crs(DerivedProjectedCRS::nn_make_shared(*this)); crs->assignSelf(crs); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Return the base CRS (a ProjectedCRS) of a DerivedProjectedCRS. * * @return the base CRS. */ const ProjectedCRSNNPtr DerivedProjectedCRS::baseCRS() const { return NN_NO_CHECK(util::nn_dynamic_pointer_cast( DerivedCRS::getPrivate()->baseCRS_)); } // --------------------------------------------------------------------------- /** \brief Instantiate a DerivedProjectedCRS from a base CRS, a deriving * conversion and a cs::CS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn base CRS. * @param derivingConversionIn the deriving conversion from the base CRS to this * CRS. * @param csIn the coordinate system. * @return new DerivedProjectedCRS. */ DerivedProjectedCRSNNPtr DerivedProjectedCRS::create( const util::PropertyMap &properties, const ProjectedCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr &csIn) { auto crs(DerivedProjectedCRS::nn_make_shared( baseCRSIn, derivingConversionIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Return a variant of this CRS "demoted" to a 2D one, if not already * the case. * * * @param newName Name of the new CRS. If empty, nameStr() will be used. * @param dbContext Database context to look for potentially already registered * 2D CRS. May be nullptr. * @return a new CRS demoted to 2D, or the current one if already 2D or not * applicable. * @since 9.1.1 */ DerivedProjectedCRSNNPtr DerivedProjectedCRS::demoteTo2D(const std::string &newName, const io::DatabaseContextPtr &dbContext) const { const auto &axisList = coordinateSystem()->axisList(); if (axisList.size() == 3) { auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], axisList[1]); auto baseProj2DCRS = util::nn_dynamic_pointer_cast( baseCRS()->demoteTo2D(std::string(), dbContext)); return DerivedProjectedCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, !newName.empty() ? newName : nameStr()), NN_CHECK_THROW(std::move(baseProj2DCRS)), derivingConversion(), std::move(cs)); } return NN_NO_CHECK(std::dynamic_pointer_cast( shared_from_this().as_nullable())); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2 || !formatter->use2019Keywords()) { io::FormattingException::Throw( "DerivedProjectedCRS can only be exported to WKT2:2019"); } formatter->startNode(io::WKTConstants::DERIVEDPROJCRS, !identifiers().empty()); formatter->addQuotedString(nameStr()); { auto l_baseProjCRS = baseCRS(); formatter->startNode(io::WKTConstants::BASEPROJCRS, !l_baseProjCRS->identifiers().empty()); formatter->addQuotedString(l_baseProjCRS->nameStr()); auto l_baseGeodCRS = l_baseProjCRS->baseCRS(); auto &geodeticCRSAxisList = l_baseGeodCRS->coordinateSystem()->axisList(); formatter->startNode( dynamic_cast(l_baseGeodCRS.get()) ? io::WKTConstants::BASEGEOGCRS : io::WKTConstants::BASEGEODCRS, !l_baseGeodCRS->identifiers().empty()); formatter->addQuotedString(l_baseGeodCRS->nameStr()); l_baseGeodCRS->exportDatumOrDatumEnsembleToWkt(formatter); // insert ellipsoidal cs unit when the units of the map // projection angular parameters are not explicitly given within those // parameters. See // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && !geodeticCRSAxisList.empty()) { geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); } l_baseGeodCRS->primeMeridian()->_exportToWKT(formatter); formatter->endNode(); l_baseProjCRS->derivingConversionRef()->_exportToWKT(formatter); const auto &baseCSAxisList = l_baseProjCRS->coordinateSystem()->axisList(); // Current WKT grammar (as of WKT2 18-010r11) does not allow a // BASEPROJCRS.CS node, but in situations where this is ambiguous, emit // one. Cf WKTParser::Private::buildProjectedCRS() for more details if (!baseCSAxisList.empty() && baseCSAxisList[0]->unit() != common::UnitOfMeasure::METRE && l_baseProjCRS->identifiers().empty()) { bool knownBaseCRS = false; auto &dbContext = formatter->databaseContext(); if (dbContext) { auto authFactory = io::AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string()); auto res = authFactory->createObjectsFromName( l_baseProjCRS->nameStr(), {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, false, 2); if (res.size() == 1) { knownBaseCRS = true; } } if (!knownBaseCRS) { l_baseProjCRS->coordinateSystem()->_exportToWKT(formatter); } } if (identifiers().empty() && !l_baseProjCRS->identifiers().empty()) { l_baseProjCRS->formatID(formatter); } formatter->endNode(); } formatter->setUseDerivingConversion(true); derivingConversionRef()->_exportToWKT(formatter); formatter->setUseDerivingConversion(false); coordinateSystem()->_exportToWKT(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- bool DerivedProjectedCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast(other); return otherDerivedCRS != nullptr && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DerivedProjectedCRS::addUnitConvertAndAxisSwap( io::PROJStringFormatter *formatter) const { ProjectedCRS::addUnitConvertAndAxisSwap(coordinateSystem()->axisList(), formatter, false); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct TemporalCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress TemporalCRS::~TemporalCRS() = default; //! @endcond // --------------------------------------------------------------------------- TemporalCRS::TemporalCRS(const datum::TemporalDatumNNPtr &datumIn, const cs::TemporalCSNNPtr &csIn) : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} // --------------------------------------------------------------------------- TemporalCRS::TemporalCRS(const TemporalCRS &other) : SingleCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- CRSNNPtr TemporalCRS::_shallowClone() const { auto crs(TemporalCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the datum::TemporalDatum associated with the CRS. * * @return a TemporalDatum */ const datum::TemporalDatumNNPtr TemporalCRS::datum() const { return NN_NO_CHECK(std::static_pointer_cast( SingleCRS::getPrivate()->datum)); } // --------------------------------------------------------------------------- /** \brief Return the cs::TemporalCS associated with the CRS. * * @return a TemporalCS */ const cs::TemporalCSNNPtr TemporalCRS::coordinateSystem() const { return util::nn_static_pointer_cast( SingleCRS::getPrivate()->coordinateSystem); } // --------------------------------------------------------------------------- /** \brief Instantiate a TemporalCRS from a datum and a coordinate system. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datumIn the datum. * @param csIn the coordinate system. * @return new TemporalCRS. */ TemporalCRSNNPtr TemporalCRS::create(const util::PropertyMap &properties, const datum::TemporalDatumNNPtr &datumIn, const cs::TemporalCSNNPtr &csIn) { auto crs(TemporalCRS::nn_make_shared(datumIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void TemporalCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { io::FormattingException::Throw( "TemporalCRS can only be exported to WKT2"); } formatter->startNode(io::WKTConstants::TIMECRS, !identifiers().empty()); formatter->addQuotedString(nameStr()); datum()->_exportToWKT(formatter); coordinateSystem()->_exportToWKT(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void TemporalCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("TemporalCRS", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("datum"); formatter->setOmitTypeInImmediateChild(); datum()->_exportToJSON(formatter); writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- bool TemporalCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherTemporalCRS = dynamic_cast(other); return otherTemporalCRS != nullptr && SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct EngineeringCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress EngineeringCRS::~EngineeringCRS() = default; //! @endcond // --------------------------------------------------------------------------- EngineeringCRS::EngineeringCRS(const datum::EngineeringDatumNNPtr &datumIn, const cs::CoordinateSystemNNPtr &csIn) : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(std::make_unique()) {} // --------------------------------------------------------------------------- EngineeringCRS::EngineeringCRS(const EngineeringCRS &other) : SingleCRS(other), d(std::make_unique(*(other.d))) {} // --------------------------------------------------------------------------- CRSNNPtr EngineeringCRS::_shallowClone() const { auto crs(EngineeringCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the datum::EngineeringDatum associated with the CRS. * * @return a EngineeringDatum */ const datum::EngineeringDatumNNPtr EngineeringCRS::datum() const { return NN_NO_CHECK(std::static_pointer_cast( SingleCRS::getPrivate()->datum)); } // --------------------------------------------------------------------------- /** \brief Instantiate a EngineeringCRS from a datum and a coordinate system. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datumIn the datum. * @param csIn the coordinate system. * @return new EngineeringCRS. */ EngineeringCRSNNPtr EngineeringCRS::create(const util::PropertyMap &properties, const datum::EngineeringDatumNNPtr &datumIn, const cs::CoordinateSystemNNPtr &csIn) { auto crs(EngineeringCRS::nn_make_shared(datumIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::ENGCRS : io::WKTConstants::LOCAL_CS, !identifiers().empty()); formatter->addQuotedString(nameStr()); const auto &datumName = datum()->nameStr(); if (isWKT2 || (!datumName.empty() && datumName != UNKNOWN_ENGINEERING_DATUM)) { datum()->_exportToWKT(formatter); } if (!isWKT2) { coordinateSystem()->axisList()[0]->unit()._exportToWKT(formatter); } const auto oldAxisOutputRule = formatter->outputAxis(); formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES); coordinateSystem()->_exportToWKT(formatter); formatter->setOutputAxis(oldAxisOutputRule); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void EngineeringCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("EngineeringCRS", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("datum"); formatter->setOmitTypeInImmediateChild(); datum()->_exportToJSON(formatter); writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- bool EngineeringCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherEngineeringCRS = dynamic_cast(other); if (otherEngineeringCRS == nullptr || (criterion == util::IComparable::Criterion::STRICT && !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } // Check datum const auto &thisDatum = datum(); const auto &otherDatum = otherEngineeringCRS->datum(); if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion, dbContext)) { return false; } // Check coordinate system const auto &thisCS = coordinateSystem(); const auto &otherCS = otherEngineeringCRS->coordinateSystem(); if (!(thisCS->_isEquivalentTo(otherCS.get(), criterion, dbContext))) { const auto thisCartCS = dynamic_cast(thisCS.get()); const auto otherCartCS = dynamic_cast(otherCS.get()); const auto &thisAxisList = thisCS->axisList(); const auto &otherAxisList = otherCS->axisList(); // Check particular case of // https://github.com/r-spatial/sf/issues/2049#issuecomment-1486600723 if (criterion != util::IComparable::Criterion::STRICT && thisCartCS && otherCartCS && thisAxisList.size() == 2 && otherAxisList.size() == 2 && ((&thisAxisList[0]->direction() == &cs::AxisDirection::UNSPECIFIED && &thisAxisList[1]->direction() == &cs::AxisDirection::UNSPECIFIED) || (&otherAxisList[0]->direction() == &cs::AxisDirection::UNSPECIFIED && &otherAxisList[1]->direction() == &cs::AxisDirection::UNSPECIFIED)) && ((thisAxisList[0]->nameStr() == "X" && otherAxisList[0]->nameStr() == "Easting" && thisAxisList[1]->nameStr() == "Y" && otherAxisList[1]->nameStr() == "Northing") || (otherAxisList[0]->nameStr() == "X" && thisAxisList[0]->nameStr() == "Easting" && otherAxisList[1]->nameStr() == "Y" && thisAxisList[1]->nameStr() == "Northing"))) { return true; } return false; } return true; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct ParametricCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress ParametricCRS::~ParametricCRS() = default; //! @endcond // --------------------------------------------------------------------------- ParametricCRS::ParametricCRS(const datum::ParametricDatumNNPtr &datumIn, const cs::ParametricCSNNPtr &csIn) : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} // --------------------------------------------------------------------------- ParametricCRS::ParametricCRS(const ParametricCRS &other) : SingleCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- CRSNNPtr ParametricCRS::_shallowClone() const { auto crs(ParametricCRS::nn_make_shared(*this)); crs->assignSelf(crs); return crs; } // --------------------------------------------------------------------------- /** \brief Return the datum::ParametricDatum associated with the CRS. * * @return a ParametricDatum */ const datum::ParametricDatumNNPtr ParametricCRS::datum() const { return NN_NO_CHECK(std::static_pointer_cast( SingleCRS::getPrivate()->datum)); } // --------------------------------------------------------------------------- /** \brief Return the cs::TemporalCS associated with the CRS. * * @return a TemporalCS */ const cs::ParametricCSNNPtr ParametricCRS::coordinateSystem() const { return util::nn_static_pointer_cast( SingleCRS::getPrivate()->coordinateSystem); } // --------------------------------------------------------------------------- /** \brief Instantiate a ParametricCRS from a datum and a coordinate system. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param datumIn the datum. * @param csIn the coordinate system. * @return new ParametricCRS. */ ParametricCRSNNPtr ParametricCRS::create(const util::PropertyMap &properties, const datum::ParametricDatumNNPtr &datumIn, const cs::ParametricCSNNPtr &csIn) { auto crs(ParametricCRS::nn_make_shared(datumIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ParametricCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { io::FormattingException::Throw( "ParametricCRS can only be exported to WKT2"); } formatter->startNode(io::WKTConstants::PARAMETRICCRS, !identifiers().empty()); formatter->addQuotedString(nameStr()); datum()->_exportToWKT(formatter); coordinateSystem()->_exportToWKT(formatter); ObjectUsage::baseExportToWKT(formatter); formatter->endNode(); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void ParametricCRS::_exportToJSON( io::JSONFormatter *formatter) const // throw(io::FormattingException) { auto writer = formatter->writer(); auto objectContext( formatter->MakeObjectContext("ParametricCRS", !identifiers().empty())); writer->AddObjKey("name"); const auto &l_name = nameStr(); if (l_name.empty()) { writer->Add("unnamed"); } else { writer->Add(l_name); } writer->AddObjKey("datum"); formatter->setOmitTypeInImmediateChild(); datum()->_exportToJSON(formatter); writer->AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); } //! @endcond // --------------------------------------------------------------------------- bool ParametricCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherParametricCRS = dynamic_cast(other); return otherParametricCRS != nullptr && SingleCRS::baseIsEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct DerivedVerticalCRS::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DerivedVerticalCRS::~DerivedVerticalCRS() = default; //! @endcond // --------------------------------------------------------------------------- DerivedVerticalCRS::DerivedVerticalCRS( const VerticalCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::VerticalCSNNPtr &csIn) : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), VerticalCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} // --------------------------------------------------------------------------- DerivedVerticalCRS::DerivedVerticalCRS(const DerivedVerticalCRS &other) : SingleCRS(other), VerticalCRS(other), DerivedCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- CRSNNPtr DerivedVerticalCRS::_shallowClone() const { auto crs(DerivedVerticalCRS::nn_make_shared(*this)); crs->assignSelf(crs); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- /** \brief Return the base CRS (a VerticalCRS) of a DerivedVerticalCRS. * * @return the base CRS. */ const VerticalCRSNNPtr DerivedVerticalCRS::baseCRS() const { return NN_NO_CHECK(util::nn_dynamic_pointer_cast( DerivedCRS::getPrivate()->baseCRS_)); } // --------------------------------------------------------------------------- /** \brief Instantiate a DerivedVerticalCRS from a base CRS, a deriving * conversion and a cs::VerticalCS. * * @param properties See \ref general_properties. * At minimum the name should be defined. * @param baseCRSIn base CRS. * @param derivingConversionIn the deriving conversion from the base CRS to this * CRS. * @param csIn the coordinate system. * @return new DerivedVerticalCRS. */ DerivedVerticalCRSNNPtr DerivedVerticalCRS::create( const util::PropertyMap &properties, const VerticalCRSNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const cs::VerticalCSNNPtr &csIn) { auto crs(DerivedVerticalCRS::nn_make_shared( baseCRSIn, derivingConversionIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress void DerivedVerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { bool useBaseMethod = true; const DerivedVerticalCRS *dvcrs = this; while (true) { // If the derived vertical CRS is obtained through simple conversion // methods that just do unit change or height/depth reversal, export // it as a regular VerticalCRS const int methodCode = dvcrs->derivingConversionRef()->method()->getEPSGCode(); if (methodCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT || methodCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR || methodCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { dvcrs = dynamic_cast(baseCRS().get()); if (dvcrs == nullptr) { break; } } else { useBaseMethod = false; break; } } if (useBaseMethod) { VerticalCRS::_exportToWKT(formatter); return; } io::FormattingException::Throw( "DerivedVerticalCRS can only be exported to WKT2"); } baseExportToWKT(formatter, io::WKTConstants::VERTCRS, io::WKTConstants::BASEVERTCRS); } //! @endcond // --------------------------------------------------------------------------- void DerivedVerticalCRS::_exportToPROJString( io::PROJStringFormatter *) const // throw(io::FormattingException) { throw io::FormattingException( "DerivedVerticalCRS cannot be exported to PROJ string"); } // --------------------------------------------------------------------------- bool DerivedVerticalCRS::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast(other); return otherDerivedCRS != nullptr && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list> DerivedVerticalCRS::_identify(const io::AuthorityFactoryPtr &factory) const { return CRS::_identify(factory); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress template struct DerivedCRSTemplate::Private {}; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress template DerivedCRSTemplate::~DerivedCRSTemplate() = default; //! @endcond // --------------------------------------------------------------------------- template DerivedCRSTemplate::DerivedCRSTemplate( const BaseNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn) : SingleCRS(baseCRSIn->datum().as_nullable(), nullptr, csIn), BaseType(baseCRSIn->datum(), csIn), DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} // --------------------------------------------------------------------------- template DerivedCRSTemplate::DerivedCRSTemplate( const DerivedCRSTemplate &other) : SingleCRS(other), BaseType(other), DerivedCRS(other), d(nullptr) {} // --------------------------------------------------------------------------- template const typename DerivedCRSTemplate::BaseNNPtr DerivedCRSTemplate::baseCRS() const { auto l_baseCRS = DerivedCRS::getPrivate()->baseCRS_; return NN_NO_CHECK(util::nn_dynamic_pointer_cast(l_baseCRS)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress template CRSNNPtr DerivedCRSTemplate::_shallowClone() const { auto crs(DerivedCRSTemplate::nn_make_shared(*this)); crs->assignSelf(crs); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- template typename DerivedCRSTemplate::NNPtr DerivedCRSTemplate::create( const util::PropertyMap &properties, const BaseNNPtr &baseCRSIn, const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn) { auto crs(DerivedCRSTemplate::nn_make_shared( baseCRSIn, derivingConversionIn, csIn)); crs->assignSelf(crs); crs->setProperties(properties); crs->setDerivingConversionCRS(); return crs; } // --------------------------------------------------------------------------- template const char *DerivedCRSTemplate::className() const { return DerivedCRSTraits::CRSName().c_str(); } // --------------------------------------------------------------------------- static void DerivedCRSTemplateCheckExportToWKT(io::WKTFormatter *formatter, const std::string &crsName, bool wkt2_2019_only) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2 || (wkt2_2019_only && !formatter->use2019Keywords())) { io::FormattingException::Throw(crsName + " can only be exported to WKT2" + (wkt2_2019_only ? ":2019" : "")); } } // --------------------------------------------------------------------------- template void DerivedCRSTemplate::_exportToWKT( io::WKTFormatter *formatter) const { DerivedCRSTemplateCheckExportToWKT(formatter, DerivedCRSTraits::CRSName(), DerivedCRSTraits::wkt2_2019_only); baseExportToWKT(formatter, DerivedCRSTraits::WKTKeyword(), DerivedCRSTraits::WKTBaseKeyword()); } // --------------------------------------------------------------------------- template bool DerivedCRSTemplate::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion, const io::DatabaseContextPtr &dbContext) const { auto otherDerivedCRS = dynamic_cast(other); return otherDerivedCRS != nullptr && DerivedCRS::_isEquivalentTo(other, criterion, dbContext); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string STRING_DerivedEngineeringCRS("DerivedEngineeringCRS"); const std::string &DerivedEngineeringCRSTraits::CRSName() { return STRING_DerivedEngineeringCRS; } const std::string &DerivedEngineeringCRSTraits::WKTKeyword() { return io::WKTConstants::ENGCRS; } const std::string &DerivedEngineeringCRSTraits::WKTBaseKeyword() { return io::WKTConstants::BASEENGCRS; } template class DerivedCRSTemplate; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string STRING_DerivedParametricCRS("DerivedParametricCRS"); const std::string &DerivedParametricCRSTraits::CRSName() { return STRING_DerivedParametricCRS; } const std::string &DerivedParametricCRSTraits::WKTKeyword() { return io::WKTConstants::PARAMETRICCRS; } const std::string &DerivedParametricCRSTraits::WKTBaseKeyword() { return io::WKTConstants::BASEPARAMCRS; } template class DerivedCRSTemplate; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const std::string STRING_DerivedTemporalCRS("DerivedTemporalCRS"); const std::string &DerivedTemporalCRSTraits::CRSName() { return STRING_DerivedTemporalCRS; } const std::string &DerivedTemporalCRSTraits::WKTKeyword() { return io::WKTConstants::TIMECRS; } const std::string &DerivedTemporalCRSTraits::WKTBaseKeyword() { return io::WKTConstants::BASETIMECRS; } template class DerivedCRSTemplate; //! @endcond // --------------------------------------------------------------------------- } // namespace crs NS_PROJ_END proj-9.8.1/src/strtod.cpp000664 001750 001750 00000013746 15166171715 015300 0ustar00eveneven000000 000000 /****************************************************************************** * * Derived from GDAL port/cpl_strtod.cpp * Purpose: Functions to convert ASCII string to floating point number. * Author: Andrey Kiselev, dron@ak4719.spb.edu. * ****************************************************************************** * Copyright (c) 2006, Andrey Kiselev * Copyright (c) 2008-2012, Even Rouault * * 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 #include #include #include #include "proj.h" #include "proj_config.h" #include "proj_internal.h" /************************************************************************/ /* pj_atof() */ /************************************************************************/ /** * Converts ASCII string to floating point number. * * This function converts the initial portion of the string pointed to * by nptr to double floating point representation. The behavior is the * same as * * pj_strtod(nptr, nullptr); * * This function does the same as standard atof(3), but does not take * locale in account. That means, the decimal delimiter is always '.' * (decimal point). * * @param nptr Pointer to string to convert. * * @return Converted value. */ double pj_atof(const char *nptr) { return pj_strtod(nptr, nullptr); } /************************************************************************/ /* replace_point_by_locale_point() */ /************************************************************************/ static char *replace_point_by_locale_point(const char *pszNumber, char point) { #if !defined(HAVE_LOCALECONV) #if defined(_MSC_VER) /* Visual C++ */ #pragma message("localeconv not available") #else #warning "localeconv not available" #endif static char byPoint = 0; if (byPoint == 0) { char szBuf[16]; snprintf(szBuf, sizeof(szBuf), "%.1f", 1.0); byPoint = szBuf[1]; } if (point != byPoint) { const char *pszPoint = strchr(pszNumber, point); if (pszPoint) { char *pszNew = pj_strdup(pszNumber); if (!pszNew) return nullptr; pszNew[pszPoint - pszNumber] = byPoint; return pszNew; } } return nullptr; #else const struct lconv *poLconv = localeconv(); if (poLconv && poLconv->decimal_point && poLconv->decimal_point[0] != '\0') { char byPoint = poLconv->decimal_point[0]; if (point != byPoint) { const char *pszLocalePoint = strchr(pszNumber, byPoint); const char *pszPoint = strchr(pszNumber, point); if (pszPoint || pszLocalePoint) { char *pszNew = pj_strdup(pszNumber); if (!pszNew) return nullptr; if (pszLocalePoint) pszNew[pszLocalePoint - pszNumber] = ' '; if (pszPoint) pszNew[pszPoint - pszNumber] = byPoint; return pszNew; } } } return nullptr; #endif } /************************************************************************/ /* pj_strtod() */ /************************************************************************/ /** * Converts ASCII string to floating point number. * * This function converts the initial portion of the string pointed to * by nptr to double floating point representation. This function does the * same as standard strtod(3), but does not take locale in account and use * decimal point. * * @param nptr Pointer to string to convert. * @param endptr If is not NULL, a pointer to the character after the last * character used in the conversion is stored in the location referenced * by endptr. * * @return Converted value. */ double pj_strtod(const char *nptr, char **endptr) { /* -------------------------------------------------------------------- */ /* We are implementing a simple method here: copy the input string */ /* into the temporary buffer, replace the specified decimal delimiter (.) */ /* with the one taken from locale settings (ex ',') and then use standard * strtod() */ /* on that buffer. */ /* -------------------------------------------------------------------- */ char *pszNumber = replace_point_by_locale_point(nptr, '.'); if (pszNumber) { char *pszNumberEnd; double dfValue = strtod(pszNumber, &pszNumberEnd); int nError = errno; if (endptr) { ptrdiff_t offset = pszNumberEnd - pszNumber; *endptr = const_cast(nptr + offset); } free(pszNumber); errno = nError; return dfValue; } else { return strtod(nptr, endptr); } } proj-9.8.1/src/aasincos.cpp000664 001750 001750 00000001644 15166171715 015553 0ustar00eveneven000000 000000 /* arc sin, cosine, tan2 and sqrt that will NOT fail */ #include #include "proj.h" #include "proj_internal.h" #define ONE_TOL 1.00000000000001 #define ATOL 1e-50 double aasin(PJ_CONTEXT *ctx, double v) { double av; if ((av = fabs(v)) >= 1.) { if (av > ONE_TOL) proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return (v < 0. ? -M_HALFPI : M_HALFPI); } return asin(v); } double aacos(PJ_CONTEXT *ctx, double v) { double av; if ((av = fabs(v)) >= 1.) { if (av > ONE_TOL) proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return (v < 0. ? M_PI : 0.); } return acos(v); } double asqrt(double v) { return ((v <= 0) ? 0. : sqrt(v)); } double aatan2(double n, double d) { return ((fabs(n) < ATOL && fabs(d) < ATOL) ? 0. : atan2(n, d)); } proj-9.8.1/src/embedded_resources.h000664 001750 001750 00000000561 15166171715 017240 0ustar00eveneven000000 000000 #ifndef EMBEDDED_RESOURCES_H #define EMBEDDED_RESOURCES_H #ifdef __cplusplus extern "C" { #endif const unsigned char *pj_get_embedded_proj_db(unsigned int *pnSize); const unsigned char *pj_get_embedded_resource(const char *filename, unsigned int *pnSize); #ifdef __cplusplus } #endif #endif /* EMBEDDED_RESOURCES_H */ proj-9.8.1/src/rtodms.cpp000664 001750 001750 00000005056 15166171715 015264 0ustar00eveneven000000 000000 /* Convert radian argument to DMS ascii format */ #include #include #include #include #include "proj.h" #include "proj_internal.h" /* ** RES is fractional second figures ** RES60 = 60 * RES ** CONV = 180 * 3600 * RES / PI (radians to RES seconds) */ static double RES = 1000., RES60 = 60000., CONV = 206264806.24709635516; static char format[50] = "%dd%d'%.3f\"%c"; static int dolong = 0; void set_rtodms(int fract, int con_w) { int i; if (fract >= 0 && fract < 9) { RES = 1.; /* following not very elegant, but used infrequently */ for (i = 0; i < fract; ++i) RES *= 10.; RES60 = RES * 60.; CONV = 180. * 3600. * RES / M_PI; if (!con_w) (void)snprintf(format, sizeof(format), "%%dd%%d'%%.%df\"%%c", fract); else (void)snprintf(format, sizeof(format), "%%dd%%02d'%%0%d.%df\"%%c", fract + 2 + (fract ? 1 : 0), fract); dolong = con_w; } } char *rtodms(char *s, size_t sizeof_s, double r, int pos, int neg) { int deg, min, sign; char *ss = s; double sec; size_t sizeof_ss = sizeof_s; if (r < 0) { r = -r; if (!pos) { if (sizeof_s == 1) { *s = 0; return s; } sizeof_ss--; *ss++ = '-'; sign = 0; } else sign = neg; } else sign = pos; r = floor(r * CONV + .5); sec = fmod(r / RES, 60.); r = floor(r / RES60); min = (int)fmod(r, 60.); r = floor(r / 60.); deg = (int)r; if (dolong) (void)snprintf(ss, sizeof_ss, format, deg, min, sec, sign); else if (sec != 0.0) { char *p, *q; /* double prime + pos/neg suffix (if included) + NUL */ size_t suffix_len = sign ? 3 : 2; (void)snprintf(ss, sizeof_ss, format, deg, min, sec, sign); /* Replace potential decimal comma by decimal point for non C locale */ for (p = ss; *p != '\0'; ++p) { if (*p == ',') { *p = '.'; break; } } if (suffix_len > strlen(ss)) return s; for (q = p = ss + strlen(ss) - suffix_len; *p == '0'; --p) ; if (*p != '.') ++p; if (++q != p) (void)memmove(p, q, suffix_len); } else if (min) (void)snprintf(ss, sizeof_ss, "%dd%d'%c", deg, min, sign); else (void)snprintf(ss, sizeof_ss, "%dd%c", deg, sign); return s; } proj-9.8.1/src/trans_bounds.cpp000664 001750 001750 00000117762 15166171715 016465 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Implements proj_trans_bounds() * * Author: Alan D. Snow * ****************************************************************************** * Copyright (c) 2021, Alan D. Snow * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj/internal/internal.hpp" #include "proj_internal.h" #include #include #include #include using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- static double simple_min(const double *data, const int arr_len) { double min_value = data[0]; for (int iii = 1; iii < arr_len; iii++) { if (data[iii] < min_value) min_value = data[iii]; } return min_value; } // --------------------------------------------------------------------------- static double simple_max(const double *data, const int arr_len) { double max_value = data[0]; for (int iii = 1; iii < arr_len; iii++) { if ((data[iii] > max_value || max_value == HUGE_VAL) && data[iii] != HUGE_VAL) max_value = data[iii]; } return max_value; } // --------------------------------------------------------------------------- static int find_previous_index(const int iii, const double *data, const int arr_len) { // find index of nearest valid previous value if exists int prev_iii = iii - 1; if (prev_iii == -1) // handle wraparound prev_iii = arr_len - 1; while (data[prev_iii] == HUGE_VAL && prev_iii != iii) { prev_iii--; if (prev_iii == -1) // handle wraparound prev_iii = arr_len - 1; } return prev_iii; } // --------------------------------------------------------------------------- /****************************************************************************** Handles the case when longitude values cross the antimeridian when calculating the minimum. Note: The data array must be in a linear ring. Note: This requires a densified ring with at least 2 additional points per edge to correctly handle global extents. If only 1 additional point: | | |RL--x0--|RL-- | | -180 180|-180 If they are evenly spaced and it crosses the antimeridian: x0 - L = 180 R - x0 = -180 For example: Let R = -179.9, x0 = 0.1, L = -179.89 x0 - L = 0.1 - -179.9 = 180 R - x0 = -179.89 - 0.1 ~= -180 This is the same in the case when it didn't cross the antimeridian. If you have 2 additional points: | | |RL--x0--x1--|RL-- | | -180 180|-180 If they are evenly spaced and it crosses the antimeridian: x0 - L = 120 x1 - x0 = 120 R - x1 = -240 For example: Let R = -179.9, x0 = -59.9, x1 = 60.1 L = -179.89 x0 - L = 59.9 - -179.9 = 120 x1 - x0 = 60.1 - 59.9 = 120 R - x1 = -179.89 - 60.1 ~= -240 However, if they are evenly spaced and it didn't cross the antimeridian: x0 - L = 120 x1 - x0 = 120 R - x1 = 120 From this, we have a delta that is guaranteed to be significantly large enough to tell the difference reguarless of the direction the antimeridian was crossed. However, even though the spacing was even in the source projection, it isn't guaranteed in the target geographic projection. So, instead of 240, 200 is used as it significantly larger than 120 to be sure that the antimeridian was crossed but smalller than 240 to account for possible irregularities in distances when re-projecting. Also, 200 ensures latitudes are ignored for axis order handling. ******************************************************************************/ static double antimeridian_min(const double *data, const int arr_len) { double positive_min = HUGE_VAL; double min_value = HUGE_VAL; int crossed_meridian_count = 0; bool positive_meridian = false; for (int iii = 0; iii < arr_len; iii++) { if (data[iii] == HUGE_VAL) continue; int prev_iii = find_previous_index(iii, data, arr_len); // check if crossed meridian double delta = data[prev_iii] - data[iii]; // 180 -> -180 if (delta >= 200 && delta != HUGE_VAL) { if (crossed_meridian_count == 0) positive_min = min_value; crossed_meridian_count++; positive_meridian = false; // -180 -> 180 } else if (delta <= -200 && delta != HUGE_VAL) { if (crossed_meridian_count == 0) positive_min = data[iii]; crossed_meridian_count++; positive_meridian = true; } // positive meridian side min if (positive_meridian && data[iii] < positive_min) positive_min = data[iii]; // track general min value if (data[iii] < min_value) min_value = data[iii]; } if (crossed_meridian_count == 2) return positive_min; else if (crossed_meridian_count == 4) // bounds extends beyond -180/180 return -180; return min_value; } // --------------------------------------------------------------------------- // Handles the case when longitude values cross the antimeridian // when calculating the minimum. // Note: The data array must be in a linear ring. // Note: This requires a densified ring with at least 2 additional // points per edge to correctly handle global extents. // See antimeridian_min docstring for reasoning. static double antimeridian_max(const double *data, const int arr_len) { double negative_max = -HUGE_VAL; double max_value = -HUGE_VAL; bool negative_meridian = false; int crossed_meridian_count = 0; for (int iii = 0; iii < arr_len; iii++) { if (data[iii] == HUGE_VAL) continue; int prev_iii = find_previous_index(iii, data, arr_len); // check if crossed meridian double delta = data[prev_iii] - data[iii]; // 180 -> -180 if (delta >= 200 && delta != HUGE_VAL) { if (crossed_meridian_count == 0) negative_max = data[iii]; crossed_meridian_count++; negative_meridian = true; // -180 -> 180 } else if (delta <= -200 && delta != HUGE_VAL) { if (crossed_meridian_count == 0) negative_max = max_value; negative_meridian = false; crossed_meridian_count++; } // negative meridian side max if (negative_meridian && (data[iii] > negative_max || negative_max == HUGE_VAL) && data[iii] != HUGE_VAL) negative_max = data[iii]; // track general max value if ((data[iii] > max_value || max_value == HUGE_VAL) && data[iii] != HUGE_VAL) max_value = data[iii]; } if (crossed_meridian_count == 2) return negative_max; else if (crossed_meridian_count == 4) // bounds extends beyond -180/180 return 180; return max_value; } // --------------------------------------------------------------------------- // Check if the original projected bounds contains // the north pole. // This assumes that the destination CRS is geographic. static bool contains_north_pole(PJ *projobj, PJ_DIRECTION pj_direction, const double xmin, const double ymin, const double xmax, const double ymax, bool lon_lat_order) { double pole_y = 90; double pole_x = 0; if (!lon_lat_order) { pole_y = 0; pole_x = 90; } proj_trans_generic(projobj, pj_opposite_direction(pj_direction), &pole_x, sizeof(double), 1, &pole_y, sizeof(double), 1, nullptr, sizeof(double), 0, nullptr, sizeof(double), 0); if (xmin < pole_x && pole_x < xmax && ymax > pole_y && pole_y > ymin) return true; return false; } // --------------------------------------------------------------------------- // Check if the original projected bounds contains // the south pole. // This assumes that the destination CRS is geographic. static bool contains_south_pole(PJ *projobj, PJ_DIRECTION pj_direction, const double xmin, const double ymin, const double xmax, const double ymax, bool lon_lat_order) { double pole_y = -90; double pole_x = 0; if (!lon_lat_order) { pole_y = 0; pole_x = -90; } proj_trans_generic(projobj, pj_opposite_direction(pj_direction), &pole_x, sizeof(double), 1, &pole_y, sizeof(double), 1, nullptr, sizeof(double), 0, nullptr, sizeof(double), 0); if (xmin < pole_x && pole_x < xmax && ymax > pole_y && pole_y > ymin) return true; return false; } // --------------------------------------------------------------------------- // Returns source_crs of pj if pj_direction == PJ_FWD, else target_crs // Return must be freed with proj_destroy() // May return nullptr is there is no CRS attached to the PJ* object static PJ *get_input_crs(PJ_CONTEXT *transformer_ctx, PJ *transformer_pj, PJ_DIRECTION pj_direction) { if (pj_direction == PJ_FWD) return proj_get_source_crs(transformer_ctx, transformer_pj); else return proj_get_target_crs(transformer_ctx, transformer_pj); } // --------------------------------------------------------------------------- // Returns target_crs of pj if pj_direction == PJ_FWD, else source_crs // Return must be freed with proj_destroy() // May return nullptr is there is no CRS attached to the PJ* object static PJ *get_output_crs(PJ_CONTEXT *transformer_ctx, PJ *transformer_pj, PJ_DIRECTION pj_direction) { if (pj_direction == PJ_FWD) return proj_get_target_crs(transformer_ctx, transformer_pj); else return proj_get_source_crs(transformer_ctx, transformer_pj); } // --------------------------------------------------------------------------- static bool is_geocentric(PJ *crs) { return proj_get_type(crs) == PJ_TYPE_GEOCENTRIC_CRS; } // --------------------------------------------------------------------------- // Check if the target CRS of the transformation // has the longitude latitude axis order. // This assumes that the destination CRS is geographic. static int target_crs_lon_lat_order(PJ_CONTEXT *transformer_ctx, PJ *transformer_pj, PJ_DIRECTION pj_direction) { PJ *target_crs = get_output_crs(transformer_ctx, transformer_pj, pj_direction); if (target_crs == nullptr) { const char *proj_string = proj_as_proj_string( transformer_ctx, transformer_pj, PJ_PROJ_5, nullptr); if (pj_direction == PJ_FWD) { if (ends_with(proj_string, "+step +proj=unitconvert +xy_in=rad +xy_out=deg")) { return true; } if (ends_with(proj_string, "+step +proj=unitconvert +xy_in=rad +xy_out=deg " "+step +proj=axisswap +order=2,1")) { return false; } } else { if (starts_with(proj_string, "+proj=pipeline +step +proj=unitconvert +xy_in=deg " "+xy_out=rad")) { return true; } if (starts_with(proj_string, "+proj=pipeline +step +proj=axisswap +order=2,1 " "+step +proj=unitconvert +xy_in=deg +xy_out=rad")) { return false; } } proj_context_log_debug(transformer_ctx, "Unable to retrieve target CRS"); return -1; } PJ *coord_system_pj; if (proj_get_type(target_crs) == PJ_TYPE_COMPOUND_CRS) { PJ *horiz_crs = proj_crs_get_sub_crs(transformer_ctx, target_crs, 0); if (!horiz_crs) return -1; coord_system_pj = proj_crs_get_coordinate_system(transformer_ctx, horiz_crs); proj_destroy(horiz_crs); } else { coord_system_pj = proj_crs_get_coordinate_system(transformer_ctx, target_crs); } proj_destroy(target_crs); if (coord_system_pj == nullptr) { proj_context_log_debug(transformer_ctx, "Unable to get target CRS coordinate system."); return -1; } const char *abbrev = nullptr; int success = proj_cs_get_axis_info(transformer_ctx, coord_system_pj, 0, nullptr, &abbrev, nullptr, nullptr, nullptr, nullptr, nullptr); proj_destroy(coord_system_pj); if (success != 1) return -1; return strcmp(abbrev, "lon") == 0 || strcmp(abbrev, "Lon") == 0; } // --------------------------------------------------------------------------- /** \brief Transform boundary. * * Transform boundary densifying the edges to account for nonlinear * transformations along these edges and extracting the outermost bounds. * * If the destination CRS is geographic, the first axis is longitude, * and *out_xmax < *out_xmin then the bounds crossed the antimeridian. * In this scenario there are two polygons, one on each side of the * antimeridian. The first polygon should be constructed with * (*out_xmin, *out_ymin, 180, ymax) and the second with * (-180, *out_ymin, *out_xmax, *out_ymax). * * If the destination CRS is geographic, the first axis is latitude, * and *out_ymax < *out_ymin then the bounds crossed the antimeridian. * In this scenario there are two polygons, one on each side of the * antimeridian. The first polygon should be constructed with * (*out_ymin, *out_xmin, *out_ymax, 180) and the second with * (*out_ymin, -180, *out_ymax, *out_xmax). * * For transformations involving a 3D CRS, consult proj_trans_bounds_3D(). * * @param context The PJ_CONTEXT object. * @param P The PJ object representing the transformation. * @param direction The direction of the transformation. * @param xmin Minimum bounding coordinate of the first axis in source CRS * (target CRS if direction is inverse). * @param ymin Minimum bounding coordinate of the second axis in source CRS. * (target CRS if direction is inverse). * @param xmax Maximum bounding coordinate of the first axis in source CRS. * (target CRS if direction is inverse). * @param ymax Maximum bounding coordinate of the second axis in source CRS. * (target CRS if direction is inverse). * @param out_xmin Minimum bounding coordinate of the first axis in target CRS * (source CRS if direction is inverse). * @param out_ymin Minimum bounding coordinate of the second axis in target CRS. * (source CRS if direction is inverse). * @param out_xmax Maximum bounding coordinate of the first axis in target CRS. * (source CRS if direction is inverse). * @param out_ymax Maximum bounding coordinate of the second axis in target CRS. * (source CRS if direction is inverse). * @param densify_pts Recommended to use 21. This is the number of points * to use to densify the bounding polygon in the transformation. * @return an integer. 1 if successful. 0 if failures encountered. * @since 8.2 * @see proj_trans_bounds_3D() */ int proj_trans_bounds(PJ_CONTEXT *context, PJ *P, PJ_DIRECTION direction, const double xmin, const double ymin, const double xmax, const double ymax, double *out_xmin, double *out_ymin, double *out_xmax, double *out_ymax, const int densify_pts) { *out_xmin = HUGE_VAL; *out_ymin = HUGE_VAL; *out_xmax = HUGE_VAL; *out_ymax = HUGE_VAL; if (P == nullptr) { proj_log_error(P, _("NULL P object not allowed.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } if (densify_pts < 0 || densify_pts > 10000) { proj_log_error(P, _("densify_pts must be between 0-10000.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } PJ_PROJ_INFO pj_info = proj_pj_info(P); if (pj_info.id == nullptr) { proj_log_error(P, _("NULL transformation not allowed,")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } if (strcmp(pj_info.id, "noop") == 0 || direction == PJ_IDENT) { *out_xmin = xmin; *out_xmax = xmax; *out_ymin = ymin; *out_ymax = ymax; return true; } bool degree_output = proj_degree_output(P, direction) != 0; bool degree_input = proj_degree_input(P, direction) != 0; if (degree_output && densify_pts < 2) { proj_log_error( P, _("densify_pts must be at least 2 if the output is geographic.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } int side_pts = densify_pts + 1; // add one because we are densifying const int boundary_len = side_pts * 4; std::vector x_boundary_array; std::vector y_boundary_array; try { x_boundary_array.resize(boundary_len); y_boundary_array.resize(boundary_len); } catch (const std::exception &e) // memory allocation failure { proj_log_error(P, e.what()); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } double delta_x = 0; double delta_y = 0; bool north_pole_in_bounds = false; bool south_pole_in_bounds = false; bool input_lon_lat_order = false; bool output_lon_lat_order = false; if (degree_input) { int in_order_lon_lat = target_crs_lon_lat_order( context, P, pj_opposite_direction(direction)); if (in_order_lon_lat == -1) return false; input_lon_lat_order = in_order_lon_lat != 0; } if (degree_output) { int out_order_lon_lat = target_crs_lon_lat_order(context, P, direction); if (out_order_lon_lat == -1) return false; output_lon_lat_order = out_order_lon_lat != 0; north_pole_in_bounds = contains_north_pole( P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order); south_pole_in_bounds = contains_south_pole( P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order); } if (degree_input && xmax < xmin) { if (!input_lon_lat_order) { proj_log_error(P, _("latitude max < latitude min.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } // handle antimeridian delta_x = (xmax - xmin + 360.0) / side_pts; } else { delta_x = (xmax - xmin) / side_pts; } if (degree_input && ymax < ymin) { if (input_lon_lat_order) { proj_log_error(P, _("latitude max < latitude min.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } // handle antimeridian delta_y = (ymax - ymin + 360.0) / side_pts; } else { delta_y = (ymax - ymin) / side_pts; } // build densified bounding box // Note: must be a linear ring for antimeridian logic for (int iii = 0; iii < side_pts; iii++) { // xmin boundary y_boundary_array[iii] = ymax - iii * delta_y; x_boundary_array[iii] = xmin; // ymin boundary y_boundary_array[iii + side_pts] = ymin; x_boundary_array[iii + side_pts] = xmin + iii * delta_x; // xmax boundary y_boundary_array[iii + side_pts * 2] = ymin + iii * delta_y; x_boundary_array[iii + side_pts * 2] = xmax; // ymax boundary y_boundary_array[iii + side_pts * 3] = ymax; x_boundary_array[iii + side_pts * 3] = xmax - iii * delta_x; } proj_trans_generic(P, direction, x_boundary_array.data(), sizeof(double), boundary_len, y_boundary_array.data(), sizeof(double), boundary_len, nullptr, 0, 0, nullptr, 0, 0); if (degree_output && !output_lon_lat_order) { // Use GIS friendly order std::swap(x_boundary_array, y_boundary_array); } bool crossed_antimeridian = false; if (!degree_output) { *out_xmin = simple_min(x_boundary_array.data(), boundary_len); *out_xmax = simple_max(x_boundary_array.data(), boundary_len); *out_ymin = simple_min(y_boundary_array.data(), boundary_len); *out_ymax = simple_max(y_boundary_array.data(), boundary_len); } else if (north_pole_in_bounds) { *out_xmin = -180; *out_xmax = 180; *out_ymin = simple_min(y_boundary_array.data(), boundary_len); *out_ymax = 90; } else if (south_pole_in_bounds) { *out_xmin = -180; *out_xmax = 180; *out_ymin = -90; *out_ymax = simple_max(y_boundary_array.data(), boundary_len); } else { *out_xmin = antimeridian_min(x_boundary_array.data(), boundary_len); *out_xmax = antimeridian_max(x_boundary_array.data(), boundary_len); crossed_antimeridian = *out_xmin > *out_xmax; *out_ymin = simple_min(y_boundary_array.data(), boundary_len); *out_ymax = simple_max(y_boundary_array.data(), boundary_len); } if (degree_output && !output_lon_lat_order) { // Go back to CRS axis order std::swap(*out_xmin, *out_ymin); std::swap(*out_xmax, *out_ymax); } if (!crossed_antimeridian) { // Sample points within the source grid for (int j = 1; j < side_pts - 1; ++j) { for (int i = 0; i < side_pts; ++i) { x_boundary_array[i] = xmin + i * delta_x; y_boundary_array[i] = ymin + j * delta_y; } proj_trans_generic(P, direction, x_boundary_array.data(), sizeof(double), side_pts, y_boundary_array.data(), sizeof(double), side_pts, nullptr, 0, 0, nullptr, 0, 0); for (int i = 0; i < side_pts; ++i) { if (std::isfinite(x_boundary_array[i]) && std::isfinite(y_boundary_array[i])) { *out_xmin = std::min(*out_xmin, x_boundary_array[i]); *out_xmax = std::max(*out_xmax, x_boundary_array[i]); *out_ymin = std::min(*out_ymin, y_boundary_array[i]); *out_ymax = std::max(*out_ymax, y_boundary_array[i]); } } } } return true; } // --------------------------------------------------------------------------- /** \brief Transform boundary, taking into account 3D coordinates. * * Transform boundary densifying the edges to account for nonlinear * transformations along these edges and extracting the outermost bounds. * * Note that the current implementation is not "perfect" when the source CRS is * geocentric, the target CRS is geographic, and the input bounding box * includes the center of the Earth, a pole or the antimeridian. In those * circumstances, exact values of the latitude of longitude of discontinuity * will not be returned. * * If one of the source or target CRS of the transformation is not 3D, the * values of *out_zmin / *out_zmax may not be significant. * * For 2D or "2.5D" transformation (that is planar component is * geographic/coordinates and 3D axis is elevation), the documentation of * proj_trans_bounds() applies. * * @param context The PJ_CONTEXT object. * @param P The PJ object representing the transformation. * @param direction The direction of the transformation. * @param xmin Minimum bounding coordinate of the first axis in source CRS * (target CRS if direction is inverse). * @param ymin Minimum bounding coordinate of the second axis in source CRS. * (target CRS if direction is inverse). * @param zmin Minimum bounding coordinate of the third axis in source CRS. * (target CRS if direction is inverse). * @param xmax Maximum bounding coordinate of the first axis in source CRS. * (target CRS if direction is inverse). * @param ymax Maximum bounding coordinate of the second axis in source CRS. * (target CRS if direction is inverse). * @param zmax Maximum bounding coordinate of the third axis in source CRS. * (target CRS if direction is inverse). * @param out_xmin Minimum bounding coordinate of the first axis in target CRS * (source CRS if direction is inverse). * @param out_ymin Minimum bounding coordinate of the second axis in target CRS. * (source CRS if direction is inverse). * @param out_zmin Minimum bounding coordinate of the third axis in target CRS. * (source CRS if direction is inverse). * @param out_xmax Maximum bounding coordinate of the first axis in target CRS. * (source CRS if direction is inverse). * @param out_ymax Maximum bounding coordinate of the second axis in target CRS. * (source CRS if direction is inverse). * @param out_zmax Maximum bounding coordinate of the third axis in target CRS. * (source CRS if direction is inverse). * @param densify_pts Recommended to use 21. This is the number of points * to use to densify the bounding polygon in the transformation. * @return an integer. 1 if successful. 0 if failures encountered. * @since 9.6 */ int proj_trans_bounds_3D(PJ_CONTEXT *context, PJ *P, PJ_DIRECTION direction, const double xmin, const double ymin, const double zmin, const double xmax, const double ymax, const double zmax, double *out_xmin, double *out_ymin, double *out_zmin, double *out_xmax, double *out_ymax, double *out_zmax, const int densify_pts) { *out_xmin = HUGE_VAL; *out_ymin = HUGE_VAL; *out_zmin = HUGE_VAL; *out_xmax = HUGE_VAL; *out_ymax = HUGE_VAL; *out_zmax = HUGE_VAL; if (P == nullptr) { proj_log_error(P, _("NULL P object not allowed.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } if (densify_pts < 0 || densify_pts > 10000) { proj_log_error(P, _("densify_pts must be between 0-10000.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } PJ_PROJ_INFO pj_info = proj_pj_info(P); if (pj_info.id == nullptr) { proj_log_error(P, _("NULL transformation not allowed,")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } if (strcmp(pj_info.id, "noop") == 0 || direction == PJ_IDENT) { *out_xmin = xmin; *out_xmax = xmax; *out_ymin = ymin; *out_ymax = ymax; *out_zmin = zmin; *out_zmax = zmax; return true; } bool degree_output = proj_degree_output(P, direction) != 0; bool degree_input = proj_degree_input(P, direction) != 0; if (degree_output && densify_pts < 2) { proj_log_error( P, _("densify_pts must be at least 2 if the output is geographic.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } int side_pts = densify_pts + 1; // add one because we are densifying PJ *input_crs = get_input_crs(context, P, direction); const bool input_is_geocentric = input_crs && is_geocentric(input_crs); proj_destroy(input_crs); const int boundary_len = input_is_geocentric ? side_pts * 12 : side_pts * 4; std::vector x_boundary_array; std::vector y_boundary_array; std::vector z_boundary_array; try { x_boundary_array.resize(boundary_len); y_boundary_array.resize(boundary_len); z_boundary_array.resize(boundary_len); } catch (const std::exception &e) // memory allocation failure { proj_log_error(P, e.what()); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } double delta_x = 0; double delta_y = 0; bool north_pole_in_bounds = false; bool south_pole_in_bounds = false; bool input_lon_lat_order = false; bool output_lon_lat_order = false; if (degree_input) { int in_order_lon_lat = target_crs_lon_lat_order( context, P, pj_opposite_direction(direction)); if (in_order_lon_lat == -1) return false; input_lon_lat_order = in_order_lon_lat != 0; } if (degree_output) { int out_order_lon_lat = target_crs_lon_lat_order(context, P, direction); if (out_order_lon_lat == -1) return false; output_lon_lat_order = out_order_lon_lat != 0; north_pole_in_bounds = contains_north_pole( P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order); south_pole_in_bounds = contains_south_pole( P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order); } if (degree_input && xmax < xmin) { if (!input_lon_lat_order) { proj_log_error(P, _("latitude max < latitude min.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } // handle antimeridian delta_x = (xmax - xmin + 360.0) / side_pts; } else { delta_x = (xmax - xmin) / side_pts; } if (degree_input && ymax < ymin) { if (input_lon_lat_order) { proj_log_error(P, _("latitude max < latitude min.")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return false; } // handle antimeridian delta_y = (ymax - ymin + 360.0) / side_pts; } else { delta_y = (ymax - ymin) / side_pts; } *out_xmin = std::numeric_limits::max(); *out_ymin = std::numeric_limits::max(); *out_zmin = std::numeric_limits::max(); *out_xmax = std::numeric_limits::lowest(); *out_ymax = std::numeric_limits::lowest(); *out_zmax = std::numeric_limits::lowest(); if (input_is_geocentric) { int iii = 0; for (int iter_z = 0; iter_z < 2; ++iter_z) { const double z = iter_z == 0 ? zmin : zmax; // xmin boundary for (int i = 0; i < side_pts; i++) { y_boundary_array[iii] = ymax - i * delta_y; x_boundary_array[iii] = xmin; z_boundary_array[iii] = z; ++iii; } // ymin boundary for (int i = 0; i < side_pts; i++) { y_boundary_array[iii] = ymin; x_boundary_array[iii] = xmin + i * delta_x; z_boundary_array[iii] = z; ++iii; } // xmax boundary for (int i = 0; i < side_pts; i++) { y_boundary_array[iii] = ymin + i * delta_y; x_boundary_array[iii] = xmax; z_boundary_array[iii] = z; ++iii; } // ymax boundary for (int i = 0; i < side_pts; i++) { y_boundary_array[iii] = ymax; x_boundary_array[iii] = xmax - i * delta_x; z_boundary_array[iii] = z; ++iii; } } const double delta_z = (zmax - zmin) / side_pts; // (xmin, ymin) edge for (int i = 0; i < side_pts; i++) { x_boundary_array[iii] = xmin; y_boundary_array[iii] = ymin; z_boundary_array[iii] = zmin + i * delta_z; ++iii; } // (xmin, ymax) edge for (int i = 0; i < side_pts; i++) { x_boundary_array[iii] = xmin; y_boundary_array[iii] = ymax; z_boundary_array[iii] = zmin + i * delta_z; ++iii; } // (xmax, ymin) edge for (int i = 0; i < side_pts; i++) { x_boundary_array[iii] = xmax; y_boundary_array[iii] = ymin; z_boundary_array[iii] = zmin + i * delta_z; ++iii; } // (xmax, ymax) edge for (int i = 0; i < side_pts; i++) { x_boundary_array[iii] = xmax; y_boundary_array[iii] = ymax; z_boundary_array[iii] = zmin + i * delta_z; ++iii; } proj_trans_generic(P, direction, x_boundary_array.data(), sizeof(double), boundary_len, y_boundary_array.data(), sizeof(double), boundary_len, z_boundary_array.data(), sizeof(double), boundary_len, nullptr, 0, 0); *out_xmin = std::min(*out_xmin, simple_min(x_boundary_array.data(), boundary_len)); *out_ymin = std::min(*out_ymin, simple_min(y_boundary_array.data(), boundary_len)); *out_zmin = std::min(*out_zmin, simple_min(z_boundary_array.data(), boundary_len)); *out_xmax = std::max(*out_xmax, simple_max(x_boundary_array.data(), boundary_len)); *out_ymax = std::max(*out_ymax, simple_max(y_boundary_array.data(), boundary_len)); *out_zmax = std::max(*out_zmax, simple_max(z_boundary_array.data(), boundary_len)); } else { for (int iter_z = 0; iter_z < 2; ++iter_z) { const double z = iter_z == 0 ? zmin : zmax; // build densified bounding box // Note: must be a linear ring for antimeridian logic for (int iii = 0; iii < side_pts; iii++) { // xmin boundary y_boundary_array[iii] = ymax - iii * delta_y; x_boundary_array[iii] = xmin; z_boundary_array[iii] = z; // ymin boundary y_boundary_array[iii + side_pts] = ymin; x_boundary_array[iii + side_pts] = xmin + iii * delta_x; z_boundary_array[iii + side_pts] = z; // xmax boundary y_boundary_array[iii + side_pts * 2] = ymin + iii * delta_y; x_boundary_array[iii + side_pts * 2] = xmax; z_boundary_array[iii + side_pts * 2] = z; // ymax boundary y_boundary_array[iii + side_pts * 3] = ymax; x_boundary_array[iii + side_pts * 3] = xmax - iii * delta_x; z_boundary_array[iii + side_pts * 3] = z; } proj_trans_generic(P, direction, x_boundary_array.data(), sizeof(double), boundary_len, y_boundary_array.data(), sizeof(double), boundary_len, z_boundary_array.data(), sizeof(double), boundary_len, nullptr, 0, 0); if (degree_output && !output_lon_lat_order) { // Use GIS friendly order std::swap(x_boundary_array, y_boundary_array); } bool crossed_antimeridian = false; if (!degree_output) { *out_xmin = std::min(*out_xmin, simple_min(x_boundary_array.data(), boundary_len)); *out_xmax = std::max(*out_xmax, simple_max(x_boundary_array.data(), boundary_len)); *out_ymin = std::min(*out_ymin, simple_min(y_boundary_array.data(), boundary_len)); *out_ymax = std::max(*out_ymax, simple_max(y_boundary_array.data(), boundary_len)); } else if (north_pole_in_bounds) { *out_xmin = -180; *out_xmax = 180; *out_ymin = std::min(*out_ymin, simple_min(y_boundary_array.data(), boundary_len)); *out_ymax = 90; } else if (south_pole_in_bounds) { *out_xmin = -180; *out_xmax = 180; *out_ymin = -90; *out_ymax = std::max(*out_ymax, simple_max(y_boundary_array.data(), boundary_len)); } else { *out_xmin = std::min( *out_xmin, antimeridian_min(x_boundary_array.data(), boundary_len)); *out_xmax = std::max( *out_xmax, antimeridian_max(x_boundary_array.data(), boundary_len)); crossed_antimeridian = *out_xmin > *out_xmax; *out_ymin = std::min(*out_ymin, simple_min(y_boundary_array.data(), boundary_len)); *out_ymax = std::max(*out_ymax, simple_max(y_boundary_array.data(), boundary_len)); } *out_zmin = std::min( *out_zmin, simple_min(z_boundary_array.data(), boundary_len)); *out_zmax = std::max( *out_zmax, simple_max(z_boundary_array.data(), boundary_len)); if (degree_output && !output_lon_lat_order) { // Go back to CRS axis order std::swap(*out_xmin, *out_ymin); std::swap(*out_xmax, *out_ymax); } if (!crossed_antimeridian) { // Sample points within the source grid for (int j = 1; j < side_pts - 1; ++j) { for (int i = 0; i < side_pts; ++i) { x_boundary_array[i] = xmin + i * delta_x; y_boundary_array[i] = ymin + j * delta_y; z_boundary_array[i] = z; } proj_trans_generic(P, direction, x_boundary_array.data(), sizeof(double), side_pts, y_boundary_array.data(), sizeof(double), side_pts, z_boundary_array.data(), sizeof(double), side_pts, nullptr, 0, 0); for (int i = 0; i < side_pts; ++i) { if (std::isfinite(x_boundary_array[i]) && std::isfinite(y_boundary_array[i])) { *out_xmin = std::min(*out_xmin, x_boundary_array[i]); *out_xmax = std::max(*out_xmax, x_boundary_array[i]); *out_ymin = std::min(*out_ymin, y_boundary_array[i]); *out_ymax = std::max(*out_ymax, y_boundary_array[i]); *out_zmin = std::min(*out_zmin, z_boundary_array[i]); *out_zmax = std::max(*out_zmax, z_boundary_array[i]); } } } } } } return true; } proj-9.8.1/src/ctx.cpp000664 001750 001750 00000033771 15166171715 014557 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the PJ_CONTEXT thread context object. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2010, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include #include #include #include #include "filemanager.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj_experimental.h" #include "proj_internal.h" /************************************************************************/ /* pj_get_ctx() */ /************************************************************************/ PJ_CONTEXT *pj_get_ctx(PJ *pj) { if (nullptr == pj) return pj_get_default_ctx(); if (nullptr == pj->ctx) return pj_get_default_ctx(); return pj->ctx; } /************************************************************************/ /* proj_assign_context() */ /************************************************************************/ /** \brief Re-assign a context to a PJ* object. * * This may be useful if the PJ* has been created with a context that is * thread-specific, and is later used in another thread. In that case, * the user may want to assign another thread-specific context to the * object. */ void proj_assign_context(PJ *pj, PJ_CONTEXT *ctx) { if (pj == nullptr) return; pj->ctx = ctx; if (pj->reassign_context) { pj->reassign_context(pj, ctx); } for (const auto &alt : pj->alternativeCoordinateOperations) { proj_assign_context(alt.pj, ctx); } } /************************************************************************/ /* createDefault() */ /************************************************************************/ pj_ctx pj_ctx::createDefault() { pj_ctx ctx; ctx.debug_level = PJ_LOG_ERROR; ctx.logger = pj_stderr_logger; NS_PROJ::FileManager::fillDefaultNetworkInterface(&ctx); const char *projDebug = getenv("PROJ_DEBUG"); if (projDebug != nullptr) { if (NS_PROJ::internal::ci_equal(projDebug, "ON")) { ctx.debug_level = PJ_LOG_DEBUG; } else if (NS_PROJ::internal::ci_equal(projDebug, "OFF")) { ctx.debug_level = PJ_LOG_ERROR; } else if (projDebug[0] == '-' || (projDebug[0] >= '0' && projDebug[0] <= '9')) { const int debugLevel = atoi(projDebug); // Negative debug levels mean that we first start logging when errno // is set Cf // https://github.com/OSGeo/PROJ/commit/1c1d04b45d76366f54e104f9346879fd48bfde8e // This isn't documented for now. Not totally sure we really want // that... if (debugLevel >= -PJ_LOG_TRACE) ctx.debug_level = debugLevel; else ctx.debug_level = PJ_LOG_TRACE; } else { fprintf(stderr, "Invalid value for PROJ_DEBUG: %s\n", projDebug); } } return ctx; } /**************************************************************************/ /* get_cpp_context() */ /**************************************************************************/ projCppContext *pj_ctx::get_cpp_context() { if (cpp_context == nullptr) { cpp_context = new projCppContext(this); } return cpp_context; } /************************************************************************/ /* set_search_paths() */ /************************************************************************/ void pj_ctx::set_search_paths(const std::vector &search_paths_in) { lookupedFiles.clear(); search_paths = search_paths_in; delete[] c_compat_paths; c_compat_paths = nullptr; if (!search_paths.empty()) { c_compat_paths = new const char *[search_paths.size()]; for (size_t i = 0; i < search_paths.size(); ++i) { c_compat_paths[i] = search_paths[i].c_str(); } } } /**************************************************************************/ /* set_ca_bundle_path() */ /**************************************************************************/ void pj_ctx::set_ca_bundle_path(const std::string &ca_bundle_path_in) { ca_bundle_path = ca_bundle_path_in; } /************************************************************************/ /* pj_ctx(const pj_ctx& other) */ /************************************************************************/ pj_ctx::pj_ctx(const pj_ctx &other) : lastFullErrorMessage(std::string()), last_errno(0), debug_level(other.debug_level), errorIfBestTransformationNotAvailableDefault( other.errorIfBestTransformationNotAvailableDefault), warnIfBestTransformationNotAvailableDefault( other.warnIfBestTransformationNotAvailableDefault), logger(other.logger), logger_app_data(other.logger_app_data), cpp_context(other.cpp_context ? other.cpp_context->clone(this) : nullptr), use_proj4_init_rules(other.use_proj4_init_rules), forceOver(other.forceOver), epsg_file_exists(other.epsg_file_exists), env_var_proj_data(other.env_var_proj_data), file_finder(other.file_finder), file_finder_user_data(other.file_finder_user_data), defer_grid_opening(false), custom_sqlite3_vfs_name(other.custom_sqlite3_vfs_name), user_writable_directory(other.user_writable_directory), // BEGIN ini file settings iniFileLoaded(other.iniFileLoaded), endpoint(other.endpoint), networking(other.networking), ca_bundle_path(other.ca_bundle_path), native_ca(other.native_ca), gridChunkCache(other.gridChunkCache), defaultTmercAlgo(other.defaultTmercAlgo), // END ini file settings projStringParserCreateFromPROJStringRecursionCounter(0), pipelineInitRecursiongCounter(0) { set_search_paths(other.search_paths); } /************************************************************************/ /* pj_get_default_ctx() */ /************************************************************************/ PJ_CONTEXT *pj_get_default_ctx() { // C++11 rules guarantee a thread-safe instantiation. static pj_ctx default_context(pj_ctx::createDefault()); return &default_context; } /************************************************************************/ /* ~pj_ctx() */ /************************************************************************/ pj_ctx::~pj_ctx() { delete[] c_compat_paths; proj_context_delete_cpp_context(cpp_context); } /************************************************************************/ /* proj_context_clone() */ /* Create a new context based on a custom context */ /************************************************************************/ PJ_CONTEXT *proj_context_clone(PJ_CONTEXT *ctx) { if (nullptr == ctx) return proj_context_create(); return new (std::nothrow) pj_ctx(*ctx); } /*****************************************************************************/ int proj_errno(const PJ *P) { /****************************************************************************** Read an error level from the context of a PJ. ******************************************************************************/ return proj_context_errno(pj_get_ctx((PJ *)P)); } /*****************************************************************************/ int proj_context_errno(PJ_CONTEXT *ctx) { /****************************************************************************** Read an error directly from a context, without going through a PJ belonging to that context. ******************************************************************************/ if (nullptr == ctx) ctx = pj_get_default_ctx(); return ctx->last_errno; } /*****************************************************************************/ int proj_errno_set(const PJ *P, int err) { /****************************************************************************** Set context-errno, bubble it up to the thread local errno, return err ******************************************************************************/ /* Use proj_errno_reset to explicitly clear the error status */ if (0 == err) return 0; /* For P==0 err goes to the default context */ proj_context_errno_set(pj_get_ctx((PJ *)P), err); errno = err; return err; } /*****************************************************************************/ int proj_errno_restore(const PJ *P, int err) { /****************************************************************************** Use proj_errno_restore when the current function succeeds, but the error flag was set on entry, and stored/reset using proj_errno_reset in order to monitor for new errors. See usage example under proj_errno_reset () ******************************************************************************/ if (0 == err) return 0; proj_errno_set(P, err); return 0; } /*****************************************************************************/ int proj_errno_reset(const PJ *P) { /****************************************************************************** Clears errno in the context and thread local levels through the low level pj_ctx interface. Returns the previous value of the errno, for convenient reset/restore operations: int foo (PJ *P) { // errno may be set on entry, but we need to reset it to be able to // check for errors from "do_something_with_P(P)" int last_errno = proj_errno_reset (P); // local failure if (0==P) return proj_errno_set (P, 42); // call to function that may fail do_something_with_P (P); // failure in do_something_with_P? - keep latest error status if (proj_errno(P)) return proj_errno (P); // success - restore previous error status, return 0 return proj_errno_restore (P, last_errno); } ******************************************************************************/ int last_errno; last_errno = proj_errno(P); proj_context_errno_set(pj_get_ctx((PJ *)P), 0); errno = 0; return last_errno; } /* Create a new context based on the default context */ PJ_CONTEXT *proj_context_create(void) { return new (std::nothrow) pj_ctx(*pj_get_default_ctx()); } PJ_CONTEXT *proj_context_destroy(PJ_CONTEXT *ctx) { if (nullptr == ctx) return nullptr; /* Trying to free the default context is a no-op (since it is statically * allocated) */ if (pj_get_default_ctx() == ctx) return nullptr; delete ctx; return nullptr; } /************************************************************************/ /* proj_context_use_proj4_init_rules() */ /************************************************************************/ void proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } ctx->use_proj4_init_rules = enable; } /************************************************************************/ /* EQUAL() */ /************************************************************************/ static int EQUAL(const char *a, const char *b) { #ifdef _MSC_VER return _stricmp(a, b) == 0; #else return strcasecmp(a, b) == 0; #endif } /************************************************************************/ /* proj_context_get_use_proj4_init_rules() */ /************************************************************************/ int proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path) { const char *val = getenv("PROJ_USE_PROJ4_INIT_RULES"); if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (val) { if (EQUAL(val, "yes") || EQUAL(val, "on") || EQUAL(val, "true")) { return TRUE; } if (EQUAL(val, "no") || EQUAL(val, "off") || EQUAL(val, "false")) { return FALSE; } pj_log(ctx, PJ_LOG_ERROR, "Invalid value for PROJ_USE_PROJ4_INIT_RULES"); } if (ctx->use_proj4_init_rules >= 0) { return ctx->use_proj4_init_rules; } return from_legacy_code_path; } proj-9.8.1/src/wkt2_grammar.y000664 001750 001750 00000203244 15166171715 016036 0ustar00eveneven000000 000000 %{ /****************************************************************************** * Project: PROJ * Purpose: WKT2 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018 Even Rouault, * * 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 "wkt2_parser.h" %} %define api.pure %define parse.error verbose %require "3.0" %parse-param {pj_wkt2_parse_context *context} %lex-param {pj_wkt2_parse_context *context} /* From WKT1 */ %token T_PROJECTION "PROJECTION" %token T_DATUM "DATUM" %token T_SPHEROID "SPHEROID" %token T_PRIMEM "PRIMEM" %token T_UNIT "UNIT" %token T_AXIS "AXIS" %token T_PARAMETER "PARAMETER" /* WKT2 preferred */ %token T_GEODCRS "GEODCRS"; %token T_LENGTHUNIT "LENGTHUNIT"; %token T_ANGLEUNIT "ANGLEUNIT"; %token T_SCALEUNIT "SCALEUNIT"; %token T_TIMEUNIT "TIMEUNIT"; %token T_ELLIPSOID "ELLIPSOID"; %token T_CS "CS"; %token T_ID "ID"; %token T_PROJCRS "PROJCRS"; %token T_BASEGEODCRS "BASEGEODCRS"; %token T_MERIDIAN "MERIDIAN"; %token T_BEARING "BEARING"; %token T_ORDER "ORDER"; %token T_ANCHOR "ANCHOR"; %token T_ANCHOREPOCH "ANCHOREPOCH"; %token T_CONVERSION "CONVERSION"; %token T_METHOD "METHOD"; %token T_REMARK "REMARK"; %token T_GEOGCRS "GEOGCRS"; %token T_BASEGEOGCRS "BASEGEOGCRS"; %token T_SCOPE "SCOPE"; %token T_AREA "AREA"; %token T_BBOX "BBOX"; %token T_CITATION "CITATION"; %token T_URI "URI"; %token T_VERTCRS "VERTCRS"; %token T_VDATUM "VDATUM"; %token T_GEOIDMODEL "GEOIDMODEL"; %token T_COMPOUNDCRS "COMPOUNDCRS"; %token T_PARAMETERFILE "PARAMETERFILE"; %token T_COORDINATEOPERATION "COORDINATEOPERATION"; %token T_SOURCECRS "SOURCECRS"; %token T_TARGETCRS "TARGETCRS"; %token T_INTERPOLATIONCRS "INTERPOLATIONCRS"; %token T_OPERATIONACCURACY "OPERATIONACCURACY"; %token T_CONCATENATEDOPERATION "CONCATENATEDOPERATION"; %token T_STEP "STEP"; %token T_BOUNDCRS "BOUNDCRS"; %token T_ABRIDGEDTRANSFORMATION "ABRIDGEDTRANSFORMATION"; %token T_DERIVINGCONVERSION "DERIVINGCONVERSION"; %token T_TDATUM "TDATUM"; %token T_CALENDAR "CALENDAR"; %token T_TIMEORIGIN "TIMEORIGIN"; %token T_TIMECRS "TIMECRS"; %token T_VERTICALEXTENT "VERTICALEXTENT"; %token T_TIMEEXTENT "TIMEEXTENT"; %token T_USAGE "USAGE"; %token T_DYNAMIC "DYNAMIC"; %token T_FRAMEEPOCH "FRAMEEPOCH"; %token T_MODEL "MODEL"; %token T_VELOCITYGRID "VELOCITYGRID"; %token T_ENSEMBLE "ENSEMBLE"; %token T_MEMBER "MEMBER"; %token T_ENSEMBLEACCURACY "ENSEMBLEACCURACY"; %token T_DERIVEDPROJCRS "DERIVEDPROJCRS"; %token T_BASEPROJCRS "BASEPROJCRS"; %token T_EDATUM "EDATUM"; %token T_ENGCRS "ENGCRS"; %token T_PDATUM "PDATUM"; %token T_PARAMETRICCRS "PARAMETRICCRS"; %token T_PARAMETRICUNIT "PARAMETRICUNIT"; %token T_BASEVERTCRS "BASEVERTCRS"; %token T_BASEENGCRS "BASEENGCRS"; %token T_BASEPARAMCRS "BASEPARAMCRS"; %token T_BASETIMECRS "BASETIMECRS"; %token T_EPOCH "EPOCH" %token T_COORDEPOCH "COORDEPOCH" %token T_COORDINATEMETADATA "COORDINATEMETADATA" %token T_POINTMOTIONOPERATION "POINTMOTIONOPERATION" %token T_VERSION "VERSION" %token T_AXISMINVALUE "AXISMINVALUE" %token T_AXISMAXVALUE "AXISMAXVALUE" %token T_RANGEMEANING "RANGEMEANING" %token T_exact "exact" %token T_wraparound "wraparound" %token T_DEFININGTRANSFORMATION "DEFININGTRANSFORMATION" /* WKT2 alternate (longer or shorter) */ %token T_GEODETICCRS "GEODETICCRS"; %token T_GEODETICDATUM "GEODETICDATUM"; %token T_PROJECTEDCRS "PROJECTEDCRS"; %token T_PRIMEMERIDIAN "PRIMEMERIDIAN"; %token T_GEOGRAPHICCRS "GEOGRAPHICCRS"; %token T_TRF "TRF"; %token T_VERTICALCRS "VERTICALCRS"; %token T_VERTICALDATUM "VERTICALDATUM"; %token T_VRF "VRF"; %token T_TIMEDATUM "TIMEDATUM"; %token T_TEMPORALQUANTITY "TEMPORALQUANTITY"; %token T_ENGINEERINGDATUM "ENGINEERINGDATUM"; %token T_ENGINEERINGCRS "ENGINEERINGCRS"; %token T_PARAMETRICDATUM "PARAMETRICDATUM"; /* CS types */ %token T_AFFINE "affine"; %token T_CARTESIAN "Cartesian"; %token T_CYLINDRICAL "cylindrical"; %token T_ELLIPSOIDAL "ellipsoidal"; %token T_LINEAR "linear"; %token T_PARAMETRIC "parametric"; %token T_POLAR "polar"; %token T_SPHERICAL "spherical"; %token T_VERTICAL "vertical"; %token T_TEMPORAL "temporal"; // WKT2_2015 only %token T_TEMPORALCOUNT "temporalCount"; %token T_TEMPORALMEASURE "temporalMeasure"; %token T_ORDINAL "ordinal"; %token T_TEMPORALDATETIME "temporalDateTime"; /* Axis directions */ %token T_NORTH "north"; %token T_NORTHNORTHEAST "northNorthEast"; %token T_NORTHEAST "northEast"; %token T_EASTNORTHEAST "eastNorthEast"; %token T_EAST "east"; %token T_EASTSOUTHEAST "eastSouthEast"; %token T_SOUTHEAST "southEast"; %token T_SOUTHSOUTHEAST "southSouthEast"; %token T_SOUTH "south"; %token T_SOUTHSOUTHWEST "southSouthWest"; %token T_SOUTHWEST "southWest"; %token T_WESTSOUTHWEST "westSouthWest"; %token T_WEST "west"; %token T_WESTNORTHWEST "westNorthWest"; %token T_NORTHWEST "northWest"; %token T_NORTHNORTHWEST "northNorthWest"; %token T_UP "up"; %token T_DOWN "down"; %token T_GEOCENTRICX "geocentricX"; %token T_GEOCENTRICY "geocentricY"; %token T_GEOCENTRICZ "geocentricZ"; %token T_COLUMNPOSITIVE "columnPositive"; %token T_COLUMNNEGATIVE "columnNegative"; %token T_ROWPOSITIVE "rowPositive"; %token T_ROWNEGATIVE "rowNegative"; %token T_DISPLAYRIGHT "displayRight"; %token T_DISPLAYLEFT "displayLeft"; %token T_DISPLAYUP "displayUp"; %token T_DISPLAYDOWN "displayDown"; %token T_FORWARD "forward"; %token T_AFT "aft"; %token T_PORT "port"; %token T_STARBOARD "starboard"; %token T_CLOCKWISE "clockwise"; %token T_COUNTERCLOCKWISE "counterClockwise"; %token T_TOWARDS "towards"; %token T_AWAYFROM "awayFrom"; %token T_FUTURE "future"; %token T_PAST "part"; %token T_UNSPECIFIED "unspecified"; %token T_STRING "string" %token T_UNSIGNED_INTEGER_DIFFERENT_ONE_TWO_THREE "unsigned integer" %token END 0 "end of string" %% /* Derived from BNF grammar in OGC 18-010r3 (WKT2:2018), with a few back additions from GC 12-063r5 (WKT2:2015) */ input: identifier | ellipsoid | datum | crs | bound_crs | coordinate_metadata | coordinate_operation | point_motion_operation | concatenated_operation | map_projection datum: geodetic_reference_frame_with_opt_pm | datum_ensemble | vertical_reference_frame | engineering_datum | parametric_datum | temporal_datum crs: single_crs | compound_crs // Basic characters period: '.' // Numbers number: signed_numeric_literal_with_sign | unsigned_numeric_literal signed_numeric_literal_with_sign: sign unsigned_numeric_literal signed_numeric_literal: opt_sign unsigned_numeric_literal unsigned_numeric_literal: exact_numeric_literal | approximate_numeric_literal opt_sign: | sign approximate_numeric_literal: mantissa 'E' exponent mantissa: exact_numeric_literal exponent: signed_integer signed_integer: opt_sign unsigned_integer exact_numeric_literal: unsigned_integer opt_period_unsigned_integer | period unsigned_integer opt_period_unsigned_integer: | period unsigned_integer unsigned_integer: '1' | '2' | '3' | T_UNSIGNED_INTEGER_DIFFERENT_ONE_TWO_THREE sign: '+' | '-' // Date and time colon: ':' hyphen: '-' // Significantly modified to avoid shift-reduce ambiguities for Bison datetime: year opt_24_hour_clock | year hyphen unsigned_integer opt_24_hour_clock | year hyphen month hyphen day opt_24_hour_clock opt_24_hour_clock: | _24_hour_clock year: unsigned_integer month: unsigned_integer day: unsigned_integer _24_hour_clock: time_designator hour opt_colon_minute_colon_second_time_zone_designator opt_colon_minute_colon_second_time_zone_designator: colon minute opt_colon_second_time_zone_designator | time_zone_designator opt_colon_second_time_zone_designator: colon second_time_zone_designator | time_zone_designator time_designator: 'T' hour: unsigned_integer minute: unsigned_integer second_time_zone_designator: seconds_integer period time_zone_designator | seconds_integer period seconds_fraction time_zone_designator | seconds_integer time_zone_designator seconds_integer: unsigned_integer seconds_fraction: unsigned_integer time_zone_designator: utc_designator | local_time_zone_designator utc_designator: 'Z' local_time_zone_designator: sign hour opt_colon_minute | hour opt_colon_minute opt_colon_minute: | colon minute // CRS WKT characters left_delimiter: '[' | '(' right_delimiter: ']' | ')' wkt_separator: ',' quoted_latin_text: T_STRING quoted_unicode_text: T_STRING // Scope, extent, identifier and remark opt_separator_scope_extent_identifier_remark: | wkt_separator no_opt_separator_scope_extent_identifier_remark no_opt_separator_scope_extent_identifier_remark: scope_extent_opt_identifier_list_opt_remark | identifier opt_identifier_list_remark | remark opt_identifier_list_remark: | wkt_separator identifier opt_identifier_list_remark | wkt_separator remark scope_extent_opt_identifier_list_opt_remark: scope_extent_opt_identifier_list_remark | usage_list_opt_identifier_list_remark // WKT2-2015 way scope_extent_opt_identifier_list_remark: scope wkt_separator extent_opt_identifier_list_remark | scope opt_identifier_list_remark | extent_opt_identifier_list_remark // WKT2-2018 way usage_list_opt_identifier_list_remark: usage | usage wkt_separator remark | usage wkt_separator identifier opt_identifier_list_remark | usage wkt_separator usage_list_opt_identifier_list_remark usage: usage_keyword left_delimiter scope wkt_separator extent right_delimiter usage_keyword: T_USAGE scope: scope_keyword left_delimiter scope_text_description right_delimiter scope_keyword: T_SCOPE scope_text_description: quoted_latin_text extent: area_description | geographic_bounding_box | vertical_extent | temporal_extent | area_description wkt_separator geographic_bounding_box | area_description wkt_separator vertical_extent | area_description wkt_separator temporal_extent | geographic_bounding_box wkt_separator vertical_extent | geographic_bounding_box wkt_separator temporal_extent | vertical_extent wkt_separator temporal_extent | area_description wkt_separator geographic_bounding_box wkt_separator vertical_extent | area_description wkt_separator geographic_bounding_box wkt_separator temporal_extent | area_description wkt_separator vertical_extent wkt_separator temporal_extent | geographic_bounding_box wkt_separator vertical_extent wkt_separator temporal_extent | area_description wkt_separator geographic_bounding_box wkt_separator vertical_extent wkt_separator temporal_extent extent_opt_identifier_list_remark: area_description opt_identifier_list_remark | geographic_bounding_box opt_identifier_list_remark | vertical_extent opt_identifier_list_remark | temporal_extent opt_identifier_list_remark | area_description wkt_separator geographic_bounding_box opt_identifier_list_remark | area_description wkt_separator vertical_extent opt_identifier_list_remark | area_description wkt_separator temporal_extent opt_identifier_list_remark | geographic_bounding_box wkt_separator vertical_extent opt_identifier_list_remark | geographic_bounding_box wkt_separator temporal_extent opt_identifier_list_remark | vertical_extent wkt_separator temporal_extent opt_identifier_list_remark | area_description wkt_separator geographic_bounding_box wkt_separator vertical_extent opt_identifier_list_remark | area_description wkt_separator geographic_bounding_box wkt_separator temporal_extent opt_identifier_list_remark | area_description wkt_separator vertical_extent wkt_separator temporal_extent opt_identifier_list_remark | geographic_bounding_box wkt_separator vertical_extent wkt_separator temporal_extent opt_identifier_list_remark | area_description wkt_separator geographic_bounding_box wkt_separator vertical_extent wkt_separator temporal_extent opt_identifier_list_remark // Area description area_description: area_description_keyword left_delimiter area_text_description right_delimiter area_description_keyword: T_AREA area_text_description: quoted_latin_text // Geographic bounding box geographic_bounding_box: geographic_bounding_box_keyword left_delimiter lower_left_latitude wkt_separator lower_left_longitude wkt_separator upper_right_latitude wkt_separator upper_right_longitude right_delimiter geographic_bounding_box_keyword: T_BBOX lower_left_latitude: number lower_left_longitude: number upper_right_latitude: number upper_right_longitude: number // Vertical extent vertical_extent: vertical_extent_keyword left_delimiter vertical_extent_minimum_height wkt_separator vertical_extent_maximum_height opt_separator_length_unit right_delimiter opt_separator_length_unit: | wkt_separator length_unit vertical_extent_keyword: T_VERTICALEXTENT vertical_extent_minimum_height: number vertical_extent_maximum_height: number // Temporal extent temporal_extent: temporal_extent_keyword left_delimiter temporal_extent_start wkt_separator temporal_extent_end right_delimiter temporal_extent_keyword: T_TIMEEXTENT temporal_extent_start: datetime | quoted_latin_text temporal_extent_end: datetime | quoted_latin_text // Identifier identifier: identifier_keyword left_delimiter authority_name wkt_separator authority_unique_identifier opt_version_authority_citation_uri right_delimiter opt_version_authority_citation_uri: | wkt_separator version | wkt_separator version wkt_separator authority_citation | wkt_separator version wkt_separator authority_citation wkt_separator id_uri | wkt_separator authority_citation | wkt_separator authority_citation wkt_separator id_uri | wkt_separator id_uri identifier_keyword: T_ID authority_name: quoted_latin_text authority_unique_identifier: number | quoted_latin_text version: number | quoted_latin_text authority_citation: citation_keyword left_delimiter citation right_delimiter citation_keyword: T_CITATION citation: quoted_latin_text id_uri: uri_keyword left_delimiter uri right_delimiter uri_keyword: T_URI uri: quoted_latin_text // Remark remark: remark_keyword left_delimiter quoted_unicode_text right_delimiter remark_keyword: T_REMARK // Unit unit: spatial_unit | time_unit //spatial_unit: angle_unit | length_unit | parametric_unit | scale_unit spatial_unit: angle_or_length_or_parametric_or_scale_unit angle_or_length_or_parametric_or_scale_unit: angle_or_length_or_parametric_or_scale_unit_keyword left_delimiter unit_name wkt_separator conversion_factor opt_separator_identifier_list right_delimiter angle_or_length_or_parametric_or_scale_unit_keyword: T_ANGLEUNIT | T_LENGTHUNIT | T_PARAMETRICUNIT | T_SCALEUNIT | T_UNIT angle_or_length_or_scale_unit: angle_or_length_or_scale_unit_keyword left_delimiter unit_name wkt_separator conversion_factor opt_separator_identifier_list right_delimiter angle_or_length_or_scale_unit_keyword: T_ANGLEUNIT | T_LENGTHUNIT | T_SCALEUNIT | T_UNIT angle_unit: angle_unit_keyword left_delimiter unit_name wkt_separator conversion_factor opt_separator_identifier_list right_delimiter opt_separator_identifier_list: | wkt_separator identifier opt_separator_identifier_list length_unit: length_unit_keyword left_delimiter unit_name wkt_separator conversion_factor opt_separator_identifier_list right_delimiter /* parametric_unit: parametric_unit_keyword left_delimiter unit_name wkt_separator conversion_factor opt_separator_identifier_list right_delimiter */ /* scale_unit: scale_unit_keyword left_delimiter unit_name wkt_separator conversion_factor opt_separator_identifier_list right_delimiter */ time_unit: time_unit_keyword left_delimiter unit_name opt_separator_conversion_factor_identifier_list right_delimiter opt_separator_conversion_factor_identifier_list: | wkt_separator conversion_factor opt_separator_identifier_list angle_unit_keyword: T_ANGLEUNIT | T_UNIT length_unit_keyword: T_LENGTHUNIT | T_UNIT // parametric_unit_keyword: T_PARAMETRICUNIT // scale_unit_keyword: T_SCALEUNIT | T_UNIT time_unit_keyword: T_TIMEUNIT | T_TEMPORALQUANTITY unit_name: quoted_latin_text conversion_factor: unsigned_numeric_literal // Coordinate system // coordinate_system: spatial_cs | temporalcountmeasure_cs | ordinatedatetime_cs coordinate_system_scope_extent_identifier_remark: spatial_cs_scope_extent_identifier_remark | wkt2015temporal_cs_scope_extent_identifier_remark | temporalcountmeasure_cs_scope_extent_identifier_remark | ordinaldatetime_cs_scope_extent_identifier_remark coordinate_system_defining_transformation_scope_extent_identifier_remark: spatial_cs_defining_transformation_scope_extent_identifier_remark spatial_cs_scope_extent_identifier_remark: cs_keyword left_delimiter spatial_cs_type wkt_separator dimension opt_separator_identifier_list right_delimiter wkt_separator spatial_axis opt_separator_spatial_axis_list_opt_separator_cs_unit_scope_extent_identifier_remark spatial_cs_defining_transformation_scope_extent_identifier_remark: cs_keyword left_delimiter spatial_cs_type wkt_separator dimension opt_separator_identifier_list right_delimiter wkt_separator spatial_axis opt_separator_spatial_axis_list_opt_defining_transformation_opt_separator_cs_unit_scope_extent_identifier_remark opt_separator_spatial_axis_list_opt_separator_cs_unit_scope_extent_identifier_remark: | wkt_separator cs_unit opt_separator_scope_extent_identifier_remark | wkt_separator spatial_axis opt_separator_spatial_axis_list_opt_separator_cs_unit_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark opt_defining_transformation_separator_scope_extent_identifier_remark: | wkt_separator no_opt_defining_transformation_separator_scope_extent_identifier_remark defining_transformation: T_DEFININGTRANSFORMATION left_delimiter defining_transformation_name opt_separator_identifier right_delimiter defining_transformation_name: quoted_latin_text no_opt_defining_transformation_separator_scope_extent_identifier_remark: defining_transformation | defining_transformation wkt_separator no_opt_defining_transformation_separator_scope_extent_identifier_remark | scope_extent_opt_identifier_list_opt_remark | identifier opt_identifier_list_remark | remark opt_separator_spatial_axis_list_opt_defining_transformation_opt_separator_cs_unit_scope_extent_identifier_remark: | wkt_separator cs_unit opt_defining_transformation_separator_scope_extent_identifier_remark | wkt_separator spatial_axis opt_separator_spatial_axis_list_opt_defining_transformation_opt_separator_cs_unit_scope_extent_identifier_remark | wkt_separator no_opt_defining_transformation_separator_scope_extent_identifier_remark wkt2015temporal_cs_scope_extent_identifier_remark: cs_keyword left_delimiter T_TEMPORAL wkt_separator dimension opt_separator_identifier_list right_delimiter wkt_separator temporalcountmeasure_axis opt_separator_cs_unit_scope_extent_identifier_remark opt_separator_cs_unit_scope_extent_identifier_remark: | wkt_separator cs_unit | wkt_separator cs_unit wkt_separator no_opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark temporalcountmeasure_cs_scope_extent_identifier_remark: cs_keyword left_delimiter temporalcountmeasure_cs_type wkt_separator dimension opt_separator_identifier_list right_delimiter wkt_separator temporalcountmeasure_axis opt_separator_scope_extent_identifier_remark ordinaldatetime_cs_scope_extent_identifier_remark: cs_keyword left_delimiter ordinaldatetime_cs_type wkt_separator dimension opt_separator_identifier_list right_delimiter wkt_separator ordinaldatetime_axis opt_separator_ordinaldatetime_axis_list_scope_extent_identifier_remark opt_separator_ordinaldatetime_axis_list_scope_extent_identifier_remark: | wkt_separator ordinaldatetime_axis opt_separator_ordinaldatetime_axis_list_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark cs_keyword: T_CS spatial_cs_type: T_AFFINE | T_CARTESIAN | T_CYLINDRICAL | T_ELLIPSOIDAL | T_LINEAR | T_PARAMETRIC | T_POLAR | T_SPHERICAL | T_VERTICAL temporalcountmeasure_cs_type: T_TEMPORALCOUNT | T_TEMPORALMEASURE ordinaldatetime_cs_type: T_ORDINAL | T_TEMPORALDATETIME dimension: '1' | '2' | '3' spatial_axis: axis_keyword left_delimiter axis_name_abbrev wkt_separator axis_direction_opt_axis_order_spatial_unit_identifier_list right_delimiter temporalcountmeasure_axis: axis_keyword left_delimiter axis_name_abbrev wkt_separator axis_direction_except_n_s_cw_ccw opt_separator_axis_time_unit_identifier_list right_delimiter ordinaldatetime_axis: axis_keyword left_delimiter axis_name_abbrev wkt_separator axis_direction_opt_axis_order_identifier_list right_delimiter axis_keyword: T_AXIS // Approximation of { | | } axis_name_abbrev: quoted_latin_text axis_direction_opt_axis_order_spatial_unit_identifier_list: axis_direction_except_n_s_cw_ccw_opt_axis_spatial_unit_identifier_list | T_NORTH | T_NORTH wkt_separator north_south_options_spatial_unit | T_SOUTH | T_SOUTH wkt_separator north_south_options_spatial_unit | T_CLOCKWISE | T_CLOCKWISE wkt_separator clockwise_counter_clockwise_options_spatial_unit | T_COUNTERCLOCKWISE | T_COUNTERCLOCKWISE wkt_separator clockwise_counter_clockwise_options_spatial_unit north_south_options_spatial_unit: identifier opt_separator_identifier_list | meridian wkt_separator axis_order opt_separator_identifier_list | meridian wkt_separator axis_order wkt_separator spatial_unit opt_separator_identifier_list | meridian wkt_separator spatial_unit opt_separator_identifier_list | meridian opt_separator_identifier_list | axis_order wkt_separator spatial_unit opt_separator_identifier_list | axis_order opt_separator_identifier_list | spatial_unit opt_separator_identifier_list clockwise_counter_clockwise_options_spatial_unit: identifier opt_separator_identifier_list | bearing wkt_separator axis_order opt_separator_identifier_list | bearing wkt_separator axis_order wkt_separator spatial_unit opt_separator_identifier_list | bearing wkt_separator spatial_unit opt_separator_identifier_list | bearing opt_separator_identifier_list | axis_order wkt_separator spatial_unit opt_separator_identifier_list | axis_order opt_separator_identifier_list | spatial_unit opt_separator_identifier_list axis_direction_except_n_s_cw_ccw_opt_axis_spatial_unit_identifier_list: axis_direction_except_n_s_cw_ccw | axis_direction_except_n_s_cw_ccw wkt_separator axis_direction_except_n_s_cw_ccw_opt_axis_spatial_unit_identifier_list_options axis_direction_except_n_s_cw_ccw_opt_axis_spatial_unit_identifier_list_options: identifier opt_separator_identifier_list | axis_range_opt_separator_identifier_list | axis_order opt_separator_axis_range_opt_separator_identifier_list | axis_order wkt_separator spatial_unit opt_separator_axis_range_opt_separator_identifier_list | spatial_unit opt_separator_axis_range_opt_separator_identifier_list axis_direction_opt_axis_order_identifier_list: axis_direction_except_n_s_cw_ccw_opt_axis_identifier_list | T_NORTH | T_NORTH wkt_separator north_south_options | T_SOUTH | T_SOUTH wkt_separator north_south_options_spatial_unit | T_CLOCKWISE | T_CLOCKWISE wkt_separator clockwise_counter_clockwise_options | T_COUNTERCLOCKWISE | T_COUNTERCLOCKWISE wkt_separator clockwise_counter_clockwise_options north_south_options: identifier opt_separator_identifier_list | meridian wkt_separator axis_order opt_separator_identifier_list | meridian opt_separator_identifier_list | axis_order opt_separator_identifier_list clockwise_counter_clockwise_options: identifier opt_separator_identifier_list | bearing wkt_separator axis_order opt_separator_identifier_list | bearing opt_separator_identifier_list | axis_order opt_separator_identifier_list axis_direction_except_n_s_cw_ccw_opt_axis_identifier_list: axis_direction_except_n_s_cw_ccw | axis_direction_except_n_s_cw_ccw wkt_separator axis_direction_except_n_s_cw_ccw_opt_axis_identifier_list_options axis_direction_except_n_s_cw_ccw_opt_axis_identifier_list_options: identifier opt_separator_identifier_list | axis_order opt_separator_axis_range_opt_separator_identifier_list | axis_range_opt_separator_identifier_list opt_separator_axis_time_unit_identifier_list: | wkt_separator axis_direction_except_n_s_cw_ccw_opt_axis_time_unit_identifier_list_options axis_direction_except_n_s_cw_ccw_opt_axis_time_unit_identifier_list_options: identifier opt_separator_identifier_list | axis_range_opt_separator_identifier_list | axis_order opt_separator_axis_range_opt_separator_identifier_list | axis_order wkt_separator time_unit opt_separator_axis_range_opt_separator_identifier_list | time_unit opt_separator_axis_range_opt_separator_identifier_list axis_direction_except_n_s_cw_ccw: T_NORTHNORTHEAST | T_NORTHEAST | T_EASTNORTHEAST | T_EAST | T_EASTSOUTHEAST | T_SOUTHEAST | T_SOUTHSOUTHEAST | T_SOUTHSOUTHWEST | T_SOUTHWEST | T_WESTSOUTHWEST | T_WEST | T_WESTNORTHWEST | T_NORTHWEST | T_NORTHNORTHWEST | T_UP | T_DOWN | T_GEOCENTRICX | T_GEOCENTRICY | T_GEOCENTRICZ | T_COLUMNPOSITIVE | T_COLUMNNEGATIVE | T_ROWPOSITIVE | T_ROWNEGATIVE | T_DISPLAYRIGHT | T_DISPLAYLEFT | T_DISPLAYUP | T_DISPLAYDOWN | T_FORWARD | T_AFT | T_PORT | T_STARBOARD | T_TOWARDS | T_AWAYFROM | T_FUTURE | T_PAST | T_UNSPECIFIED meridian: meridian_keyword left_delimiter number wkt_separator angle_unit right_delimiter meridian_keyword: T_MERIDIAN bearing: bearing_keyword left_delimiter number right_delimiter bearing_keyword: T_BEARING axis_order: axis_order_keyword left_delimiter unsigned_integer right_delimiter axis_order_keyword: T_ORDER axis_range_opt_separator_identifier_list: axis_minimum_value opt_separator_identifier_list | axis_maximum_value opt_separator_identifier_list | axis_minimum_value wkt_separator axis_maximum_value opt_separator_identifier_list | axis_minimum_value wkt_separator axis_maximum_value wkt_separator axis_range_meaning opt_separator_identifier_list opt_separator_axis_range_opt_separator_identifier_list: | wkt_separator axis_minimum_value opt_separator_identifier_list | wkt_separator axis_maximum_value opt_separator_identifier_list | wkt_separator axis_minimum_value wkt_separator axis_maximum_value opt_separator_identifier_list | wkt_separator axis_minimum_value wkt_separator axis_maximum_value wkt_separator axis_range_meaning opt_separator_identifier_list axis_minimum_value: axis_minimum_value_keyword left_delimiter number right_delimiter axis_minimum_value_keyword: T_AXISMINVALUE axis_maximum_value: axis_maximum_value_keyword left_delimiter number right_delimiter axis_maximum_value_keyword: T_AXISMAXVALUE axis_range_meaning: axis_range_meaning_keyword left_delimiter axis_range_meaning_value right_delimiter axis_range_meaning_keyword: T_RANGEMEANING axis_range_meaning_value: T_exact | T_wraparound cs_unit: unit /* ellipsoidal_2D_coordinate_system: cs_keyword left_delimiter ellipsoidal_2D_cs_type wkt_separator ellipsoidal_2D_dimension opt_separator_identifier_list right_delimiter separator_spatial_axis_list opt_separator_cs_unit ellipsoidal_2D_cs_type: T_ELLIPSOIDAL ellipsoidal_2D_dimension: '2' */ // Datum ensemble datum_ensemble: geodetic_datum_ensemble_without_pm | vertical_datum_ensemble geodetic_datum_ensemble_without_pm: datum_ensemble_keyword left_delimiter datum_ensemble_name datum_ensemble_member_list_ellipsoid_accuracy_identifier_list right_delimiter datum_ensemble_member_list_ellipsoid_accuracy_identifier_list: wkt_separator ellipsoid wkt_separator datum_ensemble_accuracy opt_separator_datum_ensemble_identifier_list | wkt_separator datum_ensemble_member datum_ensemble_member_list_ellipsoid_accuracy_identifier_list opt_separator_datum_ensemble_identifier_list: | wkt_separator datum_ensemble_identifier opt_separator_datum_ensemble_identifier_list vertical_datum_ensemble: datum_ensemble_keyword left_delimiter datum_ensemble_name wkt_separator datum_ensemble_member datum_ensemble_member_list_accuracy_identifier_list right_delimiter datum_ensemble_member_list_accuracy_identifier_list: wkt_separator datum_ensemble_accuracy opt_separator_datum_ensemble_identifier_list | wkt_separator datum_ensemble_member datum_ensemble_member_list_accuracy_identifier_list datum_ensemble_keyword: T_ENSEMBLE datum_ensemble_name: quoted_latin_text datum_ensemble_member: datum_ensemble_member_keyword left_delimiter datum_ensemble_member_name opt_datum_ensemble_member_identifier_list right_delimiter opt_datum_ensemble_member_identifier_list: | wkt_separator datum_ensemble_member_identifier opt_datum_ensemble_member_identifier_list datum_ensemble_member_keyword: T_MEMBER datum_ensemble_member_name: quoted_latin_text datum_ensemble_member_identifier: identifier datum_ensemble_accuracy: datum_ensemble_accuracy_keyword left_delimiter accuracy right_delimiter datum_ensemble_accuracy_keyword: T_ENSEMBLEACCURACY accuracy: number datum_ensemble_identifier: identifier // Dynamic coordinate reference systems dynamic_crs: dynamic_crs_keyword left_delimiter frame_reference_epoch opt_separator_deformation_model_id right_delimiter dynamic_crs_keyword: T_DYNAMIC frame_reference_epoch: frame_reference_epoch_keyword left_delimiter reference_epoch right_delimiter frame_reference_epoch_keyword: T_FRAMEEPOCH reference_epoch: unsigned_integer | unsigned_integer period | unsigned_integer period unsigned_integer opt_separator_deformation_model_id: | wkt_separator deformation_model_id deformation_model_id: deformation_model_id_keyword left_delimiter deformation_model_name opt_separator_identifier right_delimiter opt_separator_identifier: | wkt_separator identifier deformation_model_id_keyword: T_MODEL | T_VELOCITYGRID deformation_model_name: quoted_latin_text // Geodetic CRS geodetic_crs: static_geodetic_crs | dynamic_geodetic_crs | geographic_crs geographic_crs: static_geographic_crs | dynamic_geographic_crs static_geodetic_crs: geodetic_crs_keyword left_delimiter crs_name wkt_separator geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm wkt_separator opt_prime_meridian_coordinate_system_defining_transformation_scope_extent_identifier_remark right_delimiter dynamic_geodetic_crs: geodetic_crs_keyword left_delimiter crs_name wkt_separator dynamic_crs wkt_separator geodetic_reference_frame_without_pm wkt_separator opt_prime_meridian_coordinate_system_defining_transformation_scope_extent_identifier_remark right_delimiter static_geographic_crs: geographic_crs_keyword left_delimiter crs_name wkt_separator geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm wkt_separator opt_prime_meridian_coordinate_system_defining_transformation_scope_extent_identifier_remark right_delimiter dynamic_geographic_crs: geographic_crs_keyword left_delimiter crs_name wkt_separator dynamic_crs wkt_separator geodetic_reference_frame_without_pm wkt_separator opt_prime_meridian_coordinate_system_defining_transformation_scope_extent_identifier_remark right_delimiter opt_prime_meridian_coordinate_system_defining_transformation_scope_extent_identifier_remark: prime_meridian wkt_separator coordinate_system_defining_transformation_scope_extent_identifier_remark | coordinate_system_defining_transformation_scope_extent_identifier_remark crs_name: quoted_latin_text geodetic_crs_keyword: T_GEODCRS | T_GEODETICCRS geographic_crs_keyword: T_GEOGCRS | T_GEOGRAPHICCRS geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm: geodetic_reference_frame_without_pm | geodetic_datum_ensemble_without_pm // Ellipsoid ellipsoid: ellipsoid_keyword left_delimiter ellipsoid_name wkt_separator semi_major_axis wkt_separator inverse_flattening opt_separator_length_unit_identifier_list right_delimiter opt_separator_length_unit_identifier_list: | wkt_separator length_unit opt_separator_identifier_list | wkt_separator identifier opt_separator_identifier_list ellipsoid_keyword: T_ELLIPSOID | T_SPHEROID ellipsoid_name: quoted_latin_text semi_major_axis: unsigned_numeric_literal inverse_flattening: unsigned_numeric_literal // Prime meridian prime_meridian: prime_meridian_keyword left_delimiter prime_meridian_name wkt_separator irm_longitude_opt_separator_identifier_list right_delimiter prime_meridian_keyword: T_PRIMEM | T_PRIMEMERIDIAN prime_meridian_name: quoted_latin_text irm_longitude_opt_separator_identifier_list: signed_numeric_literal wkt_separator angle_unit opt_separator_identifier_list | signed_numeric_literal opt_separator_identifier_list // Geodetic reference frame geodetic_reference_frame_with_opt_pm: geodetic_reference_frame_without_pm | geodetic_reference_frame_without_pm wkt_separator prime_meridian geodetic_reference_frame_without_pm: geodetic_reference_frame_keyword left_delimiter datum_name wkt_separator ellipsoid opt_separator_datum_anchor_anchor_epoch_identifier_list right_delimiter geodetic_reference_frame_keyword: T_DATUM | T_TRF | T_GEODETICDATUM datum_name: quoted_latin_text opt_separator_datum_anchor_anchor_epoch_identifier_list: | wkt_separator datum_anchor | wkt_separator datum_anchor_epoch | wkt_separator datum_anchor wkt_separator datum_anchor_epoch | wkt_separator identifier opt_separator_identifier_list | wkt_separator datum_anchor_epoch wkt_separator identifier opt_separator_identifier_list | wkt_separator datum_anchor wkt_separator identifier opt_separator_identifier_list | wkt_separator datum_anchor wkt_separator datum_anchor_epoch wkt_separator identifier opt_separator_identifier_list datum_anchor: datum_anchor_keyword left_delimiter datum_anchor_description right_delimiter datum_anchor_keyword: T_ANCHOR datum_anchor_description: quoted_latin_text datum_anchor_epoch: datum_anchor_epoch_keyword left_delimiter anchor_epoch right_delimiter datum_anchor_epoch_keyword: T_ANCHOREPOCH anchor_epoch: unsigned_integer | unsigned_integer period | unsigned_integer period unsigned_integer // Projected CRS projected_crs: projected_crs_keyword left_delimiter crs_name wkt_separator base_geodetic_crs wkt_separator map_projection wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter projected_crs_keyword: T_PROJCRS | T_PROJECTEDCRS // Base CRS base_geodetic_crs: base_static_geodetic_crs | base_dynamic_geodetic_crs | base_static_geographic_crs | base_dynamic_geographic_crs base_static_geodetic_crs: base_geodetic_crs_keyword left_delimiter base_crs_name wkt_separator geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm opt_separator_pm_ellipsoidal_cs_unit_opt_separator_identifier_list right_delimiter opt_separator_pm_ellipsoidal_cs_unit_opt_separator_identifier_list: | wkt_separator prime_meridian opt_separator_identifier_list | wkt_separator prime_meridian wkt_separator ellipsoidal_cs_unit opt_separator_identifier_list | wkt_separator ellipsoidal_cs_unit opt_separator_identifier_list | wkt_separator identifier opt_separator_identifier_list base_dynamic_geodetic_crs: base_geodetic_crs_keyword left_delimiter base_crs_name wkt_separator dynamic_crs wkt_separator geodetic_reference_frame_without_pm opt_separator_pm_ellipsoidal_cs_unit_opt_separator_identifier_list right_delimiter base_static_geographic_crs: base_geographic_crs_keyword left_delimiter base_crs_name wkt_separator geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm opt_separator_pm_ellipsoidal_cs_unit_opt_separator_identifier_list right_delimiter base_dynamic_geographic_crs: base_geographic_crs_keyword left_delimiter base_crs_name wkt_separator dynamic_crs wkt_separator geodetic_reference_frame_without_pm opt_separator_pm_ellipsoidal_cs_unit_opt_separator_identifier_list right_delimiter base_geodetic_crs_keyword: T_BASEGEODCRS base_geographic_crs_keyword: T_BASEGEOGCRS base_crs_name: quoted_latin_text ellipsoidal_cs_unit: angle_unit // Map projection map_projection: map_projection_keyword left_delimiter map_projection_name wkt_separator map_projection_method opt_separator_parameter_list_identifier_list right_delimiter opt_separator_parameter_list_identifier_list: | wkt_separator identifier opt_separator_identifier_list | wkt_separator map_projection_parameter opt_separator_parameter_list_identifier_list map_projection_keyword: T_CONVERSION map_projection_name: quoted_latin_text map_projection_method: map_projection_method_keyword left_delimiter map_projection_method_name opt_separator_identifier_list right_delimiter map_projection_method_keyword: T_METHOD | T_PROJECTION map_projection_method_name: quoted_latin_text map_projection_parameter: parameter_keyword left_delimiter parameter_name wkt_separator parameter_value opt_separator_param_unit_identifier_list right_delimiter opt_separator_param_unit_identifier_list: | wkt_separator identifier opt_separator_identifier_list | wkt_separator map_projection_parameter_unit opt_separator_identifier_list parameter_keyword: T_PARAMETER parameter_name: quoted_latin_text parameter_value: signed_numeric_literal map_projection_parameter_unit: angle_or_length_or_scale_unit // Vertical CRS vertical_crs: static_vertical_crs | dynamic_vertical_crs static_vertical_crs: vertical_crs_keyword left_delimiter crs_name wkt_separator vertical_reference_frame_or_vertical_datum_ensemble wkt_separator vertical_cs_opt_geoid_model_id_scope_extent_identifier_remark right_delimiter dynamic_vertical_crs: vertical_crs_keyword left_delimiter crs_name wkt_separator dynamic_crs wkt_separator vertical_reference_frame wkt_separator vertical_cs_opt_geoid_model_id_scope_extent_identifier_remark right_delimiter vertical_reference_frame_or_vertical_datum_ensemble: vertical_reference_frame | vertical_datum_ensemble vertical_cs_opt_geoid_model_id_scope_extent_identifier_remark: cs_keyword left_delimiter spatial_cs_type wkt_separator dimension opt_separator_identifier_list right_delimiter wkt_separator spatial_axis opt_separator_cs_unit_opt_geoid_model_id_scope_extent_identifier_remark opt_separator_cs_unit_opt_geoid_model_id_scope_extent_identifier_remark: | wkt_separator cs_unit opt_separator_scope_extent_identifier_remark | wkt_separator cs_unit wkt_separator geoid_model_id opt_geoid_model_id_list_opt_separator_scope_extent_identifier_remark | wkt_separator geoid_model_id opt_geoid_model_id_list_opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark opt_geoid_model_id_list_opt_separator_scope_extent_identifier_remark: | wkt_separator geoid_model_id opt_geoid_model_id_list_opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark geoid_model_id: geoid_model_keyword left_delimiter geoid_model_name opt_separator_identifier right_delimiter geoid_model_keyword: T_GEOIDMODEL geoid_model_name: quoted_latin_text vertical_crs_keyword: T_VERTCRS | T_VERTICALCRS // Vertical reference frame vertical_reference_frame: vertical_reference_frame_keyword left_delimiter datum_name opt_separator_datum_anchor_anchor_epoch_identifier_list right_delimiter vertical_reference_frame_keyword: T_VDATUM | T_VRF | T_VERTICALDATUM // Engineering CRS engineering_crs: engineering_crs_keyword left_delimiter crs_name wkt_separator engineering_datum wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter engineering_crs_keyword: T_ENGCRS | T_ENGINEERINGCRS engineering_datum: engineering_datum_keyword left_delimiter datum_name opt_separator_datum_anchor_identifier_list right_delimiter engineering_datum_keyword: T_EDATUM | T_ENGINEERINGDATUM opt_separator_datum_anchor_identifier_list: | wkt_separator datum_anchor | wkt_separator identifier opt_separator_identifier_list | wkt_separator datum_anchor wkt_separator identifier opt_separator_identifier_list // Parametric CRS parametric_crs: parametric_crs_keyword left_delimiter crs_name wkt_separator parametric_datum wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter parametric_crs_keyword: T_PARAMETRICCRS parametric_datum: parametric_datum_keyword left_delimiter datum_name opt_separator_datum_anchor_identifier_list right_delimiter parametric_datum_keyword: T_PDATUM | T_PARAMETRICDATUM // Temporal CRS temporal_crs: temporal_crs_keyword left_delimiter crs_name wkt_separator temporal_datum wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter temporal_crs_keyword: T_TIMECRS temporal_datum: temporal_datum_keyword left_delimiter datum_name opt_separator_temporal_datum_end right_delimiter opt_separator_temporal_datum_end: | wkt_separator calendar opt_separator_identifier_list | wkt_separator calendar wkt_separator temporal_origin opt_separator_identifier_list | wkt_separator temporal_origin opt_separator_identifier_list | wkt_separator identifier opt_separator_identifier_list temporal_datum_keyword: T_TDATUM | T_TIMEDATUM temporal_origin: temporal_origin_keyword left_delimiter temporal_origin_description right_delimiter temporal_origin_keyword: T_TIMEORIGIN temporal_origin_description: datetime | quoted_latin_text calendar: calendar_keyword left_delimiter calendar_identifier right_delimiter calendar_keyword: T_CALENDAR calendar_identifier: quoted_latin_text // Deriving conversion deriving_conversion: deriving_conversion_keyword left_delimiter deriving_conversion_name wkt_separator operation_method opt_separator_parameter_or_parameter_file_identifier_list right_delimiter opt_separator_parameter_or_parameter_file_identifier_list: | wkt_separator operation_parameter opt_separator_parameter_or_parameter_file_identifier_list | wkt_separator operation_parameter_file opt_separator_parameter_or_parameter_file_identifier_list | wkt_separator identifier opt_separator_identifier_list deriving_conversion_keyword: T_DERIVINGCONVERSION deriving_conversion_name: quoted_latin_text // Derived CRS conversion method operation_method: operation_method_keyword left_delimiter operation_method_name opt_separator_identifier right_delimiter operation_method_keyword: T_METHOD operation_method_name: quoted_latin_text // Derived CRS conversion parameter operation_parameter: parameter_keyword left_delimiter parameter_name wkt_separator parameter_value opt_separator_parameter_unit_identifier_list right_delimiter opt_separator_parameter_unit_identifier_list: | wkt_separator parameter_unit opt_separator_identifier_list | wkt_separator identifier opt_separator_identifier_list parameter_unit: length_or_angle_or_scale_or_time_or_parametric_unit // Approximate definition: conversion_factor should be optional only for a timeunit (but not easy to detect if UNIT keyword is used!) length_or_angle_or_scale_or_time_or_parametric_unit: length_or_angle_or_scale_or_time_or_parametric_unit_keyword left_delimiter unit_name opt_separator_conversion_factor_identifier_list right_delimiter length_or_angle_or_scale_or_time_or_parametric_unit_keyword: T_LENGTHUNIT | T_ANGLEUNIT | T_SCALEUNIT | T_TIMEUNIT | T_PARAMETRICUNIT | T_UNIT // Derived CRS conversion parameter file operation_parameter_file: parameter_file_keyword left_delimiter parameter_name wkt_separator parameter_file_name opt_separator_identifier right_delimiter parameter_file_keyword: T_PARAMETERFILE parameter_file_name: quoted_latin_text // Derived geodetic CRS and derived geographic CRS derived_geodetic_crs: derived_static_geod_crs | derived_dynamic_geod_crs | derived_geographic_crs derived_geographic_crs: derived_static_geog_crs | derived_dynamic_geog_crs derived_static_geod_crs: geodetic_crs_keyword left_delimiter crs_name wkt_separator base_static_geod_crs_or_base_static_geog_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_static_geod_crs_or_base_static_geog_crs: base_static_geod_crs | base_static_geog_crs derived_dynamic_geod_crs: geodetic_crs_keyword left_delimiter crs_name wkt_separator base_dynamic_geod_crs_or_base_dynamic_geog_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_dynamic_geod_crs_or_base_dynamic_geog_crs: base_dynamic_geod_crs | base_dynamic_geog_crs derived_static_geog_crs: geographic_crs_keyword left_delimiter crs_name wkt_separator base_static_geod_crs_or_base_static_geog_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter derived_dynamic_geog_crs: geographic_crs_keyword left_delimiter crs_name wkt_separator base_dynamic_geod_crs_or_base_dynamic_geog_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_static_geod_crs: base_geodetic_crs_keyword left_delimiter base_crs_name wkt_separator geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm opt_separator_pm_opt_separator_identifier_list right_delimiter opt_separator_pm_opt_separator_identifier_list: | wkt_separator prime_meridian opt_separator_identifier_list | wkt_separator identifier opt_separator_identifier_list base_dynamic_geod_crs: base_geodetic_crs_keyword left_delimiter base_crs_name wkt_separator dynamic_crs wkt_separator geodetic_reference_frame_without_pm opt_separator_pm_opt_separator_identifier_list right_delimiter base_static_geog_crs: base_geographic_crs_keyword left_delimiter base_crs_name wkt_separator geodetic_reference_frame_or_geodetic_datum_ensemble_without_pm opt_separator_pm_opt_separator_identifier_list right_delimiter base_dynamic_geog_crs: base_geographic_crs_keyword left_delimiter base_crs_name wkt_separator dynamic_crs wkt_separator geodetic_reference_frame_without_pm opt_separator_pm_opt_separator_identifier_list right_delimiter // Derived projected CRS derived_projected_crs: derived_projected_crs_keyword left_delimiter derived_crs_name wkt_separator base_projected_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter derived_projected_crs_keyword: T_DERIVEDPROJCRS derived_crs_name: quoted_latin_text base_projected_crs: base_projected_crs_keyword left_delimiter base_crs_name wkt_separator base_geodetic_geographic_crs wkt_separator map_projection // Current WKT grammar (as of WKT2 18-010r11) does not allow a // BASEPROJCRS.CS node, but there are situations where this is // ambiguous and we want to allow one. // Cf WKTParser::Private::buildProjectedCRS() for more details // Otherwise this should only be a opt_separator_identifier_list base_projected_crs_opt_separator_cs_identifier right_delimiter base_projected_crs_opt_separator_cs_identifier: | wkt_separator spatial_cs_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark base_projected_crs_keyword: T_BASEPROJCRS base_geodetic_geographic_crs: base_static_geod_crs | base_dynamic_geod_crs | base_static_geog_crs | base_dynamic_geog_crs // Derived vertical CRS derived_vertical_crs: vertical_crs_keyword left_delimiter crs_name wkt_separator base_vertical_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_vertical_crs: base_static_vertical_crs | base_dynamic_vertical_crs base_static_vertical_crs: base_vertical_crs_keyword left_delimiter base_crs_name wkt_separator vertical_reference_frame opt_separator_identifier_list right_delimiter base_dynamic_vertical_crs: base_vertical_crs_keyword left_delimiter base_crs_name wkt_separator dynamic_crs wkt_separator vertical_reference_frame opt_separator_identifier_list right_delimiter base_vertical_crs_keyword: T_BASEVERTCRS // Derived engineering CRS derived_engineering_crs: engineering_crs_keyword left_delimiter crs_name wkt_separator base_engineering_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_engineering_crs: base_engineering_crs_keyword left_delimiter base_crs_name wkt_separator engineering_datum opt_separator_identifier_list right_delimiter base_engineering_crs_keyword: T_BASEENGCRS // Derived parametric CRS derived_parametric_crs: parametric_crs_keyword left_delimiter crs_name wkt_separator base_parametric_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_parametric_crs: base_parametric_crs_keyword left_delimiter base_crs_name wkt_separator parametric_datum opt_separator_identifier_list right_delimiter base_parametric_crs_keyword: T_BASEPARAMCRS // Derived temporal CRS derived_temporal_crs: temporal_crs_keyword left_delimiter crs_name wkt_separator base_temporal_crs wkt_separator deriving_conversion wkt_separator coordinate_system_scope_extent_identifier_remark right_delimiter base_temporal_crs: base_temporal_crs_keyword left_delimiter base_crs_name wkt_separator temporal_datum opt_separator_identifier_list right_delimiter base_temporal_crs_keyword: T_BASETIMECRS // Compound CRS compound_crs: compound_crs_keyword left_delimiter compound_crs_name wkt_separator single_crs_or_bound_crs wkt_separator single_crs_or_bound_crs opt_wkt_separator_single_crs_list_opt_separator_scope_extent_identifier_remark right_delimiter single_crs: geodetic_crs | derived_geodetic_crs | projected_crs | derived_projected_crs | vertical_crs | derived_vertical_crs | engineering_crs | derived_engineering_crs | parametric_crs | derived_parametric_crs | temporal_crs | derived_temporal_crs // PROJ extension single_crs_or_bound_crs: single_crs | bound_crs opt_wkt_separator_single_crs_list_opt_separator_scope_extent_identifier_remark: | wkt_separator single_crs_or_bound_crs opt_wkt_separator_single_crs_list_opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark compound_crs_keyword: T_COMPOUNDCRS compound_crs_name: quoted_latin_text // coordinate epoch and coordinate metadata metadata_coordinate_epoch: coordinate_epoch_keyword left_delimiter coordinate_epoch right_delimiter coordinate_epoch_keyword: T_EPOCH | T_COORDEPOCH coordinate_epoch: unsigned_integer | unsigned_integer period | unsigned_integer period unsigned_integer coordinate_metadata: coordinate_metadata_keyword left_delimiter coordinate_metadata_crs right_delimiter coordinate_metadata_crs: static_crs_coordinate_metadata | dynamic_crs_coordinate_metadata wkt_separator metadata_coordinate_epoch coordinate_metadata_keyword: T_COORDINATEMETADATA static_crs_coordinate_metadata: static_geodetic_crs | static_geographic_crs | projected_crs | static_vertical_crs | engineering_crs | parametric_crs | temporal_crs | derived_geodetic_crs | derived_projected_crs | derived_vertical_crs | derived_engineering_crs | derived_parametric_crs | derived_temporal_crs | compound_crs dynamic_crs_coordinate_metadata: dynamic_geodetic_crs | dynamic_geographic_crs | projected_crs | dynamic_vertical_crs | derived_geodetic_crs | derived_projected_crs | derived_vertical_crs // Coordinate operations coordinate_operation: operation_keyword left_delimiter operation_name coordinate_operation_next coordinate_operation_next: wkt_separator operation_version coordinate_operation_end | coordinate_operation_end coordinate_operation_end: wkt_separator source_crs wkt_separator target_crs wkt_separator operation_method opt_parameter_or_parameter_file_list_opt_interpolation_crs_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark right_delimiter opt_parameter_or_parameter_file_list_opt_interpolation_crs_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark: | wkt_separator operation_parameter opt_parameter_or_parameter_file_list_opt_interpolation_crs_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark | wkt_separator operation_parameter_file opt_parameter_or_parameter_file_list_opt_interpolation_crs_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark | wkt_separator interpolation_crs opt_separator_scope_extent_identifier_remark | wkt_separator interpolation_crs wkt_separator operation_accuracy opt_separator_scope_extent_identifier_remark | wkt_separator operation_accuracy opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark operation_keyword: T_COORDINATEOPERATION operation_name: quoted_latin_text operation_version: operation_version_keyword left_delimiter operation_version_text right_delimiter operation_version_keyword: T_VERSION operation_version_text: quoted_latin_text source_crs: source_crs_keyword left_delimiter crs right_delimiter source_crs_keyword: T_SOURCECRS target_crs: target_crs_keyword left_delimiter crs right_delimiter target_crs_keyword: T_TARGETCRS interpolation_crs: interpolation_crs_keyword left_delimiter crs right_delimiter interpolation_crs_keyword: T_INTERPOLATIONCRS operation_accuracy: operation_accuracy_keyword left_delimiter accuracy right_delimiter operation_accuracy_keyword: T_OPERATIONACCURACY // Point motion operation point_motion_operation: point_motion_keyword left_delimiter operation_name point_motion_operation_next point_motion_operation_next: wkt_separator operation_version point_motion_operation_end | point_motion_operation_end point_motion_operation_end: wkt_separator source_crs wkt_separator operation_method opt_parameter_or_parameter_file_list_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark right_delimiter opt_parameter_or_parameter_file_list_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark: | wkt_separator operation_parameter opt_parameter_or_parameter_file_list_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark | wkt_separator operation_parameter_file opt_parameter_or_parameter_file_list_opt_operation_accuracy_opt_separator_scope_extent_identifier_remark | wkt_separator operation_accuracy opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark point_motion_keyword: T_POINTMOTIONOPERATION // Concatenated operation concatenated_operation: concatenated_operation_keyword left_delimiter operation_name concatenated_operation_next concatenated_operation_next: wkt_separator operation_version concatenated_operation_end | concatenated_operation_end concatenated_operation_end: wkt_separator source_crs wkt_separator target_crs wkt_separator step_keyword left_delimiter step right_delimiter wkt_separator step_keyword left_delimiter step right_delimiter opt_concatenated_operation_end right_delimiter step: coordinate_operation | point_motion_keyword | map_projection | deriving_conversion opt_concatenated_operation_end: | wkt_separator step_keyword left_delimiter step right_delimiter opt_concatenated_operation_end | wkt_separator operation_accuracy opt_separator_scope_extent_identifier_remark | wkt_separator no_opt_separator_scope_extent_identifier_remark concatenated_operation_keyword: T_CONCATENATEDOPERATION step_keyword: T_STEP // Bound CRS bound_crs: bound_crs_keyword left_delimiter source_crs wkt_separator target_crs wkt_separator abridged_coordinate_transformation opt_separator_scope_extent_identifier_remark right_delimiter bound_crs_keyword: T_BOUNDCRS abridged_coordinate_transformation: abridged_transformation_keyword left_delimiter operation_name abridged_coordinate_transformation_next abridged_coordinate_transformation_next: wkt_separator operation_version abridged_coordinate_transformation_end | abridged_coordinate_transformation_end abridged_coordinate_transformation_end: wkt_separator operation_method // At least one parameter required by WKT2. But relax that to allow things like METHOD["PROJ-based operation method: +proj=...."] // wkt_separator abridged_parameter_or_parameter_file opt_end_abridged_coordinate_transformation right_delimiter //abridged_parameter_or_parameter_file: abridged_transformation_parameter | operation_parameter_file opt_end_abridged_coordinate_transformation: | wkt_separator abridged_transformation_parameter opt_end_abridged_coordinate_transformation | wkt_separator operation_parameter_file opt_end_abridged_coordinate_transformation | wkt_separator no_opt_separator_scope_extent_identifier_remark abridged_transformation_keyword: T_ABRIDGEDTRANSFORMATION abridged_transformation_parameter: parameter_keyword left_delimiter parameter_name wkt_separator parameter_value opt_separator_identifier_list right_delimiter proj-9.8.1/src/wkt2_parser.h000664 001750 001750 00000004023 15166171715 015655 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: WKT2 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018 Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef PJ_WKT2_PARSER_H_INCLUDED #define PJ_WKT2_PARSER_H_INCLUDED #ifndef DOXYGEN_SKIP #ifdef __cplusplus extern "C" { #endif typedef struct pj_wkt2_parse_context pj_wkt2_parse_context; #include "wkt2_generated_parser.h" void pj_wkt2_error(pj_wkt2_parse_context *context, const char *msg); int pj_wkt2_lex(YYSTYPE *pNode, pj_wkt2_parse_context *context); int pj_wkt2_parse(pj_wkt2_parse_context *context); #ifdef __cplusplus } std::string pj_wkt2_parse(const std::string &wkt); #endif #endif /* #ifndef DOXYGEN_SKIP */ #endif /* PJ_WKT2_PARSER_H_INCLUDED */ proj-9.8.1/src/coord_operation.cpp000664 001750 001750 00000014703 15166171715 017141 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: PJCoordOperation methods * * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018-2024, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" #include #include "proj/internal/internal.hpp" using namespace NS_PROJ::internal; //! @cond Doxygen_Suppress /**************************************************************************************/ PJCoordOperation::~PJCoordOperation() { /**************************************************************************************/ proj_destroy(pj); proj_destroy(pjSrcGeocentricToLonLat); proj_destroy(pjDstGeocentricToLonLat); } /**************************************************************************************/ bool PJCoordOperation::isInstantiable() const { /**************************************************************************************/ if (isInstantiableCached == INSTANTIABLE_STATUS_UNKNOWN) isInstantiableCached = proj_coordoperation_is_instantiable(pj->ctx, pj); return (isInstantiableCached == 1); } // Returns true if the passed operation uses NADCON5 grids for NAD83 to // NAD83(HARN) static bool isSpecialCaseForNAD83_to_NAD83HARN(const std::string &opName) { return opName.find("NAD83 to NAD83(HARN) (47)") != std::string::npos || opName.find("NAD83 to NAD83(HARN) (48)") != std::string::npos || opName.find("NAD83 to NAD83(HARN) (49)") != std::string::npos || opName.find("NAD83 to NAD83(HARN) (50)") != std::string::npos; } // Returns true if the passed operation uses "GDA94 to WGS 84 (1)", which // is the null transformation static bool isSpecialCaseForGDA94_to_WGS84(const std::string &opName) { return opName.find("GDA94 to WGS 84 (1)") != std::string::npos; } // Returns true if the passed operation uses "GDA2020 to WGS 84 (2)", which // is the null transformation static bool isSpecialCaseForWGS84_to_GDA2020(const std::string &opName) { return opName.find("GDA2020 to WGS 84 (2)") != std::string::npos; } PJCoordOperation::PJCoordOperation( int idxInOriginalListIn, double minxSrcIn, double minySrcIn, double maxxSrcIn, double maxySrcIn, double minxDstIn, double minyDstIn, double maxxDstIn, double maxyDstIn, PJ *pjIn, const std::string &nameIn, double accuracyIn, double pseudoAreaIn, const char *areaNameIn, const PJ *pjSrcGeocentricToLonLatIn, const PJ *pjDstGeocentricToLonLatIn) : idxInOriginalList(idxInOriginalListIn), minxSrc(minxSrcIn), minySrc(minySrcIn), maxxSrc(maxxSrcIn), maxySrc(maxySrcIn), minxDst(minxDstIn), minyDst(minyDstIn), maxxDst(maxxDstIn), maxyDst(maxyDstIn), pj(pjIn), name(nameIn), accuracy(accuracyIn), pseudoArea(pseudoAreaIn), areaName(areaNameIn ? areaNameIn : ""), isOffshore(areaName.find("- offshore") != std::string::npos), isUnknownAreaName(areaName.empty() || areaName == "unknown"), isPriorityOp(isSpecialCaseForNAD83_to_NAD83HARN(name) || isSpecialCaseForGDA94_to_WGS84(name) || isSpecialCaseForWGS84_to_GDA2020(name)), pjSrcGeocentricToLonLat(pjSrcGeocentricToLonLatIn ? proj_clone(pjSrcGeocentricToLonLatIn->ctx, pjSrcGeocentricToLonLatIn) : nullptr), pjDstGeocentricToLonLat(pjDstGeocentricToLonLatIn ? proj_clone(pjDstGeocentricToLonLatIn->ctx, pjDstGeocentricToLonLatIn) : nullptr) { const auto IsLonLatOrLatLon = [](const PJ *crs, bool &isLonLatDegreeOut, bool &isLatLonDegreeOut) { const auto eType = proj_get_type(crs); if (eType == PJ_TYPE_GEOGRAPHIC_2D_CRS || eType == PJ_TYPE_GEOGRAPHIC_3D_CRS) { const auto cs = proj_crs_get_coordinate_system(crs->ctx, crs); const char *direction = ""; double conv_factor = 0; constexpr double EPS = 1e-14; if (proj_cs_get_axis_info(crs->ctx, cs, 0, nullptr, nullptr, &direction, &conv_factor, nullptr, nullptr, nullptr) && ci_equal(direction, "East")) { isLonLatDegreeOut = fabs(conv_factor - M_PI / 180) < EPS; } else if (proj_cs_get_axis_info(crs->ctx, cs, 1, nullptr, nullptr, &direction, &conv_factor, nullptr, nullptr, nullptr) && ci_equal(direction, "East")) { isLatLonDegreeOut = fabs(conv_factor - M_PI / 180) < EPS; } proj_destroy(cs); } }; const auto source = proj_get_source_crs(pj->ctx, pj); if (source) { IsLonLatOrLatLon(source, srcIsLonLatDegree, srcIsLatLonDegree); proj_destroy(source); } const auto target = proj_get_target_crs(pj->ctx, pj); if (target) { IsLonLatOrLatLon(target, dstIsLonLatDegree, dstIsLatLonDegree); proj_destroy(target); } } //! @endcond proj-9.8.1/src/lib_proj.cmake000664 001750 001750 00000047001 15166171715 016046 0ustar00eveneven000000 000000 message(STATUS "Configuring proj library:") find_package(Threads QUIET) if(Threads_FOUND AND CMAKE_USE_PTHREADS_INIT) add_definitions(-DPROJ_HAS_PTHREADS) endif() option(ENABLE_IPO "Build library with interprocedural optimization (if available)." OFF) if(ENABLE_IPO) cmake_policy(SET CMP0069 NEW) include(CheckIPOSupported) check_ipo_supported(RESULT ENABLE_IPO) endif() print_variable(ENABLE_IPO) ############################################## ### library source list and include_list ### ############################################## set(SRC_LIBPROJ_PROJECTIONS projections/airocean.cpp projections/aeqd.cpp projections/adams.cpp projections/gnom.cpp projections/laea.cpp projections/mod_ster.cpp projections/nsper.cpp projections/nzmg.cpp projections/ortho.cpp projections/stere.cpp projections/sterea.cpp projections/aea.cpp projections/bipc.cpp projections/bonne.cpp projections/eqdc.cpp projections/isea.cpp projections/ccon.cpp projections/imw_p.cpp projections/krovak.cpp projections/lcc.cpp projections/poly.cpp projections/rpoly.cpp projections/sconics.cpp projections/rouss.cpp projections/cass.cpp projections/cc.cpp projections/cea.cpp projections/eqc.cpp projections/gall.cpp projections/labrd.cpp projections/som.cpp projections/merc.cpp projections/mill.cpp projections/ocea.cpp projections/omerc.cpp projections/somerc.cpp projections/tcc.cpp projections/tcea.cpp projections/times.cpp projections/tmerc.cpp projections/tobmerc.cpp projections/airy.cpp projections/aitoff.cpp projections/august.cpp projections/bacon.cpp projections/bertin1953.cpp projections/chamb.cpp projections/hammer.cpp projections/lagrng.cpp projections/larr.cpp projections/lask.cpp projections/latlong.cpp projections/nicol.cpp projections/ob_tran.cpp projections/oea.cpp projections/tpeqd.cpp projections/vandg.cpp projections/vandg2.cpp projections/vandg4.cpp projections/wag7.cpp projections/lcca.cpp projections/geos.cpp projections/boggs.cpp projections/collg.cpp projections/comill.cpp projections/crast.cpp projections/denoy.cpp projections/eck1.cpp projections/eck2.cpp projections/eck3.cpp projections/eck4.cpp projections/eck5.cpp projections/fahey.cpp projections/fouc_s.cpp projections/gins8.cpp projections/gstmerc.cpp projections/gn_sinu.cpp projections/goode.cpp projections/igh.cpp projections/igh_o.cpp projections/imoll.cpp projections/imoll_o.cpp projections/hatano.cpp projections/loxim.cpp projections/mbt_fps.cpp projections/mbtfpp.cpp projections/mbtfpq.cpp projections/moll.cpp projections/nell.cpp projections/nell_h.cpp projections/patterson.cpp projections/putp2.cpp projections/putp3.cpp projections/putp4p.cpp projections/putp5.cpp projections/putp6.cpp projections/qsc.cpp projections/robin.cpp projections/s2.cpp projections/sch.cpp projections/sts.cpp projections/urm5.cpp projections/urmfps.cpp projections/wag2.cpp projections/wag3.cpp projections/wink1.cpp projections/wink2.cpp projections/healpix.cpp projections/natearth.cpp projections/natearth2.cpp projections/calcofi.cpp projections/eqearth.cpp projections/col_urban.cpp projections/spilhaus.cpp ) set(SRC_LIBPROJ_CONVERSIONS conversions/axisswap.cpp conversions/cart.cpp conversions/geoc.cpp conversions/geocent.cpp conversions/noop.cpp conversions/topocentric.cpp conversions/set.cpp conversions/unitconvert.cpp ) set(SRC_LIBPROJ_TRANSFORMATIONS transformations/affine.cpp transformations/deformation.cpp transformations/gridshift.cpp transformations/helmert.cpp transformations/hgridshift.cpp transformations/horner.cpp transformations/molodensky.cpp transformations/vgridshift.cpp transformations/xyzgridshift.cpp transformations/defmodel.cpp transformations/tinshift.cpp transformations/vertoffset.cpp ) set(SRC_LIBPROJ_ISO19111 iso19111/static.cpp iso19111/util.cpp iso19111/metadata.cpp iso19111/common.cpp iso19111/coordinates.cpp iso19111/crs.cpp iso19111/datum.cpp iso19111/coordinatesystem.cpp iso19111/io.cpp iso19111/internal.cpp iso19111/factory.cpp iso19111/c_api.cpp iso19111/operation/concatenatedoperation.cpp iso19111/operation/coordinateoperationfactory.cpp iso19111/operation/conversion.cpp iso19111/operation/esriparammappings.cpp iso19111/operation/oputils.cpp iso19111/operation/parametervalue.cpp iso19111/operation/parammappings.cpp iso19111/operation/projbasedoperation.cpp iso19111/operation/singleoperation.cpp iso19111/operation/transformation.cpp iso19111/operation/vectorofvaluesparams.cpp ) set(SRC_LIBPROJ_CORE aasincos.cpp adjlon.cpp area.cpp coord_operation.cpp coordinates.cpp create.cpp crs_to_crs.cpp ctx.cpp datum_set.cpp datums.cpp deriv.cpp dist.cpp dmstor.cpp ell_set.cpp ellps.cpp factors.cpp filemanager.hpp filemanager.cpp fwd.cpp gauss.cpp generic_inverse.cpp geodesic.c grids.hpp grids.cpp info.cpp init.cpp initcache.cpp internal.cpp inv.cpp latitudes.cpp list.cpp log.cpp malloc.cpp mlfn.cpp msfn.cpp mutex.cpp networkfilemanager.cpp param.cpp phi2.cpp pipeline.cpp pj_list.h pr_list.cpp proj_internal.h proj_json_streaming_writer.hpp proj_json_streaming_writer.cpp proj_mdist.cpp release.cpp rtodms.cpp strerrno.cpp strtod.cpp sqlite3_utils.hpp sqlite3_utils.cpp tracing.cpp trans.cpp trans_bounds.cpp tsfn.cpp units.cpp wkt1_generated_parser.c wkt1_generated_parser.h wkt1_parser.cpp wkt1_parser.h wkt2_generated_parser.c wkt2_generated_parser.h wkt2_parser.cpp wkt2_parser.h wkt_parser.cpp wkt_parser.hpp zpoly1.cpp ${CMAKE_CURRENT_BINARY_DIR}/proj_config.h ) # Skip Unity build for specific files set(SKIP_UNITY_BUILD_FILES list.cpp # because if sets DO_NOT_DEFINE_PROJ_HEAD transformations/defmodel.cpp # Evaluator class conflict transformations/tinshift.cpp # Evaluator class conflict wkt1_parser.cpp wkt2_parser.cpp wkt1_generated_parser.c wkt2_generated_parser.c ) if(WIN32) list(APPEND SKIP_UNITY_BUILD_FILES networkfilemanager.cpp ) endif() set_property(SOURCE ${SKIP_UNITY_BUILD_FILES} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) set(HEADERS_LIBPROJ proj.h proj_experimental.h proj_constants.h proj_symbol_rename.h geodesic.h ) # Group source files for IDE source explorers (e.g. Visual Studio) source_group("Header Files" FILES ${HEADERS_LIBPROJ}) source_group("Source Files\\Core" FILES ${SRC_LIBPROJ_CORE}) source_group("Source Files\\Conversions" FILES ${SRC_LIBPROJ_CONVERSIONS}) source_group("Source Files\\Projections" FILES ${SRC_LIBPROJ_PROJECTIONS}) source_group("Source Files\\Transformations" FILES ${SRC_LIBPROJ_TRANSFORMATIONS}) source_group("Source Files\\ISO19111" FILES ${SRC_LIBPROJ_ISO19111}) include_directories(${PROJ_SOURCE_DIR}/include) include_directories(${CMAKE_CURRENT_BINARY_DIR}) source_group("CMake Files" FILES CMakeLists.txt) # Embed PROJ_DATA data files location if(EMBED_PROJ_DATA_PATH) add_definitions(-DPROJ_DATA="${PROJ_DATA_PATH}") endif() ########################################################### # targets to refresh wkt1_parser.cpp and wkt2_parser.cpp ########################################################### # Those targets need to be run manually each time wkt1_grammar.y / wkt2_grammar.y # is modified. # We could of course run them automatically, but that would make building # PROJ harder. # This target checks that wkt1_grammar.y md5sum has not changed # If it has, then it should be updated and the generate_wkt1_parser target # should be manually run add_custom_target(check_wkt1_grammar_md5 ALL COMMAND ${CMAKE_COMMAND} "-DIN_FILE=wkt1_grammar.y" "-DTARGET=generate_wkt1_parser" "-DEXPECTED_MD5SUM=5b4495c1ec6d2ae26b7028a9bb5d8819" -P "${CMAKE_CURRENT_SOURCE_DIR}/check_md5sum.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/wkt1_grammar.y" VERBATIM) add_custom_target(generate_wkt1_parser COMMAND ${CMAKE_COMMAND} "-DPREFIX=pj_wkt1_" "-DIN_FILE=wkt1_grammar.y" "-DOUT_FILE=wkt1_generated_parser.c" -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_wkt_parser.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" VERBATIM) # This target checks that wkt2_grammar.y md5sum has not changed # If it has, then it should be updated and the generate_wkt2_parser target # should be manually run add_custom_target(check_wkt2_grammar_md5 ALL COMMAND ${CMAKE_COMMAND} "-DIN_FILE=wkt2_grammar.y" "-DTARGET=generate_wkt2_parser" "-DEXPECTED_MD5SUM=0a58cbfb6abd2b23bbb9345e12773348" -P "${CMAKE_CURRENT_SOURCE_DIR}/check_md5sum.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/wkt2_grammar.y" VERBATIM) add_custom_target(generate_wkt2_parser COMMAND ${CMAKE_COMMAND} "-DPREFIX=pj_wkt2_" "-DIN_FILE=wkt2_grammar.y" "-DOUT_FILE=wkt2_generated_parser.c" -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_wkt_parser.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" VERBATIM) ################################################# ## targets: libproj and proj_config.h ################################################# set(ALL_LIBPROJ_SOURCES ${SRC_LIBPROJ_CORE} ${SRC_LIBPROJ_CONVERSIONS} ${SRC_LIBPROJ_PROJECTIONS} ${SRC_LIBPROJ_TRANSFORMATIONS} ${SRC_LIBPROJ_ISO19111} ${SRC_PROJAPPS_LIBS} ) set(ALL_LIBPROJ_HEADERS ${HEADERS_LIBPROJ} ${HEADERS_PROJAPPS_LIBS} ) # Configuration for the core target "proj" add_library(proj ${ALL_LIBPROJ_SOURCES} ${ALL_LIBPROJ_HEADERS} ${PROJ_RESOURCES} ) add_library(PROJ::proj ALIAS proj) if(MSVC OR MINGW) target_compile_definitions(proj PRIVATE -DNOMINMAX) endif() # Tell Intel compiler to do arithmetic accurately. This is needed to stop the # compiler from ignoring parentheses in expressions like (a + b) + c and from # simplifying 0.0 + x to x (which is wrong if x = -0.0). if("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel") if(MSVC) set(FP_PRECISE "/fp:precise") else() set(FP_PRECISE "-fp-model precise") endif() # Apply to source files that require this option set_source_files_properties( geodesic.c PROPERTIES COMPILE_FLAGS ${FP_PRECISE}) endif() if (EMBED_RESOURCE_FILES) add_library(proj_resources OBJECT embedded_resources.c) add_dependencies(proj_resources generate_proj_db) option(PROJ_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE "Set ON to produce -fPIC code" ${BUILD_SHARED_LIBS}) set_property(TARGET proj_resources PROPERTY POSITION_INDEPENDENT_CODE ${PROJ_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE}) target_sources(proj PRIVATE $) if (NOT IS_SHARP_EMBED_AVAILABLE_RES) set(EMBEDDED_PROJ_DB "file_embed/proj_db.c") add_custom_command( OUTPUT "${EMBEDDED_PROJ_DB}" COMMAND ${CMAKE_COMMAND} -DRUN_FILE_EMBED_GENERATE=1 "-DFILE_EMBED_GENERATE_PATH=${PROJECT_BINARY_DIR}/data/proj.db" -P ${PROJECT_SOURCE_DIR}/cmake/FileEmbed.cmake DEPENDS generate_proj_db "${PROJECT_BINARY_DIR}/data/proj.db" ) target_sources(proj_resources PRIVATE "${EMBEDDED_PROJ_DB}") else() target_include_directories(proj_resources PRIVATE "${PROJECT_BINARY_DIR}/data") set_source_files_properties(embedded_resources.c OBJECT_DEPENDS ${PROJECT_BINARY_DIR}/data/proj.db) target_compile_definitions(proj_resources PRIVATE "PROJ_DB=\"${PROJECT_BINARY_DIR}/data/proj.db\"") target_compile_definitions(proj_resources PRIVATE USE_SHARP_EMBED) set_target_properties(proj_resources PROPERTIES C_STANDARD 23) endif() endif() set(EMBED_RESOURCE_DIRECTORY "" CACHE PATH "Directory that contains .tif, .json or .pol files to embed into libproj") set(FILES_TO_EMBED) if (EMBED_RESOURCE_DIRECTORY) if (NOT EMBED_RESOURCE_FILES) message(FATAL_ERROR "EMBED_RESOURCE_FILES should be set to ON when EMBED_RESOURCE_DIRECTORY is set") endif() if (NOT IS_DIRECTORY ${EMBED_RESOURCE_DIRECTORY}) message(FATAL_ERROR "${EMBED_RESOURCE_DIRECTORY} is not a valid directory") endif() file(GLOB FILES_TO_EMBED "${EMBED_RESOURCE_DIRECTORY}/*.tif" "${EMBED_RESOURCE_DIRECTORY}/*.json" "${EMBED_RESOURCE_DIRECTORY}/*.pol") if (NOT FILES_TO_EMBED) message(FATAL_ERROR "No .tif, .json or .pol files found in ${EMBED_RESOURCE_DIRECTORY}") endif() endif() if (EMBED_RESOURCE_FILES) list(APPEND FILES_TO_EMBED "../data/proj.ini") foreach(FILE ${PROJ_DICTIONARY}) list(APPEND FILES_TO_EMBED "../data/${FILE}") endforeach() endif() if (FILES_TO_EMBED) set(EMBEDDED_RESOURCES_C_PROLOG_CONTENT "") set(EMBEDDED_RESOURCES_C_CONTENT "") string(APPEND EMBEDDED_RESOURCES_C_CONTENT "const unsigned char *pj_get_embedded_resource(const char* filename, unsigned int *pnSize)\n" "{\n") foreach(FILE ${FILES_TO_EMBED}) get_filename_component(FILENAME ${FILE} NAME) message(STATUS "Embedding ${FILENAME}") set(C_IDENTIFIER_RESOURCE_NAME "${FILENAME}") string(REPLACE "." "_" C_IDENTIFIER_RESOURCE_NAME "${C_IDENTIFIER_RESOURCE_NAME}") string(REPLACE "-" "_" C_IDENTIFIER_RESOURCE_NAME "${C_IDENTIFIER_RESOURCE_NAME}") set(C_FILENAME "file_embed/${C_IDENTIFIER_RESOURCE_NAME}.c") add_custom_command( OUTPUT "${C_FILENAME}" COMMAND ${CMAKE_COMMAND} -DRUN_FILE_EMBED_GENERATE=1 "-DFILE_EMBED_GENERATE_PATH=${FILE}" -P ${PROJECT_SOURCE_DIR}/cmake/FileEmbed.cmake DEPENDS "${FILE}" ) string(APPEND EMBEDDED_RESOURCES_C_CONTENT " if (strcmp(filename, \"${FILENAME}\") == 0)\n" " {\n" " *pnSize = ${C_IDENTIFIER_RESOURCE_NAME}_size;\n" " return ${C_IDENTIFIER_RESOURCE_NAME}_data;\n" " }\n") target_sources(proj_resources PRIVATE "${C_FILENAME}") string(APPEND EMBEDDED_RESOURCES_C_PROLOG_CONTENT "extern const uint8_t ${C_IDENTIFIER_RESOURCE_NAME}_data[];\n") string(APPEND EMBEDDED_RESOURCES_C_PROLOG_CONTENT "extern const unsigned ${C_IDENTIFIER_RESOURCE_NAME}_size;\n") endforeach() string(APPEND EMBEDDED_RESOURCES_C_CONTENT " *pnSize = 0;\n" " return NULL;\n" "}\n") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/file_embed/embedded_resources.c" "${EMBEDDED_RESOURCES_C_PROLOG_CONTENT}\n${EMBEDDED_RESOURCES_C_CONTENT}") endif() if (EMBED_RESOURCE_FILES) target_sources(proj PRIVATE memvfs.c) target_compile_definitions(proj PRIVATE EMBED_RESOURCE_FILES) endif() if (USE_ONLY_EMBEDDED_RESOURCE_FILES) target_compile_definitions(proj PRIVATE USE_ONLY_EMBEDDED_RESOURCE_FILES) endif() if(ENABLE_IPO) set_property(TARGET proj PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) endif() target_include_directories(proj INTERFACE $ $ $) if(WIN32) if (MINGW AND BUILD_SHARED_LIBS) option(APPEND_SOVERSION "Whether to include shared object version as a suffix in the name of the PROJ shared library name." OFF) endif() if(MINGW AND BUILD_SHARED_LIBS AND APPEND_SOVERSION) set(PROJ_OUTPUT_NAME "proj" CACHE STRING "Name of the PROJ library") else() # Detect major version update if reusing a CMake build directory where the # PROJ version major number has been updated in the meantime. math(EXPR PROJ_VERSION_MAJOR_MINUS_ONE "${PROJ_VERSION_MAJOR} - 1") if(DEFINED PROJ_OUTPUT_NAME AND PROJ_OUTPUT_NAME STREQUAL "proj_${PROJ_VERSION_MAJOR_MINUS_ONE}") message(WARNING "PROJ_OUTPUT_NAME was set to ${PROJ_OUTPUT_NAME}. Updating it to proj_${PROJ_VERSION_MAJOR}") unset(PROJ_OUTPUT_NAME CACHE) endif() set(PROJ_OUTPUT_NAME "proj_${PROJ_VERSION_MAJOR}" CACHE STRING "Name of the PROJ library") endif() else() set(PROJ_OUTPUT_NAME "proj" CACHE STRING "Name of the PROJ library") endif() set_target_properties(proj PROPERTIES OUTPUT_NAME ${PROJ_OUTPUT_NAME}) if(WIN32) set_target_properties(proj PROPERTIES VERSION "${PROJ_VERSION}" ARCHIVE_OUTPUT_NAME proj CLEAN_DIRECT_OUTPUT 1) if (MINGW AND BUILD_SHARED_LIBS AND APPEND_SOVERSION) set_target_properties(proj PROPERTIES SUFFIX "-${PROJ_SOVERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}") endif() elseif(BUILD_FRAMEWORKS_AND_BUNDLE) set_target_properties(proj PROPERTIES VERSION "${PROJ_VERSION}" INSTALL_NAME_DIR ${PROJ_INSTALL_NAME_DIR} CLEAN_DIRECT_OUTPUT 1) else() set_target_properties(proj PROPERTIES VERSION "${PROJ_BUILD_VERSION}" SOVERSION "${PROJ_SOVERSION}" CLEAN_DIRECT_OUTPUT 1) endif() set_target_properties(proj PROPERTIES LINKER_LANGUAGE CXX) ############################################## # Link properties ############################################## set(PROJ_LIBRARIES proj) # hack, required for test/unit set(PROJ_LIBRARIES ${PROJ_LIBRARIES} PARENT_SCOPE) if(WIN32) target_link_libraries (proj PRIVATE shell32.lib ole32.lib ) endif() if(UNIX) find_library(M_LIB m) if(M_LIB) target_link_libraries(proj PRIVATE -lm) endif() find_library(DL_LIB dl) if(DL_LIB) target_link_libraries(proj PRIVATE -ldl) endif() endif() if(Threads_FOUND AND CMAKE_USE_PTHREADS_INIT) target_link_libraries(proj PRIVATE ${CMAKE_THREAD_LIBS_INIT}) endif() target_link_libraries(proj PRIVATE SQLite3::SQLite3) if(NLOHMANN_JSON STREQUAL "external") target_compile_definitions(proj PRIVATE EXTERNAL_NLOHMANN_JSON) target_link_libraries(proj PRIVATE $) endif() if(TIFF_ENABLED) target_compile_definitions(proj PRIVATE -DTIFF_ENABLED) target_link_libraries(proj PRIVATE TIFF::TIFF) endif() if(CURL_ENABLED) target_compile_definitions(proj PRIVATE -DCURL_ENABLED) target_link_libraries(proj PRIVATE CURL::libcurl) target_link_libraries(proj PRIVATE $<$:ws2_32> $<$:wldap32> $<$:advapi32> $<$:crypt32> $<$:normaliz>) endif() if(EMSCRIPTEN_FETCH_ENABLED) target_compile_definitions(proj PRIVATE -DEMSCRIPTEN_FETCH_ENABLED) endif() if(BUILD_SHARED_LIBS) if(MSVC) target_compile_definitions(proj PRIVATE PROJ_MSVC_DLL_EXPORT=1) endif() else() target_compile_definitions(proj PUBLIC PROJ_DLL=) endif() ############################################## # install ############################################## install(TARGETS proj EXPORT targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} FRAMEWORK DESTINATION ${FRAMEWORKDIR}) if(NOT BUILD_FRAMEWORKS_AND_BUNDLE) install(FILES ${ALL_LIBPROJ_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() ############################################## # Core configuration summary ############################################## print_variable(PROJ_OUTPUT_NAME) print_variable(BUILD_SHARED_LIBS) print_variable(PROJ_LIBRARIES) proj-9.8.1/src/memvfs.c000664 001750 001750 00000034063 15166171715 014711 0ustar00eveneven000000 000000 /* Derived from https://www.sqlite.org/src/doc/tip/ext/misc/memvfs.c */ /* ** 2016-09-07 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This is an in-memory VFS implementation. The application supplies ** a chunk of memory to hold the database file. ** ** Because there is place to store a rollback or wal journal, the database ** must use one of journal_mode=MEMORY or journal_mode=NONE. ** ** USAGE: ** ** sqlite3_open_v2("file:/whatever?ptr=0xf05538&sz=14336&max=65536", &db, ** SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, ** "memvfs"); ** ** These are the query parameters: ** ** ptr= The address of the memory buffer that holds the database. ** ** sz= The current size the database file ** ** maxsz= The maximum size of the database. In other words, the ** amount of space allocated for the ptr= buffer. ** ** The ptr= and sz= query parameters are required. If maxsz= is omitted, ** then it defaults to the sz= value. Parameter values can be in either ** decimal or hexadecimal. The filename in the URI is ignored. */ #include #include #include #include #include "memvfs.h" #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif #ifdef _MSC_VER #pragma warning(push) // Ignore unreferenced formal parameter #pragma warning(disable : 4100) #endif /* ** Forward declaration of objects used by this utility */ typedef struct sqlite3_vfs MemVfs; typedef struct MemFile MemFile; typedef struct MemVfsAppData { const unsigned char *buffer; size_t bufferSize; sqlite3_vfs *pBaseVFS; } MemVfsAppData; /* Access to a lower-level VFS that (might) implement dynamic loading, ** access to randomness, etc. */ #define ORIGVFS(p) (((MemVfsAppData *)((p)->pAppData))->pBaseVFS) /* An open file */ struct MemFile { sqlite3_file base; /* IO methods */ sqlite3_int64 sz; /* Size of the file */ sqlite3_int64 szMax; /* Space allocated to aData */ const unsigned char *aData; /* content of the file */ }; /* ** Methods for MemFile */ static int memClose(sqlite3_file *); static int memRead(sqlite3_file *, void *, int iAmt, sqlite3_int64 iOfst); static int memWrite(sqlite3_file *, const void *, int iAmt, sqlite3_int64 iOfst); static int memTruncate(sqlite3_file *, sqlite3_int64 size); static int memSync(sqlite3_file *, int flags); static int memFileSize(sqlite3_file *, sqlite3_int64 *pSize); static int memLock(sqlite3_file *, int); static int memUnlock(sqlite3_file *, int); static int memCheckReservedLock(sqlite3_file *, int *pResOut); static int memFileControl(sqlite3_file *, int op, void *pArg); static int memSectorSize(sqlite3_file *); static int memDeviceCharacteristics(sqlite3_file *); static int memShmMap(sqlite3_file *, int iPg, int pgsz, int, void volatile **); static int memShmLock(sqlite3_file *, int offset, int n, int flags); static void memShmBarrier(sqlite3_file *); static int memShmUnmap(sqlite3_file *, int deleteFlag); static int memFetch(sqlite3_file *, sqlite3_int64 iOfst, int iAmt, void **pp); static int memUnfetch(sqlite3_file *, sqlite3_int64 iOfst, void *p); /* ** Methods for MemVfs */ static int memOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int *); static int memDelete(sqlite3_vfs *, const char *zName, int syncDir); static int memAccess(sqlite3_vfs *, const char *zName, int flags, int *); static int memFullPathname(sqlite3_vfs *, const char *zName, int, char *zOut); static void *memDlOpen(sqlite3_vfs *, const char *zFilename); static void memDlError(sqlite3_vfs *, int nByte, char *zErrMsg); static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void); static void memDlClose(sqlite3_vfs *, void *); static int memRandomness(sqlite3_vfs *, int nByte, char *zOut); static int memSleep(sqlite3_vfs *, int microseconds); static int memCurrentTime(sqlite3_vfs *, double *); static int memGetLastError(sqlite3_vfs *, int, char *); static int memCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64 *); static sqlite3_vfs mem_vfs = { 2, /* iVersion */ 0, /* szOsFile (set when registered) */ 1024, /* mxPathname */ 0, /* pNext */ "memvfs", /* zName */ 0, /* pAppData (set when registered) */ memOpen, /* xOpen */ memDelete, /* xDelete */ memAccess, /* xAccess */ memFullPathname, /* xFullPathname */ memDlOpen, /* xDlOpen */ memDlError, /* xDlError */ memDlSym, /* xDlSym */ memDlClose, /* xDlClose */ memRandomness, /* xRandomness */ memSleep, /* xSleep */ memCurrentTime, /* xCurrentTime */ memGetLastError, /* xGetLastError */ memCurrentTimeInt64 /* xCurrentTimeInt64 */ }; static const sqlite3_io_methods mem_io_methods = { 3, /* iVersion */ memClose, /* xClose */ memRead, /* xRead */ memWrite, /* xWrite */ memTruncate, /* xTruncate */ memSync, /* xSync */ memFileSize, /* xFileSize */ memLock, /* xLock */ memUnlock, /* xUnlock */ memCheckReservedLock, /* xCheckReservedLock */ memFileControl, /* xFileControl */ memSectorSize, /* xSectorSize */ memDeviceCharacteristics, /* xDeviceCharacteristics */ memShmMap, /* xShmMap */ memShmLock, /* xShmLock */ memShmBarrier, /* xShmBarrier */ memShmUnmap, /* xShmUnmap */ memFetch, /* xFetch */ memUnfetch /* xUnfetch */ }; /* ** Close an mem-file. ** ** The pData pointer is owned by the application, so there is nothing ** to free. */ static int memClose(sqlite3_file *pFile) { return SQLITE_OK; } /* ** Read data from an mem-file. */ static int memRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) { MemFile *p = (MemFile *)pFile; memcpy(zBuf, p->aData + iOfst, iAmt); return SQLITE_OK; } /* ** Write data to an mem-file. */ static int memWrite(sqlite3_file *pFile, const void *z, int iAmt, sqlite_int64 iOfst) { return SQLITE_READONLY; } /* ** Truncate an mem-file. */ static int memTruncate(sqlite3_file *pFile, sqlite_int64 size) { return SQLITE_READONLY; } /* ** Sync an mem-file. */ static int memSync(sqlite3_file *pFile, int flags) { return SQLITE_OK; } /* ** Return the current file-size of an mem-file. */ static int memFileSize(sqlite3_file *pFile, sqlite_int64 *pSize) { MemFile *p = (MemFile *)pFile; *pSize = p->sz; return SQLITE_OK; } /* ** Lock an mem-file. */ static int memLock(sqlite3_file *pFile, int eLock) { return SQLITE_OK; } /* ** Unlock an mem-file. */ static int memUnlock(sqlite3_file *pFile, int eLock) { return SQLITE_OK; } /* ** Check if another file-handle holds a RESERVED lock on an mem-file. */ static int memCheckReservedLock(sqlite3_file *pFile, int *pResOut) { *pResOut = 0; return SQLITE_OK; } /* ** File control method. For custom operations on an mem-file. */ static int memFileControl(sqlite3_file *pFile, int op, void *pArg) { MemFile *p = (MemFile *)pFile; int rc = SQLITE_NOTFOUND; if (op == SQLITE_FCNTL_VFSNAME) { *(char **)pArg = sqlite3_mprintf("mem(%p,%lld)", p->aData, p->sz); rc = SQLITE_OK; } return rc; } /* ** Return the sector-size in bytes for an mem-file. */ static int memSectorSize(sqlite3_file *pFile) { return 1024; } /* ** Return the device characteristic flags supported by an mem-file. */ static int memDeviceCharacteristics(sqlite3_file *pFile) { return SQLITE_IOCAP_ATOMIC | SQLITE_IOCAP_POWERSAFE_OVERWRITE | SQLITE_IOCAP_SAFE_APPEND | SQLITE_IOCAP_SEQUENTIAL; } /* Create a shared memory file mapping */ static int memShmMap(sqlite3_file *pFile, int iPg, int pgsz, int bExtend, void volatile **pp) { return SQLITE_IOERR_SHMMAP; } /* Perform locking on a shared-memory segment */ static int memShmLock(sqlite3_file *pFile, int offset, int n, int flags) { return SQLITE_IOERR_SHMLOCK; } /* Memory barrier operation on shared memory */ static void memShmBarrier(sqlite3_file *pFile) { return; } /* Unmap a shared memory segment */ static int memShmUnmap(sqlite3_file *pFile, int deleteFlag) { return SQLITE_OK; } /* Fetch a page of a memory-mapped file */ static int memFetch(sqlite3_file *pFile, sqlite3_int64 iOfst, int iAmt, void **pp) { MemFile *p = (MemFile *)pFile; *pp = (void *)(p->aData + iOfst); return SQLITE_OK; } /* Release a memory-mapped page */ static int memUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage) { return SQLITE_OK; } /* ** Open an mem file handle. */ static int memOpen(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags) { MemFile *p = (MemFile *)pFile; MemVfsAppData *appData = (MemVfsAppData *)(pVfs->pAppData); memset(p, 0, sizeof(*p)); if ((flags & SQLITE_OPEN_MAIN_DB) == 0) { /* Modification w.r.t upstream: instead of returning SQLITE_CANTOPEN, * delegate to origin VFS. Typically for temporary file creation. */ return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags); } if ((uintptr_t)(appData->buffer) != (uintptr_t)sqlite3_uri_int64(zName, "ptr", 0)) { return SQLITE_CANTOPEN; } p->aData = appData->buffer; p->sz = sqlite3_uri_int64(zName, "sz", 0); if (p->sz < 0 || (size_t)p->sz != appData->bufferSize) return SQLITE_CANTOPEN; p->szMax = sqlite3_uri_int64(zName, "max", p->sz); if (p->szMax < p->sz) return SQLITE_CANTOPEN; pFile->pMethods = &mem_io_methods; return SQLITE_OK; } /* ** Delete the file located at zPath. If the dirSync argument is true, ** ensure the file-system modifications are synced to disk before ** returning. */ static int memDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync) { return SQLITE_IOERR_DELETE; } /* ** Test for access permissions. Return true if the requested permission ** is available, or false otherwise. */ static int memAccess(sqlite3_vfs *pVfs, const char *zPath, int flags, int *pResOut) { *pResOut = 0; return SQLITE_OK; } /* ** Populate buffer zOut with the full canonical pathname corresponding ** to the pathname in zPath. zOut is guaranteed to point to a buffer ** of at least (INST_MAX_PATHNAME+1) bytes. */ static int memFullPathname(sqlite3_vfs *pVfs, const char *zPath, int nOut, char *zOut) { sqlite3_snprintf(nOut, zOut, "%s", zPath); return SQLITE_OK; } /* ** Open the dynamic library located at zPath and return a handle. */ static void *memDlOpen(sqlite3_vfs *pVfs, const char *zPath) { return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); } /* ** Populate the buffer zErrMsg (size nByte bytes) with a human readable ** utf-8 string describing the most recent error encountered associated ** with dynamic libraries. */ static void memDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg) { ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); } /* ** Return a pointer to the symbol zSymbol in the dynamic library pHandle. */ static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void) { return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); } /* ** Close the dynamic library handle pHandle. */ static void memDlClose(sqlite3_vfs *pVfs, void *pHandle) { ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); } /* ** Populate the buffer pointed to by zBufOut with nByte bytes of ** random data. */ static int memRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut) { return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); } /* ** Sleep for nMicro microseconds. Return the number of microseconds ** actually slept. */ static int memSleep(sqlite3_vfs *pVfs, int nMicro) { return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); } /* ** Return the current time as a Julian Day number in *pTimeOut. */ static int memCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut) { return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); } static int memGetLastError(sqlite3_vfs *pVfs, int a, char *b) { return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); } static int memCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p) { return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); } /* * Register the new VFS. */ int pj_sqlite3_memvfs_init(sqlite3_vfs *vfs, const char *vfs_name, const void *buffer, size_t bufferSize) { memcpy(vfs, &mem_vfs, sizeof(mem_vfs)); vfs->zName = vfs_name; sqlite3_vfs *defaultVFS = sqlite3_vfs_find(NULL); if (!defaultVFS) return SQLITE_ERROR; MemVfsAppData *appData = (MemVfsAppData *)sqlite3_malloc(sizeof(MemVfsAppData)); appData->buffer = buffer; appData->bufferSize = bufferSize; appData->pBaseVFS = defaultVFS; vfs->pAppData = appData; vfs->szOsFile = sizeof(MemFile); /* Modification w.r.t upstream: as we might delegate file opening * to default VFS for temporary files, we need to make sure szOsFile is * the maximum of our own need and of the default VFS. */ if (vfs->szOsFile < defaultVFS->szOsFile) vfs->szOsFile = defaultVFS->szOsFile; return sqlite3_vfs_register(vfs, 0); } void pj_sqlite3_memvfs_deallocate_user_data(sqlite3_vfs *vfs) { sqlite3_free(vfs->pAppData); vfs->pAppData = NULL; } #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef __GNUC__ #pragma GCC diagnostic pop #endif proj-9.8.1/src/proj_symbol_rename.h000664 001750 001750 00000057540 15166171715 017314 0ustar00eveneven000000 000000 /* This is a generated file by create_proj_symbol_rename.sh. *DO NOT EDIT MANUALLY !* */ #ifndef PROJ_SYMBOL_RENAME_H #define PROJ_SYMBOL_RENAME_H #define geod_direct internal_geod_direct #define geod_directline internal_geod_directline #define geod_gendirect internal_geod_gendirect #define geod_gendirectline internal_geod_gendirectline #define geod_geninverse internal_geod_geninverse #define geod_genposition internal_geod_genposition #define geod_gensetdistance internal_geod_gensetdistance #define geod_init internal_geod_init #define geod_inverse internal_geod_inverse #define geod_inverseline internal_geod_inverseline #define geod_lineinit internal_geod_lineinit #define geod_polygon_addedge internal_geod_polygon_addedge #define geod_polygon_addpoint internal_geod_polygon_addpoint #define geod_polygonarea internal_geod_polygonarea #define geod_polygon_clear internal_geod_polygon_clear #define geod_polygon_compute internal_geod_polygon_compute #define geod_polygon_init internal_geod_polygon_init #define geod_polygon_testedge internal_geod_polygon_testedge #define geod_polygon_testpoint internal_geod_polygon_testpoint #define geod_position internal_geod_position #define geod_setdistance internal_geod_setdistance #define proj_alter_id internal_proj_alter_id #define proj_alter_name internal_proj_alter_name #define proj_angular_input internal_proj_angular_input #define proj_angular_output internal_proj_angular_output #define proj_area_create internal_proj_area_create #define proj_area_destroy internal_proj_area_destroy #define proj_area_set_bbox internal_proj_area_set_bbox #define proj_area_set_name internal_proj_area_set_name #define proj_as_projjson internal_proj_as_projjson #define proj_as_proj_string internal_proj_as_proj_string #define proj_assign_context internal_proj_assign_context #define proj_as_wkt internal_proj_as_wkt #define proj_celestial_body_list_destroy internal_proj_celestial_body_list_destroy #define proj_cleanup internal_proj_cleanup #define proj_clone internal_proj_clone #define proj_concatoperation_get_step internal_proj_concatoperation_get_step #define proj_concatoperation_get_step_count internal_proj_concatoperation_get_step_count #define proj_context_clone internal_proj_context_clone #define proj_context_create internal_proj_context_create #define proj_context_destroy internal_proj_context_destroy #define proj_context_errno internal_proj_context_errno #define proj_context_errno_string internal_proj_context_errno_string #define proj_context_get_database_metadata internal_proj_context_get_database_metadata #define proj_context_get_database_path internal_proj_context_get_database_path #define proj_context_get_database_structure internal_proj_context_get_database_structure #define proj_context_get_url_endpoint internal_proj_context_get_url_endpoint #define proj_context_get_use_proj4_init_rules internal_proj_context_get_use_proj4_init_rules #define proj_context_get_user_writable_directory internal_proj_context_get_user_writable_directory #define proj_context_guess_wkt_dialect internal_proj_context_guess_wkt_dialect #define proj_context_is_network_enabled internal_proj_context_is_network_enabled #define proj_context_set_autoclose_database internal_proj_context_set_autoclose_database #define proj_context_set_ca_bundle_path internal_proj_context_set_ca_bundle_path #define proj_context_set_database_path internal_proj_context_set_database_path #define proj_context_set_enable_network internal_proj_context_set_enable_network #define proj_context_set_fileapi internal_proj_context_set_fileapi #define proj_context_set_file_finder internal_proj_context_set_file_finder #define proj_context_set_network_callbacks internal_proj_context_set_network_callbacks #define proj_context_set_search_paths internal_proj_context_set_search_paths #define proj_context_set_sqlite3_vfs_name internal_proj_context_set_sqlite3_vfs_name #define proj_context_set_url_endpoint internal_proj_context_set_url_endpoint #define proj_context_set_user_writable_directory internal_proj_context_set_user_writable_directory #define proj_context_use_proj4_init_rules internal_proj_context_use_proj4_init_rules #define proj_convert_conversion_to_other_method internal_proj_convert_conversion_to_other_method #define proj_coord internal_proj_coord #define proj_coordinate_metadata_create internal_proj_coordinate_metadata_create #define proj_coordinate_metadata_get_epoch internal_proj_coordinate_metadata_get_epoch #define proj_coordoperation_create_inverse internal_proj_coordoperation_create_inverse #define proj_coordoperation_get_accuracy internal_proj_coordoperation_get_accuracy #define proj_coordoperation_get_grid_used internal_proj_coordoperation_get_grid_used #define proj_coordoperation_get_grid_used_count internal_proj_coordoperation_get_grid_used_count #define proj_coordoperation_get_method_info internal_proj_coordoperation_get_method_info #define proj_coordoperation_get_param internal_proj_coordoperation_get_param #define proj_coordoperation_get_param_count internal_proj_coordoperation_get_param_count #define proj_coordoperation_get_param_index internal_proj_coordoperation_get_param_index #define proj_coordoperation_get_towgs84_values internal_proj_coordoperation_get_towgs84_values #define proj_coordoperation_has_ballpark_transformation internal_proj_coordoperation_has_ballpark_transformation #define proj_coordoperation_is_instantiable internal_proj_coordoperation_is_instantiable #define proj_coordoperation_requires_per_coordinate_input_time internal_proj_coordoperation_requires_per_coordinate_input_time #define proj_create internal_proj_create #define proj_create_argv internal_proj_create_argv #define proj_create_cartesian_2D_cs internal_proj_create_cartesian_2D_cs #define proj_create_compound_crs internal_proj_create_compound_crs #define proj_create_conversion internal_proj_create_conversion #define proj_create_conversion_albers_equal_area internal_proj_create_conversion_albers_equal_area #define proj_create_conversion_american_polyconic internal_proj_create_conversion_american_polyconic #define proj_create_conversion_azimuthal_equidistant internal_proj_create_conversion_azimuthal_equidistant #define proj_create_conversion_bonne internal_proj_create_conversion_bonne #define proj_create_conversion_cassini_soldner internal_proj_create_conversion_cassini_soldner #define proj_create_conversion_eckert_i internal_proj_create_conversion_eckert_i #define proj_create_conversion_eckert_ii internal_proj_create_conversion_eckert_ii #define proj_create_conversion_eckert_iii internal_proj_create_conversion_eckert_iii #define proj_create_conversion_eckert_iv internal_proj_create_conversion_eckert_iv #define proj_create_conversion_eckert_v internal_proj_create_conversion_eckert_v #define proj_create_conversion_eckert_vi internal_proj_create_conversion_eckert_vi #define proj_create_conversion_equal_earth internal_proj_create_conversion_equal_earth #define proj_create_conversion_equidistant_conic internal_proj_create_conversion_equidistant_conic #define proj_create_conversion_equidistant_cylindrical internal_proj_create_conversion_equidistant_cylindrical #define proj_create_conversion_equidistant_cylindrical_spherical internal_proj_create_conversion_equidistant_cylindrical_spherical #define proj_create_conversion_gall internal_proj_create_conversion_gall #define proj_create_conversion_gauss_schreiber_transverse_mercator internal_proj_create_conversion_gauss_schreiber_transverse_mercator #define proj_create_conversion_geostationary_satellite_sweep_x internal_proj_create_conversion_geostationary_satellite_sweep_x #define proj_create_conversion_geostationary_satellite_sweep_y internal_proj_create_conversion_geostationary_satellite_sweep_y #define proj_create_conversion_gnomonic internal_proj_create_conversion_gnomonic #define proj_create_conversion_goode_homolosine internal_proj_create_conversion_goode_homolosine #define proj_create_conversion_guam_projection internal_proj_create_conversion_guam_projection #define proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin internal_proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin #define proj_create_conversion_hotine_oblique_mercator_variant_a internal_proj_create_conversion_hotine_oblique_mercator_variant_a #define proj_create_conversion_hotine_oblique_mercator_variant_b internal_proj_create_conversion_hotine_oblique_mercator_variant_b #define proj_create_conversion_international_map_world_polyconic internal_proj_create_conversion_international_map_world_polyconic #define proj_create_conversion_interrupted_goode_homolosine internal_proj_create_conversion_interrupted_goode_homolosine #define proj_create_conversion_krovak internal_proj_create_conversion_krovak #define proj_create_conversion_krovak_north_oriented internal_proj_create_conversion_krovak_north_oriented #define proj_create_conversion_laborde_oblique_mercator internal_proj_create_conversion_laborde_oblique_mercator #define proj_create_conversion_lambert_azimuthal_equal_area internal_proj_create_conversion_lambert_azimuthal_equal_area #define proj_create_conversion_lambert_conic_conformal_1sp internal_proj_create_conversion_lambert_conic_conformal_1sp #define proj_create_conversion_lambert_conic_conformal_1sp_variant_b internal_proj_create_conversion_lambert_conic_conformal_1sp_variant_b #define proj_create_conversion_lambert_conic_conformal_2sp internal_proj_create_conversion_lambert_conic_conformal_2sp #define proj_create_conversion_lambert_conic_conformal_2sp_belgium internal_proj_create_conversion_lambert_conic_conformal_2sp_belgium #define proj_create_conversion_lambert_conic_conformal_2sp_michigan internal_proj_create_conversion_lambert_conic_conformal_2sp_michigan #define proj_create_conversion_lambert_cylindrical_equal_area internal_proj_create_conversion_lambert_cylindrical_equal_area #define proj_create_conversion_lambert_cylindrical_equal_area_spherical internal_proj_create_conversion_lambert_cylindrical_equal_area_spherical #define proj_create_conversion_local_orthographic internal_proj_create_conversion_local_orthographic #define proj_create_conversion_mercator_variant_a internal_proj_create_conversion_mercator_variant_a #define proj_create_conversion_mercator_variant_b internal_proj_create_conversion_mercator_variant_b #define proj_create_conversion_miller_cylindrical internal_proj_create_conversion_miller_cylindrical #define proj_create_conversion_mollweide internal_proj_create_conversion_mollweide #define proj_create_conversion_new_zealand_mapping_grid internal_proj_create_conversion_new_zealand_mapping_grid #define proj_create_conversion_oblique_stereographic internal_proj_create_conversion_oblique_stereographic #define proj_create_conversion_orthographic internal_proj_create_conversion_orthographic #define proj_create_conversion_polar_stereographic_variant_a internal_proj_create_conversion_polar_stereographic_variant_a #define proj_create_conversion_polar_stereographic_variant_b internal_proj_create_conversion_polar_stereographic_variant_b #define proj_create_conversion_pole_rotation_grib_convention internal_proj_create_conversion_pole_rotation_grib_convention #define proj_create_conversion_pole_rotation_netcdf_cf_convention internal_proj_create_conversion_pole_rotation_netcdf_cf_convention #define proj_create_conversion_popular_visualisation_pseudo_mercator internal_proj_create_conversion_popular_visualisation_pseudo_mercator #define proj_create_conversion_quadrilateralized_spherical_cube internal_proj_create_conversion_quadrilateralized_spherical_cube #define proj_create_conversion_robinson internal_proj_create_conversion_robinson #define proj_create_conversion_sinusoidal internal_proj_create_conversion_sinusoidal #define proj_create_conversion_spherical_cross_track_height internal_proj_create_conversion_spherical_cross_track_height #define proj_create_conversion_stereographic internal_proj_create_conversion_stereographic #define proj_create_conversion_transverse_mercator internal_proj_create_conversion_transverse_mercator #define proj_create_conversion_transverse_mercator_south_oriented internal_proj_create_conversion_transverse_mercator_south_oriented #define proj_create_conversion_tunisia_mapping_grid internal_proj_create_conversion_tunisia_mapping_grid #define proj_create_conversion_tunisia_mining_grid internal_proj_create_conversion_tunisia_mining_grid #define proj_create_conversion_two_point_equidistant internal_proj_create_conversion_two_point_equidistant #define proj_create_conversion_utm internal_proj_create_conversion_utm #define proj_create_conversion_van_der_grinten internal_proj_create_conversion_van_der_grinten #define proj_create_conversion_vertical_perspective internal_proj_create_conversion_vertical_perspective #define proj_create_conversion_wagner_i internal_proj_create_conversion_wagner_i #define proj_create_conversion_wagner_ii internal_proj_create_conversion_wagner_ii #define proj_create_conversion_wagner_iii internal_proj_create_conversion_wagner_iii #define proj_create_conversion_wagner_iv internal_proj_create_conversion_wagner_iv #define proj_create_conversion_wagner_v internal_proj_create_conversion_wagner_v #define proj_create_conversion_wagner_vi internal_proj_create_conversion_wagner_vi #define proj_create_conversion_wagner_vii internal_proj_create_conversion_wagner_vii #define proj_create_crs_to_crs internal_proj_create_crs_to_crs #define proj_create_crs_to_crs_from_pj internal_proj_create_crs_to_crs_from_pj #define proj_create_cs internal_proj_create_cs #define proj_create_derived_geographic_crs internal_proj_create_derived_geographic_crs #define proj_create_derived_projected_crs internal_proj_create_derived_projected_crs #define proj_create_ellipsoidal_2D_cs internal_proj_create_ellipsoidal_2D_cs #define proj_create_ellipsoidal_3D_cs internal_proj_create_ellipsoidal_3D_cs #define proj_create_engineering_crs internal_proj_create_engineering_crs #define proj_create_from_database internal_proj_create_from_database #define proj_create_from_name internal_proj_create_from_name #define proj_create_from_wkt internal_proj_create_from_wkt #define proj_create_geocentric_crs internal_proj_create_geocentric_crs #define proj_create_geocentric_crs_from_datum internal_proj_create_geocentric_crs_from_datum #define proj_create_geographic_crs internal_proj_create_geographic_crs #define proj_create_geographic_crs_from_datum internal_proj_create_geographic_crs_from_datum #define proj_create_linear_affine_parametric_conversion internal_proj_create_linear_affine_parametric_conversion #define proj_create_operation_factory_context internal_proj_create_operation_factory_context #define proj_create_operations internal_proj_create_operations #define proj_create_projected_crs internal_proj_create_projected_crs #define proj_create_transformation internal_proj_create_transformation #define proj_create_vertical_crs internal_proj_create_vertical_crs #define proj_create_vertical_crs_ex internal_proj_create_vertical_crs_ex #define proj_crs_add_horizontal_derived_conversion internal_proj_crs_add_horizontal_derived_conversion #define proj_crs_alter_cs_angular_unit internal_proj_crs_alter_cs_angular_unit #define proj_crs_alter_cs_linear_unit internal_proj_crs_alter_cs_linear_unit #define proj_crs_alter_geodetic_crs internal_proj_crs_alter_geodetic_crs #define proj_crs_alter_parameters_linear_unit internal_proj_crs_alter_parameters_linear_unit #define proj_crs_create_bound_crs internal_proj_crs_create_bound_crs #define proj_crs_create_bound_crs_to_WGS84 internal_proj_crs_create_bound_crs_to_WGS84 #define proj_crs_create_bound_vertical_crs internal_proj_crs_create_bound_vertical_crs #define proj_crs_create_projected_3D_crs_from_2D internal_proj_crs_create_projected_3D_crs_from_2D #define proj_crs_demote_to_2D internal_proj_crs_demote_to_2D #define proj_crs_get_coordinate_system internal_proj_crs_get_coordinate_system #define proj_crs_get_coordoperation internal_proj_crs_get_coordoperation #define proj_crs_get_datum internal_proj_crs_get_datum #define proj_crs_get_datum_ensemble internal_proj_crs_get_datum_ensemble #define proj_crs_get_datum_forced internal_proj_crs_get_datum_forced #define proj_crs_get_geodetic_crs internal_proj_crs_get_geodetic_crs #define proj_crs_get_horizontal_datum internal_proj_crs_get_horizontal_datum #define proj_crs_get_sub_crs internal_proj_crs_get_sub_crs #define proj_crs_has_point_motion_operation internal_proj_crs_has_point_motion_operation #define proj_crs_info_list_destroy internal_proj_crs_info_list_destroy #define proj_crs_is_derived internal_proj_crs_is_derived #define proj_crs_promote_to_3D internal_proj_crs_promote_to_3D #define proj_cs_get_axis_count internal_proj_cs_get_axis_count #define proj_cs_get_axis_info internal_proj_cs_get_axis_info #define proj_cs_get_type internal_proj_cs_get_type #define proj_datum_ensemble_get_accuracy internal_proj_datum_ensemble_get_accuracy #define proj_datum_ensemble_get_member internal_proj_datum_ensemble_get_member #define proj_datum_ensemble_get_member_count internal_proj_datum_ensemble_get_member_count #define proj_degree_input internal_proj_degree_input #define proj_degree_output internal_proj_degree_output #define proj_destroy internal_proj_destroy #define proj_dmstor internal_proj_dmstor #define proj_download_file internal_proj_download_file #define proj_dynamic_datum_get_frame_reference_epoch internal_proj_dynamic_datum_get_frame_reference_epoch #define proj_ellipsoid_get_parameters internal_proj_ellipsoid_get_parameters #define proj_errno internal_proj_errno #define proj_errno_reset internal_proj_errno_reset #define proj_errno_restore internal_proj_errno_restore #define proj_errno_set internal_proj_errno_set #define proj_errno_string internal_proj_errno_string #define proj_factors internal_proj_factors #define proj_geod internal_proj_geod #define proj_geod_direct internal_proj_geod_direct #define proj_get_area_of_use internal_proj_get_area_of_use #define proj_get_area_of_use_ex internal_proj_get_area_of_use_ex #define proj_get_authorities_from_database internal_proj_get_authorities_from_database #define proj_get_celestial_body_list_from_database internal_proj_get_celestial_body_list_from_database #define proj_get_celestial_body_name internal_proj_get_celestial_body_name #define proj_get_codes_from_database internal_proj_get_codes_from_database #define proj_get_crs_info_list_from_database internal_proj_get_crs_info_list_from_database #define proj_get_crs_list_parameters_create internal_proj_get_crs_list_parameters_create #define proj_get_crs_list_parameters_destroy internal_proj_get_crs_list_parameters_destroy #define proj_get_domain_count internal_proj_get_domain_count #define proj_get_ellipsoid internal_proj_get_ellipsoid #define proj_get_geoid_models_from_database internal_proj_get_geoid_models_from_database #define proj_get_id_auth_name internal_proj_get_id_auth_name #define proj_get_id_code internal_proj_get_id_code #define proj_get_insert_statements internal_proj_get_insert_statements #define proj_get_name internal_proj_get_name #define proj_get_non_deprecated internal_proj_get_non_deprecated #define proj_get_prime_meridian internal_proj_get_prime_meridian #define proj_get_remarks internal_proj_get_remarks #define proj_get_scope internal_proj_get_scope #define proj_get_scope_ex internal_proj_get_scope_ex #define proj_get_source_crs internal_proj_get_source_crs #define proj_get_suggested_operation internal_proj_get_suggested_operation #define proj_get_target_crs internal_proj_get_target_crs #define proj_get_type internal_proj_get_type #define proj_get_units_from_database internal_proj_get_units_from_database #define proj_grid_cache_clear internal_proj_grid_cache_clear #define proj_grid_cache_set_enable internal_proj_grid_cache_set_enable #define proj_grid_cache_set_filename internal_proj_grid_cache_set_filename #define proj_grid_cache_set_max_size internal_proj_grid_cache_set_max_size #define proj_grid_cache_set_ttl internal_proj_grid_cache_set_ttl #define proj_grid_get_info_from_database internal_proj_grid_get_info_from_database #define proj_grid_info internal_proj_grid_info #define proj_identify internal_proj_identify #define proj_info internal_proj_info #define projinfo internal_projinfo #define proj_init_info internal_proj_init_info #define proj_insert_object_session_create internal_proj_insert_object_session_create #define proj_insert_object_session_destroy internal_proj_insert_object_session_destroy #define proj_int_list_destroy internal_proj_int_list_destroy #define proj_is_crs internal_proj_is_crs #define proj_is_deprecated internal_proj_is_deprecated #define proj_is_derived_crs internal_proj_is_derived_crs #define proj_is_download_needed internal_proj_is_download_needed #define proj_is_equivalent_to internal_proj_is_equivalent_to #define proj_is_equivalent_to_with_ctx internal_proj_is_equivalent_to_with_ctx #define proj_list_angular_units internal_proj_list_angular_units #define proj_list_destroy internal_proj_list_destroy #define proj_list_ellps internal_proj_list_ellps #define proj_list_get internal_proj_list_get #define proj_list_get_count internal_proj_list_get_count #define proj_list_operations internal_proj_list_operations #define proj_list_prime_meridians internal_proj_list_prime_meridians #define proj_list_units internal_proj_list_units #define proj_log_func internal_proj_log_func #define proj_log_level internal_proj_log_level #define proj_lp_dist internal_proj_lp_dist #define proj_lpz_dist internal_proj_lpz_dist #define proj_normalize_for_visualization internal_proj_normalize_for_visualization #define proj_operation_factory_context_destroy internal_proj_operation_factory_context_destroy #define proj_operation_factory_context_set_allow_ballpark_transformations internal_proj_operation_factory_context_set_allow_ballpark_transformations #define proj_operation_factory_context_set_allowed_intermediate_crs internal_proj_operation_factory_context_set_allowed_intermediate_crs #define proj_operation_factory_context_set_allow_use_intermediate_crs internal_proj_operation_factory_context_set_allow_use_intermediate_crs #define proj_operation_factory_context_set_area_of_interest internal_proj_operation_factory_context_set_area_of_interest #define proj_operation_factory_context_set_area_of_interest_name internal_proj_operation_factory_context_set_area_of_interest_name #define proj_operation_factory_context_set_crs_extent_use internal_proj_operation_factory_context_set_crs_extent_use #define proj_operation_factory_context_set_desired_accuracy internal_proj_operation_factory_context_set_desired_accuracy #define proj_operation_factory_context_set_discard_superseded internal_proj_operation_factory_context_set_discard_superseded #define proj_operation_factory_context_set_grid_availability_use internal_proj_operation_factory_context_set_grid_availability_use #define proj_operation_factory_context_set_spatial_criterion internal_proj_operation_factory_context_set_spatial_criterion #define proj_operation_factory_context_set_use_proj_alternative_grid_names internal_proj_operation_factory_context_set_use_proj_alternative_grid_names #define proj_pj_info internal_proj_pj_info #define proj_prime_meridian_get_parameters internal_proj_prime_meridian_get_parameters #define proj_query_geodetic_crs_from_datum internal_proj_query_geodetic_crs_from_datum #define proj_roundtrip internal_proj_roundtrip #define proj_rtodms internal_proj_rtodms #define proj_rtodms2 internal_proj_rtodms2 #define proj_string_destroy internal_proj_string_destroy #define proj_string_list_destroy internal_proj_string_list_destroy #define proj_suggests_code_for internal_proj_suggests_code_for #define proj_todeg internal_proj_todeg #define proj_torad internal_proj_torad #define proj_trans internal_proj_trans #define proj_trans_array internal_proj_trans_array #define proj_trans_bounds internal_proj_trans_bounds #define proj_trans_bounds_3D internal_proj_trans_bounds_3D #define proj_trans_generic internal_proj_trans_generic #define proj_trans_get_last_used_operation internal_proj_trans_get_last_used_operation #define proj_unit_list_destroy internal_proj_unit_list_destroy #define proj_uom_get_info_from_database internal_proj_uom_get_info_from_database #define proj_xy_dist internal_proj_xy_dist #define proj_xyz_dist internal_proj_xyz_dist #define pj_release internal_pj_release #endif /* PROJ_SYMBOL_RENAME_H */ proj-9.8.1/src/trans.cpp000664 001750 001750 00000061764 15166171715 015113 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: proj_trans(), proj_trans_array(), proj_trans_generic(), *proj_roundtrip() * * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 * ****************************************************************************** * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" #include #include #include #include "proj/internal/io_internal.hpp" inline bool pj_coord_has_nans(PJ_COORD coo) { return std::isnan(coo.v[0]) || std::isnan(coo.v[1]) || std::isnan(coo.v[2]) || std::isnan(coo.v[3]); } /**************************************************************************************/ int pj_get_suggested_operation(PJ_CONTEXT *, const std::vector &opList, const int iExcluded[2], bool skipNonInstantiable, PJ_DIRECTION direction, PJ_COORD coord) /**************************************************************************************/ { const auto normalizeLongitude = [](double x) { if (x > 180.0) { x -= 360.0; if (x > 180.0) x = fmod(x + 180.0, 360.0) - 180.0; } else if (x < -180.0) { x += 360.0; if (x < -180.0) x = fmod(x + 180.0, 360.0) - 180.0; } return x; }; // Select the operations that match the area of use // and has the best accuracy. int iBest = -1; double bestAccuracy = std::numeric_limits::max(); const int nOperations = static_cast(opList.size()); for (int i = 0; i < nOperations; i++) { if (i == iExcluded[0] || i == iExcluded[1]) { continue; } const auto &alt = opList[i]; bool spatialCriterionOK = false; if (direction == PJ_FWD) { if (alt.pjSrcGeocentricToLonLat) { if (alt.minxSrc == -180 && alt.minySrc == -90 && alt.maxxSrc == 180 && alt.maxySrc == 90) { spatialCriterionOK = true; } else { PJ_COORD tmp = coord; pj_fwd4d(tmp, alt.pjSrcGeocentricToLonLat); if (tmp.xyzt.x >= alt.minxSrc && tmp.xyzt.y >= alt.minySrc && tmp.xyzt.x <= alt.maxxSrc && tmp.xyzt.y <= alt.maxySrc) { spatialCriterionOK = true; } } } else if (coord.xyzt.x >= alt.minxSrc && coord.xyzt.y >= alt.minySrc && coord.xyzt.x <= alt.maxxSrc && coord.xyzt.y <= alt.maxySrc) { spatialCriterionOK = true; } else if (alt.srcIsLonLatDegree && coord.xyzt.y >= alt.minySrc && coord.xyzt.y <= alt.maxySrc) { const double normalizedLon = normalizeLongitude(coord.xyzt.x); if (normalizedLon >= alt.minxSrc && normalizedLon <= alt.maxxSrc) { spatialCriterionOK = true; } } else if (alt.srcIsLatLonDegree && coord.xyzt.x >= alt.minxSrc && coord.xyzt.x <= alt.maxxSrc) { const double normalizedLon = normalizeLongitude(coord.xyzt.y); if (normalizedLon >= alt.minySrc && normalizedLon <= alt.maxySrc) { spatialCriterionOK = true; } } } else { if (alt.pjDstGeocentricToLonLat) { if (alt.minxDst == -180 && alt.minyDst == -90 && alt.maxxDst == 180 && alt.maxyDst == 90) { spatialCriterionOK = true; } else { PJ_COORD tmp = coord; pj_fwd4d(tmp, alt.pjDstGeocentricToLonLat); if (tmp.xyzt.x >= alt.minxDst && tmp.xyzt.y >= alt.minyDst && tmp.xyzt.x <= alt.maxxDst && tmp.xyzt.y <= alt.maxyDst) { spatialCriterionOK = true; } } } else if (coord.xyzt.x >= alt.minxDst && coord.xyzt.y >= alt.minyDst && coord.xyzt.x <= alt.maxxDst && coord.xyzt.y <= alt.maxyDst) { spatialCriterionOK = true; } else if (alt.dstIsLonLatDegree && coord.xyzt.y >= alt.minyDst && coord.xyzt.y <= alt.maxyDst) { const double normalizedLon = normalizeLongitude(coord.xyzt.x); if (normalizedLon >= alt.minxDst && normalizedLon <= alt.maxxDst) { spatialCriterionOK = true; } } else if (alt.dstIsLatLonDegree && coord.xyzt.x >= alt.minxDst && coord.xyzt.x <= alt.maxxDst) { const double normalizedLon = normalizeLongitude(coord.xyzt.y); if (normalizedLon >= alt.minyDst && normalizedLon <= alt.maxyDst) { spatialCriterionOK = true; } } } if (spatialCriterionOK) { // The offshore test is for the "Test bug 245 (use +datum=carthage)" // of test_cs2cs_various.yaml. The long=10 lat=34 point belongs // both to the onshore and offshore Tunisia area of uses, but is // slightly onshore. So in a general way, prefer a onshore area // to a offshore one. if (iBest < 0 || (((alt.accuracy >= 0 && alt.accuracy < bestAccuracy) || // If two operations have the same accuracy, use // the one that has the smallest area (alt.accuracy == bestAccuracy && alt.pseudoArea < opList[iBest].pseudoArea && !(alt.isUnknownAreaName && !opList[iBest].isUnknownAreaName) && !opList[iBest].isPriorityOp)) && !alt.isOffshore)) { if (skipNonInstantiable && !alt.isInstantiable()) { continue; } iBest = i; bestAccuracy = alt.accuracy; } } } return iBest; } /**************************************************************************************/ void pj_warn_about_missing_grid(PJ *P) /**************************************************************************************/ { std::string msg("Attempt to use coordinate operation "); msg += proj_get_name(P); msg += " failed."; int gridUsed = proj_coordoperation_get_grid_used_count(P->ctx, P); for (int i = 0; i < gridUsed; ++i) { const char *gridName = ""; int available = FALSE; if (proj_coordoperation_get_grid_used(P->ctx, P, i, &gridName, nullptr, nullptr, nullptr, nullptr, nullptr, &available) && !available) { msg += " Grid "; msg += gridName; msg += " is not available. " "Consult https://proj.org/resource_files.html for guidance."; } } if (!P->errorIfBestTransformationNotAvailable && P->warnIfBestTransformationNotAvailable) { msg += " This might become an error in a future PROJ major release. " "Set the ONLY_BEST option to YES or NO. " "This warning will no longer be emitted (for the current " "transformation instance)."; P->warnIfBestTransformationNotAvailable = false; } pj_log(P->ctx, P->errorIfBestTransformationNotAvailable ? PJ_LOG_ERROR : PJ_LOG_DEBUG, msg.c_str()); } /**************************************************************************************/ PJ_COORD proj_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { /*************************************************************************************** Apply the transformation P to the coordinate coord, preferring the 4D interfaces if available. See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which work similarly, but prefers the 2D resp. 3D interfaces if available. ***************************************************************************************/ if (nullptr == P || direction == PJ_IDENT) return coord; if (P->inverted) direction = pj_opposite_direction(direction); if (P->iso_obj != nullptr && !P->iso_obj_is_coordinate_operation) { pj_log(P->ctx, PJ_LOG_ERROR, "Object is not a coordinate operation"); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return proj_coord_error(); } if (!P->alternativeCoordinateOperations.empty()) { constexpr int N_MAX_RETRY = 2; int iExcluded[N_MAX_RETRY] = {-1, -1}; bool skipNonInstantiable = P->skipNonInstantiable && !P->warnIfBestTransformationNotAvailable && !P->errorIfBestTransformationNotAvailable; const int nOperations = static_cast(P->alternativeCoordinateOperations.size()); // We may need several attempts. For example the point at // long=-111.5 lat=45.26 falls into the bounding box of the Canadian // ntv2_0.gsb grid, except that it is not in any of the subgrids, being // in the US. We thus need another retry that will select the conus // grid. for (int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++) { // Do a first pass and select the operations that match the area of // use and has the best accuracy. int iBest = pj_get_suggested_operation( P->ctx, P->alternativeCoordinateOperations, iExcluded, skipNonInstantiable, direction, coord); if (iBest < 0) { break; } if (iRetry > 0) { const int oldErrno = proj_errno_reset(P); if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { pj_log(P->ctx, PJ_LOG_DEBUG, proj_context_errno_string(P->ctx, oldErrno)); } pj_log(P->ctx, PJ_LOG_DEBUG, "Did not result in valid result. " "Attempting a retry with another operation."); } const auto &alt = P->alternativeCoordinateOperations[iBest]; if (P->iCurCoordOp != iBest) { if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { std::string msg("Using coordinate operation "); msg += alt.name; pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); } P->iCurCoordOp = iBest; } PJ_COORD res = coord; if (alt.pj->hasCoordinateEpoch) coord.xyzt.t = alt.pj->coordinateEpoch; if (direction == PJ_FWD) pj_fwd4d(res, alt.pj); else pj_inv4d(res, alt.pj); if (proj_errno(alt.pj) == PROJ_ERR_OTHER_NETWORK_ERROR) { return proj_coord_error(); } if (res.xyzt.x != HUGE_VAL) { return res; } else if (P->errorIfBestTransformationNotAvailable || P->warnIfBestTransformationNotAvailable) { pj_warn_about_missing_grid(alt.pj); if (P->errorIfBestTransformationNotAvailable) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); return res; } P->warnIfBestTransformationNotAvailable = false; skipNonInstantiable = true; } if (iRetry == N_MAX_RETRY) { break; } iExcluded[iRetry] = iBest; } // In case we did not find an operation whose area of use is compatible // with the input coordinate, then goes through again the list, and // use the first operation that does not require grids. NS_PROJ::io::DatabaseContextPtr dbContext; try { if (P->ctx->cpp_context) { dbContext = P->ctx->cpp_context->getDatabaseContext().as_nullable(); } } catch (const std::exception &) { } for (int i = 0; i < nOperations; i++) { const auto &alt = P->alternativeCoordinateOperations[i]; auto coordOperation = dynamic_cast( alt.pj->iso_obj.get()); if (coordOperation) { if (coordOperation->gridsNeeded(dbContext, true).empty()) { if (P->iCurCoordOp != i) { if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { std::string msg("Using coordinate operation "); msg += alt.name; msg += " as a fallback due to lack of more " "appropriate operations"; pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); } P->iCurCoordOp = i; } if (direction == PJ_FWD) { pj_fwd4d(coord, alt.pj); } else { pj_inv4d(coord, alt.pj); } return coord; } } } proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); return proj_coord_error(); } P->iCurCoordOp = 0; // dummy value, to be used by proj_trans_get_last_used_operation() if (P->hasCoordinateEpoch) coord.xyzt.t = P->coordinateEpoch; if (pj_coord_has_nans(coord)) coord.v[0] = coord.v[1] = coord.v[2] = coord.v[3] = std::numeric_limits::quiet_NaN(); else if (direction == PJ_FWD) pj_fwd4d(coord, P); else pj_inv4d(coord, P); return coord; } /*****************************************************************************/ PJ *proj_trans_get_last_used_operation(PJ *P) /****************************************************************************** Return the operation used during the last invocation of proj_trans(). This is especially useful when P has been created with proj_create_crs_to_crs() and has several alternative operations. The returned object must be freed with proj_destroy(). ******************************************************************************/ { if (nullptr == P || P->iCurCoordOp < 0) return nullptr; if (P->alternativeCoordinateOperations.empty()) return proj_clone(P->ctx, P); return proj_clone(P->ctx, P->alternativeCoordinateOperations[P->iCurCoordOp].pj); } /*****************************************************************************/ int proj_trans_array(PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) { /****************************************************************************** Batch transform an array of PJ_COORD. Performs transformation on all points, even if errors occur on some points. Individual points that fail to transform will have their components set to HUGE_VAL Returns 0 if all coordinates are transformed without error, otherwise returns a precise error number if all coordinates that fail to transform for the same reason, or a generic error code if they fail for different reasons. ******************************************************************************/ size_t i; int retErrno = 0; bool hasSetRetErrno = false; bool sameRetErrno = true; for (i = 0; i < n; i++) { proj_context_errno_set(P->ctx, 0); coord[i] = proj_trans(P, direction, coord[i]); int thisErrno = proj_errno(P); if (thisErrno != 0) { if (!hasSetRetErrno) { retErrno = thisErrno; hasSetRetErrno = true; } else if (sameRetErrno && retErrno != thisErrno) { sameRetErrno = false; retErrno = PROJ_ERR_COORD_TRANSFM; } } } proj_context_errno_set(P->ctx, retErrno); return retErrno; } /*************************************************************************************/ size_t proj_trans_generic(PJ *P, PJ_DIRECTION direction, double *x, size_t sx, size_t nx, double *y, size_t sy, size_t ny, double *z, size_t sz, size_t nz, double *t, size_t st, size_t nt) { /************************************************************************************** Transform a series of coordinates, where the individual coordinate dimension may be represented by an array that is either 1. fully populated 2. a null pointer and/or a length of zero, which will be treated as a fully populated array of zeroes 3. of length one, i.e. a constant, which will be treated as a fully populated array of that constant value The strides, sx, sy, sz, st, represent the step length, in bytes, between consecutive elements of the corresponding array. This makes it possible for proj_transform to handle transformation of a large class of application specific data structures, without necessarily understanding the data structure format, as in: typedef struct {double x, y; int quality_level; char surveyor_name[134];} XYQS; XYQS survey[345]; double height = 23.45; PJ *P = {...}; size_t stride = sizeof (XYQS); ... proj_transform ( P, PJ_INV, sizeof(XYQS), &(survey[0].x), stride, 345, (* We have 345 eastings *) &(survey[0].y), stride, 345, (* ...and 345 northings. *) &height, 1, (* The height is the constant 23.45 m *) 0, 0 (* and the time is the constant 0.00 s *) ); This is similar to the inner workings of the pj_transform function, but the stride functionality has been generalized to work for any size of basic unit, not just a fixed number of doubles. In most cases, the stride will be identical for x, y,z, and t, since they will typically be either individual arrays (stride = sizeof(double)), or strided views into an array of application specific data structures (stride = sizeof (...)). But in order to support cases where x, y, z, and t come from heterogeneous sources, individual strides, sx, sy, sz, st, are used. Caveat: Since proj_transform does its work *in place*, this means that even the supposedly constants (i.e. length 1 arrays) will return from the call in altered state. Hence, remember to reinitialize between repeated calls. Return value: Number of transformations completed. **************************************************************************************/ PJ_COORD coord = {{0, 0, 0, 0}}; size_t i, nmin; double null_broadcast = 0; double invalid_time = HUGE_VAL; if (nullptr == P) return 0; if (P->inverted) direction = pj_opposite_direction(direction); /* ignore lengths of null arrays */ if (nullptr == x) nx = 0; if (nullptr == y) ny = 0; if (nullptr == z) nz = 0; if (nullptr == t) nt = 0; /* and make the nullities point to some real world memory for broadcasting * nulls */ if (0 == nx) x = &null_broadcast; if (0 == ny) y = &null_broadcast; if (0 == nz) z = &null_broadcast; if (0 == nt) t = &invalid_time; /* nothing to do? */ if (0 == nx + ny + nz + nt) return 0; /* arrays of length 1 are constants, which we broadcast along the longer * arrays */ /* so we need to find the length of the shortest non-unity array to figure * out */ /* how many coordinate pairs we must transform */ nmin = (nx > 1) ? nx : (ny > 1) ? ny : (nz > 1) ? nz : (nt > 1) ? nt : 1; if ((nx > 1) && (nx < nmin)) nmin = nx; if ((ny > 1) && (ny < nmin)) nmin = ny; if ((nz > 1) && (nz < nmin)) nmin = nz; if ((nt > 1) && (nt < nmin)) nmin = nt; /* Check validity of direction flag */ switch (direction) { case PJ_FWD: case PJ_INV: break; case PJ_IDENT: return nmin; } /* Arrays of length==0 are broadcast as the constant 0 */ /* Arrays of length==1 are broadcast as their single value */ /* Arrays of length >1 are iterated over (for the first nmin values) */ /* The slightly convolved incremental indexing is used due */ /* to the stride, which may be any size supported by the platform */ for (i = 0; i < nmin; i++) { coord.xyzt.x = *x; coord.xyzt.y = *y; coord.xyzt.z = *z; coord.xyzt.t = *t; coord = proj_trans(P, direction, coord); /* in all full length cases, we overwrite the input with the output, */ /* and step on to the next element. */ /* The casts are somewhat funky, but they compile down to no-ops and */ /* they tell compilers and static analyzers that we know what we do */ if (nx > 1) { *x = coord.xyzt.x; x = reinterpret_cast((reinterpret_cast(x) + sx)); } if (ny > 1) { *y = coord.xyzt.y; y = reinterpret_cast((reinterpret_cast(y) + sy)); } if (nz > 1) { *z = coord.xyzt.z; z = reinterpret_cast((reinterpret_cast(z) + sz)); } if (nt > 1) { *t = coord.xyzt.t; t = reinterpret_cast((reinterpret_cast(t) + st)); } } /* Last time around, we update the length 1 cases with their transformed * alter egos */ if (nx == 1) *x = coord.xyzt.x; if (ny == 1) *y = coord.xyzt.y; if (nz == 1) *z = coord.xyzt.z; if (nt == 1) *t = coord.xyzt.t; return i; } static bool inline coord_is_all_nans(PJ_COORD coo) { return std::isnan(coo.v[0]) && std::isnan(coo.v[1]) && std::isnan(coo.v[2]) && std::isnan(coo.v[3]); } /* Measure numerical deviation after n roundtrips fwd-inv (or inv-fwd) */ double proj_roundtrip(PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { int i; PJ_COORD t, org; if (nullptr == P) return HUGE_VAL; if (n < 1) { proj_log_error(P, _("n should be >= 1")); proj_errno_set(P, PROJ_ERR_OTHER_API_MISUSE); return HUGE_VAL; } /* in the first half-step, we generate the output value */ org = *coord; *coord = proj_trans(P, direction, org); t = *coord; /* now we take n-1 full steps in inverse direction: We are */ /* out of phase due to the half step already taken */ for (i = 0; i < n - 1; i++) t = proj_trans(P, direction, proj_trans(P, pj_opposite_direction(direction), t)); /* finally, we take the last half-step */ t = proj_trans(P, pj_opposite_direction(direction), t); /* if we start with any NaN, we expect all NaN as output */ if (pj_coord_has_nans(org) && coord_is_all_nans(t)) { return 0.0; } /* checking for angular *input* since we do a roundtrip, and end where we * begin */ if (proj_angular_input(P, direction)) return proj_lpz_dist(P, org, t); return proj_xyz_dist(org, t); } proj-9.8.1/src/filemanager.hpp000664 001750 001750 00000007603 15166171715 016233 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: File manager * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2019, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FILEMANAGER_HPP_INCLUDED #define FILEMANAGER_HPP_INCLUDED #include #include #include #include "proj.h" #include "proj/util.hpp" //! @cond Doxygen_Suppress NS_PROJ_START class File; enum class FileAccess { READ_ONLY, // "rb" READ_UPDATE, // "r+b" CREATE, // "w+b" }; class FileManager { private: FileManager() = delete; public: // "Low-level" interface. static PROJ_DLL std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename, FileAccess access); static PROJ_DLL bool exists(PJ_CONTEXT *ctx, const char *filename); static bool mkdir(PJ_CONTEXT *ctx, const char *filename); static bool unlink(PJ_CONTEXT *ctx, const char *filename); static bool rename(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath); static std::string getProjDataEnvVar(PJ_CONTEXT *ctx); // "High-level" interface, honoring PROJ_DATA and the like. static std::unique_ptr open_resource_file(PJ_CONTEXT *ctx, const char *name, char *out_full_filename = nullptr, size_t out_full_filename_size = 0); static void fillDefaultNetworkInterface(PJ_CONTEXT *ctx); static void clearMemoryCache(); }; // --------------------------------------------------------------------------- class File { protected: std::string name_; std::string readLineBuffer_{}; bool eofReadLine_ = false; explicit File(const std::string &filename); public: virtual PROJ_DLL ~File(); virtual size_t read(void *buffer, size_t sizeBytes) = 0; virtual size_t write(const void *buffer, size_t sizeBytes) = 0; virtual bool seek(unsigned long long offset, int whence = SEEK_SET) = 0; virtual unsigned long long tell() = 0; virtual void reassign_context(PJ_CONTEXT *ctx) = 0; virtual bool hasChanged() const = 0; std::string PROJ_DLL read_line(size_t maxLen, bool &maxLenReached, bool &eofReached); const std::string &name() const { return name_; } }; // --------------------------------------------------------------------------- std::unique_ptr pj_network_file_open(PJ_CONTEXT *ctx, const char *filename); NS_PROJ_END // Exported for projsync std::vector PROJ_DLL pj_get_default_searchpaths(PJ_CONTEXT *ctx); //! @endcond Doxygen_Suppress #endif // FILEMANAGER_HPP_INCLUDED proj-9.8.1/src/grids.cpp000664 001750 001750 00000442236 15166171715 015071 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Grid management * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * Copyright (c) 2019, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS #include "grids.hpp" #include "filemanager.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/lru_cache.hpp" #include "proj_internal.h" #ifdef TIFF_ENABLED #include "tiffio.h" #endif #include #include #include #include #include NS_PROJ_START using namespace internal; /************************************************************************/ /* swap_words() */ /* */ /* Convert the byte order of the given word(s) in place. */ /************************************************************************/ static const int byte_order_test = 1; #define IS_LSB \ (1 == (reinterpret_cast(&byte_order_test))[0]) static void swap_words(void *dataIn, size_t word_size, size_t word_count) { unsigned char *data = static_cast(dataIn); for (size_t word = 0; word < word_count; word++) { for (size_t i = 0; i < word_size / 2; i++) { unsigned char t; t = data[i]; data[i] = data[word_size - i - 1]; data[word_size - i - 1] = t; } data += word_size; } } // --------------------------------------------------------------------------- void ExtentAndRes::computeInvRes() { invResX = 1.0 / resX; invResY = 1.0 / resY; } // --------------------------------------------------------------------------- bool ExtentAndRes::fullWorldLongitude() const { return isGeographic && east - west + resX >= 2 * M_PI - 1e-10; } // --------------------------------------------------------------------------- bool ExtentAndRes::contains(const ExtentAndRes &other) const { return other.west >= west && other.east <= east && other.south >= south && other.north <= north; } // --------------------------------------------------------------------------- bool ExtentAndRes::intersects(const ExtentAndRes &other) const { return other.west < east && west <= other.west && other.south < north && south <= other.north; } // --------------------------------------------------------------------------- Grid::Grid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : m_name(nameIn), m_width(widthIn), m_height(heightIn), m_extent(extentIn) { } // --------------------------------------------------------------------------- Grid::~Grid() = default; // --------------------------------------------------------------------------- VerticalShiftGrid::VerticalShiftGrid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : Grid(nameIn, widthIn, heightIn, extentIn) {} // --------------------------------------------------------------------------- VerticalShiftGrid::~VerticalShiftGrid() = default; // --------------------------------------------------------------------------- static ExtentAndRes globalExtent() { ExtentAndRes extent; extent.isGeographic = true; extent.west = -M_PI; extent.south = -M_PI / 2; extent.east = M_PI; extent.north = M_PI / 2; extent.resX = M_PI; extent.resY = M_PI / 2; extent.computeInvRes(); return extent; } // --------------------------------------------------------------------------- static const std::string emptyString; class NullVerticalShiftGrid : public VerticalShiftGrid { public: NullVerticalShiftGrid() : VerticalShiftGrid("null", 3, 3, globalExtent()) {} bool isNullGrid() const override { return true; } bool valueAt(int, int, float &out) const override; bool isNodata(float, double) const override { return false; } void reassign_context(PJ_CONTEXT *) override {} bool hasChanged() const override { return false; } const std::string &metadataItem(const std::string &, int) const override { return emptyString; } }; // --------------------------------------------------------------------------- bool NullVerticalShiftGrid::valueAt(int, int, float &out) const { out = 0.0f; return true; } // --------------------------------------------------------------------------- class FloatLineCache { private: typedef uint64_t Key; lru11::Cache, lru11::NullLock> cache_; public: explicit FloatLineCache(size_t maxSize) : cache_(maxSize) {} void insert(uint32_t subgridIdx, uint32_t lineNumber, const std::vector &data); const std::vector *get(uint32_t subgridIdx, uint32_t lineNumber); }; // --------------------------------------------------------------------------- void FloatLineCache::insert(uint32_t subgridIdx, uint32_t lineNumber, const std::vector &data) { cache_.insert((static_cast(subgridIdx) << 32) | lineNumber, data); } // --------------------------------------------------------------------------- const std::vector *FloatLineCache::get(uint32_t subgridIdx, uint32_t lineNumber) { return cache_.getPtr((static_cast(subgridIdx) << 32) | lineNumber); } // --------------------------------------------------------------------------- class GTXVerticalShiftGrid : public VerticalShiftGrid { PJ_CONTEXT *m_ctx; std::unique_ptr m_fp; std::unique_ptr m_cache; mutable std::vector m_buffer{}; GTXVerticalShiftGrid(const GTXVerticalShiftGrid &) = delete; GTXVerticalShiftGrid &operator=(const GTXVerticalShiftGrid &) = delete; public: explicit GTXVerticalShiftGrid(PJ_CONTEXT *ctx, std::unique_ptr &&fp, const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn, std::unique_ptr &&cache) : VerticalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_fp(std::move(fp)), m_cache(std::move(cache)) {} ~GTXVerticalShiftGrid() override; bool valueAt(int x, int y, float &out) const override; bool isNodata(float val, double multiplier) const override; const std::string &metadataItem(const std::string &, int) const override { return emptyString; } static GTXVerticalShiftGrid *open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &name); void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; m_fp->reassign_context(ctx); } bool hasChanged() const override { return m_fp->hasChanged(); } }; // --------------------------------------------------------------------------- GTXVerticalShiftGrid::~GTXVerticalShiftGrid() = default; // --------------------------------------------------------------------------- GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &name) { unsigned char header[40]; /* -------------------------------------------------------------------- */ /* Read the header. */ /* -------------------------------------------------------------------- */ if (fp->read(header, sizeof(header)) != sizeof(header)) { pj_log(ctx, PJ_LOG_ERROR, _("Cannot read grid header")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } /* -------------------------------------------------------------------- */ /* Regularize fields of interest and extract. */ /* -------------------------------------------------------------------- */ if (IS_LSB) { swap_words(header + 0, 8, 4); swap_words(header + 32, 4, 2); } double xorigin, yorigin, xstep, ystep; int rows, columns; memcpy(&yorigin, header + 0, 8); memcpy(&xorigin, header + 8, 8); memcpy(&ystep, header + 16, 8); memcpy(&xstep, header + 24, 8); memcpy(&rows, header + 32, 4); memcpy(&columns, header + 36, 4); if (columns <= 0 || rows <= 0 || xorigin < -360 || xorigin > 360 || yorigin < -90 || yorigin > 90) { pj_log(ctx, PJ_LOG_ERROR, _("gtx file header has invalid extents, corrupt?")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } /* some GTX files come in 0-360 and we shift them back into the expected -180 to 180 range if possible. This does not solve problems with grids spanning the dateline. */ if (xorigin >= 180.0) xorigin -= 360.0; if (xorigin >= 0.0 && xorigin + xstep * columns > 180.0) { pj_log(ctx, PJ_LOG_DEBUG, "This GTX spans the dateline! This will cause problems."); } ExtentAndRes extent; extent.isGeographic = true; extent.west = xorigin * DEG_TO_RAD; extent.south = yorigin * DEG_TO_RAD; extent.resX = xstep * DEG_TO_RAD; extent.resY = ystep * DEG_TO_RAD; extent.east = (xorigin + xstep * (columns - 1)) * DEG_TO_RAD; extent.north = (yorigin + ystep * (rows - 1)) * DEG_TO_RAD; extent.computeInvRes(); // Cache up to 1 megapixel per GTX file const int maxLinesInCache = 1024 * 1024 / columns; auto cache = std::make_unique(maxLinesInCache); return new GTXVerticalShiftGrid(ctx, std::move(fp), name, columns, rows, extent, std::move(cache)); } // --------------------------------------------------------------------------- bool GTXVerticalShiftGrid::valueAt(int x, int y, float &out) const { assert(x >= 0 && y >= 0 && x < m_width && y < m_height); const std::vector *pBuffer = m_cache->get(0, y); if (pBuffer == nullptr) { try { m_buffer.resize(m_width); } catch (const std::exception &e) { pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); return false; } const size_t nLineSizeInBytes = sizeof(float) * m_width; m_fp->seek(40 + nLineSizeInBytes * static_cast(y)); if (m_fp->read(&m_buffer[0], nLineSizeInBytes) != nLineSizeInBytes) { proj_context_errno_set( m_ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (IS_LSB) { swap_words(&m_buffer[0], sizeof(float), m_width); } out = m_buffer[x]; try { m_cache->insert(0, y, m_buffer); } catch (const std::exception &e) { // Should normally not happen pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); } } else { out = (*pBuffer)[x]; } return true; } // --------------------------------------------------------------------------- bool GTXVerticalShiftGrid::isNodata(float val, double multiplier) const { /* nodata? */ /* GTX official nodata value if -88.88880f, but some grids also */ /* use other big values for nodata (e.g naptrans2008.gtx has */ /* nodata values like -2147479936), so test them too */ return val * multiplier > 1000 || val * multiplier < -1000 || val == -88.88880f; } // --------------------------------------------------------------------------- VerticalShiftGridSet::VerticalShiftGridSet() = default; // --------------------------------------------------------------------------- VerticalShiftGridSet::~VerticalShiftGridSet() = default; // --------------------------------------------------------------------------- static bool IsTIFF(size_t header_size, const unsigned char *header) { // Test combinations of signature for ClassicTIFF/BigTIFF little/big endian return header_size >= 4 && (((header[0] == 'I' && header[1] == 'I') || (header[0] == 'M' && header[1] == 'M')) && ((header[2] == 0x2A && header[3] == 0) || (header[3] == 0x2A && header[2] == 0) || (header[2] == 0x2B && header[3] == 0) || (header[3] == 0x2B && header[2] == 0))); } #ifdef TIFF_ENABLED // --------------------------------------------------------------------------- enum class TIFFDataType { Int16, UInt16, Int32, UInt32, Float32, Float64 }; // --------------------------------------------------------------------------- constexpr uint16_t TIFFTAG_GEOPIXELSCALE = 33550; constexpr uint16_t TIFFTAG_GEOTIEPOINTS = 33922; constexpr uint16_t TIFFTAG_GEOTRANSMATRIX = 34264; constexpr uint16_t TIFFTAG_GEOKEYDIRECTORY = 34735; constexpr uint16_t TIFFTAG_GEODOUBLEPARAMS = 34736; constexpr uint16_t TIFFTAG_GEOASCIIPARAMS = 34737; #ifndef TIFFTAG_GDAL_METADATA // Starting with libtiff > 4.1.0, those symbolic names are #define in tiff.h constexpr uint16_t TIFFTAG_GDAL_METADATA = 42112; constexpr uint16_t TIFFTAG_GDAL_NODATA = 42113; #endif // --------------------------------------------------------------------------- class BlockCache { public: void insert(uint32_t ifdIdx, uint32_t blockNumber, const std::vector &data); const std::vector *get(uint32_t ifdIdx, uint32_t blockNumber); private: typedef uint64_t Key; static constexpr int NUM_BLOCKS_AT_CROSSING_TILES = 4; static constexpr int MAX_SAMPLE_COUNT = 3; lru11::Cache, lru11::NullLock> cache_{ NUM_BLOCKS_AT_CROSSING_TILES * MAX_SAMPLE_COUNT}; }; // --------------------------------------------------------------------------- void BlockCache::insert(uint32_t ifdIdx, uint32_t blockNumber, const std::vector &data) { cache_.insert((static_cast(ifdIdx) << 32) | blockNumber, data); } // --------------------------------------------------------------------------- const std::vector *BlockCache::get(uint32_t ifdIdx, uint32_t blockNumber) { return cache_.getPtr((static_cast(ifdIdx) << 32) | blockNumber); } // --------------------------------------------------------------------------- class GTiffGrid : public Grid { PJ_CONTEXT *m_ctx; // owned by the belonging GTiffDataset TIFF *m_hTIFF; // owned by the belonging GTiffDataset BlockCache &m_cache; // owned by the belonging GTiffDataset File *m_fp; // owned by the belonging GTiffDataset uint32_t m_ifdIdx; TIFFDataType m_dt; uint16_t m_samplesPerPixel; uint16_t m_planarConfig; // set to -1 if m_samplesPerPixel == 1 bool m_bottomUp; toff_t m_dirOffset; bool m_tiled; uint32_t m_blockWidth = 0; uint32_t m_blockHeight = 0; mutable std::vector m_buffer{}; mutable uint32_t m_bufferBlockId = std::numeric_limits::max(); unsigned m_blocksPerRow = 0; unsigned m_blocksPerCol = 0; unsigned m_blocks = 0; std::vector m_adfOffset{}; std::vector m_adfScale{}; std::map, std::string> m_metadata{}; bool m_hasNodata = false; bool m_blockIs256Pixel = false; bool m_isSingleBlock = false; float m_noData = 0.0f; uint32_t m_subfileType = 0; GTiffGrid(const GTiffGrid &) = delete; GTiffGrid &operator=(const GTiffGrid &) = delete; template float readValue(const std::vector &buffer, uint32_t offsetInBlock, uint16_t sample) const; public: GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, File *fp, uint32_t ifdIdx, const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn, TIFFDataType dtIn, uint16_t samplesPerPixelIn, uint16_t planarConfig, bool bottomUpIn); ~GTiffGrid() override; uint16_t samplesPerPixel() const { return m_samplesPerPixel; } bool valueAt(uint16_t sample, int x, int y, float &out) const; bool valuesAt(int x_start, int y_start, int x_count, int y_count, int sample_count, const int *sample_idx, float *out, bool &nodataFound) const; bool isNodata(float val) const; const std::string &metadataItem(const std::string &key, int sample = -1) const override; uint32_t subfileType() const { return m_subfileType; } void reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; } bool hasChanged() const override { return m_fp->hasChanged(); } }; // --------------------------------------------------------------------------- GTiffGrid::GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, File *fp, uint32_t ifdIdx, const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn, TIFFDataType dtIn, uint16_t samplesPerPixelIn, uint16_t planarConfig, bool bottomUpIn) : Grid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_hTIFF(hTIFF), m_cache(cache), m_fp(fp), m_ifdIdx(ifdIdx), m_dt(dtIn), m_samplesPerPixel(samplesPerPixelIn), m_planarConfig(samplesPerPixelIn == 1 ? static_cast(-1) : planarConfig), m_bottomUp(bottomUpIn), m_dirOffset(TIFFCurrentDirOffset(hTIFF)), m_tiled(TIFFIsTiled(hTIFF) != 0) { if (m_tiled) { TIFFGetField(m_hTIFF, TIFFTAG_TILEWIDTH, &m_blockWidth); TIFFGetField(m_hTIFF, TIFFTAG_TILELENGTH, &m_blockHeight); } else { m_blockWidth = widthIn; TIFFGetField(m_hTIFF, TIFFTAG_ROWSPERSTRIP, &m_blockHeight); if (m_blockHeight > static_cast(m_height)) m_blockHeight = m_height; } m_blockIs256Pixel = (m_blockWidth == 256) && (m_blockHeight == 256); m_isSingleBlock = (m_blockWidth == static_cast(m_width)) && (m_blockHeight == static_cast(m_height)); TIFFGetField(m_hTIFF, TIFFTAG_SUBFILETYPE, &m_subfileType); m_blocksPerRow = (m_width + m_blockWidth - 1) / m_blockWidth; m_blocksPerCol = (m_height + m_blockHeight - 1) / m_blockHeight; m_blocks = m_blocksPerRow * m_blocksPerCol; const char *text = nullptr; // Poor-man XML parsing of TIFFTAG_GDAL_METADATA tag. Hopefully good // enough for our purposes. if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_METADATA, &text)) { const char *ptr = text; while (true) { ptr = strstr(ptr, "'); if (endTag == nullptr) break; const char *endValue = strchr(endTag, '<'); if (endValue == nullptr) break; std::string tag; tag.append(ptr, endTag - ptr); std::string value; value.append(endTag + 1, endValue - (endTag + 1)); std::string gridName; auto namePos = tag.find("name=\""); if (namePos == std::string::npos) break; { namePos += strlen("name=\""); const auto endQuote = tag.find('"', namePos); if (endQuote == std::string::npos) break; gridName = tag.substr(namePos, endQuote - namePos); } const auto samplePos = tag.find("sample=\""); int sample = -1; if (samplePos != std::string::npos) { sample = atoi(tag.c_str() + samplePos + strlen("sample=\"")); } m_metadata[std::pair(sample, gridName)] = value; auto rolePos = tag.find("role=\""); if (rolePos != std::string::npos) { rolePos += strlen("role=\""); const auto endQuote = tag.find('"', rolePos); if (endQuote == std::string::npos) break; const auto role = tag.substr(rolePos, endQuote - rolePos); if (role == "offset") { if (sample >= 0 && static_cast(sample) <= m_samplesPerPixel) { try { if (m_adfOffset.empty()) { m_adfOffset.resize(m_samplesPerPixel); m_adfScale.resize(m_samplesPerPixel, 1); } m_adfOffset[sample] = c_locale_stod(value); } catch (const std::exception &) { } } } else if (role == "scale") { if (sample >= 0 && static_cast(sample) <= m_samplesPerPixel) { try { if (m_adfOffset.empty()) { m_adfOffset.resize(m_samplesPerPixel); m_adfScale.resize(m_samplesPerPixel, 1); } m_adfScale[sample] = c_locale_stod(value); } catch (const std::exception &) { } } } } ptr = endValue + 1; } } if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_NODATA, &text)) { try { m_noData = static_cast(c_locale_stod(text)); m_hasNodata = true; } catch (const std::exception &) { } } auto oIter = m_metadata.find(std::pair(-1, "grid_name")); if (oIter != m_metadata.end()) { m_name += ", " + oIter->second; } } // --------------------------------------------------------------------------- GTiffGrid::~GTiffGrid() = default; // --------------------------------------------------------------------------- template float GTiffGrid::readValue(const std::vector &buffer, uint32_t offsetInBlock, uint16_t sample) const { const auto ptr = reinterpret_cast(buffer.data()); assert(offsetInBlock < buffer.size() / sizeof(T)); const auto val = ptr[offsetInBlock]; if ((!m_hasNodata || static_cast(val) != m_noData) && sample < m_adfScale.size()) { double scale = m_adfScale[sample]; double offset = m_adfOffset[sample]; return static_cast(val * scale + offset); } else { return static_cast(val); } } // --------------------------------------------------------------------------- bool GTiffGrid::valueAt(uint16_t sample, int x, int yFromBottom, float &out) const { assert(x >= 0 && yFromBottom >= 0 && x < m_width && yFromBottom < m_height); assert(sample < m_samplesPerPixel); // All non-TIFF grids have the first rows in the file being the one // corresponding to the southern-most row. In GeoTIFF, the convention is // *generally* different (when m_bottomUp == false), TIFF being an // image-oriented image. If m_bottomUp == true, then we had GeoTIFF hints // that the first row of the image is the southern-most. const int yTIFF = m_bottomUp ? yFromBottom : m_height - 1 - yFromBottom; int blockXOff; int blockYOff; uint32_t blockId; if (m_blockIs256Pixel) { const int blockX = x / 256; blockXOff = x % 256; const int blockY = yTIFF / 256; blockYOff = yTIFF % 256; blockId = blockY * m_blocksPerRow + blockX; } else if (m_isSingleBlock) { blockXOff = x; blockYOff = yTIFF; blockId = 0; } else { const int blockX = x / m_blockWidth; blockXOff = x % m_blockWidth; const int blockY = yTIFF / m_blockHeight; blockYOff = yTIFF % m_blockHeight; blockId = blockY * m_blocksPerRow + blockX; } if (m_planarConfig == PLANARCONFIG_SEPARATE) { blockId += sample * m_blocks; } const std::vector *pBuffer = blockId == m_bufferBlockId ? &m_buffer : m_cache.get(m_ifdIdx, blockId); if (pBuffer == nullptr) { if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset && !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) { return false; } if (m_buffer.empty()) { const auto blockSize = static_cast( m_tiled ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF)); try { m_buffer.resize(blockSize); } catch (const std::exception &e) { pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); return false; } } if (m_tiled) { if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(), m_buffer.size()) == -1) { return false; } } else { if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(), m_buffer.size()) == -1) { return false; } } pBuffer = &m_buffer; try { m_cache.insert(m_ifdIdx, blockId, m_buffer); m_bufferBlockId = blockId; } catch (const std::exception &e) { // Should normally not happen pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); } } uint32_t offsetInBlock; if (m_blockIs256Pixel) offsetInBlock = blockXOff + blockYOff * 256U; else offsetInBlock = blockXOff + blockYOff * m_blockWidth; if (m_planarConfig == PLANARCONFIG_CONTIG) offsetInBlock = offsetInBlock * m_samplesPerPixel + sample; switch (m_dt) { case TIFFDataType::Int16: out = readValue(*pBuffer, offsetInBlock, sample); break; case TIFFDataType::UInt16: out = readValue(*pBuffer, offsetInBlock, sample); break; case TIFFDataType::Int32: out = readValue(*pBuffer, offsetInBlock, sample); break; case TIFFDataType::UInt32: out = readValue(*pBuffer, offsetInBlock, sample); break; case TIFFDataType::Float32: out = readValue(*pBuffer, offsetInBlock, sample); break; case TIFFDataType::Float64: out = readValue(*pBuffer, offsetInBlock, sample); break; } return true; } // --------------------------------------------------------------------------- bool GTiffGrid::valuesAt(int x_start, int y_start, int x_count, int y_count, int sample_count, const int *sample_idx, float *out, bool &nodataFound) const { const auto getTIFFRow = [this](int y) { return m_bottomUp ? y : m_height - 1 - y; }; nodataFound = false; if (m_blockIs256Pixel && m_planarConfig == PLANARCONFIG_CONTIG && m_dt == TIFFDataType::Float32 && (x_start / 256) == (x_start + x_count - 1) / 256 && getTIFFRow(y_start) / 256 == getTIFFRow(y_start + y_count - 1) / 256 && !m_hasNodata && m_adfScale.empty() && (sample_count == 1 || (sample_count == 2 && sample_idx[1] == sample_idx[0] + 1) || (sample_count == 3 && sample_idx[1] == sample_idx[0] + 1 && sample_idx[2] == sample_idx[0] + 2))) { const int yTIFF = m_bottomUp ? y_start : m_height - (y_start + y_count); int blockXOff; int blockYOff; uint32_t blockId; const int blockX = x_start / 256; blockXOff = x_start % 256; const int blockY = yTIFF / 256; blockYOff = yTIFF % 256; blockId = blockY * m_blocksPerRow + blockX; const std::vector *pBuffer = blockId == m_bufferBlockId ? &m_buffer : m_cache.get(m_ifdIdx, blockId); if (pBuffer == nullptr) { if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset && !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) { return false; } if (m_buffer.empty()) { const auto blockSize = static_cast(m_tiled ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF)); try { m_buffer.resize(blockSize); } catch (const std::exception &e) { pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); return false; } } if (m_tiled) { if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(), m_buffer.size()) == -1) { return false; } } else { if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(), m_buffer.size()) == -1) { return false; } } pBuffer = &m_buffer; try { m_cache.insert(m_ifdIdx, blockId, m_buffer); m_bufferBlockId = blockId; } catch (const std::exception &e) { // Should normally not happen pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); } } uint32_t offsetInBlockStart = blockXOff + blockYOff * 256U; if (sample_count == m_samplesPerPixel) { const int sample_count_mul_x_count = sample_count * x_count; for (int y = 0; y < y_count; ++y) { uint32_t offsetInBlock = (offsetInBlockStart + 256 * (m_bottomUp ? y : y_count - 1 - y)) * m_samplesPerPixel + sample_idx[0]; memcpy(out, reinterpret_cast(pBuffer->data()) + offsetInBlock, sample_count_mul_x_count * sizeof(float)); out += sample_count_mul_x_count; } } else { switch (sample_count) { case 1: for (int y = 0; y < y_count; ++y) { uint32_t offsetInBlock = (offsetInBlockStart + 256 * (m_bottomUp ? y : y_count - 1 - y)) * m_samplesPerPixel + sample_idx[0]; const float *in_ptr = reinterpret_cast(pBuffer->data()) + offsetInBlock; for (int x = 0; x < x_count; ++x) { memcpy(out, in_ptr, sample_count * sizeof(float)); in_ptr += m_samplesPerPixel; out += sample_count; } } break; case 2: for (int y = 0; y < y_count; ++y) { uint32_t offsetInBlock = (offsetInBlockStart + 256 * (m_bottomUp ? y : y_count - 1 - y)) * m_samplesPerPixel + sample_idx[0]; const float *in_ptr = reinterpret_cast(pBuffer->data()) + offsetInBlock; for (int x = 0; x < x_count; ++x) { memcpy(out, in_ptr, sample_count * sizeof(float)); in_ptr += m_samplesPerPixel; out += sample_count; } } break; case 3: for (int y = 0; y < y_count; ++y) { uint32_t offsetInBlock = (offsetInBlockStart + 256 * (m_bottomUp ? y : y_count - 1 - y)) * m_samplesPerPixel + sample_idx[0]; const float *in_ptr = reinterpret_cast(pBuffer->data()) + offsetInBlock; for (int x = 0; x < x_count; ++x) { memcpy(out, in_ptr, sample_count * sizeof(float)); in_ptr += m_samplesPerPixel; out += sample_count; } } break; } } return true; } for (int y = y_start; y < y_start + y_count; ++y) { for (int x = x_start; x < x_start + x_count; ++x) { for (int isample = 0; isample < sample_count; ++isample) { if (!valueAt(static_cast(sample_idx[isample]), x, y, *out)) return false; if (isNodata(*out)) { nodataFound = true; } ++out; } } } return true; } // --------------------------------------------------------------------------- bool GTiffGrid::isNodata(float val) const { return (m_hasNodata && val == m_noData) || std::isnan(val); } // --------------------------------------------------------------------------- const std::string >iffGrid::metadataItem(const std::string &key, int sample) const { auto iter = m_metadata.find(std::pair(sample, key)); if (iter == m_metadata.end()) { return emptyString; } return iter->second; } // --------------------------------------------------------------------------- class GTiffDataset { PJ_CONTEXT *m_ctx; std::unique_ptr m_fp; TIFF *m_hTIFF = nullptr; bool m_hasNextGrid = false; uint32_t m_ifdIdx = 0; toff_t m_nextDirOffset = 0; std::string m_filename{}; BlockCache m_cache{}; GTiffDataset(const GTiffDataset &) = delete; GTiffDataset &operator=(const GTiffDataset &) = delete; // libtiff I/O routines static tsize_t tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) { GTiffDataset *self = static_cast(fd); return self->m_fp->read(buf, size); } static tsize_t tiffWriteProc(thandle_t, tdata_t, tsize_t) { assert(false); return 0; } static toff_t tiffSeekProc(thandle_t fd, toff_t off, int whence) { GTiffDataset *self = static_cast(fd); if (self->m_fp->seek(off, whence)) return static_cast(self->m_fp->tell()); else return static_cast(-1); } static int tiffCloseProc(thandle_t) { // done in destructor return 0; } static toff_t tiffSizeProc(thandle_t fd) { GTiffDataset *self = static_cast(fd); const auto old_off = self->m_fp->tell(); self->m_fp->seek(0, SEEK_END); const auto file_size = static_cast(self->m_fp->tell()); self->m_fp->seek(old_off); return file_size; } static int tiffMapProc(thandle_t, tdata_t *, toff_t *) { return (0); } static void tiffUnmapProc(thandle_t, tdata_t, toff_t) {} public: GTiffDataset(PJ_CONTEXT *ctx, std::unique_ptr &&fp) : m_ctx(ctx), m_fp(std::move(fp)) {} virtual ~GTiffDataset(); bool openTIFF(const std::string &filename); std::unique_ptr nextGrid(); void reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; m_fp->reassign_context(ctx); } }; // --------------------------------------------------------------------------- GTiffDataset::~GTiffDataset() { if (m_hTIFF) TIFFClose(m_hTIFF); } // --------------------------------------------------------------------------- class OneTimeTIFFTagInit { static TIFFExtendProc ParentExtender; // Function called by libtiff when initializing a TIFF directory static void GTiffTagExtender(TIFF *tif) { static const TIFFFieldInfo xtiffFieldInfo[] = { // GeoTIFF tags {TIFFTAG_GEOPIXELSCALE, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, const_cast("GeoPixelScale")}, {TIFFTAG_GEOTIEPOINTS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, const_cast("GeoTiePoints")}, {TIFFTAG_GEOTRANSMATRIX, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, const_cast("GeoTransformationMatrix")}, {TIFFTAG_GEOKEYDIRECTORY, -1, -1, TIFF_SHORT, FIELD_CUSTOM, TRUE, TRUE, const_cast("GeoKeyDirectory")}, {TIFFTAG_GEODOUBLEPARAMS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, const_cast("GeoDoubleParams")}, {TIFFTAG_GEOASCIIPARAMS, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE, const_cast("GeoASCIIParams")}, // GDAL tags {TIFFTAG_GDAL_METADATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE, const_cast("GDALMetadata")}, {TIFFTAG_GDAL_NODATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE, const_cast("GDALNoDataValue")}, }; if (ParentExtender) (*ParentExtender)(tif); TIFFMergeFieldInfo(tif, xtiffFieldInfo, sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0])); } public: OneTimeTIFFTagInit() { assert(ParentExtender == nullptr); // Install our TIFF tag extender ParentExtender = TIFFSetTagExtender(GTiffTagExtender); } }; TIFFExtendProc OneTimeTIFFTagInit::ParentExtender = nullptr; // --------------------------------------------------------------------------- bool GTiffDataset::openTIFF(const std::string &filename) { static OneTimeTIFFTagInit oneTimeTIFFTagInit; m_hTIFF = TIFFClientOpen(filename.c_str(), "r", static_cast(this), GTiffDataset::tiffReadProc, GTiffDataset::tiffWriteProc, GTiffDataset::tiffSeekProc, GTiffDataset::tiffCloseProc, GTiffDataset::tiffSizeProc, GTiffDataset::tiffMapProc, GTiffDataset::tiffUnmapProc); m_filename = filename; m_hasNextGrid = true; return m_hTIFF != nullptr; } // --------------------------------------------------------------------------- std::unique_ptr GTiffDataset::nextGrid() { if (!m_hasNextGrid) return nullptr; if (m_nextDirOffset) { TIFFSetSubDirectory(m_hTIFF, m_nextDirOffset); } uint32_t width = 0; uint32_t height = 0; TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height); if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) { pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid image size")); return nullptr; } uint16_t samplesPerPixel = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) { pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SamplesPerPixel tag")); return nullptr; } if (samplesPerPixel == 0) { pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid SamplesPerPixel value")); return nullptr; } uint16_t bitsPerSample = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) { pj_log(m_ctx, PJ_LOG_ERROR, _("Missing BitsPerSample tag")); return nullptr; } uint16_t planarConfig = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) { pj_log(m_ctx, PJ_LOG_ERROR, _("Missing PlanarConfig tag")); return nullptr; } uint16_t sampleFormat = 0; if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) { pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SampleFormat tag")); return nullptr; } TIFFDataType dt; if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 16) dt = TIFFDataType::Int16; else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 16) dt = TIFFDataType::UInt16; else if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 32) dt = TIFFDataType::Int32; else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 32) dt = TIFFDataType::UInt32; else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 32) dt = TIFFDataType::Float32; else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64) dt = TIFFDataType::Float64; else { pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported combination of SampleFormat " "and BitsPerSample values")); return nullptr; } uint16_t photometric = PHOTOMETRIC_MINISBLACK; if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric)) photometric = PHOTOMETRIC_MINISBLACK; if (photometric != PHOTOMETRIC_MINISBLACK) { pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported Photometric value")); return nullptr; } uint16_t compression = COMPRESSION_NONE; if (!TIFFGetField(m_hTIFF, TIFFTAG_COMPRESSION, &compression)) compression = COMPRESSION_NONE; if (compression != COMPRESSION_NONE && !TIFFIsCODECConfigured(compression)) { pj_log(m_ctx, PJ_LOG_ERROR, _("Cannot open TIFF file due to missing codec.")); return nullptr; } // We really don't want to try dealing with old-JPEG images if (compression == COMPRESSION_OJPEG) { pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported compression method.")); return nullptr; } const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF); if (blockSize == 0 || blockSize > 64 * 1024 * 2014) { pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported block size.")); return nullptr; } unsigned short count = 0; unsigned short *geokeys = nullptr; bool pixelIsArea = false; ExtentAndRes extent; extent.isGeographic = true; if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) { pj_log(m_ctx, PJ_LOG_TRACE, "No GeoKeys tag"); } else { if (count < 4 || (count % 4) != 0) { pj_log(m_ctx, PJ_LOG_ERROR, _("Wrong number of values in GeoKeys tag")); return nullptr; } if (geokeys[0] != 1) { pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported GeoTIFF major version")); return nullptr; } // We only know that we support GeoTIFF 1.0 and 1.1 at that time if (geokeys[1] != 1 || geokeys[2] > 1) { pj_log(m_ctx, PJ_LOG_TRACE, "GeoTIFF %d.%d possibly not handled", geokeys[1], geokeys[2]); } for (unsigned int i = 4; i + 3 < count; i += 4) { constexpr unsigned short GTModelTypeGeoKey = 1024; constexpr unsigned short ModelTypeProjected = 1; constexpr unsigned short ModelTypeGeographic = 2; constexpr unsigned short GTRasterTypeGeoKey = 1025; constexpr unsigned short RasterPixelIsArea = 1; // constexpr unsigned short RasterPixelIsPoint = 2; if (geokeys[i] == GTModelTypeGeoKey) { if (geokeys[i + 3] == ModelTypeProjected) { extent.isGeographic = false; } else if (geokeys[i + 3] != ModelTypeGeographic) { pj_log(m_ctx, PJ_LOG_ERROR, _("Only GTModelTypeGeoKey = " "ModelTypeGeographic or ModelTypeProjected are " "supported")); return nullptr; } } else if (geokeys[i] == GTRasterTypeGeoKey) { if (geokeys[i + 3] == RasterPixelIsArea) { pixelIsArea = true; } } } } double hRes = 0; double vRes = 0; double west = 0; double north = 0; double *matrix = nullptr; if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTRANSMATRIX, &count, &matrix) && count == 16) { // If using GDAL to produce a bottom-up georeferencing, it will produce // a GeoTransformationMatrix, since negative values in GeoPixelScale // have historically been implementation bugs. if (matrix[1] != 0 || matrix[4] != 0) { pj_log(m_ctx, PJ_LOG_ERROR, _("Rotational terms not supported in " "GeoTransformationMatrix tag")); return nullptr; } west = matrix[3]; hRes = matrix[0]; north = matrix[7]; vRes = -matrix[5]; // negation to simulate GeoPixelScale convention } else { double *geopixelscale = nullptr; if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count, &geopixelscale) != 1) { pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoPixelScale tag")); return nullptr; } if (count != 3) { pj_log(m_ctx, PJ_LOG_ERROR, _("Wrong number of values in GeoPixelScale tag")); return nullptr; } hRes = geopixelscale[0]; vRes = geopixelscale[1]; double *geotiepoints = nullptr; if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count, &geotiepoints) != 1) { pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoTiePoints tag")); return nullptr; } if (count != 6) { pj_log(m_ctx, PJ_LOG_ERROR, _("Wrong number of values in GeoTiePoints tag")); return nullptr; } west = geotiepoints[3] - geotiepoints[0] * hRes; north = geotiepoints[4] + geotiepoints[1] * vRes; } if (pixelIsArea) { west += 0.5 * hRes; north -= 0.5 * vRes; } const double mulFactor = extent.isGeographic ? DEG_TO_RAD : 1; extent.west = west * mulFactor; extent.north = north * mulFactor; extent.resX = hRes * mulFactor; extent.resY = fabs(vRes) * mulFactor; extent.east = (west + hRes * (width - 1)) * mulFactor; extent.south = (north - vRes * (height - 1)) * mulFactor; extent.computeInvRes(); if (vRes < 0) { std::swap(extent.north, extent.south); } if (!((!extent.isGeographic || (fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI && fabs(extent.north) <= M_PI + 1e-5 && fabs(extent.south) <= M_PI + 1e-5)) && extent.west < extent.east && extent.south < extent.north && extent.resX > 1e-10 && extent.resY > 1e-10)) { pj_log(m_ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), m_filename.c_str()); return nullptr; } auto ret = std::unique_ptr(new GTiffGrid( m_ctx, m_hTIFF, m_cache, m_fp.get(), m_ifdIdx, m_filename, width, height, extent, dt, samplesPerPixel, planarConfig, vRes < 0)); m_ifdIdx++; m_hasNextGrid = TIFFReadDirectory(m_hTIFF) != 0; m_nextDirOffset = TIFFCurrentDirOffset(m_hTIFF); // If the TIFF file contains multiple grids, append the index of the grid // in the grid name to help debugging. if (m_ifdIdx >= 2 || m_hasNextGrid) { ret->m_name += " (index "; ret->m_name += std::to_string(m_ifdIdx); // 1-based ret->m_name += ')'; } return ret; } // --------------------------------------------------------------------------- class GTiffVGridShiftSet : public VerticalShiftGridSet { std::unique_ptr m_GTiffDataset; GTiffVGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr &&fp) : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {} public: ~GTiffVGridShiftSet() override; static std::unique_ptr open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename); void reassign_context(PJ_CONTEXT *ctx) override { VerticalShiftGridSet::reassign_context(ctx); if (m_GTiffDataset) { m_GTiffDataset->reassign_context(ctx); } } bool reopen(PJ_CONTEXT *ctx) override { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); m_grids.clear(); m_GTiffDataset.reset(); auto fp = FileManager::open_resource_file(ctx, m_name.c_str()); if (!fp) { return false; } auto newGS = open(ctx, std::move(fp), m_name); if (newGS) { m_grids = std::move(newGS->m_grids); m_GTiffDataset = std::move(newGS->m_GTiffDataset); } return !m_grids.empty(); } }; // --------------------------------------------------------------------------- template static void insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr &&grid, const std::string &gridName, const std::string &parentName, std::vector> &topGrids, std::map &mapGrids) { const auto &extent = grid->extentAndRes(); // If we have one or both of grid_name and parent_grid_name, try to use // the names to recreate the hierarchy if (!gridName.empty()) { if (mapGrids.find(gridName) != mapGrids.end()) { pj_log(ctx, PJ_LOG_DEBUG, "Several grids called %s found!", gridName.c_str()); } mapGrids[gridName] = grid.get(); } if (!parentName.empty()) { auto iter = mapGrids.find(parentName); if (iter == mapGrids.end()) { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s refers to non-existing parent %s. " "Using bounding-box method.", gridName.c_str(), parentName.c_str()); } else { if (iter->second->extentAndRes().contains(extent)) { iter->second->m_children.emplace_back(std::move(grid)); return; } else { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s refers to parent %s, but its extent is " "not included in it. Using bounding-box method.", gridName.c_str(), parentName.c_str()); } } } else if (!gridName.empty()) { topGrids.emplace_back(std::move(grid)); return; } const std::string &type = grid->metadataItem("TYPE"); // Fallback to analyzing spatial extents for (const auto &candidateParent : topGrids) { if (!type.empty() && candidateParent->metadataItem("TYPE") != type) { continue; } const auto &candidateParentExtent = candidateParent->extentAndRes(); if (candidateParentExtent.contains(extent)) { static_cast(candidateParent.get()) ->insertGrid(ctx, std::move(grid)); return; } else if (candidateParentExtent.intersects(extent)) { pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } topGrids.emplace_back(std::move(grid)); } // --------------------------------------------------------------------------- class GTiffVGrid : public VerticalShiftGrid { friend void insertIntoHierarchy( PJ_CONTEXT *ctx, std::unique_ptr &&grid, const std::string &gridName, const std::string &parentName, std::vector> &topGrids, std::map &mapGrids); std::unique_ptr m_grid; uint16_t m_idxSample; public: GTiffVGrid(std::unique_ptr &&grid, uint16_t idxSample); ~GTiffVGrid() override; bool valueAt(int x, int y, float &out) const override { return m_grid->valueAt(m_idxSample, x, y, out); } bool isNodata(float val, double /* multiplier */) const override { return m_grid->isNodata(val); } const std::string &metadataItem(const std::string &key, int sample = -1) const override { return m_grid->metadataItem(key, sample); } void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr &&subgrid); void reassign_context(PJ_CONTEXT *ctx) override { m_grid->reassign_context(ctx); } bool hasChanged() const override { return m_grid->hasChanged(); } }; // --------------------------------------------------------------------------- GTiffVGridShiftSet::~GTiffVGridShiftSet() = default; // --------------------------------------------------------------------------- GTiffVGrid::GTiffVGrid(std::unique_ptr &&grid, uint16_t idxSample) : VerticalShiftGrid(grid->name(), grid->width(), grid->height(), grid->extentAndRes()), m_grid(std::move(grid)), m_idxSample(idxSample) {} // --------------------------------------------------------------------------- GTiffVGrid::~GTiffVGrid() = default; // --------------------------------------------------------------------------- void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx, std::unique_ptr &&subgrid) { bool gridInserted = false; const auto &extent = subgrid->extentAndRes(); for (const auto &candidateParent : m_children) { const auto &candidateParentExtent = candidateParent->extentAndRes(); if (candidateParentExtent.contains(extent)) { static_cast(candidateParent.get()) ->insertGrid(ctx, std::move(subgrid)); gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } if (!gridInserted) { m_children.emplace_back(std::move(subgrid)); } } // --------------------------------------------------------------------------- std::unique_ptr GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename) { auto set = std::unique_ptr( new GTiffVGridShiftSet(ctx, std::move(fp))); set->m_name = filename; set->m_format = "gtiff"; if (!set->m_GTiffDataset->openTIFF(filename)) { return nullptr; } uint16_t idxSample = 0; std::map mapGrids; for (int ifd = 0;; ++ifd) { auto grid = set->m_GTiffDataset->nextGrid(); if (!grid) { if (ifd == 0) { return nullptr; } break; } const auto subfileType = grid->subfileType(); if (subfileType != 0 && subfileType != FILETYPE_PAGE) { if (ifd == 0) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType")); return nullptr; } else { pj_log(ctx, PJ_LOG_DEBUG, "Ignoring IFD %d as it has a unsupported subfileType", ifd); continue; } } // Identify the index of the vertical correction bool foundDescriptionForAtLeastOneSample = false; bool foundDescriptionForShift = false; for (int i = 0; i < static_cast(grid->samplesPerPixel()); ++i) { const auto &desc = grid->metadataItem("DESCRIPTION", i); if (!desc.empty()) { foundDescriptionForAtLeastOneSample = true; } if (desc == "geoid_undulation" || desc == "vertical_offset" || desc == "hydroid_height" || desc == "ellipsoidal_height_offset") { idxSample = static_cast(i); foundDescriptionForShift = true; } } if (foundDescriptionForAtLeastOneSample) { if (!foundDescriptionForShift) { if (ifd > 0) { // Assuming that extra IFD without our channel of interest // can be ignored // One could imagine to put the accuracy values in separate // IFD for example pj_log(ctx, PJ_LOG_DEBUG, "Ignoring IFD %d as it has no " "geoid_undulation/vertical_offset/hydroid_height/" "ellipsoidal_height_offset channel", ifd); continue; } else { pj_log(ctx, PJ_LOG_DEBUG, "IFD 0 has channel descriptions, but no " "geoid_undulation/vertical_offset/hydroid_height/" "ellipsoidal_height_offset channel"); return nullptr; } } } if (idxSample >= grid->samplesPerPixel()) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index")); return nullptr; } const std::string &gridName = grid->metadataItem("grid_name"); const std::string &parentName = grid->metadataItem("parent_grid_name"); auto vgrid = std::make_unique(std::move(grid), idxSample); insertIntoHierarchy(ctx, std::move(vgrid), gridName, parentName, set->m_grids, mapGrids); } return set; } #endif // TIFF_ENABLED // --------------------------------------------------------------------------- std::unique_ptr VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { if (filename == "null") { auto set = std::unique_ptr(new VerticalShiftGridSet()); set->m_name = filename; set->m_format = "null"; set->m_grids.push_back(std::unique_ptr( new NullVerticalShiftGrid())); return set; } auto fp = FileManager::open_resource_file(ctx, filename.c_str()); if (!fp) { return nullptr; } const auto &actualName(fp->name()); if (ends_with(actualName, "gtx") || ends_with(actualName, "GTX")) { auto grid = GTXVerticalShiftGrid::open(ctx, std::move(fp), actualName); if (!grid) { return nullptr; } auto set = std::unique_ptr(new VerticalShiftGridSet()); set->m_name = actualName; set->m_format = "gtx"; set->m_grids.push_back(std::unique_ptr(grid)); return set; } /* -------------------------------------------------------------------- */ /* Load a header, to determine the file type. */ /* -------------------------------------------------------------------- */ unsigned char header[4]; size_t header_size = fp->read(header, sizeof(header)); if (header_size != sizeof(header)) { return nullptr; } fp->seek(0); if (IsTIFF(header_size, header)) { #ifdef TIFF_ENABLED auto set = std::unique_ptr( GTiffVGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return set; #else pj_log(ctx, PJ_LOG_ERROR, _("TIFF grid, but TIFF support disabled in this build")); return nullptr; #endif } pj_log(ctx, PJ_LOG_ERROR, "Unrecognized vertical grid format for filename '%s'", filename.c_str()); return nullptr; } // --------------------------------------------------------------------------- bool VerticalShiftGridSet::reopen(PJ_CONTEXT *ctx) { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); auto newGS = open(ctx, m_name); m_grids.clear(); if (newGS) { m_grids = std::move(newGS->m_grids); } return !m_grids.empty(); } // --------------------------------------------------------------------------- static bool isPointInExtent(double x, double y, const ExtentAndRes &extent, double eps = 0) { if (!(y + eps >= extent.south && y - eps <= extent.north)) return false; if (extent.fullWorldLongitude()) return true; if (extent.isGeographic) { if (x + eps < extent.west) x += 2 * M_PI; else if (x - eps > extent.east) x -= 2 * M_PI; } if (!(x + eps >= extent.west && x - eps <= extent.east)) return false; return true; } // --------------------------------------------------------------------------- const VerticalShiftGrid *VerticalShiftGrid::gridAt(double longitude, double lat) const { for (const auto &child : m_children) { const auto &extentChild = child->extentAndRes(); if (isPointInExtent(longitude, lat, extentChild)) { return child->gridAt(longitude, lat); } } return this; } // --------------------------------------------------------------------------- const VerticalShiftGrid *VerticalShiftGridSet::gridAt(double longitude, double lat) const { for (const auto &grid : m_grids) { if (grid->isNullGrid()) { return grid.get(); } const auto &extent = grid->extentAndRes(); if (isPointInExtent(longitude, lat, extent)) { return grid->gridAt(longitude, lat); } } return nullptr; } // --------------------------------------------------------------------------- void VerticalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) { for (const auto &grid : m_grids) { grid->reassign_context(ctx); } } // --------------------------------------------------------------------------- HorizontalShiftGrid::HorizontalShiftGrid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : Grid(nameIn, widthIn, heightIn, extentIn) {} // --------------------------------------------------------------------------- HorizontalShiftGrid::~HorizontalShiftGrid() = default; // --------------------------------------------------------------------------- HorizontalShiftGridSet::HorizontalShiftGridSet() = default; // --------------------------------------------------------------------------- HorizontalShiftGridSet::~HorizontalShiftGridSet() = default; // --------------------------------------------------------------------------- class NullHorizontalShiftGrid : public HorizontalShiftGrid { public: NullHorizontalShiftGrid() : HorizontalShiftGrid("null", 3, 3, globalExtent()) {} bool isNullGrid() const override { return true; } bool valueAt(int, int, bool, float &longShift, float &latShift) const override; void reassign_context(PJ_CONTEXT *) override {} bool hasChanged() const override { return false; } const std::string &metadataItem(const std::string &, int) const override { return emptyString; } }; // --------------------------------------------------------------------------- bool NullHorizontalShiftGrid::valueAt(int, int, bool, float &longShift, float &latShift) const { longShift = 0.0f; latShift = 0.0f; return true; } // --------------------------------------------------------------------------- static double to_double(const void *data) { double d; memcpy(&d, data, sizeof(d)); return d; } // --------------------------------------------------------------------------- class NTv1Grid : public HorizontalShiftGrid { PJ_CONTEXT *m_ctx; std::unique_ptr m_fp; NTv1Grid(const NTv1Grid &) = delete; NTv1Grid &operator=(const NTv1Grid &) = delete; public: explicit NTv1Grid(PJ_CONTEXT *ctx, std::unique_ptr &&fp, const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_fp(std::move(fp)) {} ~NTv1Grid() override; bool valueAt(int, int, bool, float &longShift, float &latShift) const override; static NTv1Grid *open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename); void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; m_fp->reassign_context(ctx); } bool hasChanged() const override { return m_fp->hasChanged(); } const std::string &metadataItem(const std::string &, int) const override { return emptyString; } }; // --------------------------------------------------------------------------- NTv1Grid::~NTv1Grid() = default; // --------------------------------------------------------------------------- NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename) { unsigned char header[192]; /* -------------------------------------------------------------------- */ /* Read the header. */ /* -------------------------------------------------------------------- */ if (fp->read(header, sizeof(header)) != sizeof(header)) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } /* -------------------------------------------------------------------- */ /* Regularize fields of interest. */ /* -------------------------------------------------------------------- */ if (IS_LSB) { swap_words(header + 8, sizeof(int), 1); swap_words(header + 24, sizeof(double), 1); swap_words(header + 40, sizeof(double), 1); swap_words(header + 56, sizeof(double), 1); swap_words(header + 72, sizeof(double), 1); swap_words(header + 88, sizeof(double), 1); swap_words(header + 104, sizeof(double), 1); } int recordCount; memcpy(&recordCount, header + 8, sizeof(recordCount)); if (recordCount != 12) { pj_log(ctx, PJ_LOG_ERROR, _("NTv1 grid shift file has wrong record count, corrupt?")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } ExtentAndRes extent; extent.isGeographic = true; extent.west = -to_double(header + 72) * DEG_TO_RAD; extent.south = to_double(header + 24) * DEG_TO_RAD; extent.east = -to_double(header + 56) * DEG_TO_RAD; extent.north = to_double(header + 40) * DEG_TO_RAD; extent.resX = to_double(header + 104) * DEG_TO_RAD; extent.resY = to_double(header + 88) * DEG_TO_RAD; extent.computeInvRes(); if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI && fabs(extent.north) <= M_PI + 1e-5 && fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east && extent.south < extent.north && extent.resX > 1e-10 && extent.resY > 1e-10)) { pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), filename.c_str()); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } const int columns = static_cast( fabs((extent.east - extent.west) * extent.invResX + 0.5) + 1); const int rows = static_cast( fabs((extent.north - extent.south) * extent.invResY + 0.5) + 1); return new NTv1Grid(ctx, std::move(fp), filename, columns, rows, extent); } // --------------------------------------------------------------------------- bool NTv1Grid::valueAt(int x, int y, bool compensateNTConvention, float &longShift, float &latShift) const { assert(x >= 0 && y >= 0 && x < m_width && y < m_height); double two_doubles[2]; // NTv1 is organized from east to west ! m_fp->seek(192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x)); if (m_fp->read(&two_doubles[0], sizeof(two_doubles)) != sizeof(two_doubles)) { proj_context_errno_set(m_ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (IS_LSB) { swap_words(&two_doubles[0], sizeof(double), 2); } /* convert seconds to radians */ latShift = static_cast(two_doubles[0] * ((M_PI / 180.0) / 3600.0)); // west longitude positive convention ! longShift = (compensateNTConvention ? -1 : 1) * static_cast(two_doubles[1] * ((M_PI / 180.0) / 3600.0)); return true; } // --------------------------------------------------------------------------- class CTable2Grid : public HorizontalShiftGrid { PJ_CONTEXT *m_ctx; std::unique_ptr m_fp; CTable2Grid(const CTable2Grid &) = delete; CTable2Grid &operator=(const CTable2Grid &) = delete; public: CTable2Grid(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_fp(std::move(fp)) {} ~CTable2Grid() override; bool valueAt(int, int, bool, float &longShift, float &latShift) const override; static CTable2Grid *open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename); void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; m_fp->reassign_context(ctx); } bool hasChanged() const override { return m_fp->hasChanged(); } const std::string &metadataItem(const std::string &, int) const override { return emptyString; } }; // --------------------------------------------------------------------------- CTable2Grid::~CTable2Grid() = default; // --------------------------------------------------------------------------- CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename) { unsigned char header[160]; /* -------------------------------------------------------------------- */ /* Read the header. */ /* -------------------------------------------------------------------- */ if (fp->read(header, sizeof(header)) != sizeof(header)) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } /* -------------------------------------------------------------------- */ /* Regularize fields of interest. */ /* -------------------------------------------------------------------- */ if (!IS_LSB) { swap_words(header + 96, sizeof(double), 4); swap_words(header + 128, sizeof(int), 2); } ExtentAndRes extent; extent.isGeographic = true; static_assert(sizeof(extent.west) == 8, "wrong sizeof"); static_assert(sizeof(extent.south) == 8, "wrong sizeof"); static_assert(sizeof(extent.resX) == 8, "wrong sizeof"); static_assert(sizeof(extent.resY) == 8, "wrong sizeof"); memcpy(&extent.west, header + 96, 8); memcpy(&extent.south, header + 104, 8); memcpy(&extent.resX, header + 112, 8); memcpy(&extent.resY, header + 120, 8); if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.south) <= M_PI + 1e-5 && extent.resX > 1e-10 && extent.resY > 1e-10)) { pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), filename.c_str()); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } int width; int height; memcpy(&width, header + 128, 4); memcpy(&height, header + 132, 4); if (width <= 0 || height <= 0) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } extent.east = extent.west + (width - 1) * extent.resX; extent.north = extent.south + (height - 1) * extent.resX; extent.computeInvRes(); return new CTable2Grid(ctx, std::move(fp), filename, width, height, extent); } // --------------------------------------------------------------------------- bool CTable2Grid::valueAt(int x, int y, bool compensateNTConvention, float &longShift, float &latShift) const { assert(x >= 0 && y >= 0 && x < m_width && y < m_height); float two_floats[2]; m_fp->seek(160 + 2 * sizeof(float) * (y * m_width + x)); if (m_fp->read(&two_floats[0], sizeof(two_floats)) != sizeof(two_floats)) { proj_context_errno_set(m_ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } if (!IS_LSB) { swap_words(&two_floats[0], sizeof(float), 2); } latShift = two_floats[1]; // west longitude positive convention ! longShift = (compensateNTConvention ? -1 : 1) * two_floats[0]; return true; } // --------------------------------------------------------------------------- class NTv2GridSet : public HorizontalShiftGridSet { std::unique_ptr m_fp; std::unique_ptr m_cache{}; NTv2GridSet(const NTv2GridSet &) = delete; NTv2GridSet &operator=(const NTv2GridSet &) = delete; explicit NTv2GridSet(std::unique_ptr &&fp) : m_fp(std::move(fp)) {} public: ~NTv2GridSet() override; static std::unique_ptr open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename); void reassign_context(PJ_CONTEXT *ctx) override { HorizontalShiftGridSet::reassign_context(ctx); m_fp->reassign_context(ctx); } }; // --------------------------------------------------------------------------- class NTv2Grid : public HorizontalShiftGrid { friend class NTv2GridSet; PJ_CONTEXT *m_ctx; // owned by the parent NTv2GridSet File *m_fp; // owned by the parent NTv2GridSet FloatLineCache *m_cache = nullptr; // owned by the parent NTv2GridSet uint32_t m_gridIdx; unsigned long long m_offset; bool m_mustSwap; mutable std::vector m_buffer{}; NTv2Grid(const NTv2Grid &) = delete; NTv2Grid &operator=(const NTv2Grid &) = delete; public: NTv2Grid(const std::string &nameIn, PJ_CONTEXT *ctx, File *fp, uint32_t gridIdx, unsigned long long offsetIn, bool mustSwapIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_fp(fp), m_gridIdx(gridIdx), m_offset(offsetIn), m_mustSwap(mustSwapIn) {} bool valueAt(int, int, bool, float &longShift, float &latShift) const override; const std::string &metadataItem(const std::string &, int) const override { return emptyString; } void setCache(FloatLineCache *cache) { m_cache = cache; } void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; m_fp->reassign_context(ctx); } bool hasChanged() const override { return m_fp->hasChanged(); } }; // --------------------------------------------------------------------------- bool NTv2Grid::valueAt(int x, int y, bool compensateNTConvention, float &longShift, float &latShift) const { assert(x >= 0 && y >= 0 && x < m_width && y < m_height); const std::vector *pBuffer = m_cache->get(m_gridIdx, y); if (pBuffer == nullptr) { try { m_buffer.resize(4 * m_width); } catch (const std::exception &e) { pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); return false; } const size_t nLineSizeInBytes = 4 * sizeof(float) * m_width; // there are 4 components: lat shift, long shift, lat error, long error m_fp->seek(m_offset + nLineSizeInBytes * static_cast(y)); if (m_fp->read(&m_buffer[0], nLineSizeInBytes) != nLineSizeInBytes) { proj_context_errno_set( m_ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } // Remove lat and long error for (int i = 1; i < m_width; ++i) { m_buffer[2 * i] = m_buffer[4 * i]; m_buffer[2 * i + 1] = m_buffer[4 * i + 1]; } m_buffer.resize(2 * m_width); if (m_mustSwap) { swap_words(&m_buffer[0], sizeof(float), 2 * m_width); } // NTv2 is organized from east to west ! for (int i = 0; i < m_width / 2; ++i) { std::swap(m_buffer[2 * i], m_buffer[2 * (m_width - 1 - i)]); std::swap(m_buffer[2 * i + 1], m_buffer[2 * (m_width - 1 - i) + 1]); } try { m_cache->insert(m_gridIdx, y, m_buffer); } catch (const std::exception &e) { // Should normally not happen pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what()); } } const std::vector &buffer = pBuffer ? *pBuffer : m_buffer; /* convert seconds to radians */ latShift = static_cast(buffer[2 * x] * ((M_PI / 180.0) / 3600.0)); // west longitude positive convention ! longShift = (compensateNTConvention ? -1 : 1) * static_cast(buffer[2 * x + 1] * ((M_PI / 180.0) / 3600.0)); return true; } // --------------------------------------------------------------------------- NTv2GridSet::~NTv2GridSet() = default; // --------------------------------------------------------------------------- std::unique_ptr NTv2GridSet::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename) { File *fpRaw = fp.get(); auto set = std::unique_ptr(new NTv2GridSet(std::move(fp))); set->m_name = filename; set->m_format = "ntv2"; char header[11 * 16]; /* -------------------------------------------------------------------- */ /* Read the header. */ /* -------------------------------------------------------------------- */ if (fpRaw->read(header, sizeof(header)) != sizeof(header)) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } constexpr int OFFSET_GS_TYPE = 56; if (memcmp(header + OFFSET_GS_TYPE, "SECONDS", 7) != 0) { pj_log(ctx, PJ_LOG_ERROR, _("Only GS_TYPE=SECONDS is supported")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } const bool must_swap = (header[8] == 11) ? !IS_LSB : IS_LSB; constexpr int OFFSET_NUM_SUBFILES = 8 + 32; if (must_swap) { // swap_words( header+8, 4, 1 ); // swap_words( header+8+16, 4, 1 ); swap_words(header + OFFSET_NUM_SUBFILES, 4, 1); // swap_words( header+8+7*16, 8, 1 ); // swap_words( header+8+8*16, 8, 1 ); // swap_words( header+8+9*16, 8, 1 ); // swap_words( header+8+10*16, 8, 1 ); } /* -------------------------------------------------------------------- */ /* Get the subfile count out ... all we really use for now. */ /* -------------------------------------------------------------------- */ unsigned int num_subfiles; memcpy(&num_subfiles, header + OFFSET_NUM_SUBFILES, 4); std::map mapGrids; /* ==================================================================== */ /* Step through the subfiles, creating a grid for each. */ /* ==================================================================== */ int largestLine = 1; for (unsigned subfile = 0; subfile < num_subfiles; subfile++) { // Read header if (fpRaw->read(header, sizeof(header)) != sizeof(header)) { proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } if (strncmp(header, "SUB_NAME", 8) != 0) { proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } // Byte swap interesting fields if needed. constexpr int OFFSET_GS_COUNT = 8 + 16 * 10; constexpr int OFFSET_SOUTH_LAT = 8 + 16 * 4; if (must_swap) { // 6 double values: south, north, east, west, resY, // resX for (int i = 0; i < 6; i++) { swap_words(header + OFFSET_SOUTH_LAT + 16 * i, sizeof(double), 1); } swap_words(header + OFFSET_GS_COUNT, sizeof(int), 1); } std::string gridName; gridName.append(header + 8, 8); ExtentAndRes extent; extent.isGeographic = true; extent.south = to_double(header + OFFSET_SOUTH_LAT) * DEG_TO_RAD / 3600.0; /* S_LAT */ extent.north = to_double(header + OFFSET_SOUTH_LAT + 16) * DEG_TO_RAD / 3600.0; /* N_LAT */ extent.east = -to_double(header + OFFSET_SOUTH_LAT + 16 * 2) * DEG_TO_RAD / 3600.0; /* E_LONG */ extent.west = -to_double(header + OFFSET_SOUTH_LAT + 16 * 3) * DEG_TO_RAD / 3600.0; /* W_LONG */ extent.resY = to_double(header + OFFSET_SOUTH_LAT + 16 * 4) * DEG_TO_RAD / 3600.0; extent.resX = to_double(header + OFFSET_SOUTH_LAT + 16 * 5) * DEG_TO_RAD / 3600.0; extent.computeInvRes(); if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI && fabs(extent.north) <= M_PI + 1e-5 && fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east && extent.south < extent.north && extent.resX > 1e-10 && extent.resY > 1e-10)) { pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"), filename.c_str()); proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } const int columns = static_cast( fabs((extent.east - extent.west) * extent.invResX + 0.5) + 1); const int rows = static_cast( fabs((extent.north - extent.south) * extent.invResY + 0.5) + 1); if (columns > largestLine) largestLine = columns; pj_log(ctx, PJ_LOG_TRACE, "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", gridName.c_str(), columns, rows, extent.west * RAD_TO_DEG, extent.south * RAD_TO_DEG, extent.east * RAD_TO_DEG, extent.north * RAD_TO_DEG); unsigned int gs_count; memcpy(&gs_count, header + OFFSET_GS_COUNT, 4); if (gs_count / columns != static_cast(rows)) { pj_log(ctx, PJ_LOG_ERROR, _("GS_COUNT(%u) does not match expected cells (%dx%d)"), gs_count, columns, rows); proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return nullptr; } const auto offset = fpRaw->tell(); auto grid = std::unique_ptr(new NTv2Grid( std::string(filename).append(", ").append(gridName), ctx, fpRaw, subfile, offset, must_swap, columns, rows, extent)); std::string parentName; parentName.assign(header + 24, 8); auto iter = mapGrids.find(parentName); auto gridPtr = grid.get(); if (iter == mapGrids.end()) { set->m_grids.emplace_back(std::move(grid)); } else { iter->second->m_children.emplace_back(std::move(grid)); } mapGrids[gridName] = gridPtr; // Skip grid data. 4 components of size float fpRaw->seek(static_cast(gs_count) * 4 * 4, SEEK_CUR); } // Cache up to 1 megapixel per NTv2 file const int maxLinesInCache = 1024 * 1024 / largestLine; set->m_cache = std::make_unique(maxLinesInCache); for (const auto &kv : mapGrids) { kv.second->setCache(set->m_cache.get()); } return set; } #ifdef TIFF_ENABLED // --------------------------------------------------------------------------- class GTiffHGridShiftSet : public HorizontalShiftGridSet { std::unique_ptr m_GTiffDataset; GTiffHGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr &&fp) : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {} public: ~GTiffHGridShiftSet() override; static std::unique_ptr open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename); void reassign_context(PJ_CONTEXT *ctx) override { HorizontalShiftGridSet::reassign_context(ctx); if (m_GTiffDataset) { m_GTiffDataset->reassign_context(ctx); } } bool reopen(PJ_CONTEXT *ctx) override { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); m_grids.clear(); m_GTiffDataset.reset(); auto fp = FileManager::open_resource_file(ctx, m_name.c_str()); if (!fp) { return false; } auto newGS = open(ctx, std::move(fp), m_name); if (newGS) { m_grids = std::move(newGS->m_grids); m_GTiffDataset = std::move(newGS->m_GTiffDataset); } return !m_grids.empty(); } }; // --------------------------------------------------------------------------- class GTiffHGrid : public HorizontalShiftGrid { friend void insertIntoHierarchy( PJ_CONTEXT *ctx, std::unique_ptr &&grid, const std::string &gridName, const std::string &parentName, std::vector> &topGrids, std::map &mapGrids); std::unique_ptr m_grid; uint16_t m_idxLatShift; uint16_t m_idxLongShift; double m_convFactorToRadian; bool m_positiveEast; public: GTiffHGrid(std::unique_ptr &&grid, uint16_t idxLatShift, uint16_t idxLongShift, double convFactorToRadian, bool positiveEast); ~GTiffHGrid() override; bool valueAt(int x, int y, bool, float &longShift, float &latShift) const override; const std::string &metadataItem(const std::string &key, int sample = -1) const override { return m_grid->metadataItem(key, sample); } void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr &&subgrid); void reassign_context(PJ_CONTEXT *ctx) override { m_grid->reassign_context(ctx); } bool hasChanged() const override { return m_grid->hasChanged(); } }; // --------------------------------------------------------------------------- GTiffHGridShiftSet::~GTiffHGridShiftSet() = default; // --------------------------------------------------------------------------- GTiffHGrid::GTiffHGrid(std::unique_ptr &&grid, uint16_t idxLatShift, uint16_t idxLongShift, double convFactorToRadian, bool positiveEast) : HorizontalShiftGrid(grid->name(), grid->width(), grid->height(), grid->extentAndRes()), m_grid(std::move(grid)), m_idxLatShift(idxLatShift), m_idxLongShift(idxLongShift), m_convFactorToRadian(convFactorToRadian), m_positiveEast(positiveEast) {} // --------------------------------------------------------------------------- GTiffHGrid::~GTiffHGrid() = default; // --------------------------------------------------------------------------- bool GTiffHGrid::valueAt(int x, int y, bool, float &longShift, float &latShift) const { if (!m_grid->valueAt(m_idxLatShift, x, y, latShift) || !m_grid->valueAt(m_idxLongShift, x, y, longShift)) { return false; } // From arc-seconds to radians latShift = static_cast(latShift * m_convFactorToRadian); longShift = static_cast(longShift * m_convFactorToRadian); if (!m_positiveEast) { longShift = -longShift; } return true; } // --------------------------------------------------------------------------- void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx, std::unique_ptr &&subgrid) { bool gridInserted = false; const auto &extent = subgrid->extentAndRes(); for (const auto &candidateParent : m_children) { const auto &candidateParentExtent = candidateParent->extentAndRes(); if (candidateParentExtent.contains(extent)) { static_cast(candidateParent.get()) ->insertGrid(ctx, std::move(subgrid)); gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } if (!gridInserted) { m_children.emplace_back(std::move(subgrid)); } } // --------------------------------------------------------------------------- std::unique_ptr GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename) { auto set = std::unique_ptr( new GTiffHGridShiftSet(ctx, std::move(fp))); set->m_name = filename; set->m_format = "gtiff"; if (!set->m_GTiffDataset->openTIFF(filename)) { return nullptr; } // Defaults inspired from NTv2 uint16_t idxLatShift = 0; uint16_t idxLongShift = 1; constexpr double ARC_SECOND_TO_RADIAN = (M_PI / 180.0) / 3600.0; double convFactorToRadian = ARC_SECOND_TO_RADIAN; bool positiveEast = true; std::map mapGrids; for (int ifd = 0;; ++ifd) { auto grid = set->m_GTiffDataset->nextGrid(); if (!grid) { if (ifd == 0) { return nullptr; } break; } const auto subfileType = grid->subfileType(); if (subfileType != 0 && subfileType != FILETYPE_PAGE) { if (ifd == 0) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType")); return nullptr; } else { pj_log(ctx, PJ_LOG_DEBUG, _("Ignoring IFD %d as it has a unsupported subfileType"), ifd); continue; } } if (grid->samplesPerPixel() < 2) { if (ifd == 0) { pj_log(ctx, PJ_LOG_ERROR, _("At least 2 samples per pixel needed")); return nullptr; } else { pj_log(ctx, PJ_LOG_DEBUG, _("Ignoring IFD %d as it has not at least 2 samples"), ifd); continue; } } // Identify the index of the latitude and longitude offset channels bool foundDescriptionForAtLeastOneSample = false; bool foundDescriptionForLatOffset = false; bool foundDescriptionForLongOffset = false; for (int i = 0; i < static_cast(grid->samplesPerPixel()); ++i) { const auto &desc = grid->metadataItem("DESCRIPTION", i); if (!desc.empty()) { foundDescriptionForAtLeastOneSample = true; } if (desc == "latitude_offset") { idxLatShift = static_cast(i); foundDescriptionForLatOffset = true; } else if (desc == "longitude_offset") { idxLongShift = static_cast(i); foundDescriptionForLongOffset = true; } } if (foundDescriptionForAtLeastOneSample) { if (!foundDescriptionForLongOffset && !foundDescriptionForLatOffset) { if (ifd > 0) { // Assuming that extra IFD without // longitude_offset/latitude_offset can be ignored // One could imagine to put the accuracy values in separate // IFD for example pj_log(ctx, PJ_LOG_DEBUG, "Ignoring IFD %d as it has no " "longitude_offset/latitude_offset channel", ifd); continue; } else { pj_log(ctx, PJ_LOG_DEBUG, "IFD 0 has channel descriptions, but no " "longitude_offset/latitude_offset channel"); return nullptr; } } } if (foundDescriptionForLatOffset && !foundDescriptionForLongOffset) { pj_log( ctx, PJ_LOG_ERROR, _("Found latitude_offset channel, but not longitude_offset")); return nullptr; } else if (foundDescriptionForLongOffset && !foundDescriptionForLatOffset) { pj_log( ctx, PJ_LOG_ERROR, _("Found longitude_offset channel, but not latitude_offset")); return nullptr; } if (idxLatShift >= grid->samplesPerPixel() || idxLongShift >= grid->samplesPerPixel()) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index")); return nullptr; } if (foundDescriptionForLongOffset) { const std::string &positiveValue = grid->metadataItem("positive_value", idxLongShift); if (!positiveValue.empty()) { if (positiveValue == "west") { positiveEast = false; } else if (positiveValue == "east") { positiveEast = true; } else { pj_log(ctx, PJ_LOG_ERROR, _("Unsupported value %s for 'positive_value'"), positiveValue.c_str()); return nullptr; } } } // Identify their unit { const auto &unitLatShift = grid->metadataItem("UNITTYPE", idxLatShift); const auto &unitLongShift = grid->metadataItem("UNITTYPE", idxLongShift); if (unitLatShift != unitLongShift) { pj_log(ctx, PJ_LOG_ERROR, _("Different unit for longitude and latitude offset")); return nullptr; } if (!unitLatShift.empty()) { if (unitLatShift == "arc-second" || unitLatShift == "arc-seconds per year") { convFactorToRadian = ARC_SECOND_TO_RADIAN; } else if (unitLatShift == "radian") { convFactorToRadian = 1.0; } else if (unitLatShift == "degree") { convFactorToRadian = M_PI / 180.0; } else { pj_log(ctx, PJ_LOG_ERROR, _("Unsupported unit %s"), unitLatShift.c_str()); return nullptr; } } } const std::string &gridName = grid->metadataItem("grid_name"); const std::string &parentName = grid->metadataItem("parent_grid_name"); auto hgrid = std::make_unique( std::move(grid), idxLatShift, idxLongShift, convFactorToRadian, positiveEast); insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName, set->m_grids, mapGrids); } return set; } #endif // TIFF_ENABLED // --------------------------------------------------------------------------- std::unique_ptr HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { if (filename == "null") { auto set = std::unique_ptr( new HorizontalShiftGridSet()); set->m_name = filename; set->m_format = "null"; set->m_grids.push_back(std::unique_ptr( new NullHorizontalShiftGrid())); return set; } auto fp = FileManager::open_resource_file(ctx, filename.c_str()); if (!fp) { return nullptr; } const auto &actualName(fp->name()); char header[160]; /* -------------------------------------------------------------------- */ /* Load a header, to determine the file type. */ /* -------------------------------------------------------------------- */ size_t header_size = fp->read(header, sizeof(header)); if (header_size != sizeof(header)) { /* some files may be smaller that sizeof(header), eg 160, so */ ctx->last_errno = 0; /* don't treat as a persistent error */ pj_log(ctx, PJ_LOG_DEBUG, "pj_gridinfo_init: short header read of %d bytes", (int)header_size); } fp->seek(0); /* -------------------------------------------------------------------- */ /* Determine file type. */ /* -------------------------------------------------------------------- */ if (header_size >= 144 + 16 && strncmp(header + 0, "HEADER", 6) == 0 && strncmp(header + 96, "W GRID", 6) == 0 && strncmp(header + 144, "TO NAD83 ", 16) == 0) { auto grid = NTv1Grid::open(ctx, std::move(fp), actualName); if (!grid) { return nullptr; } auto set = std::unique_ptr( new HorizontalShiftGridSet()); set->m_name = actualName; set->m_format = "ntv1"; set->m_grids.push_back(std::unique_ptr(grid)); return set; } else if (header_size >= 9 && strncmp(header + 0, "CTABLE V2", 9) == 0) { auto grid = CTable2Grid::open(ctx, std::move(fp), actualName); if (!grid) { return nullptr; } auto set = std::unique_ptr( new HorizontalShiftGridSet()); set->m_name = actualName; set->m_format = "ctable2"; set->m_grids.push_back(std::unique_ptr(grid)); return set; } else if (header_size >= 48 + 7 && strncmp(header + 0, "NUM_OREC", 8) == 0 && strncmp(header + 48, "GS_TYPE", 7) == 0) { return NTv2GridSet::open(ctx, std::move(fp), actualName); } else if (IsTIFF(header_size, reinterpret_cast(header))) { #ifdef TIFF_ENABLED auto set = std::unique_ptr( GTiffHGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return set; #else pj_log(ctx, PJ_LOG_ERROR, _("TIFF grid, but TIFF support disabled in this build")); return nullptr; #endif } pj_log(ctx, PJ_LOG_ERROR, "Unrecognized horizontal grid format for filename '%s'", filename.c_str()); return nullptr; } // --------------------------------------------------------------------------- bool HorizontalShiftGridSet::reopen(PJ_CONTEXT *ctx) { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); auto newGS = open(ctx, m_name); m_grids.clear(); if (newGS) { m_grids = std::move(newGS->m_grids); } return !m_grids.empty(); } // --------------------------------------------------------------------------- #define REL_TOLERANCE_HGRIDSHIFT 1e-5 const HorizontalShiftGrid *HorizontalShiftGrid::gridAt(double longitude, double lat) const { for (const auto &child : m_children) { const auto &extentChild = child->extentAndRes(); const double epsilon = (extentChild.resX + extentChild.resY) * REL_TOLERANCE_HGRIDSHIFT; if (isPointInExtent(longitude, lat, extentChild, epsilon)) { return child->gridAt(longitude, lat); } } return this; } // --------------------------------------------------------------------------- const HorizontalShiftGrid *HorizontalShiftGridSet::gridAt(double longitude, double lat) const { for (const auto &grid : m_grids) { if (grid->isNullGrid()) { return grid.get(); } const auto &extent = grid->extentAndRes(); const double epsilon = (extent.resX + extent.resY) * REL_TOLERANCE_HGRIDSHIFT; if (isPointInExtent(longitude, lat, extent, epsilon)) { return grid->gridAt(longitude, lat); } } return nullptr; } // --------------------------------------------------------------------------- void HorizontalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) { for (const auto &grid : m_grids) { grid->reassign_context(ctx); } } #ifdef TIFF_ENABLED // --------------------------------------------------------------------------- class GTiffGenericGridShiftSet : public GenericShiftGridSet { std::unique_ptr m_GTiffDataset; GTiffGenericGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr &&fp) : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {} public: ~GTiffGenericGridShiftSet() override; static std::unique_ptr open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename); void reassign_context(PJ_CONTEXT *ctx) override { GenericShiftGridSet::reassign_context(ctx); if (m_GTiffDataset) { m_GTiffDataset->reassign_context(ctx); } } bool reopen(PJ_CONTEXT *ctx) override { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); m_grids.clear(); m_GTiffDataset.reset(); auto fp = FileManager::open_resource_file(ctx, m_name.c_str()); if (!fp) { return false; } auto newGS = open(ctx, std::move(fp), m_name); if (newGS) { m_grids = std::move(newGS->m_grids); m_GTiffDataset = std::move(newGS->m_GTiffDataset); } return !m_grids.empty(); } }; // --------------------------------------------------------------------------- class GTiffGenericGrid final : public GenericShiftGrid { friend void insertIntoHierarchy( PJ_CONTEXT *ctx, std::unique_ptr &&grid, const std::string &gridName, const std::string &parentName, std::vector> &topGrids, std::map &mapGrids); std::unique_ptr m_grid; const GenericShiftGrid *m_firstGrid = nullptr; mutable std::string m_type{}; mutable bool m_bTypeSet = false; public: GTiffGenericGrid(std::unique_ptr &&grid); ~GTiffGenericGrid() override; bool valueAt(int x, int y, int sample, float &out) const override; bool valuesAt(int x_start, int y_start, int x_count, int y_count, int sample_count, const int *sample_idx, float *out, bool &nodataFound) const override; int samplesPerPixel() const override { return m_grid->samplesPerPixel(); } std::string unit(int sample) const override { return metadataItem("UNITTYPE", sample); } std::string description(int sample) const override { return metadataItem("DESCRIPTION", sample); } const std::string &metadataItem(const std::string &key, int sample = -1) const override { const std::string &ret = m_grid->metadataItem(key, sample); if (ret.empty() && m_firstGrid) { return m_firstGrid->metadataItem(key, sample); } return ret; } const std::string &type() const override { if (!m_bTypeSet) { m_bTypeSet = true; m_type = metadataItem("TYPE"); } return m_type; } void setFirstGrid(const GenericShiftGrid *firstGrid) { m_firstGrid = firstGrid; } void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr &&subgrid); void reassign_context(PJ_CONTEXT *ctx) override { m_grid->reassign_context(ctx); } bool hasChanged() const override { return m_grid->hasChanged(); } private: GTiffGenericGrid(const GTiffGenericGrid &) = delete; GTiffGenericGrid &operator=(const GTiffGenericGrid &) = delete; }; // --------------------------------------------------------------------------- GTiffGenericGridShiftSet::~GTiffGenericGridShiftSet() = default; // --------------------------------------------------------------------------- GTiffGenericGrid::GTiffGenericGrid(std::unique_ptr &&grid) : GenericShiftGrid(grid->name(), grid->width(), grid->height(), grid->extentAndRes()), m_grid(std::move(grid)) {} // --------------------------------------------------------------------------- GTiffGenericGrid::~GTiffGenericGrid() = default; // --------------------------------------------------------------------------- bool GTiffGenericGrid::valueAt(int x, int y, int sample, float &out) const { if (sample < 0 || static_cast(sample) >= m_grid->samplesPerPixel()) return false; return m_grid->valueAt(static_cast(sample), x, y, out); } // --------------------------------------------------------------------------- bool GTiffGenericGrid::valuesAt(int x_start, int y_start, int x_count, int y_count, int sample_count, const int *sample_idx, float *out, bool &nodataFound) const { return m_grid->valuesAt(x_start, y_start, x_count, y_count, sample_count, sample_idx, out, nodataFound); } // --------------------------------------------------------------------------- void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx, std::unique_ptr &&subgrid) { bool gridInserted = false; const auto &extent = subgrid->extentAndRes(); for (const auto &candidateParent : m_children) { const auto &candidateParentExtent = candidateParent->extentAndRes(); if (candidateParentExtent.contains(extent)) { static_cast(candidateParent.get()) ->insertGrid(ctx, std::move(subgrid)); gridInserted = true; break; } else if (candidateParentExtent.intersects(extent)) { pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!"); } } if (!gridInserted) { m_children.emplace_back(std::move(subgrid)); } } #endif // TIFF_ENABLED // --------------------------------------------------------------------------- class NullGenericShiftGrid : public GenericShiftGrid { public: NullGenericShiftGrid() : GenericShiftGrid("null", 3, 3, globalExtent()) {} bool isNullGrid() const override { return true; } bool valueAt(int, int, int, float &out) const override; const std::string &type() const override { return emptyString; } int samplesPerPixel() const override { return 0; } std::string unit(int) const override { return std::string(); } std::string description(int) const override { return std::string(); } const std::string &metadataItem(const std::string &, int) const override { return emptyString; } void reassign_context(PJ_CONTEXT *) override {} bool hasChanged() const override { return false; } }; // --------------------------------------------------------------------------- bool NullGenericShiftGrid::valueAt(int, int, int, float &out) const { out = 0.0f; return true; } // --------------------------------------------------------------------------- #ifdef TIFF_ENABLED std::unique_ptr GTiffGenericGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr fp, const std::string &filename) { auto set = std::unique_ptr( new GTiffGenericGridShiftSet(ctx, std::move(fp))); set->m_name = filename; set->m_format = "gtiff"; if (!set->m_GTiffDataset->openTIFF(filename)) { return nullptr; } std::map mapGrids; for (int ifd = 0;; ++ifd) { auto grid = set->m_GTiffDataset->nextGrid(); if (!grid) { if (ifd == 0) { return nullptr; } break; } const auto subfileType = grid->subfileType(); if (subfileType != 0 && subfileType != FILETYPE_PAGE) { if (ifd == 0) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType")); return nullptr; } else { pj_log(ctx, PJ_LOG_DEBUG, _("Ignoring IFD %d as it has a unsupported subfileType"), ifd); continue; } } const std::string &gridName = grid->metadataItem("grid_name"); const std::string &parentName = grid->metadataItem("parent_grid_name"); auto ggrid = std::make_unique(std::move(grid)); if (!set->m_grids.empty() && ggrid->metadataItem("TYPE").empty() && !set->m_grids[0]->metadataItem("TYPE").empty()) { ggrid->setFirstGrid(set->m_grids[0].get()); } insertIntoHierarchy(ctx, std::move(ggrid), gridName, parentName, set->m_grids, mapGrids); } return set; } #endif // TIFF_ENABLED // --------------------------------------------------------------------------- GenericShiftGrid::GenericShiftGrid(const std::string &nameIn, int widthIn, int heightIn, const ExtentAndRes &extentIn) : Grid(nameIn, widthIn, heightIn, extentIn) {} // --------------------------------------------------------------------------- GenericShiftGrid::~GenericShiftGrid() = default; // --------------------------------------------------------------------------- bool GenericShiftGrid::valuesAt(int x_start, int y_start, int x_count, int y_count, int sample_count, const int *sample_idx, float *out, bool &nodataFound) const { nodataFound = false; for (int y = y_start; y < y_start + y_count; ++y) { for (int x = x_start; x < x_start + x_count; ++x) { for (int isample = 0; isample < sample_count; ++isample) { if (!valueAt(x, y, sample_idx[isample], *out)) return false; ++out; } } } return true; } // --------------------------------------------------------------------------- GenericShiftGridSet::GenericShiftGridSet() = default; // --------------------------------------------------------------------------- GenericShiftGridSet::~GenericShiftGridSet() = default; // --------------------------------------------------------------------------- std::unique_ptr GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) { if (filename == "null") { auto set = std::unique_ptr(new GenericShiftGridSet()); set->m_name = filename; set->m_format = "null"; set->m_grids.push_back( std::unique_ptr(new NullGenericShiftGrid())); return set; } auto fp = FileManager::open_resource_file(ctx, filename.c_str()); if (!fp) { return nullptr; } /* -------------------------------------------------------------------- */ /* Load a header, to determine the file type. */ /* -------------------------------------------------------------------- */ unsigned char header[4]; size_t header_size = fp->read(header, sizeof(header)); if (header_size != sizeof(header)) { return nullptr; } fp->seek(0); if (IsTIFF(header_size, header)) { #ifdef TIFF_ENABLED const std::string actualName(fp->name()); auto set = std::unique_ptr( GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName)); if (!set) proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return set; #else pj_log(ctx, PJ_LOG_ERROR, _("TIFF grid, but TIFF support disabled in this build")); return nullptr; #endif } pj_log(ctx, PJ_LOG_ERROR, "Unrecognized generic grid format for filename '%s'", filename.c_str()); return nullptr; } // --------------------------------------------------------------------------- bool GenericShiftGridSet::reopen(PJ_CONTEXT *ctx) { pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it", m_name.c_str()); auto newGS = open(ctx, m_name); m_grids.clear(); if (newGS) { m_grids = std::move(newGS->m_grids); } return !m_grids.empty(); } // --------------------------------------------------------------------------- const GenericShiftGrid *GenericShiftGrid::gridAt(double x, double y) const { for (const auto &child : m_children) { const auto &extentChild = child->extentAndRes(); if (isPointInExtent(x, y, extentChild)) { return child->gridAt(x, y); } } return this; } // --------------------------------------------------------------------------- const GenericShiftGrid *GenericShiftGridSet::gridAt(double x, double y) const { for (const auto &grid : m_grids) { if (grid->isNullGrid()) { return grid.get(); } const auto &extent = grid->extentAndRes(); if (isPointInExtent(x, y, extent)) { return grid->gridAt(x, y); } } return nullptr; } // --------------------------------------------------------------------------- const GenericShiftGrid *GenericShiftGridSet::gridAt(const std::string &type, double x, double y) const { for (const auto &grid : m_grids) { if (grid->isNullGrid()) { return grid.get(); } if (grid->type() != type) { continue; } const auto &extent = grid->extentAndRes(); if (isPointInExtent(x, y, extent)) { return grid->gridAt(x, y); } } return nullptr; } // --------------------------------------------------------------------------- void GenericShiftGridSet::reassign_context(PJ_CONTEXT *ctx) { for (const auto &grid : m_grids) { grid->reassign_context(ctx); } } // --------------------------------------------------------------------------- ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) { std::string key("s"); key += gridkey; const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s; if (gridnames == nullptr) return {}; auto listOfGridNames = internal::split(std::string(gridnames), ','); ListOfGenericGrids grids; for (const auto &gridnameStr : listOfGridNames) { const char *gridname = gridnameStr.c_str(); bool canFail = false; if (gridname[0] == '@') { canFail = true; gridname++; } auto gridSet = GenericShiftGridSet::open(P->ctx, gridname); if (!gridSet) { if (!canFail) { if (proj_context_errno(P->ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) { proj_context_errno_set( P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } return {}; } proj_context_errno_set(P->ctx, 0); // don't treat as a persistent error } else { grids.emplace_back(std::move(gridSet)); } } return grids; } // --------------------------------------------------------------------------- static const HorizontalShiftGrid * findGrid(const ListOfHGrids &grids, const PJ_LP &input, HorizontalShiftGridSet *&gridSetOut) { for (const auto &gridset : grids) { auto grid = gridset->gridAt(input.lam, input.phi); if (grid) { gridSetOut = gridset.get(); return grid; } } return nullptr; } // --------------------------------------------------------------------------- static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) { ListOfHGrids list; auto listOfGrids = internal::split(std::string(grids), ','); for (const auto &grid : listOfGrids) { const char *gridname = grid.c_str(); bool canFail = false; if (gridname[0] == '@') { canFail = true; gridname++; } auto gridSet = HorizontalShiftGridSet::open(ctx, gridname); if (!gridSet) { if (!canFail) { if (proj_context_errno(ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) { proj_context_errno_set( ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } return {}; } proj_context_errno_set(ctx, 0); // don't treat as a persistent error } else { list.emplace_back(std::move(gridSet)); } } return list; } /**********************************************/ ListOfHGrids pj_hgrid_init(PJ *P, const char *gridkey) { /********************************************** Initizalize and populate list of horizontal grids. Takes a PJ-object and the plus-parameter name that is used in the proj-string to specify the grids to load, e.g. "+grids". The + should be left out here. Returns the number of loaded grids. ***********************************************/ std::string key("s"); key += gridkey; const char *grids = pj_param(P->ctx, P->params, key.c_str()).s; if (grids == nullptr) return {}; return getListOfGridSets(P->ctx, grids); } // --------------------------------------------------------------------------- typedef struct { int32_t lam, phi; } ILP; // Apply bilinear interpolation for horizontal shift grids static PJ_LP pj_hgrid_interpolate(PJ_LP t, const HorizontalShiftGrid *grid, bool compensateNTConvention) { PJ_LP val, frct; ILP indx; int in; const auto &extent = grid->extentAndRes(); t.lam /= extent.resX; indx.lam = std::isnan(t.lam) ? 0 : (int32_t)lround(floor(t.lam)); t.phi /= extent.resY; indx.phi = std::isnan(t.phi) ? 0 : (int32_t)lround(floor(t.phi)); frct.lam = t.lam - indx.lam; frct.phi = t.phi - indx.phi; val.lam = val.phi = HUGE_VAL; if (indx.lam < 0) { if (indx.lam == -1 && frct.lam > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) { ++indx.lam; frct.lam = 0.; } else return val; } else if ((in = indx.lam + 1) >= grid->width()) { if (in == grid->width() && frct.lam < 10 * REL_TOLERANCE_HGRIDSHIFT) { --indx.lam; frct.lam = 1.; } else return val; } if (indx.phi < 0) { if (indx.phi == -1 && frct.phi > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) { ++indx.phi; frct.phi = 0.; } else return val; } else if ((in = indx.phi + 1) >= grid->height()) { if (in == grid->height() && frct.phi < 10 * REL_TOLERANCE_HGRIDSHIFT) { --indx.phi; frct.phi = 1.; } else return val; } float f00Long = 0, f00Lat = 0; float f10Long = 0, f10Lat = 0; float f01Long = 0, f01Lat = 0; float f11Long = 0, f11Lat = 0; if (!grid->valueAt(indx.lam, indx.phi, compensateNTConvention, f00Long, f00Lat) || !grid->valueAt(indx.lam + 1, indx.phi, compensateNTConvention, f10Long, f10Lat) || !grid->valueAt(indx.lam, indx.phi + 1, compensateNTConvention, f01Long, f01Lat) || !grid->valueAt(indx.lam + 1, indx.phi + 1, compensateNTConvention, f11Long, f11Lat)) { return val; } double m10 = frct.lam; double m11 = m10; double m01 = 1. - frct.lam; double m00 = m01; m11 *= frct.phi; m01 *= frct.phi; frct.phi = 1. - frct.phi; m00 *= frct.phi; m10 *= frct.phi; val.lam = m00 * f00Long + m10 * f10Long + m01 * f01Long + m11 * f11Long; val.phi = m00 * f00Lat + m10 * f10Lat + m01 * f01Lat + m11 * f11Lat; return val; } // --------------------------------------------------------------------------- #define MAX_ITERATIONS 10 #define TOL 1e-12 static PJ_LP pj_hgrid_apply_internal(PJ_CONTEXT *ctx, PJ_LP in, PJ_DIRECTION direction, const HorizontalShiftGrid *grid, HorizontalShiftGridSet *gridset, const ListOfHGrids &grids, bool &shouldRetry) { PJ_LP t, tb, del, dif; int i = MAX_ITERATIONS; const double toltol = TOL * TOL; shouldRetry = false; if (in.lam == HUGE_VAL) return in; /* normalize input to ll origin */ tb = in; const auto *extent = &(grid->extentAndRes()); const double epsilon = (extent->resX + extent->resY) * REL_TOLERANCE_HGRIDSHIFT; tb.lam -= extent->west; if (tb.lam + epsilon < 0) tb.lam += 2 * M_PI; else if (tb.lam - epsilon > extent->east - extent->west) tb.lam -= 2 * M_PI; tb.phi -= extent->south; t = pj_hgrid_interpolate(tb, grid, true); if (grid->hasChanged()) { shouldRetry = gridset->reopen(ctx); return t; } if (t.lam == HUGE_VAL) return t; if (direction == PJ_FWD) { in.lam += t.lam; in.phi += t.phi; return in; } t.lam = tb.lam - t.lam; t.phi = tb.phi - t.phi; do { del = pj_hgrid_interpolate(t, grid, true); if (grid->hasChanged()) { shouldRetry = gridset->reopen(ctx); return t; } /* We can possibly go outside of the initial guessed grid, so try */ /* to fetch a new grid into which iterate... */ if (del.lam == HUGE_VAL) { PJ_LP lp; lp.lam = t.lam + extent->west; lp.phi = t.phi + extent->south; auto newGrid = findGrid(grids, lp, gridset); if (newGrid == nullptr || newGrid == grid || newGrid->isNullGrid()) break; pj_log(ctx, PJ_LOG_TRACE, "Switching from grid %s to grid %s", grid->name().c_str(), newGrid->name().c_str()); grid = newGrid; extent = &(grid->extentAndRes()); t.lam = lp.lam - extent->west; t.phi = lp.phi - extent->south; tb = in; tb.lam -= extent->west; if (tb.lam + epsilon < 0) tb.lam += 2 * M_PI; else if (tb.lam - epsilon > extent->east - extent->west) tb.lam -= 2 * M_PI; tb.phi -= extent->south; dif.lam = std::numeric_limits::max(); dif.phi = std::numeric_limits::max(); continue; } dif.lam = t.lam + del.lam - tb.lam; dif.phi = t.phi + del.phi - tb.phi; t.lam -= dif.lam; t.phi -= dif.phi; } while (--i && (dif.lam * dif.lam + dif.phi * dif.phi > toltol)); /* prob. slightly faster than hypot() */ if (i == 0) { pj_log(ctx, PJ_LOG_TRACE, "Inverse grid shift iterator failed to converge.\n"); proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); t.lam = t.phi = HUGE_VAL; return t; } /* and again: pj_log and ctx->errno */ if (del.lam == HUGE_VAL) { pj_log(ctx, PJ_LOG_TRACE, "Inverse grid shift iteration failed, presumably at " "grid edge.\nUsing first approximation.\n"); } in.lam = adjlon(t.lam + extent->west); in.phi = t.phi + extent->south; return in; } // --------------------------------------------------------------------------- PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp, PJ_DIRECTION direction) { PJ_LP out; out.lam = HUGE_VAL; out.phi = HUGE_VAL; while (true) { HorizontalShiftGridSet *gridset = nullptr; const auto grid = findGrid(grids, lp, gridset); if (!grid) { proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } if (grid->isNullGrid()) { return lp; } bool shouldRetry = false; out = pj_hgrid_apply_internal(ctx, lp, direction, grid, gridset, grids, shouldRetry); if (!shouldRetry) { break; } } if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } /********************************************/ /* proj_hgrid_value() */ /* */ /* Return coordinate offset in grid */ /********************************************/ PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) { PJ_LP out = proj_coord_error().lp; HorizontalShiftGridSet *gridset = nullptr; const auto grid = findGrid(grids, lp, gridset); if (!grid) { proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } /* normalize input to ll origin */ const auto &extent = grid->extentAndRes(); if (!extent.isGeographic) { pj_log(P->ctx, PJ_LOG_ERROR, _("Can only handle grids referenced in a geographic CRS")); proj_context_errno_set(P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return out; } const double epsilon = (extent.resX + extent.resY) * REL_TOLERANCE_HGRIDSHIFT; lp.lam -= extent.west; if (lp.lam + epsilon < 0) lp.lam += 2 * M_PI; else if (lp.lam - epsilon > extent.east - extent.west) lp.lam -= 2 * M_PI; lp.phi -= extent.south; out = pj_hgrid_interpolate(lp, grid, false); if (grid->hasChanged()) { if (gridset->reopen(P->ctx)) { return pj_hgrid_value(P, grids, lp); } out.lam = HUGE_VAL; out.phi = HUGE_VAL; } if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) { proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); } return out; } // --------------------------------------------------------------------------- static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids, const PJ_LP &input, const double vmultiplier) { /* do not deal with NaN coordinates */ /* cppcheck-suppress duplicateExpression */ if (std::isnan(input.phi) || std::isnan(input.lam)) { return HUGE_VAL; } VerticalShiftGridSet *curGridset = nullptr; const VerticalShiftGrid *grid = nullptr; for (const auto &gridset : grids) { grid = gridset->gridAt(input.lam, input.phi); if (grid) { curGridset = gridset.get(); break; } } if (!grid) { proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return HUGE_VAL; } if (grid->isNullGrid()) { return 0; } const auto &extent = grid->extentAndRes(); if (!extent.isGeographic) { pj_log(ctx, PJ_LOG_ERROR, _("Can only handle grids referenced in a geographic CRS")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return HUGE_VAL; } /* Interpolation of a location within the grid */ double grid_x = (input.lam - extent.west) * extent.invResX; if (input.lam < extent.west) { if (extent.fullWorldLongitude()) { // The first fmod goes to ]-lim, lim[ range // So we add lim again to be in ]0, 2*lim[ and fmod again grid_x = fmod(fmod(grid_x + grid->width(), grid->width()) + grid->width(), grid->width()); } else { grid_x = (input.lam + 2 * M_PI - extent.west) * extent.invResX; } } else if (input.lam > extent.east) { if (extent.fullWorldLongitude()) { // The first fmod goes to ]-lim, lim[ range // So we add lim again to be in ]0, 2*lim[ and fmod again grid_x = fmod(fmod(grid_x + grid->width(), grid->width()) + grid->width(), grid->width()); } else { grid_x = (input.lam - 2 * M_PI - extent.west) * extent.invResX; } } double grid_y = (input.phi - extent.south) * extent.invResY; int grid_ix = static_cast(lround(floor(grid_x))); if (!(grid_ix >= 0 && grid_ix < grid->width())) { // in the unlikely case we end up here... pj_log(ctx, PJ_LOG_ERROR, _("grid_ix not in grid")); proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return HUGE_VAL; } int grid_iy = static_cast(lround(floor(grid_y))); assert(grid_iy >= 0 && grid_iy < grid->height()); grid_x -= grid_ix; grid_y -= grid_iy; int grid_ix2 = grid_ix + 1; if (grid_ix2 >= grid->width()) { if (extent.fullWorldLongitude()) { grid_ix2 = 0; } else { grid_ix2 = grid->width() - 1; } } int grid_iy2 = grid_iy + 1; if (grid_iy2 >= grid->height()) grid_iy2 = grid->height() - 1; float value_a = 0; float value_b = 0; float value_c = 0; float value_d = 0; bool error = (!grid->valueAt(grid_ix, grid_iy, value_a) || !grid->valueAt(grid_ix2, grid_iy, value_b) || !grid->valueAt(grid_ix, grid_iy2, value_c) || !grid->valueAt(grid_ix2, grid_iy2, value_d)); if (grid->hasChanged()) { if (curGridset->reopen(ctx)) { return read_vgrid_value(ctx, grids, input, vmultiplier); } error = true; } if (error) { return HUGE_VAL; } double value = 0.0; const double grid_x_y = grid_x * grid_y; const bool a_valid = !grid->isNodata(value_a, vmultiplier); const bool b_valid = !grid->isNodata(value_b, vmultiplier); const bool c_valid = !grid->isNodata(value_c, vmultiplier); const bool d_valid = !grid->isNodata(value_d, vmultiplier); const int countValid = static_cast(a_valid) + static_cast(b_valid) + static_cast(c_valid) + static_cast(d_valid); if (countValid == 4) { { double weight = 1.0 - grid_x - grid_y + grid_x_y; value = value_a * weight; } { double weight = grid_x - grid_x_y; value += value_b * weight; } { double weight = grid_y - grid_x_y; value += value_c * weight; } { double weight = grid_x_y; value += value_d * weight; } } else if (countValid == 0) { proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA); value = HUGE_VAL; } else { double total_weight = 0.0; if (a_valid) { double weight = 1.0 - grid_x - grid_y + grid_x_y; value = value_a * weight; total_weight = weight; } if (b_valid) { double weight = grid_x - grid_x_y; value += value_b * weight; total_weight += weight; } if (c_valid) { double weight = grid_y - grid_x_y; value += value_c * weight; total_weight += weight; } if (d_valid) { double weight = grid_x_y; value += value_d * weight; total_weight += weight; } value /= total_weight; } return value * vmultiplier; } /**********************************************/ ListOfVGrids pj_vgrid_init(PJ *P, const char *gridkey) { /********************************************** Initizalize and populate gridlist. Takes a PJ-object and the plus-parameter name that is used in the proj-string to specify the grids to load, e.g. "+grids". The + should be left out here. Returns the number of loaded grids. ***********************************************/ std::string key("s"); key += gridkey; const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s; if (gridnames == nullptr) return {}; auto listOfGridNames = internal::split(std::string(gridnames), ','); ListOfVGrids grids; for (const auto &gridnameStr : listOfGridNames) { const char *gridname = gridnameStr.c_str(); bool canFail = false; if (gridname[0] == '@') { canFail = true; gridname++; } auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname); if (!gridSet) { if (!canFail) { if (proj_context_errno(P->ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) { proj_context_errno_set( P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } return {}; } proj_context_errno_set(P->ctx, 0); // don't treat as a persistent error } else { grids.emplace_back(std::move(gridSet)); } } return grids; } /***********************************************/ double pj_vgrid_value(PJ *P, const ListOfVGrids &grids, PJ_LP lp, double vmultiplier) { /*********************************************** Read grid value at position lp in grids loaded with proj_grid_init. Returns the grid value of the given coordinate. ************************************************/ double value; value = read_vgrid_value(P->ctx, grids, lp, vmultiplier); if (pj_log_active(P->ctx, PJ_LOG_TRACE)) { proj_log_trace(P, "proj_vgrid_value: (%f, %f) = %f", lp.lam * RAD_TO_DEG, lp.phi * RAD_TO_DEG, value); } return value; } // --------------------------------------------------------------------------- const GenericShiftGrid *pj_find_generic_grid(const ListOfGenericGrids &grids, const PJ_LP &input, GenericShiftGridSet *&gridSetOut) { for (const auto &gridset : grids) { auto grid = gridset->gridAt(input.lam, input.phi); if (grid) { gridSetOut = gridset.get(); return grid; } } return nullptr; } // --------------------------------------------------------------------------- // Used by +proj=deformation and +proj=xyzgridshift to do bilinear interpolation // on 3 sample values per node. bool pj_bilinear_interpolation_three_samples( PJ_CONTEXT *ctx, const GenericShiftGrid *grid, const PJ_LP &lp, int idx1, int idx2, int idx3, double &v1, double &v2, double &v3, bool &must_retry) { must_retry = false; if (grid->isNullGrid()) { v1 = 0.0; v2 = 0.0; v3 = 0.0; return true; } const auto &extent = grid->extentAndRes(); if (!extent.isGeographic) { pj_log(ctx, PJ_LOG_ERROR, "Can only handle grids referenced in a geographic CRS"); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); return false; } // From a input location lp, determine the grid cell into which it falls, // by identifying the lower-left x,y of it (ix, iy), and the upper-right // (ix2, iy2) double grid_x = (lp.lam - extent.west) * extent.invResX; // Special case for grids with world extent, and dealing with wrap-around if (lp.lam < extent.west) { grid_x = (lp.lam + 2 * M_PI - extent.west) * extent.invResX; } else if (lp.lam > extent.east) { grid_x = (lp.lam - 2 * M_PI - extent.west) * extent.invResX; } double grid_y = (lp.phi - extent.south) * extent.invResY; int ix = static_cast(grid_x); int iy = static_cast(grid_y); int ix2 = std::min(ix + 1, grid->width() - 1); int iy2 = std::min(iy + 1, grid->height() - 1); float dx1 = 0.0f, dy1 = 0.0f, dz1 = 0.0f; float dx2 = 0.0f, dy2 = 0.0f, dz2 = 0.0f; float dx3 = 0.0f, dy3 = 0.0f, dz3 = 0.0f; float dx4 = 0.0f, dy4 = 0.0f, dz4 = 0.0f; bool error = (!grid->valueAt(ix, iy, idx1, dx1) || !grid->valueAt(ix, iy, idx2, dy1) || !grid->valueAt(ix, iy, idx3, dz1) || !grid->valueAt(ix2, iy, idx1, dx2) || !grid->valueAt(ix2, iy, idx2, dy2) || !grid->valueAt(ix2, iy, idx3, dz2) || !grid->valueAt(ix, iy2, idx1, dx3) || !grid->valueAt(ix, iy2, idx2, dy3) || !grid->valueAt(ix, iy2, idx3, dz3) || !grid->valueAt(ix2, iy2, idx1, dx4) || !grid->valueAt(ix2, iy2, idx2, dy4) || !grid->valueAt(ix2, iy2, idx3, dz4)); if (grid->hasChanged()) { must_retry = true; return false; } if (error) { return false; } // Bilinear interpolation double frct_lam = grid_x - ix; double frct_phi = grid_y - iy; double m10 = frct_lam; double m11 = m10; double m01 = 1. - frct_lam; double m00 = m01; m11 *= frct_phi; m01 *= frct_phi; frct_phi = 1. - frct_phi; m00 *= frct_phi; m10 *= frct_phi; v1 = m00 * dx1 + m10 * dx2 + m01 * dx3 + m11 * dx4; v2 = m00 * dy1 + m10 * dy2 + m01 * dy3 + m11 * dy4; v3 = m00 * dz1 + m10 * dz2 + m01 * dz3 + m11 * dz4; return true; } NS_PROJ_END /*****************************************************************************/ PJ_GRID_INFO proj_grid_info(const char *gridname) { /****************************************************************************** Information about a named datum grid. Returns PJ_GRID_INFO struct. ******************************************************************************/ PJ_GRID_INFO grinfo; /*PJ_CONTEXT *ctx = proj_context_create(); */ PJ_CONTEXT *ctx = pj_get_default_ctx(); memset(&grinfo, 0, sizeof(PJ_GRID_INFO)); const auto fillGridInfo = [&grinfo, ctx, gridname](const NS_PROJ::Grid &grid, const std::string &format) { const auto &extent = grid.extentAndRes(); /* name of grid */ strncpy(grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1); /* full path of grid */ if (!pj_find_file(ctx, gridname, grinfo.filename, sizeof(grinfo.filename) - 1)) { // Can happen when using a remote grid grinfo.filename[0] = 0; } /* grid format */ strncpy(grinfo.format, format.c_str(), sizeof(grinfo.format) - 1); /* grid size */ grinfo.n_lon = grid.width(); grinfo.n_lat = grid.height(); /* cell size */ grinfo.cs_lon = extent.resX; grinfo.cs_lat = extent.resY; /* bounds of grid */ grinfo.lowerleft.lam = extent.west; grinfo.lowerleft.phi = extent.south; grinfo.upperright.lam = extent.east; grinfo.upperright.phi = extent.north; }; { const auto gridSet = NS_PROJ::VerticalShiftGridSet::open(ctx, gridname); if (gridSet) { const auto &grids = gridSet->grids(); if (!grids.empty()) { const auto &grid = grids.front(); fillGridInfo(*grid, gridSet->format()); return grinfo; } } } { const auto gridSet = NS_PROJ::HorizontalShiftGridSet::open(ctx, gridname); if (gridSet) { const auto &grids = gridSet->grids(); if (!grids.empty()) { const auto &grid = grids.front(); fillGridInfo(*grid, gridSet->format()); return grinfo; } } } strcpy(grinfo.format, "missing"); return grinfo; } /*****************************************************************************/ PJ_INIT_INFO proj_init_info(const char *initname) { /****************************************************************************** Information about a named init file. Maximum length of initname is 64. Returns PJ_INIT_INFO struct. If the init file is not found all members of the return struct are set to the empty string. If the init file is found, but the metadata is missing, the value is set to "Unknown". ******************************************************************************/ int file_found; char param[80], key[74]; paralist *start, *next; PJ_INIT_INFO ininfo; PJ_CONTEXT *ctx = pj_get_default_ctx(); memset(&ininfo, 0, sizeof(PJ_INIT_INFO)); file_found = pj_find_file(ctx, initname, ininfo.filename, sizeof(ininfo.filename)); if (!file_found || strlen(initname) > 64) { if (strcmp(initname, "epsg") == 0 || strcmp(initname, "EPSG") == 0) { const char *val; proj_context_errno_set(ctx, 0); strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1); strcpy(ininfo.origin, "EPSG"); val = proj_context_get_database_metadata(ctx, "EPSG.VERSION"); if (val) { strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); } val = proj_context_get_database_metadata(ctx, "EPSG.DATE"); if (val) { strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); } return ininfo; } if (strcmp(initname, "IGNF") == 0) { const char *val; proj_context_errno_set(ctx, 0); strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1); strcpy(ininfo.origin, "IGNF"); val = proj_context_get_database_metadata(ctx, "IGNF.VERSION"); if (val) { strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); } val = proj_context_get_database_metadata(ctx, "IGNF.DATE"); if (val) { strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); } return ininfo; } return ininfo; } /* The initial memset (0) makes strncpy safe here */ strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1); strcpy(ininfo.origin, "Unknown"); strcpy(ininfo.version, "Unknown"); strcpy(ininfo.lastupdate, "Unknown"); strncpy(key, initname, 64); /* make room for ":metadata\0" at the end */ key[64] = 0; memcpy(key + strlen(key), ":metadata", 9 + 1); strcpy(param, "+init="); /* The +strlen(param) avoids a cppcheck false positive warning */ strncat(param + strlen(param), key, sizeof(param) - 1 - strlen(param)); start = pj_mkparam(param); pj_expand_init(ctx, start); if (pj_param(ctx, start, "tversion").i) strncpy(ininfo.version, pj_param(ctx, start, "sversion").s, sizeof(ininfo.version) - 1); if (pj_param(ctx, start, "torigin").i) strncpy(ininfo.origin, pj_param(ctx, start, "sorigin").s, sizeof(ininfo.origin) - 1); if (pj_param(ctx, start, "tlastupdate").i) strncpy(ininfo.lastupdate, pj_param(ctx, start, "slastupdate").s, sizeof(ininfo.lastupdate) - 1); for (; start; start = next) { next = start->next; free(start); } return ininfo; } proj-9.8.1/src/wkt1_generated_parser.h000664 001750 001750 00000005514 15166171715 017700 0ustar00eveneven000000 000000 /* A Bison parser, made by GNU Bison 3.5.1. */ /* Bison interface for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* Undocumented macros, especially those whose name start with YY_, are private implementation details. Do not rely on them. */ #ifndef YY_PJ_WKT1_WKT1_GENERATED_PARSER_H_INCLUDED # define YY_PJ_WKT1_WKT1_GENERATED_PARSER_H_INCLUDED /* Debug traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif #if YYDEBUG extern int pj_wkt1_debug; #endif /* Token type. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { END = 0, T_PARAM_MT = 258, T_CONCAT_MT = 259, T_INVERSE_MT = 260, T_PASSTHROUGH_MT = 261, T_PROJCS = 262, T_PROJECTION = 263, T_GEOGCS = 264, T_DATUM = 265, T_SPHEROID = 266, T_PRIMEM = 267, T_UNIT = 268, T_LINUNIT = 269, T_GEOCCS = 270, T_AUTHORITY = 271, T_VERT_CS = 272, T_VERTCS = 273, T_VERT_DATUM = 274, T_VDATUM = 275, T_COMPD_CS = 276, T_AXIS = 277, T_TOWGS84 = 278, T_FITTED_CS = 279, T_LOCAL_CS = 280, T_LOCAL_DATUM = 281, T_PARAMETER = 282, T_EXTENSION = 283, T_STRING = 284, T_NUMBER = 285, T_IDENTIFIER = 286 }; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef int YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif int pj_wkt1_parse (pj_wkt1_parse_context *context); #endif /* !YY_PJ_WKT1_WKT1_GENERATED_PARSER_H_INCLUDED */ proj-9.8.1/src/malloc.cpp000664 001750 001750 00000020242 15166171715 015215 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Memory management for proj.4. * This version includes an implementation of generic destructors, * for memory deallocation for the large majority of PJ-objects * that do not allocate anything else than the PJ-object itself, * and its associated opaque object - i.e. no additional malloc'ed * memory inside the opaque object. * * Author: Gerald I. Evenden (Original proj.4 author), * Frank Warmerdam (2000) pj_malloc? * Thomas Knudsen (2016) - freeup/dealloc parts * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * Copyright (c) 2016, Thomas Knudsen / SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif /* allocate and deallocate memory */ /* These routines are used so that applications can readily replace ** projection system memory allocation/deallocation call with custom ** application procedures. */ #include #include #include #include #include #include "proj/internal/io_internal.hpp" #include "filemanager.hpp" #include "grids.hpp" #include "proj.h" #include "proj_internal.h" using namespace NS_PROJ; /**********************************************************************/ char *pj_strdup(const char *str) /**********************************************************************/ { size_t len = strlen(str) + 1; char *dup = static_cast(malloc(len)); if (dup) memcpy(dup, str, len); return dup; } /*****************************************************************************/ void *free_params(PJ_CONTEXT *ctx, paralist *start, int errlev) { /***************************************************************************** Companion to pj_default_destructor (below). Deallocates a linked list of "+proj=xxx" initialization parameters. Also called from pj_init_ctx when encountering errors before the PJ proper is allocated. ******************************************************************************/ paralist *t, *n; for (t = start; t; t = n) { n = t->next; free(t); } proj_context_errno_set(ctx, errlev); return (void *)nullptr; } /************************************************************************/ /* proj_destroy() */ /* */ /* This is the application callable entry point for destroying */ /* a projection definition. It does work generic to all */ /* projection types, and then calls the projection specific */ /* free function, P->destructor(), to do local work. */ /* In most cases P->destructor()==pj_default_destructor. */ /************************************************************************/ PJ *proj_destroy(PJ *P) { if (nullptr == P || !P->destructor) return nullptr; /* free projection parameters - all the hard work is done by */ /* pj_default_destructor, which is supposed */ /* to be called as the last step of the local destructor */ /* pointed to by P->destructor. In most cases, */ /* pj_default_destructor actually *is* what is pointed to */ P->destructor(P, proj_errno(P)); return nullptr; } /*****************************************************************************/ // cppcheck-suppress uninitMemberVar PJconsts::PJconsts() : destructor(pj_default_destructor) {} /*****************************************************************************/ /*****************************************************************************/ /* void PJconsts::copyStateFrom(const PJconsts& other) */ /*****************************************************************************/ void PJconsts::copyStateFrom(const PJconsts &other) { over = other.over; errorIfBestTransformationNotAvailable = other.errorIfBestTransformationNotAvailable; warnIfBestTransformationNotAvailable = other.warnIfBestTransformationNotAvailable; skipNonInstantiable = other.skipNonInstantiable; } /*****************************************************************************/ PJ *pj_new() { /*****************************************************************************/ return new (std::nothrow) PJ(); } /*****************************************************************************/ PJ *pj_default_destructor(PJ *P, int errlev) { /* Destructor */ /***************************************************************************** Does memory deallocation for "plain" PJ objects, i.e. that vast majority of PJs where the opaque object does not contain any additionally allocated memory below the P->opaque level. ******************************************************************************/ /* Even if P==0, we set the errlev on pj_error and the default context */ /* Note that both, in the multithreaded case, may then contain undefined */ /* values. This is expected behavior. For MT have one ctx per thread */ if (0 != errlev) proj_context_errno_set(pj_get_ctx(P), errlev); if (nullptr == P) return nullptr; free(P->def_size); free(P->def_shape); free(P->def_spherification); free(P->def_ellps); delete static_cast(P->hgrids_legacy); delete static_cast(P->vgrids_legacy); /* We used to call free( P->catalog ), but this will leak */ /* memory. The safe way to clear catalog and grid is to call */ /* pj_gc_unloadall(pj_get_default_ctx()); and freeate_grids(); */ /* TODO: we should probably have a public pj_cleanup() method to do all */ /* that */ /* free the interface to Charles Karney's geodesic library */ free(P->geod); /* free parameter list elements */ free_params(pj_get_ctx(P), P->params, errlev); free(P->def_full); /* free the cs2cs emulation elements */ proj_destroy(P->axisswap); proj_destroy(P->helmert); proj_destroy(P->cart); proj_destroy(P->cart_wgs84); proj_destroy(P->hgridshift); proj_destroy(P->vgridshift); proj_destroy(P->cached_op_for_proj_factors); free(static_cast(P->opaque)); delete P; return nullptr; } /*****************************************************************************/ void proj_cleanup() { /*****************************************************************************/ // Close the database context of the default PJ_CONTEXT auto ctx = pj_get_default_ctx(); ctx->iniFileLoaded = false; auto cpp_context = ctx->cpp_context; if (cpp_context) { cpp_context->closeDb(); } pj_clear_initcache(); FileManager::clearMemoryCache(); pj_clear_hgridshift_knowngrids_cache(); pj_clear_vgridshift_knowngrids_cache(); pj_clear_gridshift_knowngrids_cache(); pj_clear_sqlite_cache(); } proj-9.8.1/src/crs_to_crs.cpp000664 001750 001750 00000075311 15166171715 016115 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Implements proj_create_crs_to_crs() and the like * * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2018-2024, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" #include #include #include #include "proj/internal/internal.hpp" using namespace NS_PROJ::internal; /** Adds a " +type=crs" suffix to a PROJ string (if it is a PROJ string) */ std::string pj_add_type_crs_if_needed(const std::string &str) { std::string ret(str); if ((starts_with(str, "proj=") || starts_with(str, "+proj=") || starts_with(str, "+init=") || starts_with(str, "+title=")) && str.find("type=crs") == std::string::npos) { ret += " +type=crs"; } return ret; } /*****************************************************************************/ static void reproject_bbox(PJ *pjGeogToCrs, double west_lon, double south_lat, double east_lon, double north_lat, double &minx, double &miny, double &maxx, double &maxy) { /*****************************************************************************/ minx = -std::numeric_limits::max(); miny = -std::numeric_limits::max(); maxx = std::numeric_limits::max(); maxy = std::numeric_limits::max(); if (!(west_lon == -180.0 && east_lon == 180.0 && south_lat == -90.0 && north_lat == 90.0)) { minx = -minx; miny = -miny; maxx = -maxx; maxy = -maxy; constexpr int N_STEPS = 20; constexpr int N_STEPS_P1 = N_STEPS + 1; constexpr int XY_SIZE = N_STEPS_P1 * 4; std::vector x(XY_SIZE); std::vector y(XY_SIZE); const double step_lon = (east_lon - west_lon) / N_STEPS; const double step_lat = (north_lat - south_lat) / N_STEPS; for (int j = 0; j <= N_STEPS; j++) { x[j] = west_lon + j * step_lon; y[j] = south_lat; x[N_STEPS_P1 + j] = x[j]; y[N_STEPS_P1 + j] = north_lat; x[N_STEPS_P1 * 2 + j] = west_lon; y[N_STEPS_P1 * 2 + j] = south_lat + j * step_lat; x[N_STEPS_P1 * 3 + j] = east_lon; y[N_STEPS_P1 * 3 + j] = y[N_STEPS_P1 * 2 + j]; } proj_trans_generic(pjGeogToCrs, PJ_FWD, &x[0], sizeof(double), XY_SIZE, &y[0], sizeof(double), XY_SIZE, nullptr, 0, 0, nullptr, 0, 0); for (int j = 0; j < XY_SIZE; j++) { if (x[j] != HUGE_VAL && y[j] != HUGE_VAL) { minx = std::min(minx, x[j]); miny = std::min(miny, y[j]); maxx = std::max(maxx, x[j]); maxy = std::max(maxy, y[j]); } } } } /*****************************************************************************/ static PJ *add_coord_op_to_list( int idxInOriginalList, PJ *op, double west_lon, double south_lat, double east_lon, double north_lat, PJ *pjGeogToSrc, PJ *pjGeogToDst, const PJ *pjSrcGeocentricToLonLat, const PJ *pjDstGeocentricToLonLat, const char *areaName, std::vector &altCoordOps) { /*****************************************************************************/ double minxSrc; double minySrc; double maxxSrc; double maxySrc; double minxDst; double minyDst; double maxxDst; double maxyDst; double w = west_lon / 180 * M_PI; double s = south_lat / 180 * M_PI; double e = east_lon / 180 * M_PI; double n = north_lat / 180 * M_PI; if (w > e) { e += 2 * M_PI; } // Integrate cos(lat) between south_lat and north_lat const double pseudoArea = (e - w) * (std::sin(n) - std::sin(s)); if (pjSrcGeocentricToLonLat) { minxSrc = west_lon; minySrc = south_lat; maxxSrc = east_lon; maxySrc = north_lat; } else { reproject_bbox(pjGeogToSrc, west_lon, south_lat, east_lon, north_lat, minxSrc, minySrc, maxxSrc, maxySrc); } if (pjDstGeocentricToLonLat) { minxDst = west_lon; minyDst = south_lat; maxxDst = east_lon; maxyDst = north_lat; } else { reproject_bbox(pjGeogToDst, west_lon, south_lat, east_lon, north_lat, minxDst, minyDst, maxxDst, maxyDst); } if (minxSrc <= maxxSrc && minxDst <= maxxDst) { const char *c_name = proj_get_name(op); std::string name(c_name ? c_name : ""); const double accuracy = proj_coordoperation_get_accuracy(op->ctx, op); altCoordOps.emplace_back( idxInOriginalList, minxSrc, minySrc, maxxSrc, maxySrc, minxDst, minyDst, maxxDst, maxyDst, op, name, accuracy, pseudoArea, areaName, pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat); op = nullptr; } return op; } namespace { struct ObjectKeeper { PJ *m_obj = nullptr; explicit ObjectKeeper(PJ *obj) : m_obj(obj) {} ~ObjectKeeper() { proj_destroy(m_obj); } ObjectKeeper(const ObjectKeeper &) = delete; ObjectKeeper &operator=(const ObjectKeeper &) = delete; }; } // namespace /*****************************************************************************/ static PJ *create_operation_to_geog_crs(PJ_CONTEXT *ctx, const PJ *crs) { /*****************************************************************************/ std::unique_ptr keeper; if (proj_get_type(crs) == PJ_TYPE_COORDINATE_METADATA) { auto tmp = proj_get_source_crs(ctx, crs); assert(tmp); keeper.reset(new ObjectKeeper(tmp)); crs = tmp; } (void)keeper; // Create a geographic 2D long-lat degrees CRS that is related to the // CRS auto geodetic_crs = proj_crs_get_geodetic_crs(ctx, crs); if (!geodetic_crs) { proj_context_log_debug(ctx, "Cannot find geodetic CRS matching CRS"); return nullptr; } auto geodetic_crs_type = proj_get_type(geodetic_crs); if (geodetic_crs_type == PJ_TYPE_GEOCENTRIC_CRS || geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_2D_CRS || geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_3D_CRS) { auto datum = proj_crs_get_datum_forced(ctx, geodetic_crs); assert(datum); auto cs = proj_create_ellipsoidal_2D_cs( ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); auto ellps = proj_get_ellipsoid(ctx, datum); proj_destroy(datum); double semi_major_metre = 0; double inv_flattening = 0; proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr, nullptr, &inv_flattening); // It is critical to set the prime meridian to 0 auto temp = proj_create_geographic_crs( ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps), semi_major_metre, inv_flattening, "Reference prime meridian", 0, nullptr, 0, cs); proj_destroy(ellps); proj_destroy(cs); proj_destroy(geodetic_crs); geodetic_crs = temp; geodetic_crs_type = proj_get_type(geodetic_crs); } if (geodetic_crs_type != PJ_TYPE_GEOGRAPHIC_2D_CRS) { // Shouldn't happen proj_context_log_debug(ctx, "Cannot find geographic CRS matching CRS"); proj_destroy(geodetic_crs); return nullptr; } // Create the transformation from this geographic 2D CRS to the source CRS auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr); proj_operation_factory_context_set_spatial_criterion( ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); auto target_crs_2D = proj_crs_demote_to_2D(ctx, nullptr, crs); auto op_list_to_geodetic = proj_create_operations(ctx, geodetic_crs, target_crs_2D, operation_ctx); proj_destroy(target_crs_2D); proj_operation_factory_context_destroy(operation_ctx); proj_destroy(geodetic_crs); const int nOpCount = op_list_to_geodetic == nullptr ? 0 : proj_list_get_count(op_list_to_geodetic); if (nOpCount == 0) { proj_context_log_debug( ctx, "Cannot compute transformation from geographic CRS to CRS"); proj_list_destroy(op_list_to_geodetic); return nullptr; } PJ *opGeogToCrs = nullptr; // Use in priority operations *without* grids for (int i = 0; i < nOpCount; i++) { auto op = proj_list_get(ctx, op_list_to_geodetic, i); assert(op); if (proj_coordoperation_get_grid_used_count(ctx, op) == 0) { opGeogToCrs = op; break; } proj_destroy(op); } if (opGeogToCrs == nullptr) { opGeogToCrs = proj_list_get(ctx, op_list_to_geodetic, 0); assert(opGeogToCrs); } proj_list_destroy(op_list_to_geodetic); return opGeogToCrs; } /*****************************************************************************/ static PJ *create_operation_geocentric_crs_to_geog_crs(PJ_CONTEXT *ctx, const PJ *geocentric_crs) /*****************************************************************************/ { assert(proj_get_type(geocentric_crs) == PJ_TYPE_GEOCENTRIC_CRS); auto datum = proj_crs_get_datum_forced(ctx, geocentric_crs); assert(datum); auto cs = proj_create_ellipsoidal_2D_cs(ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); auto ellps = proj_get_ellipsoid(ctx, datum); proj_destroy(datum); double semi_major_metre = 0; double inv_flattening = 0; proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr, nullptr, &inv_flattening); // It is critical to set the prime meridian to 0 auto lon_lat_crs = proj_create_geographic_crs( ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps), semi_major_metre, inv_flattening, "Reference prime meridian", 0, nullptr, 0, cs); proj_destroy(ellps); proj_destroy(cs); // Create the transformation from this geocentric CRS to the lon-lat one auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr); proj_operation_factory_context_set_spatial_criterion( ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); auto op_list = proj_create_operations(ctx, geocentric_crs, lon_lat_crs, operation_ctx); proj_operation_factory_context_destroy(operation_ctx); proj_destroy(lon_lat_crs); const int nOpCount = op_list == nullptr ? 0 : proj_list_get_count(op_list); if (nOpCount != 1) { proj_context_log_debug(ctx, "Cannot compute transformation from " "geocentric CRS to geographic CRS"); proj_list_destroy(op_list); return nullptr; } auto op = proj_list_get(ctx, op_list, 0); assert(op); proj_list_destroy(op_list); return op; } /*****************************************************************************/ PJ *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *source_crs, const char *target_crs, PJ_AREA *area) { /****************************************************************************** Create a transformation pipeline between two known coordinate reference systems. See docs/source/development/reference/functions.rst ******************************************************************************/ if (!ctx) { ctx = pj_get_default_ctx(); } PJ *src; PJ *dst; try { std::string source_crs_modified(pj_add_type_crs_if_needed(source_crs)); std::string target_crs_modified(pj_add_type_crs_if_needed(target_crs)); src = proj_create(ctx, source_crs_modified.c_str()); if (!src) { proj_context_log_debug(ctx, "Cannot instantiate source_crs"); return nullptr; } dst = proj_create(ctx, target_crs_modified.c_str()); if (!dst) { proj_context_log_debug(ctx, "Cannot instantiate target_crs"); proj_destroy(src); return nullptr; } } catch (const std::exception &) { return nullptr; } auto ret = proj_create_crs_to_crs_from_pj(ctx, src, dst, area, nullptr); proj_destroy(src); proj_destroy(dst); return ret; } /*****************************************************************************/ std::vector pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, PJ_OBJ_LIST *op_list) /*****************************************************************************/ { PJ *pjGeogToSrc = nullptr; PJ *pjSrcGeocentricToLonLat = nullptr; if (proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS) { pjSrcGeocentricToLonLat = create_operation_geocentric_crs_to_geog_crs(ctx, source_crs); if (!pjSrcGeocentricToLonLat) { // shouldn't happen return {}; } } else { pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs); if (!pjGeogToSrc) { proj_context_log_debug( ctx, "Cannot create transformation from geographic " "CRS of source CRS to source CRS"); return {}; } } PJ *pjGeogToDst = nullptr; PJ *pjDstGeocentricToLonLat = nullptr; if (proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS) { pjDstGeocentricToLonLat = create_operation_geocentric_crs_to_geog_crs(ctx, target_crs); if (!pjDstGeocentricToLonLat) { // shouldn't happen proj_destroy(pjSrcGeocentricToLonLat); proj_destroy(pjGeogToSrc); return {}; } } else { pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs); if (!pjGeogToDst) { proj_context_log_debug( ctx, "Cannot create transformation from geographic " "CRS of target CRS to target CRS"); proj_destroy(pjSrcGeocentricToLonLat); proj_destroy(pjGeogToSrc); return {}; } } try { std::vector preparedOpList; // Iterate over source->target candidate transformations and reproject // their long-lat bounding box into the source CRS. const auto op_count = proj_list_get_count(op_list); for (int i = 0; i < op_count; i++) { auto op = proj_list_get(ctx, op_list, i); assert(op); double west_lon = 0.0; double south_lat = 0.0; double east_lon = 0.0; double north_lat = 0.0; const char *areaName = nullptr; if (!proj_get_area_of_use(ctx, op, &west_lon, &south_lat, &east_lon, &north_lat, &areaName)) { west_lon = -180; south_lat = -90; east_lon = 180; north_lat = 90; } if (west_lon <= east_lon) { op = add_coord_op_to_list( i, op, west_lon, south_lat, east_lon, north_lat, pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat, areaName, preparedOpList); } else { auto op_clone = proj_clone(ctx, op); op = add_coord_op_to_list( i, op, west_lon, south_lat, 180, north_lat, pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat, areaName, preparedOpList); op_clone = add_coord_op_to_list( i, op_clone, -180, south_lat, east_lon, north_lat, pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat, areaName, preparedOpList); proj_destroy(op_clone); } proj_destroy(op); } proj_destroy(pjGeogToSrc); proj_destroy(pjGeogToDst); proj_destroy(pjSrcGeocentricToLonLat); proj_destroy(pjDstGeocentricToLonLat); return preparedOpList; } catch (const std::exception &) { proj_destroy(pjGeogToSrc); proj_destroy(pjGeogToDst); proj_destroy(pjSrcGeocentricToLonLat); proj_destroy(pjDstGeocentricToLonLat); return {}; } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static const char *getOptionValue(const char *option, const char *keyWithEqual) noexcept { if (ci_starts_with(option, keyWithEqual)) { return option + strlen(keyWithEqual); } return nullptr; } //! @endcond /*****************************************************************************/ PJ *proj_create_crs_to_crs_from_pj(PJ_CONTEXT *ctx, const PJ *source_crs, const PJ *target_crs, PJ_AREA *area, const char *const *options) { /****************************************************************************** Create a transformation pipeline between two known coordinate reference systems. See docs/source/development/reference/functions.rst ******************************************************************************/ if (!ctx) { ctx = pj_get_default_ctx(); } pj_load_ini( ctx); // to set ctx->errorIfBestTransformationNotAvailableDefault const char *authority = nullptr; double accuracy = -1; bool allowBallparkTransformations = true; bool forceOver = false; bool warnIfBestTransformationNotAvailable = ctx->warnIfBestTransformationNotAvailableDefault; bool errorIfBestTransformationNotAvailable = ctx->errorIfBestTransformationNotAvailableDefault; for (auto iter = options; iter && iter[0]; ++iter) { const char *value; if ((value = getOptionValue(*iter, "AUTHORITY="))) { authority = value; } else if ((value = getOptionValue(*iter, "ACCURACY="))) { accuracy = pj_atof(value); } else if ((value = getOptionValue(*iter, "ALLOW_BALLPARK="))) { if (ci_equal(value, "yes")) allowBallparkTransformations = true; else if (ci_equal(value, "no")) allowBallparkTransformations = false; else { ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, "Invalid value for ALLOW_BALLPARK option."); return nullptr; } } else if ((value = getOptionValue(*iter, "ONLY_BEST="))) { warnIfBestTransformationNotAvailable = false; if (ci_equal(value, "yes")) errorIfBestTransformationNotAvailable = true; else if (ci_equal(value, "no")) errorIfBestTransformationNotAvailable = false; else { ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, "Invalid value for ONLY_BEST option."); return nullptr; } } else if ((value = getOptionValue(*iter, "FORCE_OVER="))) { if (ci_equal(value, "yes")) { forceOver = true; } } else { std::string msg("Unknown option :"); msg += *iter; ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, msg.c_str()); return nullptr; } } auto operation_ctx = proj_create_operation_factory_context(ctx, authority); if (!operation_ctx) { return nullptr; } proj_operation_factory_context_set_allow_ballpark_transformations( ctx, operation_ctx, allowBallparkTransformations); if (accuracy >= 0) { proj_operation_factory_context_set_desired_accuracy(ctx, operation_ctx, accuracy); } if (area && area->bbox_set) { proj_operation_factory_context_set_area_of_interest( ctx, operation_ctx, area->west_lon_degree, area->south_lat_degree, area->east_lon_degree, area->north_lat_degree); if (!area->name.empty()) { proj_operation_factory_context_set_area_of_interest_name( ctx, operation_ctx, area->name.c_str()); } } proj_operation_factory_context_set_spatial_criterion( ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( ctx, operation_ctx, (errorIfBestTransformationNotAvailable || warnIfBestTransformationNotAvailable || proj_context_is_network_enabled(ctx)) ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE : PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); auto op_list = proj_create_operations(ctx, source_crs, target_crs, operation_ctx); proj_operation_factory_context_destroy(operation_ctx); if (!op_list) { return nullptr; } auto op_count = proj_list_get_count(op_list); if (op_count == 0) { proj_list_destroy(op_list); proj_context_log_debug(ctx, "No operation found matching criteria"); return nullptr; } ctx->forceOver = forceOver; const int old_debug_level = ctx->debug_level; if (errorIfBestTransformationNotAvailable || warnIfBestTransformationNotAvailable) ctx->debug_level = PJ_LOG_NONE; PJ *P = proj_list_get(ctx, op_list, 0); ctx->debug_level = old_debug_level; assert(P); if (P != nullptr) { P->errorIfBestTransformationNotAvailable = errorIfBestTransformationNotAvailable; P->warnIfBestTransformationNotAvailable = warnIfBestTransformationNotAvailable; P->skipNonInstantiable = warnIfBestTransformationNotAvailable; } const bool mayNeedToReRunWithDiscardMissing = (errorIfBestTransformationNotAvailable || warnIfBestTransformationNotAvailable) && !proj_context_is_network_enabled(ctx); int singleOpIsInstanciable = -1; if (P != nullptr && op_count == 1 && mayNeedToReRunWithDiscardMissing) { singleOpIsInstanciable = proj_coordoperation_is_instantiable(ctx, P); } const auto backup_errno = proj_context_errno(ctx); if (P == nullptr || (op_count == 1 && (!mayNeedToReRunWithDiscardMissing || errorIfBestTransformationNotAvailable || singleOpIsInstanciable == static_cast(true)))) { proj_list_destroy(op_list); ctx->forceOver = false; if (P != nullptr && (errorIfBestTransformationNotAvailable || warnIfBestTransformationNotAvailable)) { if (singleOpIsInstanciable < 0) { singleOpIsInstanciable = proj_coordoperation_is_instantiable(ctx, P); } if (!singleOpIsInstanciable) { pj_warn_about_missing_grid(P); if (errorIfBestTransformationNotAvailable) { proj_destroy(P); return nullptr; } } } if (P != nullptr) { P->over = forceOver; } return P; } else if (op_count == 1 && mayNeedToReRunWithDiscardMissing && !singleOpIsInstanciable) { pj_warn_about_missing_grid(P); } if (errorIfBestTransformationNotAvailable || warnIfBestTransformationNotAvailable) ctx->debug_level = PJ_LOG_NONE; auto preparedOpList = pj_create_prepared_operations(ctx, source_crs, target_crs, op_list); ctx->debug_level = old_debug_level; ctx->forceOver = false; proj_list_destroy(op_list); if (preparedOpList.empty()) { proj_destroy(P); return nullptr; } bool foundInstanciableAndNonBallpark = false; for (auto &op : preparedOpList) { op.pj->over = forceOver; op.pj->errorIfBestTransformationNotAvailable = errorIfBestTransformationNotAvailable; op.pj->warnIfBestTransformationNotAvailable = warnIfBestTransformationNotAvailable; if (mayNeedToReRunWithDiscardMissing && !foundInstanciableAndNonBallpark) { if (!proj_coordoperation_has_ballpark_transformation(op.pj->ctx, op.pj) && op.isInstantiable()) { foundInstanciableAndNonBallpark = true; } } } if (mayNeedToReRunWithDiscardMissing && !foundInstanciableAndNonBallpark) { // Re-run proj_create_operations with // PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID // Can happen for example for NAD27->NAD83 transformation when we // have no grid and thus have fallback to Helmert transformation and // a WGS84 intermediate. operation_ctx = proj_create_operation_factory_context(ctx, authority); if (operation_ctx) { proj_operation_factory_context_set_allow_ballpark_transformations( ctx, operation_ctx, allowBallparkTransformations); if (accuracy >= 0) { proj_operation_factory_context_set_desired_accuracy( ctx, operation_ctx, accuracy); } if (area && area->bbox_set) { proj_operation_factory_context_set_area_of_interest( ctx, operation_ctx, area->west_lon_degree, area->south_lat_degree, area->east_lon_degree, area->north_lat_degree); if (!area->name.empty()) { proj_operation_factory_context_set_area_of_interest_name( ctx, operation_ctx, area->name.c_str()); } } proj_operation_factory_context_set_spatial_criterion( ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); proj_operation_factory_context_set_grid_availability_use( ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); op_list = proj_create_operations(ctx, source_crs, target_crs, operation_ctx); proj_operation_factory_context_destroy(operation_ctx); if (op_list) { ctx->forceOver = forceOver; ctx->debug_level = PJ_LOG_NONE; auto preparedOpList2 = pj_create_prepared_operations( ctx, source_crs, target_crs, op_list); ctx->debug_level = old_debug_level; ctx->forceOver = false; proj_list_destroy(op_list); if (!preparedOpList2.empty()) { // Append new lists of operations to previous one std::vector newOpList; for (auto &&op : preparedOpList) { if (!proj_coordoperation_has_ballpark_transformation( op.pj->ctx, op.pj)) { newOpList.emplace_back(std::move(op)); } } for (auto &&op : preparedOpList2) { op.pj->over = forceOver; op.pj->errorIfBestTransformationNotAvailable = errorIfBestTransformationNotAvailable; op.pj->warnIfBestTransformationNotAvailable = warnIfBestTransformationNotAvailable; newOpList.emplace_back(std::move(op)); } preparedOpList = std::move(newOpList); } else { // We get there in "cs2cs --only-best --no-ballpark // EPSG:4326+3855 EPSG:4979" use case, where the initial // create_operations returned 1 operation, and the retry // with // PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID // returned 0. if (op_count == 1 && errorIfBestTransformationNotAvailable) { if (singleOpIsInstanciable < 0) { singleOpIsInstanciable = proj_coordoperation_is_instantiable(ctx, P); } if (!singleOpIsInstanciable) { proj_destroy(P); proj_context_errno_set(ctx, backup_errno); return nullptr; } } } } } } // If there's finally juste a single result, return it directly if (preparedOpList.size() == 1) { auto retP = preparedOpList[0].pj; preparedOpList[0].pj = nullptr; proj_destroy(P); return retP; } P->alternativeCoordinateOperations = std::move(preparedOpList); // The returned P is rather dummy P->descr = "Set of coordinate operations"; P->over = forceOver; P->iso_obj = nullptr; P->fwd = nullptr; P->inv = nullptr; P->fwd3d = nullptr; P->inv3d = nullptr; P->fwd4d = nullptr; P->inv4d = nullptr; return P; } proj-9.8.1/src/generate_wkt_parser.cmake000664 001750 001750 00000002437 15166171715 020305 0ustar00eveneven000000 000000 message("Generating ${OUT_FILE}") execute_process(COMMAND "bison" "-v" "--no-lines" "-d" "-p" "${PREFIX}" "-o${OUT_FILE}" "${IN_FILE}" RESULT_VARIABLE STATUS) if(STATUS AND NOT STATUS EQUAL 0) message(FATAL_ERROR "bison failed") endif() # Post processing of the generated file # All those replacements are to please MSVC file(READ ${OUT_FILE} CONTENTS) string(REPLACE "yyerrorlab:" "#if 0\nyyerrorlab:" CONTENTS "${CONTENTS}") string(REPLACE "yyerrlab1:" "#endif\nyyerrlab1:" CONTENTS "${CONTENTS}") string(REPLACE "for (yylen = 0; yystr[yylen]; yylen++)" "for (yylen = 0; yystr && yystr[yylen]; yylen++)" CONTENTS "${CONTENTS}") string(REPLACE "return yystpcpy (yyres, yystr) - yyres;" "return (YYPTRDIFF_T)(yystpcpy (yyres, yystr) - yyres);" CONTENTS "${CONTENTS}") string(REPLACE "YYPTRDIFF_T yysize = yyssp - yyss + 1;" "YYPTRDIFF_T yysize = (YYPTRDIFF_T)(yyssp - yyss + 1);" CONTENTS "${CONTENTS}") string(REPLACE "#define yynerrs pj_wkt1_nerrs" "/* #define yynerrs pj_wkt1_nerrs */" CONTENTS "${CONTENTS}") string(REPLACE "int yynerrs;" "/* int yynerrs; */" CONTENTS "${CONTENTS}") string(REPLACE "yynerrs = 0;" "/* yynerrs = 0; */" CONTENTS "${CONTENTS}") string(REPLACE "++yynerrs;" "/* ++yynerrs; */" CONTENTS "${CONTENTS}") file(WRITE "${OUT_FILE}" "${CONTENTS}") proj-9.8.1/src/geodesic.h000664 001750 001750 00000116321 15166171715 015201 0ustar00eveneven000000 000000 /** * \file geodesic.h * \brief API for the geodesic routines in C * * These routines are a simple transcription of the corresponding C++ classes * in GeographicLib. The * "class data" is represented by the structs geod_geodesic, geod_geodesicline, * geod_polygon and pointers to these objects are passed as initial arguments * to the member functions. Most of the internal comments have been retained. * However, in the process of transcription some documentation has been lost * and the documentation for the C++ classes, GeographicLib::Geodesic, * GeographicLib::GeodesicLine, and GeographicLib::PolygonAreaT, should be * consulted. The C++ code remains the "reference implementation". Think * twice about restructuring the internals of the C code since this may make * porting fixes from the C++ code more difficult. * * Copyright (c) Charles Karney (2012-2022) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #if !defined(GEODESIC_H) #define GEODESIC_H 1 /** * The major version of the geodesic library. (This tracks the version of * GeographicLib.) **********************************************************************/ #define GEODESIC_VERSION_MAJOR 2 /** * The minor version of the geodesic library. (This tracks the version of * GeographicLib.) **********************************************************************/ #define GEODESIC_VERSION_MINOR 2 /** * The patch level of the geodesic library. (This tracks the version of * GeographicLib.) **********************************************************************/ #define GEODESIC_VERSION_PATCH 0 /** * Pack the version components into a single integer. Users should not rely on * this particular packing of the components of the version number; see the * documentation for ::GEODESIC_VERSION, below. **********************************************************************/ #define GEODESIC_VERSION_NUM(a,b,c) ((((a) * 10000 + (b)) * 100) + (c)) /** * The version of the geodesic library as a single integer, packed as MMmmmmpp * where MM is the major version, mmmm is the minor version, and pp is the * patch level. Users should not rely on this particular packing of the * components of the version number. Instead they should use a test such as * @code{.c} #if GEODESIC_VERSION >= GEODESIC_VERSION_NUM(1,40,0) ... #endif * @endcode **********************************************************************/ #define GEODESIC_VERSION \ GEODESIC_VERSION_NUM(GEODESIC_VERSION_MAJOR, \ GEODESIC_VERSION_MINOR, \ GEODESIC_VERSION_PATCH) #if !defined(GEOD_DLL) #if defined(_MSC_VER) && defined(PROJ_MSVC_DLL_EXPORT) #define GEOD_DLL __declspec(dllexport) #elif defined(__GNUC__) #define GEOD_DLL __attribute__ ((visibility("default"))) #else #define GEOD_DLL #endif #endif #if defined(PROJ_RENAME_SYMBOLS) #include "proj_symbol_rename.h" #endif #if defined(__cplusplus) extern "C" { #endif /** * The struct containing information about the ellipsoid. This must be * initialized by geod_init() before use. **********************************************************************/ struct geod_geodesic { double a; /**< the equatorial radius */ double f; /**< the flattening */ /**< @cond SKIP */ double f1, e2, ep2, n, b, c2, etol2; double A3x[6], C3x[15], C4x[21]; /**< @endcond */ }; /** * The struct containing information about a single geodesic. This must be * initialized by geod_lineinit(), geod_directline(), geod_gendirectline(), * or geod_inverseline() before use. **********************************************************************/ struct geod_geodesicline { double lat1; /**< the starting latitude */ double lon1; /**< the starting longitude */ double azi1; /**< the starting azimuth */ double a; /**< the equatorial radius */ double f; /**< the flattening */ double salp1; /**< sine of \e azi1 */ double calp1; /**< cosine of \e azi1 */ double a13; /**< arc length to reference point */ double s13; /**< distance to reference point */ /**< @cond SKIP */ double b, c2, f1, salp0, calp0, k2, ssig1, csig1, dn1, stau1, ctau1, somg1, comg1, A1m1, A2m1, A3c, B11, B21, B31, A4, B41; double C1a[6+1], C1pa[6+1], C2a[6+1], C3a[6], C4a[6]; /**< @endcond */ unsigned caps; /**< the capabilities */ }; /** * The struct for accumulating information about a geodesic polygon. This is * used for computing the perimeter and area of a polygon. This must be * initialized by geod_polygon_init() before use. **********************************************************************/ struct geod_polygon { double lat; /**< the current latitude */ double lon; /**< the current longitude */ /**< @cond SKIP */ double lat0; double lon0; double A[2]; double P[2]; int polyline; int crossings; /**< @endcond */ unsigned num; /**< the number of points so far */ }; /** * Initialize a geod_geodesic object. * * @param[out] g a pointer to the object to be initialized. * @param[in] a the equatorial radius (meters). * @param[in] f the flattening. **********************************************************************/ void GEOD_DLL geod_init(struct geod_geodesic* g, double a, double f); /** * Solve the direct geodesic problem. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] azi1 azimuth at point 1 (degrees). * @param[in] s12 distance from point 1 to point 2 (meters); it can be * negative. * @param[out] plat2 pointer to the latitude of point 2 (degrees). * @param[out] plon2 pointer to the longitude of point 2 (degrees). * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). * * \e g must have been initialized with a call to geod_init(). \e lat1 * should be in the range [−90°, 90°]. The values of \e lon2 * and \e azi2 returned are in the range [−180°, 180°]. Any of * the "return" arguments \e plat2, etc., may be replaced by 0, if you do not * need some quantities computed. * * If either point is at a pole, the azimuth is defined by keeping the * longitude fixed, writing \e lat = ±(90° − ε), and * taking the limit ε → 0+. An arc length greater that 180° * signifies a geodesic which is not a shortest path. (For a prolate * ellipsoid, an additional condition is necessary for a shortest path: the * longitudinal extent must not exceed of 180°.) * * Example, determine the point 10000 km NE of JFK: @code{.c} struct geod_geodesic g; double lat, lon; geod_init(&g, 6378137, 1/298.257223563); geod_direct(&g, 40.64, -73.78, 45.0, 10e6, &lat, &lon, 0); printf("%.5f %.5f\n", lat, lon); @endcode **********************************************************************/ void GEOD_DLL geod_direct(const struct geod_geodesic* g, double lat1, double lon1, double azi1, double s12, double* plat2, double* plon2, double* pazi2); /** * The general direct geodesic problem. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] azi1 azimuth at point 1 (degrees). * @param[in] flags bitor'ed combination of ::geod_flags; \e flags & * ::GEOD_ARCMODE determines the meaning of \e s12_a12 and \e flags & * ::GEOD_LONG_UNROLL "unrolls" \e lon2. * @param[in] s12_a12 if \e flags & ::GEOD_ARCMODE is 0, this is the distance * from point 1 to point 2 (meters); otherwise it is the arc length * from point 1 to point 2 (degrees); it can be negative. * @param[out] plat2 pointer to the latitude of point 2 (degrees). * @param[out] plon2 pointer to the longitude of point 2 (degrees). * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). * @param[out] ps12 pointer to the distance from point 1 to point 2 * (meters). * @param[out] pm12 pointer to the reduced length of geodesic (meters). * @param[out] pM12 pointer to the geodesic scale of point 2 relative to * point 1 (dimensionless). * @param[out] pM21 pointer to the geodesic scale of point 1 relative to * point 2 (dimensionless). * @param[out] pS12 pointer to the area under the geodesic * (meters2). * @return \e a12 arc length from point 1 to point 2 (degrees). * * \e g must have been initialized with a call to geod_init(). \e lat1 * should be in the range [−90°, 90°]. The function value \e * a12 equals \e s12_a12 if \e flags & ::GEOD_ARCMODE. Any of the "return" * arguments, \e plat2, etc., may be replaced by 0, if you do not need some * quantities computed. * * With \e flags & ::GEOD_LONG_UNROLL bit set, the longitude is "unrolled" so * that the quantity \e lon2 − \e lon1 indicates how many times and in * what sense the geodesic encircles the ellipsoid. **********************************************************************/ double GEOD_DLL geod_gendirect(const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned flags, double s12_a12, double* plat2, double* plon2, double* pazi2, double* ps12, double* pm12, double* pM12, double* pM21, double* pS12); /** * Solve the inverse geodesic problem. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] lat2 latitude of point 2 (degrees). * @param[in] lon2 longitude of point 2 (degrees). * @param[out] ps12 pointer to the distance from point 1 to point 2 * (meters). * @param[out] pazi1 pointer to the azimuth at point 1 (degrees). * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). * * \e g must have been initialized with a call to geod_init(). \e lat1 and * \e lat2 should be in the range [−90°, 90°]. The values of * \e azi1 and \e azi2 returned are in the range [−180°, 180°]. * Any of the "return" arguments, \e ps12, etc., may be replaced by 0, if you * do not need some quantities computed. * * If either point is at a pole, the azimuth is defined by keeping the * longitude fixed, writing \e lat = ±(90° − ε), and * taking the limit ε → 0+. * * The solution to the inverse problem is found using Newton's method. If * this fails to converge (this is very unlikely in geodetic applications * but does occur for very eccentric ellipsoids), then the bisection method * is used to refine the solution. * * Example, determine the distance between JFK and Singapore Changi Airport: @code{.c} struct geod_geodesic g; double s12; geod_init(&g, 6378137, 1/298.257223563); geod_inverse(&g, 40.64, -73.78, 1.36, 103.99, &s12, 0, 0); printf("%.3f\n", s12); @endcode **********************************************************************/ void GEOD_DLL geod_inverse(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* pazi1, double* pazi2); /** * The general inverse geodesic calculation. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] lat2 latitude of point 2 (degrees). * @param[in] lon2 longitude of point 2 (degrees). * @param[out] ps12 pointer to the distance from point 1 to point 2 * (meters). * @param[out] pazi1 pointer to the azimuth at point 1 (degrees). * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). * @param[out] pm12 pointer to the reduced length of geodesic (meters). * @param[out] pM12 pointer to the geodesic scale of point 2 relative to * point 1 (dimensionless). * @param[out] pM21 pointer to the geodesic scale of point 1 relative to * point 2 (dimensionless). * @param[out] pS12 pointer to the area under the geodesic * (meters2). * @return \e a12 arc length from point 1 to point 2 (degrees). * * \e g must have been initialized with a call to geod_init(). \e lat1 and * \e lat2 should be in the range [−90°, 90°]. Any of the * "return" arguments \e ps12, etc., may be replaced by 0, if you do not need * some quantities computed. **********************************************************************/ double GEOD_DLL geod_geninverse(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* pazi1, double* pazi2, double* pm12, double* pM12, double* pM21, double* pS12); /** * Initialize a geod_geodesicline object. * * @param[out] l a pointer to the object to be initialized. * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] azi1 azimuth at point 1 (degrees). * @param[in] caps bitor'ed combination of ::geod_mask values specifying the * capabilities the geod_geodesicline object should possess, i.e., which * quantities can be returned in calls to geod_position() and * geod_genposition(). * * \e g must have been initialized with a call to geod_init(). \e lat1 * should be in the range [−90°, 90°]. * * The ::geod_mask values are: * - \e caps |= ::GEOD_LATITUDE for the latitude \e lat2; this is * added automatically, * - \e caps |= ::GEOD_LONGITUDE for the latitude \e lon2, * - \e caps |= ::GEOD_AZIMUTH for the latitude \e azi2; this is * added automatically, * - \e caps |= ::GEOD_DISTANCE for the distance \e s12, * - \e caps |= ::GEOD_REDUCEDLENGTH for the reduced length \e m12, * - \e caps |= ::GEOD_GEODESICSCALE for the geodesic scales \e M12 * and \e M21, * - \e caps |= ::GEOD_AREA for the area \e S12, * - \e caps |= ::GEOD_DISTANCE_IN permits the length of the * geodesic to be given in terms of \e s12; without this capability the * length can only be specified in terms of arc length. * . * A value of \e caps = 0 is treated as ::GEOD_LATITUDE | ::GEOD_LONGITUDE | * ::GEOD_AZIMUTH | ::GEOD_DISTANCE_IN (to support the solution of the * "standard" direct problem). * * When initialized by this function, point 3 is undefined (l->s13 = l->a13 = * NaN). **********************************************************************/ void GEOD_DLL geod_lineinit(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned caps); /** * Initialize a geod_geodesicline object in terms of the direct geodesic * problem. * * @param[out] l a pointer to the object to be initialized. * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] azi1 azimuth at point 1 (degrees). * @param[in] s12 distance from point 1 to point 2 (meters); it can be * negative. * @param[in] caps bitor'ed combination of ::geod_mask values specifying the * capabilities the geod_geodesicline object should possess, i.e., which * quantities can be returned in calls to geod_position() and * geod_genposition(). * * This function sets point 3 of the geod_geodesicline to correspond to point * 2 of the direct geodesic problem. See geod_lineinit() for more * information. **********************************************************************/ void GEOD_DLL geod_directline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, double s12, unsigned caps); /** * Initialize a geod_geodesicline object in terms of the direct geodesic * problem specified in terms of either distance or arc length. * * @param[out] l a pointer to the object to be initialized. * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] azi1 azimuth at point 1 (degrees). * @param[in] flags either ::GEOD_NOFLAGS or ::GEOD_ARCMODE to determining * the meaning of the \e s12_a12. * @param[in] s12_a12 if \e flags = ::GEOD_NOFLAGS, this is the distance * from point 1 to point 2 (meters); if \e flags = ::GEOD_ARCMODE, it is * the arc length from point 1 to point 2 (degrees); it can be * negative. * @param[in] caps bitor'ed combination of ::geod_mask values specifying the * capabilities the geod_geodesicline object should possess, i.e., which * quantities can be returned in calls to geod_position() and * geod_genposition(). * * This function sets point 3 of the geod_geodesicline to correspond to point * 2 of the direct geodesic problem. See geod_lineinit() for more * information. **********************************************************************/ void GEOD_DLL geod_gendirectline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned flags, double s12_a12, unsigned caps); /** * Initialize a geod_geodesicline object in terms of the inverse geodesic * problem. * * @param[out] l a pointer to the object to be initialized. * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lat1 latitude of point 1 (degrees). * @param[in] lon1 longitude of point 1 (degrees). * @param[in] lat2 latitude of point 2 (degrees). * @param[in] lon2 longitude of point 2 (degrees). * @param[in] caps bitor'ed combination of ::geod_mask values specifying the * capabilities the geod_geodesicline object should possess, i.e., which * quantities can be returned in calls to geod_position() and * geod_genposition(). * * This function sets point 3 of the geod_geodesicline to correspond to point * 2 of the inverse geodesic problem. See geod_lineinit() for more * information. **********************************************************************/ void GEOD_DLL geod_inverseline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, unsigned caps); /** * Compute the position along a geod_geodesicline. * * @param[in] l a pointer to the geod_geodesicline object specifying the * geodesic line. * @param[in] s12 distance from point 1 to point 2 (meters); it can be * negative. * @param[out] plat2 pointer to the latitude of point 2 (degrees). * @param[out] plon2 pointer to the longitude of point 2 (degrees); requires * that \e l was initialized with \e caps |= ::GEOD_LONGITUDE. * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). * * \e l must have been initialized with a call, e.g., to geod_lineinit(), * with \e caps |= ::GEOD_DISTANCE_IN (or \e caps = 0). The values of \e * lon2 and \e azi2 returned are in the range [−180°, 180°]. * Any of the "return" arguments \e plat2, etc., may be replaced by 0, if you * do not need some quantities computed. * * Example, compute way points between JFK and Singapore Changi Airport * the "obvious" way using geod_direct(): @code{.c} struct geod_geodesic g; double s12, azi1, lat[101], lon[101]; int i; geod_init(&g, 6378137, 1/298.257223563); geod_inverse(&g, 40.64, -73.78, 1.36, 103.99, &s12, &azi1, 0); for (i = 0; i < 101; ++i) { geod_direct(&g, 40.64, -73.78, azi1, i * s12 * 0.01, lat + i, lon + i, 0); printf("%.5f %.5f\n", lat[i], lon[i]); } @endcode * A faster way using geod_position(): @code{.c} struct geod_geodesic g; struct geod_geodesicline l; double lat[101], lon[101]; int i; geod_init(&g, 6378137, 1/298.257223563); geod_inverseline(&l, &g, 40.64, -73.78, 1.36, 103.99, 0); for (i = 0; i <= 100; ++i) { geod_position(&l, i * l.s13 * 0.01, lat + i, lon + i, 0); printf("%.5f %.5f\n", lat[i], lon[i]); } @endcode **********************************************************************/ void GEOD_DLL geod_position(const struct geod_geodesicline* l, double s12, double* plat2, double* plon2, double* pazi2); /** * The general position function. * * @param[in] l a pointer to the geod_geodesicline object specifying the * geodesic line. * @param[in] flags bitor'ed combination of ::geod_flags; \e flags & * ::GEOD_ARCMODE determines the meaning of \e s12_a12 and \e flags & * ::GEOD_LONG_UNROLL "unrolls" \e lon2; if \e flags & ::GEOD_ARCMODE is 0, * then \e l must have been initialized with \e caps |= ::GEOD_DISTANCE_IN. * @param[in] s12_a12 if \e flags & ::GEOD_ARCMODE is 0, this is the * distance from point 1 to point 2 (meters); otherwise it is the * arc length from point 1 to point 2 (degrees); it can be * negative. * @param[out] plat2 pointer to the latitude of point 2 (degrees). * @param[out] plon2 pointer to the longitude of point 2 (degrees); requires * that \e l was initialized with \e caps |= ::GEOD_LONGITUDE. * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). * @param[out] ps12 pointer to the distance from point 1 to point 2 * (meters); requires that \e l was initialized with \e caps |= * ::GEOD_DISTANCE. * @param[out] pm12 pointer to the reduced length of geodesic (meters); * requires that \e l was initialized with \e caps |= ::GEOD_REDUCEDLENGTH. * @param[out] pM12 pointer to the geodesic scale of point 2 relative to * point 1 (dimensionless); requires that \e l was initialized with \e caps * |= ::GEOD_GEODESICSCALE. * @param[out] pM21 pointer to the geodesic scale of point 1 relative to * point 2 (dimensionless); requires that \e l was initialized with \e caps * |= ::GEOD_GEODESICSCALE. * @param[out] pS12 pointer to the area under the geodesic * (meters2); requires that \e l was initialized with \e caps |= * ::GEOD_AREA. * @return \e a12 arc length from point 1 to point 2 (degrees). * * \e l must have been initialized with a call to geod_lineinit() with \e * caps |= ::GEOD_DISTANCE_IN. The value \e azi2 returned is in the range * [−180°, 180°]. Any of the "return" arguments \e plat2, * etc., may be replaced by 0, if you do not need some quantities * computed. Requesting a value which \e l is not capable of computing * is not an error; the corresponding argument will not be altered. * * With \e flags & ::GEOD_LONG_UNROLL bit set, the longitude is "unrolled" so * that the quantity \e lon2 − \e lon1 indicates how many times and in * what sense the geodesic encircles the ellipsoid. * * Example, compute way points between JFK and Singapore Changi Airport using * geod_genposition(). In this example, the points are evenly spaced in arc * length (and so only approximately equally spaced in distance). This is * faster than using geod_position() and would be appropriate if drawing the * path on a map. @code{.c} struct geod_geodesic g; struct geod_geodesicline l; double lat[101], lon[101]; int i; geod_init(&g, 6378137, 1/298.257223563); geod_inverseline(&l, &g, 40.64, -73.78, 1.36, 103.99, GEOD_LATITUDE | GEOD_LONGITUDE); for (i = 0; i <= 100; ++i) { geod_genposition(&l, GEOD_ARCMODE, i * l.a13 * 0.01, lat + i, lon + i, 0, 0, 0, 0, 0, 0); printf("%.5f %.5f\n", lat[i], lon[i]); } @endcode **********************************************************************/ double GEOD_DLL geod_genposition(const struct geod_geodesicline* l, unsigned flags, double s12_a12, double* plat2, double* plon2, double* pazi2, double* ps12, double* pm12, double* pM12, double* pM21, double* pS12); /** * Specify position of point 3 in terms of distance. * * @param[in,out] l a pointer to the geod_geodesicline object. * @param[in] s13 the distance from point 1 to point 3 (meters); it * can be negative. * * This is only useful if the geod_geodesicline object has been constructed * with \e caps |= ::GEOD_DISTANCE_IN. **********************************************************************/ void GEOD_DLL geod_setdistance(struct geod_geodesicline* l, double s13); /** * Specify position of point 3 in terms of either distance or arc length. * * @param[in,out] l a pointer to the geod_geodesicline object. * @param[in] flags either ::GEOD_NOFLAGS or ::GEOD_ARCMODE to determining * the meaning of the \e s13_a13. * @param[in] s13_a13 if \e flags = ::GEOD_NOFLAGS, this is the distance * from point 1 to point 3 (meters); if \e flags = ::GEOD_ARCMODE, it is * the arc length from point 1 to point 3 (degrees); it can be * negative. * * If flags = ::GEOD_NOFLAGS, this calls geod_setdistance(). If flags = * ::GEOD_ARCMODE, the \e s13 is only set if the geod_geodesicline object has * been constructed with \e caps |= ::GEOD_DISTANCE. **********************************************************************/ void GEOD_DLL geod_gensetdistance(struct geod_geodesicline* l, unsigned flags, double s13_a13); /** * Initialize a geod_polygon object. * * @param[out] p a pointer to the object to be initialized. * @param[in] polylinep non-zero if a polyline instead of a polygon. * * If \e polylinep is zero, then the sequence of vertices and edges added by * geod_polygon_addpoint() and geod_polygon_addedge() define a polygon and * the perimeter and area are returned by geod_polygon_compute(). If \e * polylinep is non-zero, then the vertices and edges define a polyline and * only the perimeter is returned by geod_polygon_compute(). * * The area and perimeter are accumulated at two times the standard floating * point precision to guard against the loss of accuracy with many-sided * polygons. At any point you can ask for the perimeter and area so far. * * An example of the use of this function is given in the documentation for * geod_polygon_compute(). **********************************************************************/ void GEOD_DLL geod_polygon_init(struct geod_polygon* p, int polylinep); /** * Clear the polygon, allowing a new polygon to be started. * * @param[in,out] p a pointer to the object to be cleared. **********************************************************************/ void GEOD_DLL geod_polygon_clear(struct geod_polygon* p); /** * Add a point to the polygon or polyline. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in,out] p a pointer to the geod_polygon object specifying the * polygon. * @param[in] lat the latitude of the point (degrees). * @param[in] lon the longitude of the point (degrees). * * \e g and \e p must have been initialized with calls to geod_init() and * geod_polygon_init(), respectively. The same \e g must be used for all the * points and edges in a polygon. \e lat should be in the range * [−90°, 90°]. * * An example of the use of this function is given in the documentation for * geod_polygon_compute(). **********************************************************************/ void GEOD_DLL geod_polygon_addpoint(const struct geod_geodesic* g, struct geod_polygon* p, double lat, double lon); /** * Add an edge to the polygon or polyline. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in,out] p a pointer to the geod_polygon object specifying the * polygon. * @param[in] azi azimuth at current point (degrees). * @param[in] s distance from current point to next point (meters). * * \e g and \e p must have been initialized with calls to geod_init() and * geod_polygon_init(), respectively. The same \e g must be used for all the * points and edges in a polygon. This does nothing if no points have been * added yet. The \e lat and \e lon fields of \e p give the location of the * new vertex. **********************************************************************/ void GEOD_DLL geod_polygon_addedge(const struct geod_geodesic* g, struct geod_polygon* p, double azi, double s); /** * Return the results for a polygon. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] p a pointer to the geod_polygon object specifying the polygon. * @param[in] reverse if non-zero then clockwise (instead of * counter-clockwise) traversal counts as a positive area. * @param[in] sign if non-zero then return a signed result for the area if * the polygon is traversed in the "wrong" direction instead of returning * the area for the rest of the earth. * @param[out] pA pointer to the area of the polygon (meters2); * only set if \e polyline is non-zero in the call to geod_polygon_init(). * @param[out] pP pointer to the perimeter of the polygon or length of the * polyline (meters). * @return the number of points. * * The area and perimeter are accumulated at two times the standard floating * point precision to guard against the loss of accuracy with many-sided * polygons. Arbitrarily complex polygons are allowed. In the case of * self-intersecting polygons the area is accumulated "algebraically", e.g., * the areas of the 2 loops in a figure-8 polygon will partially cancel. * There's no need to "close" the polygon by repeating the first vertex. Set * \e pA or \e pP to zero, if you do not want the corresponding quantity * returned. * * More points can be added to the polygon after this call. * * Example, compute the perimeter and area of the geodesic triangle with * vertices (0°N,0°E), (0°N,90°E), (90°N,0°E). @code{.c} double A, P; int n; struct geod_geodesic g; struct geod_polygon p; geod_init(&g, 6378137, 1/298.257223563); geod_polygon_init(&p, 0); geod_polygon_addpoint(&g, &p, 0, 0); geod_polygon_addpoint(&g, &p, 0, 90); geod_polygon_addpoint(&g, &p, 90, 0); n = geod_polygon_compute(&g, &p, 0, 1, &A, &P); printf("%d %.8f %.3f\n", n, P, A); @endcode **********************************************************************/ unsigned GEOD_DLL geod_polygon_compute(const struct geod_geodesic* g, const struct geod_polygon* p, int reverse, int sign, double* pA, double* pP); /** * Return the results assuming a tentative final test point is added; * however, the data for the test point is not saved. This lets you report a * running result for the perimeter and area as the user moves the mouse * cursor. Ordinary floating point arithmetic is used to accumulate the data * for the test point; thus the area and perimeter returned are less accurate * than if geod_polygon_addpoint() and geod_polygon_compute() are used. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] p a pointer to the geod_polygon object specifying the polygon. * @param[in] lat the latitude of the test point (degrees). * @param[in] lon the longitude of the test point (degrees). * @param[in] reverse if non-zero then clockwise (instead of * counter-clockwise) traversal counts as a positive area. * @param[in] sign if non-zero then return a signed result for the area if * the polygon is traversed in the "wrong" direction instead of returning * the area for the rest of the earth. * @param[out] pA pointer to the area of the polygon (meters2); * only set if \e polyline is non-zero in the call to geod_polygon_init(). * @param[out] pP pointer to the perimeter of the polygon or length of the * polyline (meters). * @return the number of points. * * \e lat should be in the range [−90°, 90°]. **********************************************************************/ unsigned GEOD_DLL geod_polygon_testpoint(const struct geod_geodesic* g, const struct geod_polygon* p, double lat, double lon, int reverse, int sign, double* pA, double* pP); /** * Return the results assuming a tentative final test point is added via an * azimuth and distance; however, the data for the test point is not saved. * This lets you report a running result for the perimeter and area as the * user moves the mouse cursor. Ordinary floating point arithmetic is used * to accumulate the data for the test point; thus the area and perimeter * returned are less accurate than if geod_polygon_addedge() and * geod_polygon_compute() are used. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] p a pointer to the geod_polygon object specifying the polygon. * @param[in] azi azimuth at current point (degrees). * @param[in] s distance from current point to final test point (meters). * @param[in] reverse if non-zero then clockwise (instead of * counter-clockwise) traversal counts as a positive area. * @param[in] sign if non-zero then return a signed result for the area if * the polygon is traversed in the "wrong" direction instead of returning * the area for the rest of the earth. * @param[out] pA pointer to the area of the polygon (meters2); * only set if \e polyline is non-zero in the call to geod_polygon_init(). * @param[out] pP pointer to the perimeter of the polygon or length of the * polyline (meters). * @return the number of points. **********************************************************************/ unsigned GEOD_DLL geod_polygon_testedge(const struct geod_geodesic* g, const struct geod_polygon* p, double azi, double s, int reverse, int sign, double* pA, double* pP); /** * A simple interface for computing the area of a geodesic polygon. * * @param[in] g a pointer to the geod_geodesic object specifying the * ellipsoid. * @param[in] lats an array of latitudes of the polygon vertices (degrees). * @param[in] lons an array of longitudes of the polygon vertices (degrees). * @param[in] n the number of vertices. * @param[out] pA pointer to the area of the polygon (meters2). * @param[out] pP pointer to the perimeter of the polygon (meters). * * \e lats should be in the range [−90°, 90°]. * * Arbitrarily complex polygons are allowed. In the case self-intersecting * of polygons the area is accumulated "algebraically", e.g., the areas of * the 2 loops in a figure-8 polygon will partially cancel. There's no need * to "close" the polygon by repeating the first vertex. The area returned * is signed with counter-clockwise traversal being treated as positive. * * Example, compute the area of Antarctica: @code{.c} double lats[] = {-72.9, -71.9, -74.9, -74.3, -77.5, -77.4, -71.7, -65.9, -65.7, -66.6, -66.9, -69.8, -70.0, -71.0, -77.3, -77.9, -74.7}, lons[] = {-74, -102, -102, -131, -163, 163, 172, 140, 113, 88, 59, 25, -4, -14, -33, -46, -61}; struct geod_geodesic g; double A, P; geod_init(&g, 6378137, 1/298.257223563); geod_polygonarea(&g, lats, lons, (sizeof lats) / (sizeof lats[0]), &A, &P); printf("%.0f %.2f\n", A, P); @endcode **********************************************************************/ void GEOD_DLL geod_polygonarea(const struct geod_geodesic* g, double lats[], double lons[], int n, double* pA, double* pP); /** * mask values for the \e caps argument to geod_lineinit(). **********************************************************************/ enum geod_mask { GEOD_NONE = 0U, /**< Calculate nothing */ GEOD_LATITUDE = 1U<<7 | 0U, /**< Calculate latitude */ GEOD_LONGITUDE = 1U<<8 | 1U<<3, /**< Calculate longitude */ GEOD_AZIMUTH = 1U<<9 | 0U, /**< Calculate azimuth */ GEOD_DISTANCE = 1U<<10 | 1U<<0, /**< Calculate distance */ GEOD_DISTANCE_IN = 1U<<11 | 1U<<0 | 1U<<1,/**< Allow distance as input */ GEOD_REDUCEDLENGTH= 1U<<12 | 1U<<0 | 1U<<2,/**< Calculate reduced length */ GEOD_GEODESICSCALE= 1U<<13 | 1U<<0 | 1U<<2,/**< Calculate geodesic scale */ GEOD_AREA = 1U<<14 | 1U<<4, /**< Calculate reduced length */ GEOD_ALL = 0x7F80U| 0x1FU /**< Calculate everything */ }; /** * flag values for the \e flags argument to geod_gendirect() and * geod_genposition() **********************************************************************/ enum geod_flags { GEOD_NOFLAGS = 0U, /**< No flags */ GEOD_ARCMODE = 1U<<0, /**< Position given in terms of arc distance */ GEOD_LONG_UNROLL = 1U<<15 /**< Unroll the longitude */ }; #if defined(__cplusplus) } #endif #endif proj-9.8.1/src/conversions/000775 001750 001750 00000000000 15166171735 015614 5ustar00eveneven000000 000000 proj-9.8.1/src/conversions/topocentric.cpp000664 001750 001750 00000014610 15166171715 020651 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Convert between geocentric coordinates and topocentric (ENU) *coordinates * ****************************************************************************** * Copyright (c) 2020, Even Rouault * * 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 "proj_internal.h" #include #include PROJ_HEAD(topocentric, "Geocentric/Topocentric conversion"); // Notations and formulas taken from IOGP Publication 373-7-2 - // Geomatics Guidance Note number 7, part 2 - October 2020 namespace { // anonymous namespace struct pj_opaque { double X0; double Y0; double Z0; double sinphi0; double cosphi0; double sinlam0; double coslam0; }; } // anonymous namespace // Convert from geocentric to topocentric static void topocentric_fwd(PJ_COORD &coo, PJ *P) { struct pj_opaque *Q = static_cast(P->opaque); const double dX = coo.xyz.x - Q->X0; const double dY = coo.xyz.y - Q->Y0; const double dZ = coo.xyz.z - Q->Z0; coo.xyz.x = -dX * Q->sinlam0 + dY * Q->coslam0; coo.xyz.y = -dX * Q->sinphi0 * Q->coslam0 - dY * Q->sinphi0 * Q->sinlam0 + dZ * Q->cosphi0; coo.xyz.z = dX * Q->cosphi0 * Q->coslam0 + dY * Q->cosphi0 * Q->sinlam0 + dZ * Q->sinphi0; } // Convert from topocentric to geocentric static void topocentric_inv(PJ_COORD &coo, PJ *P) { struct pj_opaque *Q = static_cast(P->opaque); const double x = coo.xyz.x; const double y = coo.xyz.y; const double z = coo.xyz.z; coo.xyz.x = Q->X0 - x * Q->sinlam0 - y * Q->sinphi0 * Q->coslam0 + z * Q->cosphi0 * Q->coslam0; coo.xyz.y = Q->Y0 + x * Q->coslam0 - y * Q->sinphi0 * Q->sinlam0 + z * Q->cosphi0 * Q->sinlam0; coo.xyz.z = Q->Z0 + y * Q->cosphi0 + z * Q->sinphi0; } /*********************************************************************/ PJ *PJ_CONVERSION(topocentric, 1) { /*********************************************************************/ struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = static_cast(Q); // The topocentric origin can be specified either in geocentric coordinates // (X_0,Y_0,Z_0) or as geographic coordinates (lon_0,lat_0,h_0) // Checks: // - X_0 or lon_0 must be specified // - If X_0 is specified, the Y_0 and Z_0 must also be // - If lon_0 is specified, then lat_0 must also be // - If any of X_0, Y_0, Z_0 is specified, then any of lon_0,lat_0,h_0 must // not be, and vice versa. const auto hasX0 = pj_param_exists(P->params, "X_0"); const auto hasY0 = pj_param_exists(P->params, "Y_0"); const auto hasZ0 = pj_param_exists(P->params, "Z_0"); const auto hasLon0 = pj_param_exists(P->params, "lon_0"); const auto hasLat0 = pj_param_exists(P->params, "lat_0"); const auto hash0 = pj_param_exists(P->params, "h_0"); if (!hasX0 && !hasLon0) { proj_log_error(P, _("missing X_0 or lon_0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if ((hasX0 || hasY0 || hasZ0) && (hasLon0 || hasLat0 || hash0)) { proj_log_error( P, _("(X_0,Y_0,Z_0) and (lon_0,lat_0,h_0) are mutually exclusive")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); } if (hasX0 && (!hasY0 || !hasZ0)) { proj_log_error(P, _("missing Y_0 and/or Z_0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (hasLon0 && !hasLat0) // allow missing h_0 { proj_log_error(P, _("missing lat_0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } // Pass a dummy ellipsoid definition that will be overridden just afterwards PJ *cart = proj_create(P->ctx, "+proj=cart +a=1"); if (cart == nullptr) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to cart */ pj_inherit_ellipsoid_def(P, cart); if (hasX0) { Q->X0 = pj_param(P->ctx, P->params, "dX_0").f; Q->Y0 = pj_param(P->ctx, P->params, "dY_0").f; Q->Z0 = pj_param(P->ctx, P->params, "dZ_0").f; // Compute lam0, phi0 from X0,Y0,Z0 PJ_XYZ xyz; xyz.x = Q->X0; xyz.y = Q->Y0; xyz.z = Q->Z0; const auto lpz = pj_inv3d(xyz, cart); Q->sinphi0 = sin(lpz.phi); Q->cosphi0 = cos(lpz.phi); Q->sinlam0 = sin(lpz.lam); Q->coslam0 = cos(lpz.lam); } else { // Compute X0,Y0,Z0 from lam0, phi0, h0 PJ_LPZ lpz; lpz.lam = P->lam0; lpz.phi = P->phi0; lpz.z = pj_param(P->ctx, P->params, "dh_0").f; const auto xyz = pj_fwd3d(lpz, cart); Q->X0 = xyz.x; Q->Y0 = xyz.y; Q->Z0 = xyz.z; Q->sinphi0 = sin(P->phi0); Q->cosphi0 = cos(P->phi0); Q->sinlam0 = sin(P->lam0); Q->coslam0 = cos(P->lam0); } proj_destroy(cart); P->fwd4d = topocentric_fwd; P->inv4d = topocentric_inv; P->left = PJ_IO_UNITS_CARTESIAN; P->right = PJ_IO_UNITS_CARTESIAN; return P; } proj-9.8.1/src/conversions/set.cpp000664 001750 001750 00000003053 15166171715 017112 0ustar00eveneven000000 000000 #include "proj_internal.h" #include PROJ_HEAD(set, "Set coordinate value"); /* Projection specific elements for the PJ object */ namespace { // anonymous namespace struct Set { bool v1; bool v2; bool v3; bool v4; double v1_val; double v2_val; double v3_val; double v4_val; }; } // anonymous namespace static void set_fwd_inv(PJ_COORD &point, PJ *P) { struct Set *set = static_cast(P->opaque); if (set->v1) point.v[0] = set->v1_val; if (set->v2) point.v[1] = set->v2_val; if (set->v3) point.v[2] = set->v3_val; if (set->v4) point.v[3] = set->v4_val; } PJ *OPERATION(set, 0) { P->inv4d = set_fwd_inv; P->fwd4d = set_fwd_inv; auto set = static_cast(calloc(1, sizeof(struct Set))); P->opaque = set; if (nullptr == P->opaque) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if (pj_param_exists(P->params, "v_1")) { set->v1 = true; set->v1_val = pj_param(P->ctx, P->params, "dv_1").f; } if (pj_param_exists(P->params, "v_2")) { set->v2 = true; set->v2_val = pj_param(P->ctx, P->params, "dv_2").f; } if (pj_param_exists(P->params, "v_3")) { set->v3 = true; set->v3_val = pj_param(P->ctx, P->params, "dv_3").f; } if (pj_param_exists(P->params, "v_4")) { set->v4 = true; set->v4_val = pj_param(P->ctx, P->params, "dv_4").f; } P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; return P; } proj-9.8.1/src/conversions/axisswap.cpp000664 001750 001750 00000023006 15166171715 020156 0ustar00eveneven000000 000000 /*********************************************************************** Axis order operation for use with transformation pipelines. Kristian Evers, kreve@sdfe.dk, 2017-10-31 ************************************************************************ Change the order and sign of 2,3 or 4 axes. Each of the possible four axes are numbered with 1-4, such that the first input axis is 1, the second is 2 and so on. The output ordering is controlled by a list of the input axes re-ordered to the new mapping. Examples: Reversing the order of the axes: +proj=axisswap +order=4,3,2,1 Swapping the first two axes (x and y): +proj=axisswap +order=2,1,3,4 The direction, or sign, of an axis can be changed by adding a minus in front of the axis-number: +proj=axisswap +order=1,-2,3,4 It is only necessary to specify the axes that are affected by the swap operation: +proj=axisswap +order=2,1 ************************************************************************ * Copyright (c) 2017, Kristian Evers / SDFE * * 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 #include #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(axisswap, "Axis ordering"); namespace { // anonymous namespace struct pj_axisswap_data { unsigned int axis[4]; int sign[4]; }; } // anonymous namespace static int sign(int x) { return (x > 0) - (x < 0); } static PJ_XY pj_axisswap_forward_2d(PJ_LP lp, PJ *P) { struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque; PJ_XY xy; double in[2] = {lp.lam, lp.phi}; xy.x = in[Q->axis[0]] * Q->sign[0]; xy.y = in[Q->axis[1]] * Q->sign[1]; return xy; } static PJ_LP pj_axisswap_reverse_2d(PJ_XY xy, PJ *P) { struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque; unsigned int i; PJ_COORD out, in; in.v[0] = xy.x; in.v[1] = xy.y; out = proj_coord_error(); for (i = 0; i < 2; i++) out.v[Q->axis[i]] = in.v[i] * Q->sign[i]; return out.lp; } static PJ_XYZ pj_axisswap_forward_3d(PJ_LPZ lpz, PJ *P) { struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque; unsigned int i; PJ_COORD out, in; in.v[0] = lpz.lam; in.v[1] = lpz.phi; in.v[2] = lpz.z; out = proj_coord_error(); for (i = 0; i < 3; i++) out.v[i] = in.v[Q->axis[i]] * Q->sign[i]; return out.xyz; } static PJ_LPZ pj_axisswap_reverse_3d(PJ_XYZ xyz, PJ *P) { struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque; unsigned int i; PJ_COORD in, out; out = proj_coord_error(); in.v[0] = xyz.x; in.v[1] = xyz.y; in.v[2] = xyz.z; for (i = 0; i < 3; i++) out.v[Q->axis[i]] = in.v[i] * Q->sign[i]; return out.lpz; } static void swap_xy_4d(PJ_COORD &coo, PJ *) { std::swap(coo.xyzt.x, coo.xyzt.y); } static void pj_axisswap_forward_4d(PJ_COORD &coo, PJ *P) { struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque; unsigned int i; PJ_COORD out; for (i = 0; i < 4; i++) out.v[i] = coo.v[Q->axis[i]] * Q->sign[i]; coo = out; } static void pj_axisswap_reverse_4d(PJ_COORD &coo, PJ *P) { struct pj_axisswap_data *Q = (struct pj_axisswap_data *)P->opaque; unsigned int i; PJ_COORD out; for (i = 0; i < 4; i++) out.v[Q->axis[i]] = coo.v[i] * Q->sign[i]; coo = out; } /***********************************************************************/ PJ *PJ_CONVERSION(axisswap, 0) { /***********************************************************************/ struct pj_axisswap_data *Q = static_cast( calloc(1, sizeof(struct pj_axisswap_data))); char *s; unsigned int i, j, n = 0; if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; /* +order and +axis are mutually exclusive */ if (!pj_param_exists(P->params, "order") == !pj_param_exists(P->params, "axis")) { proj_log_error(P, _("must provide EITHER 'order' OR 'axis' parameter.")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); } /* fill axis list with indices from 4-7 to simplify duplicate search further * down */ for (i = 0; i < 4; i++) { Q->axis[i] = i + 4; Q->sign[i] = 1; } /* if the "order" parameter is used */ if (pj_param_exists(P->params, "order")) { /* read axis order */ char *order = pj_param(P->ctx, P->params, "sorder").s; /* check that all characters are valid */ for (i = 0; i < strlen(order); i++) if (strchr("1234-,", order[i]) == nullptr) { proj_log_error(P, _("unknown axis '%c'"), order[i]); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* read axes numbers and signs */ s = order; n = 0; while (*s != '\0' && n < 4) { Q->axis[n] = abs(atoi(s)) - 1; if (Q->axis[n] > 3) { proj_log_error(P, _("invalid axis '%d'"), Q->axis[n]); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->sign[n++] = sign(atoi(s)); while (*s != '\0' && *s != ',') s++; if (*s == ',') s++; } } /* if the "axis" parameter is used */ if (pj_param_exists(P->params, "axis")) { /* parse the classic PROJ.4 enu axis specification */ for (i = 0; i < 3; i++) { switch (P->axis[i]) { case 'w': Q->sign[i] = -1; Q->axis[i] = 0; break; case 'e': Q->sign[i] = 1; Q->axis[i] = 0; break; case 's': Q->sign[i] = -1; Q->axis[i] = 1; break; case 'n': Q->sign[i] = 1; Q->axis[i] = 1; break; case 'd': Q->sign[i] = -1; Q->axis[i] = 2; break; case 'u': Q->sign[i] = 1; Q->axis[i] = 2; break; default: proj_log_error(P, _("unknown axis '%c'"), P->axis[i]); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } n = 3; } /* check for duplicate axes */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { if (i == j) continue; if (Q->axis[i] == Q->axis[j]) { proj_log_error(P, _("axisswap: duplicate axes specified")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } /* only map fwd/inv functions that are possible with the given axis setup */ if (n == 4) { P->fwd4d = pj_axisswap_forward_4d; P->inv4d = pj_axisswap_reverse_4d; } if (n == 3 && Q->axis[0] < 3 && Q->axis[1] < 3 && Q->axis[2] < 3) { P->fwd3d = pj_axisswap_forward_3d; P->inv3d = pj_axisswap_reverse_3d; } if (n == 2) { if (Q->axis[0] == 1 && Q->sign[0] == 1 && Q->axis[1] == 0 && Q->sign[1] == 1) { P->fwd4d = swap_xy_4d; P->inv4d = swap_xy_4d; } else if (Q->axis[0] < 2 && Q->axis[1] < 2) { P->fwd = pj_axisswap_forward_2d; P->inv = pj_axisswap_reverse_2d; } } if (P->fwd4d == nullptr && P->fwd3d == nullptr && P->fwd == nullptr) { proj_log_error(P, _("axisswap: bad axis order")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (pj_param(P->ctx, P->params, "tangularunits").i) { P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; } else { P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; } /* Preparation and finalization steps are skipped, since the reason */ /* d'etre of axisswap is to bring input coordinates in line with the */ /* the internally expected order (ENU), such that handling of offsets */ /* etc. can be done correctly in a later step of a pipeline */ P->skip_fwd_prepare = 1; P->skip_fwd_finalize = 1; P->skip_inv_prepare = 1; P->skip_inv_finalize = 1; return P; } proj-9.8.1/src/conversions/unitconvert.cpp000664 001750 001750 00000053112 15166171715 020700 0ustar00eveneven000000 000000 /*********************************************************************** Unit conversion pseudo-projection for use with transformation pipelines. Kristian Evers, 2017-05-16 ************************************************************************ A pseudo-projection that can be used to convert units of input and output data. Primarily useful in pipelines. Unit conversion is performed by means of a pivot unit. The pivot unit for distance units are the meter and for time we use the modified julian date. A time unit conversion is performed like Unit A -> Modified Julian date -> Unit B distance units are converted in the same manner, with meter being the central unit. The modified Julian date is chosen as the pivot unit since it has a fairly high precision, goes sufficiently long backwards in time, has no danger of hitting the upper limit in the near future and it is a fairly common time unit in astronomy and geodesy. Note that we are using the Julian date and not day. The difference being that the latter is defined as an integer and is thus limited to days in resolution. This approach has been extended wherever it makes sense, e.g. the GPS week unit also has a fractional part that makes it possible to determine the day, hour and minute of an observation. In- and output units are controlled with the parameters +xy_in, +xy_out, +z_in, +z_out, +t_in and +t_out where xy denotes horizontal units, z vertical units and t time units. ************************************************************************ Kristian Evers, kreve@sdfe.dk, 2017-05-09 Last update: 2017-05-16 ************************************************************************ * Copyright (c) 2017, Kristian Evers / SDFE * * 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 #include #include #include #include "proj_internal.h" #include PROJ_HEAD(unitconvert, "Unit conversion"); typedef double (*tconvert)(double); namespace { // anonymous namespace struct TIME_UNITS { const char *id; /* units keyword */ tconvert t_in; /* unit -> mod. julian date function pointer */ tconvert t_out; /* mod. julian date > unit function pointer */ const char *name; /* comments */ }; } // anonymous namespace namespace { // anonymous namespace struct pj_opaque_unitconvert { int t_in_id; /* time unit id for the time input unit */ int t_out_id; /* time unit id for the time output unit */ double xy_factor; /* unit conversion factor for horizontal components */ double z_factor; /* unit conversion factor for vertical components */ }; } // anonymous namespace /***********************************************************************/ static int is_leap_year(long year) { /***********************************************************************/ return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); } /***********************************************************************/ static int days_in_year(long year) { /***********************************************************************/ return is_leap_year(year) ? 366 : 365; } /***********************************************************************/ static unsigned int days_in_month(unsigned long year, unsigned long month) { /***********************************************************************/ const unsigned int month_table[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; unsigned int days; if (month > 12) month = 12; if (month == 0) month = 1; days = month_table[month - 1]; if (is_leap_year(year) && month == 2) days++; return days; } /***********************************************************************/ static int daynumber_in_year(unsigned long year, unsigned long month, unsigned long day) { /***********************************************************************/ unsigned int daynumber = 0, i; if (month > 12) month = 12; if (month == 0) month = 1; if (day > days_in_month(year, month)) day = days_in_month(year, month); for (i = 1; i < month; i++) daynumber += days_in_month(year, i); daynumber += day; return daynumber; } /***********************************************************************/ static double mjd_to_mjd(double mjd) { /*********************************************************************** Modified julian date no-op function. The Julian date is defined as (fractional) days since midnight on 16th of November in 1858. ************************************************************************/ return mjd; } /***********************************************************************/ static double decimalyear_to_mjd(double decimalyear) { /*********************************************************************** Epoch of modified julian date is 1858-11-16 00:00 ************************************************************************/ long year; double fractional_year; double mjd; // Written this way to deal with NaN input if (!(decimalyear >= -10000 && decimalyear <= 10000)) return 0; year = lround(floor(decimalyear)); fractional_year = decimalyear - year; mjd = (year - 1859) * 365 + 14 + 31; mjd += (double)fractional_year * (double)days_in_year(year); /* take care of leap days */ year--; for (; year > 1858; year--) if (is_leap_year(year)) mjd++; return mjd; } /***********************************************************************/ static double mjd_to_decimalyear(double mjd) { /*********************************************************************** Epoch of modified julian date is 1858-11-16 00:00 ************************************************************************/ double decimalyear = mjd; double mjd_iter = 14 + 31; int year = 1859; /* a smarter brain than mine could probably to do this more elegantly - I'll just brute-force my way out of this... */ for (; mjd >= mjd_iter; year++) { mjd_iter += days_in_year(year); } year--; mjd_iter -= days_in_year(year); decimalyear = year + (mjd - mjd_iter) / days_in_year(year); return decimalyear; } /***********************************************************************/ static double gps_week_to_mjd(double gps_week) { /*********************************************************************** GPS weeks are defined as the number of weeks since January the 6th 1980. Epoch of gps weeks is 1980-01-06 00:00, which in modified Julian date is 44244. ************************************************************************/ return 44244.0 + gps_week * 7.0; } /***********************************************************************/ static double mjd_to_gps_week(double mjd) { /*********************************************************************** GPS weeks are defined as the number of weeks since January the 6th 1980. Epoch of gps weeks is 1980-01-06 00:00, which in modified Julian date is 44244. ************************************************************************/ return (mjd - 44244.0) / 7.0; } /***********************************************************************/ static double yyyymmdd_to_mjd(double yyyymmdd) { /************************************************************************ Date given in YYYY-MM-DD format. ************************************************************************/ long year = lround(floor(yyyymmdd / 10000)); long month = lround(floor((yyyymmdd - year * 10000) / 100)); long day = lround(floor(yyyymmdd - year * 10000 - month * 100)); double mjd = daynumber_in_year(year, month, day); for (year -= 1; year > 1858; year--) mjd += days_in_year(year); return mjd + 13 + 31; } /***********************************************************************/ static double mjd_to_yyyymmdd(double mjd) { /************************************************************************ Date returned in YYYY-MM-DD format. ************************************************************************/ unsigned int date_iter = 14 + 31; unsigned int year = 1859, month = 0, day = 0; unsigned int date = (int)lround(mjd); for (; date >= date_iter; year++) { date_iter += days_in_year(year); } year--; date_iter -= days_in_year(year); for (month = 1; date_iter + days_in_month(year, month) <= date; month++) date_iter += days_in_month(year, month); day = date - date_iter + 1; return year * 10000.0 + month * 100.0 + day; } static const struct TIME_UNITS time_units[] = { {"mjd", mjd_to_mjd, mjd_to_mjd, "Modified julian date"}, {"decimalyear", decimalyear_to_mjd, mjd_to_decimalyear, "Decimal year"}, {"gps_week", gps_week_to_mjd, mjd_to_gps_week, "GPS Week"}, {"yyyymmdd", yyyymmdd_to_mjd, mjd_to_yyyymmdd, "YYYYMMDD date"}, {nullptr, nullptr, nullptr, nullptr}}; /***********************************************************************/ static PJ_XY forward_2d(PJ_LP lp, PJ *P) { /************************************************************************ Forward unit conversions in the plane ************************************************************************/ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.lp = lp; point.xy.x *= Q->xy_factor; point.xy.y *= Q->xy_factor; return point.xy; } /***********************************************************************/ static PJ_LP reverse_2d(PJ_XY xy, PJ *P) { /************************************************************************ Reverse unit conversions in the plane ************************************************************************/ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.xy = xy; point.xy.x /= Q->xy_factor; point.xy.y /= Q->xy_factor; return point.lp; } /***********************************************************************/ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) { /************************************************************************ Forward unit conversions the vertical component ************************************************************************/ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; /* take care of the horizontal components in the 2D function */ // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xy = forward_2d(point.lp, P); point.xy = xy; point.xyz.z *= Q->z_factor; return point.xyz; } /***********************************************************************/ static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) { /************************************************************************ Reverse unit conversions the vertical component ************************************************************************/ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; /* take care of the horizontal components in the 2D function */ // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lp = reverse_2d(point.xy, P); point.lp = lp; point.xyz.z /= Q->z_factor; return point.lpz; } /***********************************************************************/ static void forward_4d(PJ_COORD &coo, PJ *P) { /************************************************************************ Forward conversion of time units ************************************************************************/ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *)P->opaque; /* delegate unit conversion of physical dimensions to the 3D function */ // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = forward_3d(coo.lpz, P); coo.xyz = xyz; if (Q->t_in_id >= 0) coo.xyzt.t = time_units[Q->t_in_id].t_in(coo.xyzt.t); if (Q->t_out_id >= 0) coo.xyzt.t = time_units[Q->t_out_id].t_out(coo.xyzt.t); } /***********************************************************************/ static void reverse_4d(PJ_COORD &coo, PJ *P) { /************************************************************************ Reverse conversion of time units ************************************************************************/ struct pj_opaque_unitconvert *Q = (struct pj_opaque_unitconvert *)P->opaque; /* delegate unit conversion of physical dimensions to the 3D function */ // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = reverse_3d(coo.xyz, P); coo.lpz = lpz; if (Q->t_out_id >= 0) coo.xyzt.t = time_units[Q->t_out_id].t_in(coo.xyzt.t); if (Q->t_in_id >= 0) coo.xyzt.t = time_units[Q->t_in_id].t_out(coo.xyzt.t); } /***********************************************************************/ static double get_unit_conversion_factor(const char *name, int *p_is_linear, const char **p_normalized_name) { /***********************************************************************/ int i; const char *s; const PJ_UNITS *units = pj_list_linear_units(); /* Try first with linear units */ for (i = 0; (s = units[i].id); ++i) { if (strcmp(s, name) == 0) { if (p_normalized_name) { *p_normalized_name = units[i].name; } if (p_is_linear) { *p_is_linear = 1; } return units[i].factor; } } /* And then angular units */ units = pj_list_angular_units(); for (i = 0; (s = units[i].id); ++i) { if (strcmp(s, name) == 0) { if (p_normalized_name) { *p_normalized_name = units[i].name; } if (p_is_linear) { *p_is_linear = 0; } return units[i].factor; } } if (p_normalized_name) { *p_normalized_name = nullptr; } if (p_is_linear) { *p_is_linear = -1; } return 0.0; } /***********************************************************************/ PJ *PJ_CONVERSION(unitconvert, 0) { /***********************************************************************/ struct pj_opaque_unitconvert *Q = static_cast( calloc(1, sizeof(struct pj_opaque_unitconvert))); const char *s, *name; int i; double f; int xy_in_is_linear = -1; /* unknown */ int xy_out_is_linear = -1; /* unknown */ int z_in_is_linear = -1; /* unknown */ int z_out_is_linear = -1; /* unknown */ if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; P->fwd4d = forward_4d; P->inv4d = reverse_4d; P->fwd3d = forward_3d; P->inv3d = reverse_3d; P->fwd = forward_2d; P->inv = reverse_2d; P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; P->skip_fwd_prepare = 1; P->skip_inv_prepare = 1; /* if no time input/output unit is specified we can skip them */ Q->t_in_id = -1; Q->t_out_id = -1; Q->xy_factor = 1.0; Q->z_factor = 1.0; if ((name = pj_param(P->ctx, P->params, "sxy_in").s) != nullptr) { const char *normalized_name = nullptr; f = get_unit_conversion_factor(name, &xy_in_is_linear, &normalized_name); if (f != 0.0) { proj_log_trace(P, "xy_in unit: %s", normalized_name); } else { f = pj_param(P->ctx, P->params, "dxy_in").f; if (f == 0.0 || 1.0 / f == 0.0) { proj_log_error(P, _("unknown xy_in unit")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->xy_factor = f; if (normalized_name != nullptr) { if (strcmp(normalized_name, "Radian") == 0) P->left = PJ_IO_UNITS_RADIANS; if (strcmp(normalized_name, "Degree") == 0) P->left = PJ_IO_UNITS_DEGREES; } } if ((name = pj_param(P->ctx, P->params, "sxy_out").s) != nullptr) { const char *normalized_name = nullptr; f = get_unit_conversion_factor(name, &xy_out_is_linear, &normalized_name); if (f != 0.0) { proj_log_trace(P, "xy_out unit: %s", normalized_name); } else { f = pj_param(P->ctx, P->params, "dxy_out").f; if (f == 0.0 || 1.0 / f == 0.0) { proj_log_error(P, _("unknown xy_out unit")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->xy_factor /= f; if (normalized_name != nullptr) { if (strcmp(normalized_name, "Radian") == 0) P->right = PJ_IO_UNITS_RADIANS; if (strcmp(normalized_name, "Degree") == 0) P->right = PJ_IO_UNITS_DEGREES; } } if (xy_in_is_linear >= 0 && xy_out_is_linear >= 0 && xy_in_is_linear != xy_out_is_linear) { proj_log_error(P, _("inconsistent unit type between xy_in and xy_out")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if ((name = pj_param(P->ctx, P->params, "sz_in").s) != nullptr) { const char *normalized_name = nullptr; f = get_unit_conversion_factor(name, &z_in_is_linear, &normalized_name); if (f != 0.0) { proj_log_trace(P, "z_in unit: %s", normalized_name); } else { f = pj_param(P->ctx, P->params, "dz_in").f; if (f == 0.0 || 1.0 / f == 0.0) { proj_log_error(P, _("unknown z_in unit")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->z_factor = f; } if ((name = pj_param(P->ctx, P->params, "sz_out").s) != nullptr) { const char *normalized_name = nullptr; f = get_unit_conversion_factor(name, &z_out_is_linear, &normalized_name); if (f != 0.0) { proj_log_trace(P, "z_out unit: %s", normalized_name); } else { f = pj_param(P->ctx, P->params, "dz_out").f; if (f == 0.0 || 1.0 / f == 0.0) { proj_log_error(P, _("unknown z_out unit")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->z_factor /= f; } if (z_in_is_linear >= 0 && z_out_is_linear >= 0 && z_in_is_linear != z_out_is_linear) { proj_log_error(P, _("inconsistent unit type between z_in and z_out")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if ((name = pj_param(P->ctx, P->params, "st_in").s) != nullptr) { for (i = 0; (s = time_units[i].id) && strcmp(name, s); ++i) ; if (!s) { proj_log_error(P, _("unknown t_in unit")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->t_in_id = i; proj_log_trace(P, "t_in unit: %s", time_units[i].name); } s = nullptr; if ((name = pj_param(P->ctx, P->params, "st_out").s) != nullptr) { for (i = 0; (s = time_units[i].id) && strcmp(name, s); ++i) ; if (!s) { proj_log_error(P, _("unknown t_out unit")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->t_out_id = i; proj_log_trace(P, "t_out unit: %s", time_units[i].name); } return P; } proj-9.8.1/src/conversions/noop.cpp000664 001750 001750 00000000407 15166171715 017272 0ustar00eveneven000000 000000 #include "proj_internal.h" PROJ_HEAD(noop, "No operation"); static void noop(PJ_COORD &, PJ *) {} PJ *PJ_CONVERSION(noop, 0) { P->fwd4d = noop; P->inv4d = noop; P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; return P; } proj-9.8.1/src/conversions/geoc.cpp000664 001750 001750 00000006601 15166171715 017236 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Conversion from geographic to geocentric latitude and back. * Author: Thomas Knudsen (2017) * ****************************************************************************** * Copyright (c) 2017, SDFE, http://www.sdfe.dk * Copyright (c) 2017, Thomas Knudsen * * 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 #include "proj.h" #include "proj_internal.h" PROJ_HEAD(geoc, "Geocentric Latitude"); /*************************************************************************************/ PJ_COORD pj_geocentric_latitude(const PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { /************************************************************************************** Convert geographical latitude to geocentric (or the other way round if direction = PJ_INV) The conversion involves a call to the tangent function, which goes through the roof at the poles, so very close (the last centimeter) to the poles no conversion takes place and the input latitude is copied directly to the output. Fortunately, the geocentric latitude converges to the geographical at the poles, so the difference is negligible. For the spherical case, the geographical latitude equals the geocentric, and consequently, the input is copied directly to the output. **************************************************************************************/ const double limit = M_HALFPI - 1e-9; PJ_COORD res = coord; if ((coord.lp.phi > limit) || (coord.lp.phi < -limit) || (P->es == 0)) return res; if (direction == PJ_FWD) res.lp.phi = atan(P->one_es * tan(coord.lp.phi)); else res.lp.phi = atan(P->rone_es * tan(coord.lp.phi)); return res; } /* Geographical to geocentric */ static void forward(PJ_COORD &coo, PJ *P) { coo = pj_geocentric_latitude(P, PJ_FWD, coo); } /* Geocentric to geographical */ static void inverse(PJ_COORD &coo, PJ *P) { coo = pj_geocentric_latitude(P, PJ_INV, coo); } static PJ *PJ_CONVERSION(geoc, 1) { P->inv4d = inverse; P->fwd4d = forward; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; P->is_latlong = 1; return P; } proj-9.8.1/src/conversions/geocent.cpp000664 001750 001750 00000004233 15166171715 017744 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Stub projection for geocentric. The transformation isn't * really done here since this code is 2D. The real transformation * is handled by pj_transform.c. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2002, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include "proj.h" #include "proj_internal.h" PROJ_HEAD(geocent, "Geocentric") "\n\t"; static PJ_XY forward(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = lp.lam; xy.y = lp.phi; return xy; } static PJ_LP inverse(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = xy.y; lp.lam = xy.x; return lp; } PJ *PJ_CONVERSION(geocent, 0) { P->is_geocent = 1; P->x0 = 0.0; P->y0 = 0.0; P->inv = inverse; P->fwd = forward; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_CARTESIAN; return P; } proj-9.8.1/src/conversions/cart.cpp000664 001750 001750 00000023555 15166171715 017261 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Convert between ellipsoidal, geodetic coordinates and * cartesian, geocentric coordinates. * * Formally, this functionality is also found in the PJ_geocent.c * code. * * Actually, however, the PJ_geocent transformations are carried * out in concert between 2D stubs in PJ_geocent.c and 3D code * placed in pj_transform.c. * * For pipeline-style datum shifts, we do need direct access * to the full 3D interface for this functionality. * * Hence this code, which may look like "just another PJ_geocent" * but really is something substantially different. * * Author: Thomas Knudsen, thokn@sdfe.dk * ****************************************************************************** * Copyright (c) 2016, Thomas Knudsen / SDFE * * 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 "proj_internal.h" #include PROJ_HEAD(cart, "Geodetic/cartesian conversions"); /************************************************************** CARTESIAN / GEODETIC CONVERSIONS *************************************************************** This material follows: Bernhard Hofmann-Wellenhof & Helmut Moritz: Physical Geodesy, 2nd edition. Springer, 2005. chapter 5.6: Coordinate transformations (HM, below), and Wikipedia: Geographic Coordinate Conversion, https://en.wikipedia.org/wiki/Geographic_coordinate_conversion (WP, below). The cartesian-to-geodetic conversion is based on Bowring's celebrated method: B. R. Bowring: Transformation from spatial to geographical coordinates Survey Review 23(181), pp. 323-327, 1976 (BB, below), but could probably use some TLC from a newer and faster algorithm: Toshio Fukushima: Transformation from Cartesian to Geodetic Coordinates Accelerated by Halley’s Method Journal of Geodesy, February 2006 (TF, below). Close to the poles, we avoid singularities by switching to an approximation requiring knowledge of the geocentric radius at the given latitude. For this, we use an adaptation of the formula given in: Wikipedia: Earth Radius https://en.wikipedia.org/wiki/Earth_radius#Radius_at_a_given_geodetic_latitude (Derivation and commentary at https://gis.stackexchange.com/q/20200) (WP2, below) These routines are probably not as robust at those in geocent.c, at least they haven't been through as heavy use as their geocent sisters. Some care has been taken to avoid singularities, but extreme cases (e.g. setting es, the squared eccentricity, to 1), will cause havoc. **************************************************************/ /*********************************************************************/ static double normal_radius_of_curvature(double a, double es, double sinphi) { /*********************************************************************/ if (es == 0) return a; /* This is from WP. HM formula 2-149 gives an a,b version */ return a / sqrt(1 - es * sinphi * sinphi); } /*********************************************************************/ static double geocentric_radius(double a, double b_div_a, double cosphi, double sinphi) { /********************************************************************* Return the geocentric radius at latitude phi, of an ellipsoid with semimajor axis a and semiminor axis b. This is from WP2, but uses hypot() for potentially better numerical robustness ***********************************************************************/ // Non-optimized version: // const double b = a * b_div_a; // return hypot(a * a * cosphi, b * b * sinphi) / // hypot(a * cosphi, b * sinphi); const double cosphi_squared = cosphi * cosphi; const double sinphi_squared = sinphi * sinphi; const double b_div_a_squared = b_div_a * b_div_a; const double b_div_a_squared_mul_sinphi_squared = b_div_a_squared * sinphi_squared; return a * sqrt((cosphi_squared + b_div_a_squared * b_div_a_squared_mul_sinphi_squared) / (cosphi_squared + b_div_a_squared_mul_sinphi_squared)); } /*********************************************************************/ static PJ_XYZ cartesian(PJ_LPZ geod, PJ *P) { /*********************************************************************/ PJ_XYZ xyz; const double cosphi = cos(geod.phi); const double sinphi = sin(geod.phi); const double N = normal_radius_of_curvature(P->a, P->es, sinphi); /* HM formula 5-27 (z formula follows WP) */ xyz.x = (N + geod.z) * cosphi * cos(geod.lam); xyz.y = (N + geod.z) * cosphi * sin(geod.lam); xyz.z = (N * (1 - P->es) + geod.z) * sinphi; return xyz; } /*********************************************************************/ static PJ_LPZ geodetic(PJ_XYZ cart, PJ *P) { /*********************************************************************/ PJ_LPZ lpz; // Normalize (x,y,z) to the unit sphere/ellipsoid. #if (defined(__i386__) && !defined(__SSE__)) || defined(_M_IX86) // i386 (actually non-SSE) code path to make following test case of // testvarious happy // "echo 6378137.00 -0.00 0.00 | bin/cs2cs +proj=geocent +datum=WGS84 +to // +proj=latlong +datum=WGS84" const double x_div_a = cart.x / P->a; const double y_div_a = cart.y / P->a; const double z_div_a = cart.z / P->a; #else const double x_div_a = cart.x * P->ra; const double y_div_a = cart.y * P->ra; const double z_div_a = cart.z * P->ra; #endif /* Perpendicular distance from point to Z-axis (HM eq. 5-28) */ const double p_div_a = sqrt(x_div_a * x_div_a + y_div_a * y_div_a); #if 0 /* HM eq. (5-37) */ const double theta = atan2 (cart.z * P->a, p * P->b); /* HM eq. (5-36) (from BB, 1976) */ const double c = cos(theta); const double s = sin(theta); #else const double b_div_a = 1 - P->f; // = P->b / P->a const double p_div_a_b_div_a = p_div_a * b_div_a; const double norm = sqrt(z_div_a * z_div_a + p_div_a_b_div_a * p_div_a_b_div_a); double c, s; if (norm != 0) { const double inv_norm = 1.0 / norm; c = p_div_a_b_div_a * inv_norm; s = z_div_a * inv_norm; } else { c = 1; s = 0; } #endif const double y_phi = z_div_a + P->e2s * b_div_a * s * s * s; const double x_phi = p_div_a - P->es * c * c * c; const double norm_phi = sqrt(y_phi * y_phi + x_phi * x_phi); double cosphi, sinphi; if (norm_phi != 0) { const double inv_norm_phi = 1.0 / norm_phi; cosphi = x_phi * inv_norm_phi; sinphi = y_phi * inv_norm_phi; } else { cosphi = 1; sinphi = 0; } if (x_phi <= 0) { // this happen on non-sphere ellipsoid when x,y,z is very close to 0 // there is no single solution to the cart->geodetic conversion in // that case, clamp to -90/90 deg and avoid a discontinuous boundary // near the poles lpz.phi = cart.z >= 0 ? M_HALFPI : -M_HALFPI; cosphi = 0; sinphi = cart.z >= 0 ? 1 : -1; } else { lpz.phi = atan(y_phi / x_phi); } lpz.lam = atan2(y_div_a, x_div_a); if (cosphi < 1e-6) { /* poleward of 89.99994 deg, we avoid division by zero */ /* by computing the height as the cartesian z value */ /* minus the geocentric radius of the Earth at the given */ /* latitude */ const double r = geocentric_radius(P->a, b_div_a, cosphi, sinphi); lpz.z = fabs(cart.z) - r; } else { const double N = normal_radius_of_curvature(P->a, P->es, sinphi); lpz.z = P->a * p_div_a / cosphi - N; } return lpz; } /* In effect, 2 cartesian coordinates of a point on the ellipsoid. Rather * pointless, but... */ static PJ_XY cart_forward(PJ_LP lp, PJ *P) { PJ_COORD point; point.lp = lp; point.lpz.z = 0; const auto xyz = cartesian(point.lpz, P); point.xyz = xyz; return point.xy; } /* And the other way round. Still rather pointless, but... */ static PJ_LP cart_reverse(PJ_XY xy, PJ *P) { PJ_COORD point; point.xy = xy; point.xyz.z = 0; const auto lpz = geodetic(point.xyz, P); point.lpz = lpz; return point.lp; } /*********************************************************************/ PJ *PJ_CONVERSION(cart, 1) { /*********************************************************************/ P->fwd3d = cartesian; P->inv3d = geodetic; P->fwd = cart_forward; P->inv = cart_reverse; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_CARTESIAN; return P; } proj-9.8.1/src/ell_set.cpp000664 001750 001750 00000051715 15166171715 015406 0ustar00eveneven000000 000000 /* set ellipsoid parameters a and es */ #include #include #include #include "proj.h" #include "proj_internal.h" /* Prototypes of the pj_ellipsoid helper functions */ static int ellps_ellps(PJ *P); static int ellps_size(PJ *P); static int ellps_shape(PJ *P); static int ellps_spherification(PJ *P); static paralist *pj_get_param(paralist *list, const char *key); static char *pj_param_value(paralist *list); static const PJ_ELLPS *pj_find_ellps(const char *name); /***************************************************************************************/ int pj_ellipsoid(PJ *P) { /**************************************************************************************** This is a replacement for the classic PROJ pj_ell_set function. The main difference is that pj_ellipsoid augments the PJ object with a copy of the exact tags used to define its related ellipsoid. This makes it possible to let a new PJ object inherit the geometrical properties of an existing one. A complete ellipsoid definition comprises a size (primary) and a shape (secondary) parameter. Size parameters supported are: R, defining the radius of a spherical planet a, defining the semimajor axis of an ellipsoidal planet Shape parameters supported are: rf, the reverse flattening of the ellipsoid f, the flattening of the ellipsoid es, the eccentricity squared e, the eccentricity b, the semiminor axis The ellps=xxx parameter provides both size and shape for a number of built in ellipsoid definitions. The ellipsoid definition may be augmented with a spherification flag, turning the ellipsoid into a sphere with features defined by the ellipsoid. Spherification parameters supported are: R_A, which gives a sphere with the same surface area as the ellipsoid R_V, which gives a sphere with the same volume as the ellipsoid R_a, which gives a sphere with R = (a + b)/2 (arithmetic mean) R_g, which gives a sphere with R = sqrt(a*b) (geometric mean) R_h, which gives a sphere with R = 2*a*b/(a+b) (harmonic mean) R_lat_a=phi, which gives a sphere with R being the arithmetic mean of of the corresponding ellipsoid at latitude phi. R_lat_g=phi, which gives a sphere with R being the geometric mean of of the corresponding ellipsoid at latitude phi. R_C, which gives a sphere with the radius of the conformal sphere at phi0. If R is given as size parameter, any shape and spherification parameters given are ignored. If size and shape are given as ellps=xxx, later shape and size parameters are are taken into account as modifiers for the built in ellipsoid definition. While this may seem strange, it is in accordance with historical PROJ behavior. It can e.g. be used to define coordinates on the ellipsoid scaled to unit semimajor axis by specifying "+ellps=xxx +a=1" ****************************************************************************************/ int err = proj_errno_reset(P); const char *empty = {""}; free(P->def_size); P->def_size = nullptr; free(P->def_shape); P->def_shape = nullptr; free(P->def_spherification); P->def_spherification = nullptr; free(P->def_ellps); P->def_ellps = nullptr; /* Specifying R overrules everything */ if (pj_get_param(P->params, "R")) { if (0 != ellps_size(P)) return 1; pj_calc_ellipsoid_params(P, P->a, 0); if (proj_errno(P)) return 1; return proj_errno_restore(P, err); } /* If an ellps argument is specified, start by using that */ if (0 != ellps_ellps(P)) return 1; /* We may overwrite the size */ if (0 != ellps_size(P)) return 2; /* We may also overwrite the shape */ if (0 != ellps_shape(P)) return 3; /* When we're done with it, we compute all related ellipsoid parameters */ pj_calc_ellipsoid_params(P, P->a, P->es); /* And finally, we may turn it into a sphere */ if (0 != ellps_spherification(P)) return 4; proj_log_trace(P, "pj_ellipsoid - final: a=%.3f f=1/%7.3f, errno=%d", P->a, P->f != 0 ? 1 / P->f : 0, proj_errno(P)); proj_log_trace(P, "pj_ellipsoid - final: %s %s %s %s", P->def_size ? P->def_size : empty, P->def_shape ? P->def_shape : empty, P->def_spherification ? P->def_spherification : empty, P->def_ellps ? P->def_ellps : empty); if (proj_errno(P)) return 5; /* success */ return proj_errno_restore(P, err); } /***************************************************************************************/ static int ellps_ellps(PJ *P) { /***************************************************************************************/ const PJ_ELLPS *ellps; paralist *par = nullptr; char *name; int err; /* Sail home if ellps=xxx is not specified */ par = pj_get_param(P->params, "ellps"); if (nullptr == par) return 0; /* Then look up the right size and shape parameters from the builtin list */ if (strlen(par->param) < 7) { proj_log_error(P, _("Invalid value for +ellps")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } name = par->param + 6; ellps = pj_find_ellps(name); if (nullptr == ellps) { proj_log_error(P, _("Unrecognized value for +ellps")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* Now, get things ready for ellps_size/ellps_shape, make them do their * thing */ err = proj_errno_reset(P); paralist *new_params = pj_mkparam(ellps->major); if (nullptr == new_params) return proj_errno_set(P, PROJ_ERR_OTHER /*ENOMEM*/); new_params->next = pj_mkparam(ellps->ell); if (nullptr == new_params->next) { free(new_params); return proj_errno_set(P, PROJ_ERR_OTHER /*ENOMEM*/); } paralist *old_params = P->params; P->params = new_params; { PJ empty_PJ; pj_inherit_ellipsoid_def(&empty_PJ, P); } const bool errorOnSizeOrShape = (ellps_size(P) || ellps_shape(P)); P->params = old_params; free(new_params->next); free(new_params); if (errorOnSizeOrShape) return proj_errno_set(P, PROJ_ERR_OTHER /*ENOMEM*/); if (proj_errno(P)) return proj_errno(P); /* Finally update P and sail home */ P->def_ellps = pj_strdup(par->param); par->used = 1; return proj_errno_restore(P, err); } /***************************************************************************************/ static int ellps_size(PJ *P) { /***************************************************************************************/ paralist *par = nullptr; int a_was_set = 0; free(P->def_size); P->def_size = nullptr; /* A size parameter *must* be given, but may have been given as ellps prior */ if (P->a != 0) a_was_set = 1; /* Check which size key is specified */ par = pj_get_param(P->params, "R"); if (nullptr == par) par = pj_get_param(P->params, "a"); if (nullptr == par) { if (a_was_set) return 0; if (P->need_ellps) proj_log_error(P, _("Major axis not given")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } P->def_size = pj_strdup(par->param); par->used = 1; P->a = pj_atof(pj_param_value(par)); if (P->a <= 0) { proj_log_error(P, _("Invalid value for major axis")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (HUGE_VAL == P->a) { proj_log_error(P, _("Invalid value for major axis")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if ('R' == par->param[0]) { P->es = P->f = P->e = P->rf = 0; P->b = P->a; } return 0; } /***************************************************************************************/ static int ellps_shape(PJ *P) { /***************************************************************************************/ const char *keys[] = {"rf", "f", "es", "e", "b"}; paralist *par = nullptr; size_t i, len; par = nullptr; len = sizeof(keys) / sizeof(char *); free(P->def_shape); P->def_shape = nullptr; /* Check which shape key is specified */ for (i = 0; i < len; i++) { par = pj_get_param(P->params, keys[i]); if (par) break; } /* Not giving a shape parameter means selecting a sphere, unless shape */ /* has been selected previously via ellps=xxx */ if (nullptr == par && P->es != 0) return 0; if (nullptr == par && P->es == 0) { P->es = P->f = 0; P->b = P->a; return 0; } P->def_shape = pj_strdup(par->param); par->used = 1; P->es = P->f = P->b = P->e = P->rf = 0; switch (i) { /* reverse flattening, rf */ case 0: P->rf = pj_atof(pj_param_value(par)); if (HUGE_VAL == P->rf || P->rf <= 0) { proj_log_error(P, _("Invalid value for rf. Should be > 0")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->f = 1 / P->rf; P->es = 2 * P->f - P->f * P->f; break; /* flattening, f */ case 1: P->f = pj_atof(pj_param_value(par)); if (HUGE_VAL == P->f || P->f < 0) { proj_log_error(P, _("Invalid value for f. Should be >= 0")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->rf = P->f != 0.0 ? 1.0 / P->f : HUGE_VAL; P->es = 2 * P->f - P->f * P->f; break; /* eccentricity squared, es */ case 2: P->es = pj_atof(pj_param_value(par)); if (HUGE_VAL == P->es || P->es < 0 || P->es >= 1) { proj_log_error(P, _("Invalid value for es. Should be in [0,1[ range")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } break; /* eccentricity, e */ case 3: P->e = pj_atof(pj_param_value(par)); if (HUGE_VAL == P->e || P->e < 0 || P->e >= 1) { proj_log_error(P, _("Invalid value for e. Should be in [0,1[ range")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->es = P->e * P->e; break; /* semiminor axis, b */ case 4: P->b = pj_atof(pj_param_value(par)); if (HUGE_VAL == P->b || P->b <= 0) { proj_log_error(P, _("Invalid value for b. Should be > 0")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (P->b == P->a) break; // coverity[division_by_zero] P->f = (P->a - P->b) / P->a; P->es = 2 * P->f - P->f * P->f; break; default: // shouldn't happen return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } // Written that way to catch NaN if (!(P->es >= 0)) { proj_log_error(P, _("Invalid eccentricity")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return 0; } /* series coefficients for calculating ellipsoid-equivalent spheres */ static const double SIXTH = 1 / 6.; static const double RA4 = 17 / 360.; static const double RA6 = 67 / 3024.; static const double RV4 = 5 / 72.; static const double RV6 = 55 / 1296.; /***************************************************************************************/ static int ellps_spherification(PJ *P) { /***************************************************************************************/ const char *keys[] = {"R_A", "R_V", "R_a", "R_g", "R_h", "R_lat_a", "R_lat_g", "R_C"}; size_t len, i; paralist *par = nullptr; double t; char *v, *endp; len = sizeof(keys) / sizeof(char *); /* Check which spherification key is specified */ for (i = 0; i < len; i++) { par = pj_get_param(P->params, keys[i]); if (par) break; } /* No spherification specified? Then we're done */ if (i == len) return 0; /* Store definition */ P->def_spherification = pj_strdup(par->param); par->used = 1; switch (i) { /* R_A - a sphere with same area as ellipsoid */ case 0: P->a *= 1. - P->es * (SIXTH + P->es * (RA4 + P->es * RA6)); break; /* R_V - a sphere with same volume as ellipsoid */ case 1: P->a *= 1. - P->es * (SIXTH + P->es * (RV4 + P->es * RV6)); break; /* R_a - a sphere with R = the arithmetic mean of the ellipsoid */ case 2: P->a = (P->a + P->b) / 2; break; /* R_g - a sphere with R = the geometric mean of the ellipsoid */ case 3: P->a = sqrt(P->a * P->b); break; /* R_h - a sphere with R = the harmonic mean of the ellipsoid */ case 4: if (P->a + P->b == 0) return proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); P->a = (2 * P->a * P->b) / (P->a + P->b); break; /* R_lat_a - a sphere with R = the arithmetic mean of the ellipsoid at given * latitude */ case 5: /* R_lat_g - a sphere with R = the geometric mean of the ellipsoid at given * latitude */ case 6: v = pj_param_value(par); t = proj_dmstor(v, &endp); if (fabs(t) > M_HALFPI) { proj_log_error( P, _("Invalid value for lat_g. |lat_g| should be <= 90°")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } t = sin(t); t = 1 - P->es * t * t; if (t == 0.) { proj_log_error(P, _("Invalid eccentricity")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (i == 5) /* arithmetic */ P->a *= (1. - P->es + t) / (2 * t * sqrt(t)); else /* geometric */ P->a *= sqrt(1 - P->es) / t; break; /* R_C - a sphere with R = radius of conformal sphere, taken at a * latitude that is phi0 (note: at least for mercator. for other * projection methods, this could be phi1) * Formula from IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, * part 2 1.1 Ellipsoid parameters */ case 7: t = sin(P->phi0); t = 1 - P->es * t * t; if (t == 0.) { proj_log_error(P, _("Invalid eccentricity")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->a *= sqrt(1 - P->es) / t; break; } if (P->a <= 0.) { proj_log_error(P, _("Invalid or missing major axis")); return proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* Clean up the ellipsoidal parameters to reflect the sphere */ P->es = P->e = P->f = 0; P->rf = HUGE_VAL; P->b = P->a; pj_calc_ellipsoid_params(P, P->a, 0); return 0; } /* locate parameter in list */ static paralist *pj_get_param(paralist *list, const char *key) { size_t l = strlen(key); while (list && !(0 == strncmp(list->param, key, l) && (0 == list->param[l] || list->param[l] == '='))) list = list->next; return list; } static char *pj_param_value(paralist *list) { char *key, *value; if (nullptr == list) return nullptr; key = list->param; value = strchr(key, '='); /* a flag (i.e. a key without value) has its own name (key) as value */ return value ? value + 1 : key; } static const PJ_ELLPS *pj_find_ellps(const char *name) { int i; const char *s; const PJ_ELLPS *ellps; if (nullptr == name) return nullptr; ellps = proj_list_ellps(); /* Search through internal ellipsoid list for name */ for (i = 0; (s = ellps[i].id) && strcmp(name, s); ++i) ; if (nullptr == s) return nullptr; return ellps + i; } /**************************************************************************************/ void pj_inherit_ellipsoid_def(const PJ *src, PJ *dst) { /*************************************************************************************** Brute force copy the ellipsoidal parameters from src to dst. This code was written before the actual ellipsoid setup parameters were kept available in the PJ->def_xxx elements. ***************************************************************************************/ /* The linear parameters */ dst->a = src->a; dst->b = src->b; dst->ra = src->ra; dst->rb = src->rb; /* The eccentricities */ dst->alpha = src->alpha; dst->e = src->e; dst->es = src->es; dst->e2 = src->e2; dst->e2s = src->e2s; dst->e3 = src->e3; dst->e3s = src->e3s; dst->one_es = src->one_es; dst->rone_es = src->rone_es; /* The flattenings */ dst->f = src->f; dst->f2 = src->f2; dst->n = src->n; dst->rf = src->rf; dst->rf2 = src->rf2; dst->rn = src->rn; /* This one's for GRS80 */ dst->J = src->J; /* es and a before any +proj related adjustment */ dst->es_orig = src->es_orig; dst->a_orig = src->a_orig; } /***************************************************************************************/ int pj_calc_ellipsoid_params(PJ *P, double a, double es) { /**************************************************************************************** Calculate a large number of ancillary ellipsoidal parameters, in addition to the two traditional PROJ defining parameters: Semimajor axis, a, and the eccentricity squared, es. Most of these parameters are fairly cheap to compute in comparison to the overall effort involved in initializing a PJ object. They may, however, take a substantial part of the time taken in computing an individual point transformation. So by providing them up front, we can amortize the (already modest) cost over all transformations carried out over the entire lifetime of a PJ object, rather than incur that cost for every single transformation. Most of the parameter calculations here are based on the "angular eccentricity", i.e. the angle, measured from the semiminor axis, of a line going from the north pole to one of the foci of the ellipsoid - or in other words: The arc sine of the eccentricity. The formulae used are mostly taken from: Richard H. Rapp: Geometric Geodesy, Part I, (178 pp, 1991). Columbus, Ohio: Dept. of Geodetic Science and Surveying, Ohio State University. ****************************************************************************************/ P->a = a; P->es = es; /* Compute some ancillary ellipsoidal parameters */ if (P->e == 0) P->e = sqrt(P->es); /* eccentricity */ P->alpha = asin(P->e); /* angular eccentricity */ /* second eccentricity */ P->e2 = tan(P->alpha); P->e2s = P->e2 * P->e2; /* third eccentricity */ P->e3 = (0 != P->alpha) ? sin(P->alpha) / sqrt(2 - sin(P->alpha) * sin(P->alpha)) : 0; P->e3s = P->e3 * P->e3; /* flattening */ if (0 == P->f) P->f = 1 - cos(P->alpha); /* = 1 - sqrt (1 - PIN->es); */ if (!(P->f >= 0.0 && P->f < 1.0)) { proj_log_error(P, _("Invalid eccentricity")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } P->rf = P->f != 0.0 ? 1.0 / P->f : HUGE_VAL; /* second flattening */ P->f2 = (cos(P->alpha) != 0) ? 1 / cos(P->alpha) - 1 : 0; P->rf2 = P->f2 != 0.0 ? 1 / P->f2 : HUGE_VAL; /* third flattening */ P->n = pow(tan(P->alpha / 2), 2); P->rn = P->n != 0.0 ? 1 / P->n : HUGE_VAL; /* ...and a few more */ if (0 == P->b) P->b = (1 - P->f) * P->a; P->rb = 1. / P->b; P->ra = 1. / P->a; P->one_es = 1. - P->es; if (P->one_es == 0.) { proj_log_error(P, _("Invalid eccentricity")); proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } P->rone_es = 1. / P->one_es; return 0; } /**************************************************************************************/ int pj_ell_set(PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) { /*************************************************************************************** Initialize ellipsoidal parameters by emulating the original ellipsoid setup function by Gerald Evenden, through a call to pj_ellipsoid ***************************************************************************************/ PJ B; int ret; B.ctx = ctx; B.params = pl; ret = pj_ellipsoid(&B); free(B.def_size); free(B.def_shape); free(B.def_spherification); free(B.def_ellps); if (ret) return ret; *a = B.a; *es = B.es; return 0; } proj-9.8.1/src/wkt1_grammar.y000664 001750 001750 00000021307 15166171715 016033 0ustar00eveneven000000 000000 %{ /****************************************************************************** * Project: PROJ * Purpose: WKT1 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2013-2018 Even Rouault, * * 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 "wkt1_parser.h" %} %define api.pure /* if the next %define is commented out, Bison 2.4 should be sufficient */ /* but will produce less prettier error messages */ %define parse.error verbose %require "3.0" %parse-param {pj_wkt1_parse_context *context} %lex-param {pj_wkt1_parse_context *context} %token T_PARAM_MT "PARAM_MT" %token T_CONCAT_MT "CONCAT_MT" %token T_INVERSE_MT "INVERSE_MT" %token T_PASSTHROUGH_MT "PASSTHROUGH_MT" %token T_PROJCS "PROJCS" %token T_PROJECTION "PROJECTION" %token T_GEOGCS "GEOGCS" %token T_DATUM "DATUM" %token T_SPHEROID "SPHEROID" %token T_PRIMEM "PRIMEM" %token T_UNIT "UNIT" %token T_LINUNIT "LINUNIT" %token T_GEOCCS "GEOCCS" %token T_AUTHORITY "AUTHORITY" %token T_VERT_CS "VERT_CS" // ESRI variation %token T_VERTCS "VERTCS" %token T_VERT_DATUM "VERT_DATUM" // ESRI variation %token T_VDATUM "VDATUM" %token T_COMPD_CS "COMPD_CS" %token T_AXIS "AXIS" %token T_TOWGS84 "TOWGS84" %token T_FITTED_CS "FITTED_CS" %token T_LOCAL_CS "LOCAL_CS" %token T_LOCAL_DATUM "LOCAL_DATUM" %token T_PARAMETER "PARAMETER" %token T_EXTENSION "EXTENSION" %token T_STRING "string" %token T_NUMBER "number" %token T_IDENTIFIER "identifier" %token END 0 "end of string" %% input: coordinate_system /* Derived from BNF grammar in OGC 01-009 OpenGIS Implementation */ /* Coordinate Transformation Services Revision 1.00 */ /* with the following additions : */ /* - accept an EXTENSION node at the end of GEOGCS, GEOCCS, PROJCS, COMPD_CS, VERT_DATUM */ /* - accept 3 parameters in TOWGS84 */ /* - accept LOCAL_CS["foo"] */ /* 7.1 Math Transform WKT */ begin_node: '[' | '(' begin_node_name: begin_node T_STRING end_node: ']' | ')' math_transform: param_mt | concat_mt | inv_mt | passthrough_mt param_mt: T_PARAM_MT begin_node_name opt_parameter_list end_node parameter: T_PARAMETER begin_node_name ',' T_NUMBER end_node opt_parameter_list: | ',' parameter opt_parameter_list concat_mt: T_CONCAT_MT begin_node math_transform opt_math_transform_list end_node opt_math_transform_list: | ',' math_transform opt_math_transform_list inv_mt: T_INVERSE_MT begin_node math_transform end_node passthrough_mt: T_PASSTHROUGH_MT begin_node integer ',' math_transform end_node /* FIXME */ integer: T_NUMBER /* 7.2 Coordinate System WKT */ coordinate_system: horz_cs_with_opt_esri_vertcs | geocentric_cs | vert_cs | compd_cs | fitted_cs | local_cs horz_cs_with_opt_esri_vertcs: horz_cs | horz_cs ',' esri_vert_cs horz_cs: geographic_cs | projected_cs /* opt_extension is an extension of the CT spec */ projected_cs: T_PROJCS begin_node_name ',' geographic_cs ',' projection ',' opt_parameter_list_linear_unit opt_twin_axis_extension_authority end_node opt_parameter_list_linear_unit: linear_unit | parameter_list_linear_unit parameter_list_linear_unit: parameter ',' parameter_list_linear_unit | parameter ',' linear_unit opt_twin_axis_extension_authority: | ',' twin_axis opt_extension_authority | ',' extension opt_authority | ',' authority opt_authority: | ',' authority extension: T_EXTENSION begin_node_name ',' T_STRING end_node projection: T_PROJECTION begin_node_name opt_authority end_node geographic_cs: T_GEOGCS begin_node_name',' datum ',' prime_meridian ',' angular_unit opt_linunit_or_twin_axis_extension_authority end_node /* ESRI extension for geographic 3D CRS */ linunit: T_LINUNIT begin_node_name',' conversion_factor opt_authority end_node opt_linunit_or_twin_axis_extension_authority: | ',' linunit opt_authority | ',' twin_axis opt_extension_authority | ',' extension opt_authority | ',' authority datum: T_DATUM begin_node_name ',' spheroid opt_towgs84_authority_extension end_node opt_towgs84_authority_extension: | ',' towgs84 opt_extension_authority | ',' extension opt_authority | ',' authority spheroid: T_SPHEROID begin_node_name ',' semi_major_axis ',' inverse_flattening opt_authority end_node semi_major_axis: T_NUMBER // Some WKT in the wild use "inf". Cf SPHEROID["unnamed",6370997,"inf"] // in https://zenodo.org/record/3878979#.Y_P4g4CZNH4, // https://zenodo.org/record/5831940#.Y_P4i4CZNH5 // or https://grasswiki.osgeo.org/wiki/Marine_Science inverse_flattening: T_NUMBER | T_STRING prime_meridian: T_PRIMEM begin_node_name ',' longitude opt_authority end_node longitude: T_NUMBER angular_unit: unit linear_unit: unit unit: T_UNIT begin_node_name ',' conversion_factor opt_authority end_node conversion_factor: T_NUMBER geocentric_cs: T_GEOCCS begin_node_name ',' datum ',' prime_meridian ',' linear_unit opt_three_axis_extension_authority end_node opt_three_axis_extension_authority: | ',' three_axis opt_extension_authority | ',' extension opt_authority | ',' authority three_axis: axis ',' axis ',' axis authority: T_AUTHORITY begin_node_name ',' T_STRING end_node vert_cs: T_VERT_CS begin_node_name ',' vert_datum ',' linear_unit opt_axis_authority end_node | esri_vert_cs esri_vert_cs: T_VERTCS begin_node_name ',' vdatum_or_datum ',' opt_parameter_list_linear_unit end_node opt_axis_authority: | ',' axis opt_authority | ',' authority vert_datum: T_VERT_DATUM begin_node_name ',' datum_type opt_extension_authority end_node vdatum_or_datum: vdatum | datum vdatum: T_VDATUM begin_node_name end_node opt_extension_authority: | ',' extension opt_authority | ',' authority datum_type: T_NUMBER compd_cs: T_COMPD_CS begin_node_name ',' head_cs ',' tail_cs opt_extension_authority end_node head_cs: horz_cs // Accepting a geographic CRS as part of the second CRS of a COMPD_CS is horrible // but found in LAS WKT tail_cs: geographic_cs | vert_cs twin_axis: axis ',' axis axis: T_AXIS begin_node_name ',' T_IDENTIFIER end_node /* Extension of the CT spec */ /* | T_AXIS '[' T_STRING ',' T_STRING ']'*/ towgs84: T_TOWGS84 begin_node towgs84_parameters end_node towgs84_parameters: seven_parameters /* Extension of the CT spec */ | three_parameters three_parameters: dx ',' dy ',' dz seven_parameters: dx ',' dy ',' dz ',' ex ',' ey ',' ez ',' ppm dx: T_NUMBER dy: T_NUMBER dz: T_NUMBER ex: T_NUMBER ey: T_NUMBER ez: T_NUMBER ppm: T_NUMBER fitted_cs: T_FITTED_CS begin_node_name ',' to_base ',' base_cs end_node to_base: math_transform base_cs: coordinate_system local_cs: T_LOCAL_CS begin_node_name ',' local_datum ',' unit ',' axis opt_axis_list_authority end_node /* Extension of the CT spec: accept only name */ | T_LOCAL_CS begin_node_name end_node opt_axis_list_authority: | ',' authority | ',' axis opt_axis_list_authority local_datum: T_LOCAL_DATUM begin_node_name ',' datum_type opt_authority end_node proj-9.8.1/src/internal.cpp000664 001750 001750 00000033272 15166171715 015571 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: This is primarily material originating from pj_obs_api.c * (now proj_4D_api.c), that does not fit into the API * category. Hence this pile of tubings and fittings for * PROJ.4 internal plumbing. * * Author: Thomas Knudsen, thokn@sdfe.dk, 2017-07-05 * ****************************************************************************** * Copyright (c) 2016, 2017, 2018, Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include #include #include #include #include #include #include #include "geodesic.h" #include "proj_internal.h" #include "proj/internal/internal.hpp" using namespace NS_PROJ::internal; enum pj_io_units pj_left(PJ *P) { enum pj_io_units u = P->inverted ? P->right : P->left; if (u == PJ_IO_UNITS_CLASSIC) return PJ_IO_UNITS_PROJECTED; return u; } enum pj_io_units pj_right(PJ *P) { enum pj_io_units u = P->inverted ? P->left : P->right; if (u == PJ_IO_UNITS_CLASSIC) return PJ_IO_UNITS_PROJECTED; return u; } /* Work around non-constness of MSVC HUGE_VAL by providing functions rather than * constants */ PJ_COORD proj_coord_error(void) { PJ_COORD c; c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL; return c; } /**************************************************************************************/ PJ_COORD pj_approx_2D_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { /*************************************************************************************** Behave mostly as proj_trans, but attempt to use 2D interfaces only. Used in gie.c, to enforce testing 2D code, and by PJ_pipeline.c to implement chained calls starting out with a call to its 2D interface. ***************************************************************************************/ if (nullptr == P) return coo; if (P->inverted) direction = static_cast(-direction); switch (direction) { case PJ_FWD: { const auto xy = pj_fwd(coo.lp, P); coo.xy = xy; return coo; } case PJ_INV: { const auto lp = pj_inv(coo.xy, P); coo.lp = lp; return coo; } case PJ_IDENT: break; } return coo; } /**************************************************************************************/ PJ_COORD pj_approx_3D_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { /*************************************************************************************** Companion to pj_approx_2D_trans. Behave mostly as proj_trans, but attempt to use 3D interfaces only. Used in gie.c, to enforce testing 3D code, and by PJ_pipeline.c to implement chained calls starting out with a call to its 3D interface. ***************************************************************************************/ if (nullptr == P) return coo; if (P->inverted) direction = static_cast(-direction); switch (direction) { case PJ_FWD: { const auto xyz = pj_fwd3d(coo.lpz, P); coo.xyz = xyz; return coo; } case PJ_INV: { const auto lpz = pj_inv3d(coo.xyz, P); coo.lpz = lpz; return coo; } case PJ_IDENT: break; } return coo; } /**************************************************************************************/ int pj_has_inverse(PJ *P) { /*************************************************************************************** Check if a a PJ has an inverse. ***************************************************************************************/ return ((P->inverted && (P->fwd || P->fwd3d || P->fwd4d)) || (P->inv || P->inv3d || P->inv4d)); } /* Move P to a new context - or to the default context if 0 is specified */ void proj_context_set(PJ *P, PJ_CONTEXT *ctx) { if (nullptr == ctx) ctx = pj_get_default_ctx(); proj_assign_context(P, ctx); } void proj_context_inherit(PJ *parent, PJ *child) { if (nullptr == parent) proj_assign_context(child, pj_get_default_ctx()); else proj_assign_context(child, pj_get_ctx(parent)); } /*****************************************************************************/ char *pj_chomp(char *c) { /****************************************************************************** Strip pre- and postfix whitespace. Inline comments (indicated by '#') are considered whitespace. ******************************************************************************/ size_t i, n; char *comment; char *start = c; if (nullptr == c) return nullptr; comment = strchr(c, '#'); if (comment) *comment = 0; n = strlen(c); if (0 == n) return c; /* Eliminate postfix whitespace */ for (i = n - 1; (i > 0) && (isspace(c[i]) || ';' == c[i]); i--) c[i] = 0; /* Find start of non-whitespace */ while (0 != *start && (';' == *start || isspace(*start))) start++; n = strlen(start); if (0 == n) { c[0] = 0; return c; } memmove(c, start, n + 1); return c; } /*****************************************************************************/ char *pj_shrink(char *c) { /****************************************************************************** Collapse repeated whitespace. Remove '+' and ';'. Make ',' and '=' greedy, consuming their surrounding whitespace. ******************************************************************************/ size_t i, j, n; /* Flag showing that a whitespace (ws) has been written after last non-ws */ bool ws = false; if (nullptr == c) return nullptr; pj_chomp(c); n = strlen(c); if (n == 0) return c; /* First collapse repeated whitespace (including +/;) */ i = 0; bool in_string = false; for (j = 0; j < n; j++) { if (in_string) { if (c[j] == '"' && c[j + 1] == '"') { c[i++] = c[j]; j++; } else if (c[j] == '"') { in_string = false; } c[i++] = c[j]; continue; } /* Eliminate prefix '+', only if preceded by whitespace */ /* (i.e. keep it in 1.23e+08) */ if ((i > 0) && ('+' == c[j]) && ws) c[j] = ' '; if ((i == 0) && ('+' == c[j])) c[j] = ' '; // Detect a string beginning after '=' if (c[j] == '"' && i > 0 && c[i - 1] == '=') { in_string = true; ws = false; c[i++] = c[j]; continue; } if (isspace(c[j]) || ';' == c[j]) { if (false == ws && (i > 0)) c[i++] = ' '; ws = true; continue; } else { ws = false; c[i++] = c[j]; } } c[i] = 0; n = strlen(c); /* Then make ',' and '=' greedy */ i = 0; for (j = 0; j < n; j++) { if (i == 0) { c[i++] = c[j]; continue; } /* Skip space before '='/',' */ if ('=' == c[j] || ',' == c[j]) { if (c[i - 1] == ' ') c[i - 1] = c[j]; else c[i++] = c[j]; continue; } if (' ' == c[j] && ('=' == c[i - 1] || ',' == c[i - 1])) continue; c[i++] = c[j]; } c[i] = 0; return c; } /*****************************************************************************/ size_t pj_trim_argc(char *args) { /****************************************************************************** Trim all unnecessary whitespace (and non-essential syntactic tokens) from the argument string, args, and count its number of elements. ******************************************************************************/ size_t i, m, n; pj_shrink(args); n = strlen(args); if (n == 0) return 0; bool in_string = false; for (i = m = 0; i < n; i++) { if (in_string) { if (args[i] == '"' && args[i + 1] == '"') { i++; } else if (args[i] == '"') { in_string = false; } } else if (args[i] == '=' && args[i + 1] == '"') { i++; in_string = true; } else if (' ' == args[i]) { args[i] = 0; m++; } } return m + 1; } static void unquote_string(char *param_str) { size_t len = strlen(param_str); // Remove leading and terminating spaces after equal sign const char *equal = strstr(param_str, "=\""); if (equal && equal - param_str + 1 >= 2 && param_str[len - 1] == '"') { size_t dst = equal + 1 - param_str; size_t src = dst + 1; for (; param_str[src]; dst++, src++) { if (param_str[src] == '"') { if (param_str[src + 1] == '"') { src++; } else { break; } } param_str[dst] = param_str[src]; } param_str[dst] = '\0'; } } /*****************************************************************************/ char **pj_trim_argv(size_t argc, char *args) { /****************************************************************************** Create an argv-style array from elements placed in the argument string, args. args is a trimmed string as returned by pj_trim_argc(), and argc is the number of trimmed strings found (i.e. the return value of pj_trim_args()). Hence, int argc = pj_trim_argc (args); char **argv = pj_trim_argv (argc, args); will produce a classic style (argc, argv) pair from a string of whitespace separated args. No new memory is allocated for storing the individual args (they stay in the args string), but for the pointers to the args a new array is allocated and returned. It is the duty of the caller to free this array. ******************************************************************************/ if (nullptr == args) return nullptr; if (0 == argc) return nullptr; /* turn the input string into an array of strings */ char **argv = (char **)calloc(argc, sizeof(char *)); if (nullptr == argv) return nullptr; for (size_t i = 0, j = 0; j < argc; j++) { argv[j] = args + i; char *str = argv[j]; size_t nLen = strlen(str); i += nLen + 1; unquote_string(str); } return argv; } /*****************************************************************************/ std::string pj_double_quote_string_param_if_needed(const std::string &str) { /*****************************************************************************/ if (str.find(' ') == std::string::npos) { return str; } std::string ret; ret += '"'; ret += replaceAll(str, "\"", "\"\""); ret += '"'; return ret; } /*****************************************************************************/ char *pj_make_args(size_t argc, char **argv) { /****************************************************************************** pj_make_args is the inverse of the pj_trim_argc/pj_trim_argv combo: It converts free format command line input to something proj_create can consume. Allocates, and returns, an array of char, large enough to hold a whitespace separated copy of the args in argv. It is the duty of the caller to free this array. ******************************************************************************/ try { std::string s; for (size_t i = 0; i < argc; i++) { const char *equal = strchr(argv[i], '='); if (equal) { s += std::string(argv[i], equal - argv[i] + 1); s += pj_double_quote_string_param_if_needed(equal + 1); } else { s += argv[i]; } s += ' '; } char *p = pj_strdup(s.c_str()); return pj_shrink(p); } catch (const std::exception &) { return nullptr; } } /*****************************************************************************/ void proj_context_errno_set(PJ_CONTEXT *ctx, int err) { /****************************************************************************** Raise an error directly on a context, without going through a PJ belonging to that context. ******************************************************************************/ if (nullptr == ctx) ctx = pj_get_default_ctx(); ctx->last_errno = err; if (err == 0) return; errno = err; } proj-9.8.1/src/mutex.cpp000664 001750 001750 00000004565 15166171715 015122 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Mutex (thread lock) functions. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2009, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include "proj.h" #include "proj_internal.h" static std::recursive_mutex core_lock; /************************************************************************/ /* pj_acquire_lock() */ /* */ /* Acquire the PROJ.4 lock. */ /************************************************************************/ void pj_acquire_lock() { core_lock.lock(); } /************************************************************************/ /* pj_release_lock() */ /* */ /* Release the PROJ.4 lock. */ /************************************************************************/ void pj_release_lock() { core_lock.unlock(); } proj-9.8.1/src/init.cpp000664 001750 001750 00000066361 15166171715 014725 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Initialize projection object from string definition. Includes * pj_init(), and pj_init_plus() function. * Author: Gerald Evenden, Frank Warmerdam * ****************************************************************************** * Copyright (c) 1995, Gerald Evenden * Copyright (c) 2002, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include #include #include #include "filemanager.hpp" #include "geodesic.h" #include "proj.h" #include "proj_internal.h" #include /**************************************************************************************/ static paralist *string_to_paralist(PJ_CONTEXT *ctx, char *definition) { /*************************************************************************************** Convert a string (presumably originating from get_init_string) to a paralist. ***************************************************************************************/ const char *c = definition; paralist *first = nullptr, *last = nullptr; while (*c) { /* Keep a handle to the start of the list, so we have something to * return */ auto param = pj_mkparam_ws(c, &c); if (nullptr == param) { free_params(ctx, first, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } if (nullptr == last) { first = param; } else { last->next = param; } last = param; } return first; } /**************************************************************************************/ static char *get_init_string(PJ_CONTEXT *ctx, const char *name) { /*************************************************************************************** Read a section of an init file. Return its contents as a plain character string. It is the duty of the caller to free the memory allocated for the string. ***************************************************************************************/ #define MAX_LINE_LENGTH 1000 size_t current_buffer_size = 5 * (MAX_LINE_LENGTH + 1); char *fname, *section; const char *key; char *buffer = nullptr; size_t n; fname = static_cast(malloc(MAX_PATH_FILENAME + ID_TAG_MAX + 3)); if (nullptr == fname) { return nullptr; } /* Support "init=file:section", "+init=file:section", and "file:section" * format */ key = strstr(name, "init="); if (nullptr == key) key = name; else key += 5; if (MAX_PATH_FILENAME + ID_TAG_MAX + 2 < strlen(key)) { free(fname); return nullptr; } memmove(fname, key, strlen(key) + 1); /* Locate the name of the section we search for */ section = strrchr(fname, ':'); if (nullptr == section) { pj_log(ctx, PJ_LOG_ERROR, _("Missing colon in +init")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); free(fname); return nullptr; } *section = 0; section++; n = strlen(section); pj_log(ctx, PJ_LOG_TRACE, "get_init_string: searching for section [%s] in init file [%s]", section, fname); auto file = NS_PROJ::FileManager::open_resource_file(ctx, fname); if (nullptr == file) { pj_log(ctx, PJ_LOG_ERROR, _("Cannot open %s"), fname); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); free(fname); return nullptr; } /* Search for section in init file */ std::string line; for (;;) { bool eofReached = false; bool maxLenReached = false; line = file->read_line(MAX_LINE_LENGTH, maxLenReached, eofReached); /* End of file? */ if (maxLenReached || eofReached) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid content for %s"), fname); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); free(fname); return nullptr; } /* At start of right section? */ pj_chomp(&line[0]); if ('<' != line[0]) continue; if (strlen(line.c_str()) < n + 2) continue; if (line[n + 1] != '>') continue; if (0 == strncmp(line.data() + 1, section, n)) break; } /* We're at the first line of the right section - copy line to buffer */ buffer = static_cast(malloc(current_buffer_size)); if (nullptr == buffer) { free(fname); return nullptr; } /* Skip the "
" indicator, and copy the rest of the line over */ strcpy(buffer, line.data() + strlen(section) + 2); /* Copy the remaining lines of the section to buffer */ for (;;) { char *end_i_cator; size_t next_length, buffer_length; /* Did the section end somewhere in the most recently read line? */ end_i_cator = strchr(buffer, '<'); if (end_i_cator) { *end_i_cator = 0; break; } bool eofReached = false; bool maxLenReached = false; line = file->read_line(MAX_LINE_LENGTH, maxLenReached, eofReached); /* End of file? - done! */ if (maxLenReached || eofReached) break; /* Otherwise, handle the line. It MAY be the start of the next section, */ /* but that will be handled at the start of next trip through the loop */ buffer_length = strlen(buffer); pj_chomp(&line[0]); /* Remove '#' style comments */ next_length = strlen(line.data()) + buffer_length + 2; if (next_length > current_buffer_size) { char *b = static_cast(malloc(2 * current_buffer_size)); if (nullptr == b) { free(buffer); buffer = nullptr; break; } strcpy(b, buffer); current_buffer_size *= 2; free(buffer); buffer = b; } buffer[buffer_length] = ' '; strcpy(buffer + buffer_length + 1, line.data()); } free(fname); if (nullptr == buffer) return nullptr; pj_shrink(buffer); pj_log(ctx, PJ_LOG_TRACE, "key=%s, value: [%s]", key, buffer); return buffer; } /************************************************************************/ static paralist *get_init(PJ_CONTEXT *ctx, const char *key, int allow_init_epsg) { /************************************************************************* Expand key from buffer or (if not in buffer) from init file *************************************************************************/ const char *xkey; char *definition = nullptr; paralist *init_items = nullptr; if (!ctx) { ctx = pj_get_default_ctx(); } /* support "init=file:section", "+init=file:section", and "file:section" * format */ xkey = strstr(key, "init="); if (nullptr == xkey) xkey = key; else xkey += 5; pj_log(ctx, PJ_LOG_TRACE, "get_init: searching cache for key: [%s]", xkey); /* Is file/key pair already in cache? */ init_items = pj_search_initcache(xkey); if (init_items) return init_items; if ((strncmp(xkey, "epsg:", 5) == 0 || strncmp(xkey, "IGNF:", 5) == 0)) { char unused[256]; char initname[5]; int exists; strncpy(initname, xkey, 4); initname[4] = 0; if (strncmp(xkey, "epsg:", 5) == 0) { exists = ctx->epsg_file_exists; if (exists < 0) { exists = pj_find_file(ctx, initname, unused, sizeof(unused)); ctx->epsg_file_exists = exists; } } else { exists = pj_find_file(ctx, initname, unused, sizeof(unused)); } if (!exists) { char szInitStr[7 + 64]; PJ *src; const char *proj_string; proj_context_errno_set(ctx, 0); if (!allow_init_epsg) { pj_log(ctx, PJ_LOG_TRACE, "%s expansion disallowed", xkey); return nullptr; } if (strlen(xkey) > 64) { return nullptr; } strcpy(szInitStr, "+init="); strcat(szInitStr, xkey); auto old_proj4_init_rules = ctx->use_proj4_init_rules; ctx->use_proj4_init_rules = true; src = proj_create(ctx, szInitStr); ctx->use_proj4_init_rules = old_proj4_init_rules; if (!src) { return nullptr; } proj_string = proj_as_proj_string(ctx, src, PJ_PROJ_4, nullptr); if (!proj_string) { proj_destroy(src); return nullptr; } definition = (char *)calloc(1, strlen(proj_string) + 1); if (definition) { strcpy(definition, proj_string); } proj_destroy(src); } } if (!definition) { /* If not, we must read it from file */ pj_log(ctx, PJ_LOG_TRACE, "get_init: searching on in init files for [%s]", xkey); definition = get_init_string(ctx, xkey); } if (nullptr == definition) return nullptr; init_items = string_to_paralist(ctx, definition); if (init_items) pj_log(ctx, PJ_LOG_TRACE, "get_init: got [%s], paralist[0,1]: [%s,%s]", definition, init_items->param, init_items->next ? init_items->next->param : "(empty)"); free(definition); if (nullptr == init_items) return nullptr; /* We found it in file - now insert into the cache, before returning */ pj_insert_initcache(xkey, init_items); return init_items; } static void append_default_ellipsoid_to_paralist(paralist *start) { if (nullptr == start) return; /* Set defaults, unless inhibited (either explicitly through a "no_defs" * token */ /* or implicitly, because we are initializing a pipeline) */ if (pj_param_exists(start, "no_defs")) return; auto proj = pj_param_exists(start, "proj"); if (nullptr == proj) return; if (strlen(proj->param) < 6) return; if (0 == strcmp("pipeline", proj->param + 5)) return; /* Don't default ellipse if datum, ellps or any ellipsoid information is set */ if (pj_param_exists(start, "datum")) return; if (pj_param_exists(start, "ellps")) return; if (pj_param_exists(start, "a")) return; if (pj_param_exists(start, "b")) return; if (pj_param_exists(start, "rf")) return; if (pj_param_exists(start, "f")) return; if (pj_param_exists(start, "e")) return; if (pj_param_exists(start, "es")) return; /* Locate end of start-list */ paralist *last = nullptr; for (last = start; last->next; last = last->next) ; /* If we're here, it's OK to append the current default item */ last->next = pj_mkparam("ellps=GRS80"); } /*****************************************************************************/ static paralist *pj_expand_init_internal(PJ_CONTEXT *ctx, paralist *init, int allow_init_epsg) { /****************************************************************************** Append expansion of to the paralist . The expansion is appended, rather than inserted at 's place, since may contain overrides to the expansion. These must take precedence, and hence come first in the expanded list. Consider e.g. the key 'foo:bar' which (hypothetically) expands to 'proj=utm zone=32 ellps=GRS80', i.e. a UTM projection on the GRS80 ellipsoid. The expression 'init=foo:bar ellps=intl' will then expand to: 'init=foo:bar ellps=intl proj=utm zone=32 ellps=GRS80', where 'ellps=intl' precedes 'ellps=GRS80', and hence takes precedence, turning the expansion into an UTM projection on the Hayford ellipsoid. Note that 'init=foo:bar' stays in the list. It is ignored after expansion. ******************************************************************************/ paralist *last; paralist *expn; /* Nowhere to start? */ if (nullptr == init) return nullptr; expn = get_init(ctx, init->param, allow_init_epsg); /* Nothing in expansion? */ if (nullptr == expn) return nullptr; /* Locate the end of the list */ for (last = init; last && last->next; last = last->next) ; /* Then append and return */ last->next = expn; return init; } paralist *pj_expand_init(PJ_CONTEXT *ctx, paralist *init) { return pj_expand_init_internal(ctx, init, TRUE); } /************************************************************************/ /* pj_init() */ /* */ /* Main entry point for initialing a PJ projections */ /* definition. Note that the projection specific function is */ /* called to do the initial allocation so it can be created */ /* large enough to hold projection specific parameters. */ /************************************************************************/ static PJ_CONSTRUCTOR locate_constructor(const char *name) { int i; const char *s; const PJ_OPERATIONS *operations; operations = proj_list_operations(); for (i = 0; (s = operations[i].id) && strcmp(name, s); ++i) ; if (nullptr == s) return nullptr; return (PJ_CONSTRUCTOR)operations[i].proj; } PJ *pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int allow_init_epsg) { const char *s; char *name; PJ_CONSTRUCTOR proj; paralist *curr, *init, *start; int i; int err; PJ *PIN = nullptr; int n_pipelines = 0; int n_inits = 0; const PJ_UNITS *units; const PJ_PRIME_MERIDIANS *prime_meridians; if (nullptr == ctx) ctx = pj_get_default_ctx(); ctx->last_errno = 0; if (argc <= 0) { pj_log(ctx, PJ_LOG_ERROR, _("No arguments")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } /* count occurrences of pipelines and inits */ for (i = 0; i < argc; ++i) { if (!strcmp(argv[i], "+proj=pipeline") || !strcmp(argv[i], "proj=pipeline")) n_pipelines++; if (!strncmp(argv[i], "+init=", 6) || !strncmp(argv[i], "init=", 5)) n_inits++; } /* can't have nested pipelines directly */ if (n_pipelines > 1) { pj_log(ctx, PJ_LOG_ERROR, _("Nested pipelines are not supported")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); return nullptr; } /* don't allow more than one +init in non-pipeline operations */ if (n_pipelines == 0 && n_inits > 1) { pj_log(ctx, PJ_LOG_ERROR, _("Too many inits")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); return nullptr; } /* put arguments into internal linked list */ start = curr = pj_mkparam(argv[0]); if (!curr) { free_params(ctx, start, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } for (i = 1; i < argc; ++i) { curr->next = pj_mkparam(argv[i]); if (!curr->next) { free_params(ctx, start, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } curr = curr->next; } /* Only expand '+init's in non-pipeline operations. '+init's in pipelines * are */ /* expanded in the individual pipeline steps during pipeline initialization. */ /* Potentially this leads to many nested pipelines, which shouldn't be a */ /* problem when '+init's are expanded as late as possible. */ init = pj_param_exists(start, "init"); if (init && n_pipelines == 0) { init = pj_expand_init_internal(ctx, init, allow_init_epsg); if (!init) { free_params(ctx, start, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); return nullptr; } } if (ctx->last_errno) { free_params(ctx, start, ctx->last_errno); return nullptr; } /* Find projection selection */ curr = pj_param_exists(start, "proj"); if (nullptr == curr) { pj_log(ctx, PJ_LOG_ERROR, _("Missing proj")); free_params(ctx, start, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } name = curr->param; if (strlen(name) < 6) { pj_log(ctx, PJ_LOG_ERROR, _("Invalid value for proj")); free_params(ctx, start, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return nullptr; } name += 5; proj = locate_constructor(name); if (nullptr == proj) { pj_log(ctx, PJ_LOG_ERROR, _("Unknown projection")); free_params(ctx, start, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return nullptr; } append_default_ellipsoid_to_paralist(start); /* Allocate projection structure */ PIN = proj(nullptr); if (nullptr == PIN) { free_params(ctx, start, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } PIN->ctx = ctx; PIN->params = start; PIN->is_latlong = 0; PIN->is_geocent = 0; PIN->is_long_wrap_set = 0; PIN->long_wrap_center = 0.0; strcpy(PIN->axis, "enu"); /* Set datum parameters. Similarly to +init parameters we want to expand */ /* +datum parameters as late as possible when dealing with pipelines. */ /* otherwise only the first occurrence of +datum will be expanded and that */ if (n_pipelines == 0) { if (pj_datum_set(ctx, start, PIN)) return pj_default_destructor(PIN, proj_errno(PIN)); } err = pj_ellipsoid(PIN); if (err) { /* Didn't get an ellps, but doesn't need one: Get a free WGS84 */ if (PIN->need_ellps) { pj_log(ctx, PJ_LOG_ERROR, _("pj_init_ctx: Must specify ellipsoid or sphere")); return pj_default_destructor(PIN, proj_errno(PIN)); } else { if (PIN->a == 0) proj_errno_reset(PIN); PIN->f = 1.0 / 298.257223563; PIN->a = 6378137.0; PIN->es = PIN->f * (2 - PIN->f); } } PIN->a_orig = PIN->a; PIN->es_orig = PIN->es; if (pj_calc_ellipsoid_params(PIN, PIN->a, PIN->es)) return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); /* Now that we have ellipse information check for WGS84 datum */ if (PIN->datum_type == PJD_3PARAM && PIN->datum_params[0] == 0.0 && PIN->datum_params[1] == 0.0 && PIN->datum_params[2] == 0.0 && PIN->a == 6378137.0 && ABS(PIN->es - 0.006694379990) < 0.000000000050) /*WGS84/GRS80*/ { PIN->datum_type = PJD_WGS84; } /* Set PIN->geoc coordinate system */ PIN->geoc = (PIN->es != 0.0 && pj_param(ctx, start, "bgeoc").i); /* Over-ranging flag */ PIN->over = pj_param(ctx, start, "bover").i; if (ctx->forceOver) { PIN->over = ctx->forceOver; } /* Vertical datum geoid grids */ PIN->has_geoid_vgrids = pj_param(ctx, start, "tgeoidgrids").i; if (PIN->has_geoid_vgrids) /* we need to mark it as used. */ pj_param(ctx, start, "sgeoidgrids"); /* Longitude center for wrapping */ PIN->is_long_wrap_set = pj_param(ctx, start, "tlon_wrap").i; if (PIN->is_long_wrap_set) { PIN->long_wrap_center = pj_param(ctx, start, "rlon_wrap").f; /* Don't accept excessive values otherwise we might perform badly */ /* when correcting longitudes around it */ /* The test is written this way to error on long_wrap_center "=" NaN */ if (!(fabs(PIN->long_wrap_center) < 10 * M_TWOPI)) { proj_log_error(PIN, _("Invalid value for lon_wrap")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } /* Axis orientation */ if ((pj_param(ctx, start, "saxis").s) != nullptr) { const char *axis_legal = "ewnsud"; const char *axis_arg = pj_param(ctx, start, "saxis").s; if (strlen(axis_arg) != 3) { proj_log_error(PIN, _("Invalid value for axis")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (strchr(axis_legal, axis_arg[0]) == nullptr || strchr(axis_legal, axis_arg[1]) == nullptr || strchr(axis_legal, axis_arg[2]) == nullptr) { proj_log_error(PIN, _("Invalid value for axis")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* TODO: it would be nice to validate we don't have on axis repeated */ strcpy(PIN->axis, axis_arg); } /* Central meridian */ PIN->lam0 = pj_param(ctx, start, "rlon_0").f; /* Central latitude */ PIN->phi0 = pj_param(ctx, start, "rlat_0").f; if (fabs(PIN->phi0) > M_HALFPI) { proj_log_error(PIN, _("Invalid value for lat_0: |lat_0| should be <= 90°")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* False easting and northing */ PIN->x0 = pj_param(ctx, start, "dx_0").f; PIN->y0 = pj_param(ctx, start, "dy_0").f; PIN->z0 = pj_param(ctx, start, "dz_0").f; PIN->t0 = pj_param(ctx, start, "dt_0").f; /* General scaling factor */ if (pj_param(ctx, start, "tk_0").i) PIN->k0 = pj_param(ctx, start, "dk_0").f; else if (pj_param(ctx, start, "tk").i) PIN->k0 = pj_param(ctx, start, "dk").f; else PIN->k0 = 1.; if (PIN->k0 <= 0.) { proj_log_error(PIN, _("Invalid value for k/k_0: it should be > 0")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* Set units */ units = pj_list_linear_units(); s = nullptr; if ((name = pj_param(ctx, start, "sunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s); ++i) ; if (!s) { proj_log_error(PIN, _("Invalid value for units")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } s = units[i].to_meter; } if (s || (s = pj_param(ctx, start, "sto_meter").s)) { char *end_ptr = const_cast(s); PIN->to_meter = pj_strtod(s, &end_ptr); s = end_ptr; if (*s == '/') { /* ratio number */ ++s; double denom = pj_strtod(s, nullptr); if (denom == 0.0) { proj_log_error(PIN, _("Invalid value for to_meter donominator")); return pj_default_destructor( PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } PIN->to_meter /= denom; } if (PIN->to_meter <= 0.0) { proj_log_error(PIN, _("Invalid value for to_meter")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } PIN->fr_meter = 1 / PIN->to_meter; } else PIN->to_meter = PIN->fr_meter = 1.; /* Set vertical units */ s = nullptr; if ((name = pj_param(ctx, start, "svunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s); ++i) ; if (!s) { proj_log_error(PIN, _("Invalid value for vunits")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } s = units[i].to_meter; } if (s || (s = pj_param(ctx, start, "svto_meter").s)) { char *end_ptr = const_cast(s); PIN->vto_meter = pj_strtod(s, &end_ptr); s = end_ptr; if (*s == '/') { /* ratio number */ ++s; double denom = pj_strtod(s, nullptr); if (denom == 0.0) { proj_log_error(PIN, _("Invalid value for vto_meter donominator")); return pj_default_destructor( PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } PIN->vto_meter /= denom; } if (PIN->vto_meter <= 0.0) { proj_log_error(PIN, _("Invalid value for vto_meter")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } PIN->vfr_meter = 1. / PIN->vto_meter; } else { PIN->vto_meter = PIN->to_meter; PIN->vfr_meter = PIN->fr_meter; } /* Prime meridian */ prime_meridians = proj_list_prime_meridians(); s = nullptr; if ((name = pj_param(ctx, start, "spm").s) != nullptr) { const char *value = nullptr; char *next_str = nullptr; for (i = 0; prime_meridians[i].id != nullptr; ++i) { if (strcmp(name, prime_meridians[i].id) == 0) { value = prime_meridians[i].defn; break; } } if (value == nullptr && (dmstor_ctx(ctx, name, &next_str) != 0.0 || *name == '0') && *next_str == '\0') value = name; if (!value) { proj_log_error(PIN, _("Invalid value for pm")); return pj_default_destructor(PIN, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } PIN->from_greenwich = dmstor_ctx(ctx, value, nullptr); } else PIN->from_greenwich = 0.0; /* Private object for the geodesic functions */ PIN->geod = static_cast( calloc(1, sizeof(struct geod_geodesic))); if (nullptr == PIN->geod) return pj_default_destructor(PIN, PROJ_ERR_OTHER /*ENOMEM*/); geod_init(PIN->geod, PIN->a, PIN->f); /* Projection specific initialization */ err = proj_errno_reset(PIN); PIN = proj(PIN); if (proj_errno(PIN)) { proj_destroy(PIN); return nullptr; } proj_errno_restore(PIN, err); return PIN; } proj-9.8.1/src/inv.cpp000664 001750 001750 00000020052 15166171715 014541 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Inverse operation invocation * Author: Thomas Knudsen, thokn@sdfe.dk, 2018-01-02 * Based on material from Gerald Evenden (original pj_inv) * and Piyush Agram (original pj_inv3d) * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * Copyright (c) 2018, Thomas Knudsen / SDFE * * 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 #include #include "proj_internal.h" #include #define INPUT_UNITS P->right #define OUTPUT_UNITS P->left static void inv_prepare(PJ *P, PJ_COORD &coo) { if (coo.v[0] == HUGE_VAL || coo.v[1] == HUGE_VAL || coo.v[2] == HUGE_VAL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); coo = proj_coord_error(); return; } /* The helmert datum shift will choke unless it gets a sensible 4D * coordinate */ if (HUGE_VAL == coo.v[2] && P->helmert) coo.v[2] = 0.0; if (HUGE_VAL == coo.v[3] && P->helmert) coo.v[3] = 0.0; if (P->axisswap) coo = proj_trans(P->axisswap, PJ_INV, coo); /* Handle remaining possible input types */ switch (INPUT_UNITS) { case PJ_IO_UNITS_WHATEVER: break; case PJ_IO_UNITS_DEGREES: break; /* de-scale and de-offset */ case PJ_IO_UNITS_CARTESIAN: coo.xyz.x *= P->to_meter; coo.xyz.y *= P->to_meter; coo.xyz.z *= P->to_meter; if (P->is_geocent) { coo = proj_trans(P->cart, PJ_INV, coo); } break; case PJ_IO_UNITS_PROJECTED: case PJ_IO_UNITS_CLASSIC: coo.xyz.x = P->to_meter * coo.xyz.x - P->x0; coo.xyz.y = P->to_meter * coo.xyz.y - P->y0; coo.xyz.z = P->vto_meter * coo.xyz.z - P->z0; if (INPUT_UNITS == PJ_IO_UNITS_PROJECTED) return; /* Classic proj.4 functions expect plane coordinates in units of the * semimajor axis */ /* Multiplying by ra, rather than dividing by a because the CalCOFI * projection */ /* stomps on a and hence (apparently) depends on this to roundtrip * correctly */ /* (CalCOFI avoids further scaling by stomping - but a better solution * is possible) */ coo.xyz.x *= P->ra; coo.xyz.y *= P->ra; break; case PJ_IO_UNITS_RADIANS: coo.lpz.z = P->vto_meter * coo.lpz.z - P->z0; break; } } static void inv_finalize(PJ *P, PJ_COORD &coo) { if (coo.xyz.x == HUGE_VAL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); coo = proj_coord_error(); } if (OUTPUT_UNITS == PJ_IO_UNITS_RADIANS) { /* Distance from central meridian, taking system zero meridian into * account */ coo.lp.lam = coo.lp.lam + P->from_greenwich + P->lam0; /* adjust longitude to central meridian */ if (0 == P->over) coo.lpz.lam = adjlon(coo.lpz.lam); if (P->vgridshift) coo = proj_trans(P->vgridshift, PJ_INV, coo); /* Go geometric from orthometric */ if (coo.lp.lam == HUGE_VAL) return; if (P->hgridshift) coo = proj_trans(P->hgridshift, PJ_FWD, coo); else if (P->helmert || (P->cart_wgs84 != nullptr && P->cart != nullptr)) { coo = proj_trans(P->cart, PJ_FWD, coo); /* Go cartesian in local frame */ if (P->helmert) coo = proj_trans(P->helmert, PJ_FWD, coo); /* Step into WGS84 */ coo = proj_trans(P->cart_wgs84, PJ_INV, coo); /* Go back to angular using WGS84 ellps */ } if (coo.lp.lam == HUGE_VAL) return; /* If input latitude was geocentrical, convert back to geocentrical */ if (P->geoc) coo = pj_geocentric_latitude(P, PJ_FWD, coo); } } static inline PJ_COORD error_or_coord(PJ *P, PJ_COORD coord, int last_errno) { if (P->ctx->last_errno) return proj_coord_error(); P->ctx->last_errno = last_errno; return coord; } PJ_LP pj_inv(PJ_XY xy, PJ *P) { PJ_COORD coo = {{0, 0, 0, 0}}; coo.xy = xy; const int last_errno = P->ctx->last_errno; P->ctx->last_errno = 0; if (!P->skip_inv_prepare) inv_prepare(P, coo); if (HUGE_VAL == coo.v[0]) return proj_coord_error().lp; /* Do the transformation, using the lowest dimensional transformer available */ if (P->inv) { const auto lp = P->inv(coo.xy, P); coo.lp = lp; } else if (P->inv3d) { const auto lpz = P->inv3d(coo.xyz, P); coo.lpz = lpz; } else if (P->inv4d) P->inv4d(coo, P); else { proj_errno_set(P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error().lp; } if (HUGE_VAL == coo.v[0]) return proj_coord_error().lp; if (!P->skip_inv_finalize) inv_finalize(P, coo); return error_or_coord(P, coo, last_errno).lp; } PJ_LPZ pj_inv3d(PJ_XYZ xyz, PJ *P) { PJ_COORD coo = {{0, 0, 0, 0}}; coo.xyz = xyz; const int last_errno = P->ctx->last_errno; P->ctx->last_errno = 0; if (!P->skip_inv_prepare) inv_prepare(P, coo); if (HUGE_VAL == coo.v[0]) return proj_coord_error().lpz; /* Do the transformation, using the lowest dimensional transformer feasible */ if (P->inv3d) { const auto lpz = P->inv3d(coo.xyz, P); coo.lpz = lpz; } else if (P->inv4d) P->inv4d(coo, P); else if (P->inv) { const auto lp = P->inv(coo.xy, P); coo.lp = lp; } else { proj_errno_set(P, PROJ_ERR_OTHER_NO_INVERSE_OP); return proj_coord_error().lpz; } if (HUGE_VAL == coo.v[0]) return proj_coord_error().lpz; if (!P->skip_inv_finalize) inv_finalize(P, coo); return error_or_coord(P, coo, last_errno).lpz; } bool pj_inv4d(PJ_COORD &coo, PJ *P) { const int last_errno = P->ctx->last_errno; P->ctx->last_errno = 0; if (!P->skip_inv_prepare) inv_prepare(P, coo); if (HUGE_VAL == coo.v[0]) { coo = proj_coord_error(); return false; } /* Call the highest dimensional converter available */ if (P->inv4d) P->inv4d(coo, P); else if (P->inv3d) { const auto lpz = P->inv3d(coo.xyz, P); coo.lpz = lpz; } else if (P->inv) { const auto lp = P->inv(coo.xy, P); coo.lp = lp; } else { proj_errno_set(P, PROJ_ERR_OTHER_NO_INVERSE_OP); coo = proj_coord_error(); return false; } if (HUGE_VAL == coo.v[0]) { coo = proj_coord_error(); return false; } if (!P->skip_inv_finalize) inv_finalize(P, coo); if (P->ctx->last_errno) { coo = proj_coord_error(); return false; } P->ctx->last_errno = last_errno; return true; } proj-9.8.1/src/ellps.cpp000664 001750 001750 00000006346 15166171715 015076 0ustar00eveneven000000 000000 /* definition of standard geoids */ #include #include "proj.h" #include "proj_internal.h" static const struct PJ_ELLPS pj_ellps[] = { {"MERIT", "a=6378137.0", "rf=298.257", "MERIT 1983"}, {"SGS85", "a=6378136.0", "rf=298.257", "Soviet Geodetic System 85"}, {"GRS80", "a=6378137.0", "rf=298.257222101", "GRS 1980(IUGG, 1980)"}, {"IAU76", "a=6378140.0", "rf=298.257", "IAU 1976"}, {"airy", "a=6377563.396", "rf=299.3249646", "Airy 1830"}, {"APL4.9", "a=6378137.0", "rf=298.25", "Appl. Physics. 1965"}, {"NWL9D", "a=6378145.0", "rf=298.25", "Naval Weapons Lab., 1965"}, {"mod_airy", "a=6377340.189", "b=6356034.446", "Modified Airy"}, {"andrae", "a=6377104.43", "rf=300.0", "Andrae 1876 (Den., Iclnd.)"}, {"danish", "a=6377019.2563", "rf=300.0", "Andrae 1876 (Denmark, Iceland)"}, {"aust_SA", "a=6378160.0", "rf=298.25", "Australian Natl & S. Amer. 1969"}, {"GRS67", "a=6378160.0", "rf=298.2471674270", "GRS 67(IUGG 1967)"}, {"GSK2011", "a=6378136.5", "rf=298.2564151", "GSK-2011"}, {"bessel", "a=6377397.155", "rf=299.1528128", "Bessel 1841"}, {"bess_nam", "a=6377483.865", "rf=299.1528128", "Bessel 1841 (Namibia)"}, {"clrk66", "a=6378206.4", "b=6356583.8", "Clarke 1866"}, {"clrk80", "a=6378249.145", "rf=293.4663", "Clarke 1880 mod."}, {"clrk80ign", "a=6378249.2", "rf=293.4660212936269", "Clarke 1880 (IGN)."}, {"CPM", "a=6375738.7", "rf=334.29", "Comm. des Poids et Mesures 1799"}, {"delmbr", "a=6376428.", "rf=311.5", "Delambre 1810 (Belgium)"}, {"engelis", "a=6378136.05", "rf=298.2566", "Engelis 1985"}, {"evrst30", "a=6377276.345", "rf=300.8017", "Everest 1830"}, {"evrst48", "a=6377304.063", "rf=300.8017", "Everest 1948"}, {"evrst56", "a=6377301.243", "rf=300.8017", "Everest 1956"}, {"evrst69", "a=6377295.664", "rf=300.8017", "Everest 1969"}, {"evrstSS", "a=6377298.556", "rf=300.8017", "Everest (Sabah & Sarawak)"}, {"fschr60", "a=6378166.", "rf=298.3", "Fischer (Mercury Datum) 1960"}, {"fschr60m", "a=6378155.", "rf=298.3", "Modified Fischer 1960"}, {"fschr68", "a=6378150.", "rf=298.3", "Fischer 1968"}, {"helmert", "a=6378200.", "rf=298.3", "Helmert 1906"}, {"hough", "a=6378270.0", "rf=297.", "Hough"}, {"intl", "a=6378388.0", "rf=297.", "International 1924 (Hayford 1909, 1910)"}, {"krass", "a=6378245.0", "rf=298.3", "Krassovsky, 1942"}, {"kaula", "a=6378163.", "rf=298.24", "Kaula 1961"}, {"lerch", "a=6378139.", "rf=298.257", "Lerch 1979"}, {"mprts", "a=6397300.", "rf=191.", "Maupertius 1738"}, {"new_intl", "a=6378157.5", "b=6356772.2", "New International 1967"}, {"plessis", "a=6376523.", "b=6355863.", "Plessis 1817 (France)"}, {"PZ90", "a=6378136.0", "rf=298.25784", "PZ-90"}, {"SEasia", "a=6378155.0", "b=6356773.3205", "Southeast Asia"}, {"walbeck", "a=6376896.0", "b=6355834.8467", "Walbeck"}, {"WGS60", "a=6378165.0", "rf=298.3", "WGS 60"}, {"WGS66", "a=6378145.0", "rf=298.25", "WGS 66"}, {"WGS72", "a=6378135.0", "rf=298.26", "WGS 72"}, {"WGS84", "a=6378137.0", "rf=298.257223563", "WGS 84"}, {"sphere", "a=6370997.0", "b=6370997.0", "Normal Sphere (r=6370997)"}, {nullptr, nullptr, nullptr, nullptr}}; const PJ_ELLPS *proj_list_ellps(void) { return pj_ellps; } proj-9.8.1/src/geodesic.c000664 001750 001750 00000217000 15166171715 015170 0ustar00eveneven000000 000000 /** * \file geodesic.c * \brief Implementation of the geodesic routines in C * * For the full documentation see geodesic.h. **********************************************************************/ /** @cond SKIP */ /* * This is a C implementation of the geodesic algorithms described in * * C. F. F. Karney, * Algorithms for geodesics, * J. Geodesy 87, 43--55 (2013); * https://doi.org/10.1007/s00190-012-0578-z * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html * * See the comments in geodesic.h for documentation. * * Copyright (c) Charles Karney (2012-2025) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ */ #include "geodesic.h" #include #include #if defined(__cplusplus) #define NULLPTR nullptr #elif defined(NULL) #define NULLPTR NULL #else #define NULLPTR 0 #endif #define GEOGRAPHICLIB_GEODESIC_ORDER 6 #define nA1 GEOGRAPHICLIB_GEODESIC_ORDER #define nC1 GEOGRAPHICLIB_GEODESIC_ORDER #define nC1p GEOGRAPHICLIB_GEODESIC_ORDER #define nA2 GEOGRAPHICLIB_GEODESIC_ORDER #define nC2 GEOGRAPHICLIB_GEODESIC_ORDER #define nA3 GEOGRAPHICLIB_GEODESIC_ORDER #define nA3x nA3 #define nC3 GEOGRAPHICLIB_GEODESIC_ORDER #define nC3x ((nC3 * (nC3 - 1)) / 2) #define nC4 GEOGRAPHICLIB_GEODESIC_ORDER #define nC4x ((nC4 * (nC4 + 1)) / 2) #define nC (GEOGRAPHICLIB_GEODESIC_ORDER + 1) typedef int boolx; enum booly { FALSE = 0, TRUE = 1 }; /* qd = quarter turn / degree * hd = half turn / degree * td = full turn / degree */ enum dms { qd = 90, hd = 2 * qd, td = 2 * hd }; static unsigned init = 0; static unsigned digits, maxit1, maxit2; static double epsilon, realmin, pi, degree, NaN, tiny, tol0, tol1, tol2, tolb, xthresh; static void Init(void) { if (!init) { digits = DBL_MANT_DIG; epsilon = DBL_EPSILON; realmin = DBL_MIN; #if defined(M_PI) pi = M_PI; #else pi = atan2(0.0, -1.0); #endif maxit1 = 20; maxit2 = maxit1 + digits + 10; tiny = sqrt(realmin); tol0 = epsilon; /* Increase multiplier in defn of tol1 from 100 to 200 to fix inverse case * 52.784459512564 0 -52.784459512563990912 179.634407464943777557 * which otherwise failed for Visual Studio 10 (Release and Debug) */ tol1 = 200 * tol0; tol2 = sqrt(tol0); /* Check on bisection interval */ tolb = tol0; xthresh = 1000 * tol2; degree = pi/hd; NaN = nan("0"); init = 1; } } enum captype { CAP_NONE = 0U, CAP_C1 = 1U<<0, CAP_C1p = 1U<<1, CAP_C2 = 1U<<2, CAP_C3 = 1U<<3, CAP_C4 = 1U<<4, CAP_ALL = 0x1FU, OUT_ALL = 0x7F80U }; static double sq(double x) { return x * x; } static double sumx(double u, double v, double* t) { volatile double s = u + v; volatile double up = s - v; volatile double vpp = s - up; up -= u; vpp -= v; if (t) *t = s != 0 ? 0 - (up + vpp) : s; /* error-free sum: * u + v = s + t * = round(u + v) + t */ return s; } static double polyvalx(int N, const double p[], double x) { double y = N < 0 ? 0 : *p++; while (--N >= 0) y = y * x + *p++; return y; } static void swapx(double* x, double* y) { double t = *x; *x = *y; *y = t; } static void norm2(double* sinx, double* cosx) { #if defined(_MSC_VER) && defined(_M_IX86) /* hypot for Visual Studio (A=win32) fails monotonicity, e.g., with * x = 0.6102683302836215 * y1 = 0.7906090004346522 * y2 = y1 + 1e-16 * the test * hypot(x, y2) >= hypot(x, y1) * fails. See also * https://bugs.python.org/issue43088 */ double r = sqrt(*sinx * *sinx + *cosx * *cosx); #else double r = hypot(*sinx, *cosx); #endif *sinx /= r; *cosx /= r; } static double AngNormalize(double x) { double y = remainder(x, (double)td); return fabs(y) == hd ? copysign((double)hd, x) : y; } static double LatFix(double x) { return fabs(x) > qd ? NaN : x; } static double AngDiff(double x, double y, double* e) { /* Use remainder instead of AngNormalize, since we treat boundary cases * later taking account of the error */ double t, d = sumx(remainder(-x, (double)td), remainder( y, (double)td), &t); /* This second sum can only change d if abs(d) < 128, so don't need to * apply remainder yet again. */ d = sumx(remainder(d, (double)td), t, &t); /* Fix the sign if d = -180, 0, 180. */ if (d == 0 || fabs(d) == hd) /* If t == 0, take sign from y - x * else (t != 0, implies d = +/-180), d and t must have opposite signs */ d = copysign(d, t == 0 ? y - x : -t); if (e) *e = t; return d; } static double AngRound(double x) { /* False positive in cppcheck requires "1.0" instead of "1" */ const double z = 1.0/16.0; volatile double y = fabs(x); volatile double w = z - y; /* The compiler mustn't "simplify" z - (z - y) to y */ y = w > 0 ? z - w : y; return copysign(y, x); } static void sincosdx(double x, double* sinx, double* cosx) { /* In order to minimize round-off errors, this function exactly reduces * the argument to the range [-45, 45] before converting it to radians. */ double r, s, c; int q = 0; r = remquo(x, (double)qd, &q); /* now abs(r) <= 45 */ r *= degree; /* Possibly could call the gnu extension sincos */ s = sin(r); c = cos(r); switch ((unsigned)q & 3U) { case 0U: *sinx = s; *cosx = c; break; case 1U: *sinx = c; *cosx = -s; break; case 2U: *sinx = -s; *cosx = -c; break; default: *sinx = -c; *cosx = s; break; /* case 3U */ } /* http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf */ *cosx += 0; /* special values from F.10.1.12 */ /* special values from F.10.1.13 */ if (*sinx == 0) *sinx = copysign(*sinx, x); } static void sincosde(double x, double t, double* sinx, double* cosx) { /* In order to minimize round-off errors, this function exactly reduces * the argument to the range [-45, 45] before converting it to radians. */ double r, s, c; int q = 0; r = AngRound(remquo(x, (double)qd, &q) + t); /* now abs(r) <= 45 */ r *= degree; /* Possibly could call the gnu extension sincos */ s = sin(r); c = cos(r); switch ((unsigned)q & 3U) { case 0U: *sinx = s; *cosx = c; break; case 1U: *sinx = c; *cosx = -s; break; case 2U: *sinx = -s; *cosx = -c; break; default: *sinx = -c; *cosx = s; break; /* case 3U */ } /* http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf */ *cosx += 0; /* special values from F.10.1.12 */ /* special values from F.10.1.13 */ if (*sinx == 0) *sinx = copysign(*sinx, x); } static double atan2dx(double y, double x) { /* In order to minimize round-off errors, this function rearranges the * arguments so that result of atan2 is in the range [-pi/4, pi/4] before * converting it to degrees and mapping the result to the correct * quadrant. */ int q = 0; double ang; if (fabs(y) > fabs(x)) { swapx(&x, &y); q = 2; } if (signbit(x)) { x = -x; ++q; } /* here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] */ ang = atan2(y, x) / degree; switch (q) { case 1: ang = copysign((double)hd, y) - ang; break; case 2: ang = qd - ang; break; case 3: ang = -qd + ang; break; default: break; } return ang; } static void A3coeff(struct geod_geodesic* g); static void C3coeff(struct geod_geodesic* g); static void C4coeff(struct geod_geodesic* g); static double SinCosSeries(boolx sinp, double sinx, double cosx, const double c[], int n); static void Lengths(const struct geod_geodesic* g, double eps, double sig12, double ssig1, double csig1, double dn1, double ssig2, double csig2, double dn2, double cbet1, double cbet2, double* ps12b, double* pm12b, double* pm0, double* pM12, double* pM21, /* Scratch area of the right size */ double Ca[]); static double Astroid(double x, double y); static double InverseStart(const struct geod_geodesic* g, double sbet1, double cbet1, double dn1, double sbet2, double cbet2, double dn2, double lam12, double slam12, double clam12, double* psalp1, double* pcalp1, /* Only updated if return val >= 0 */ double* psalp2, double* pcalp2, /* Only updated for short lines */ double* pdnm, /* Scratch area of the right size */ double Ca[]); static double Lambda12(const struct geod_geodesic* g, double sbet1, double cbet1, double dn1, double sbet2, double cbet2, double dn2, double salp1, double calp1, double slam120, double clam120, double* psalp2, double* pcalp2, double* psig12, double* pssig1, double* pcsig1, double* pssig2, double* pcsig2, double* peps, double* pdomg12, boolx diffp, double* pdlam12, /* Scratch area of the right size */ double Ca[]); static double A3f(const struct geod_geodesic* g, double eps); static void C3f(const struct geod_geodesic* g, double eps, double c[]); static void C4f(const struct geod_geodesic* g, double eps, double c[]); static double A1m1f(double eps); static void C1f(double eps, double c[]); static void C1pf(double eps, double c[]); static double A2m1f(double eps); static void C2f(double eps, double c[]); static int transit(double lon1, double lon2); static int transitdirect(double lon1, double lon2); static void accini(double s[]); static void acccopy(const double s[], double t[]); static void accadd(double s[], double y); static double accsum(const double s[], double y); static void accneg(double s[]); static void accrem(double s[], double y); static double areareduceA(double area[], double area0, int crossings, boolx reverse, boolx sign); static double areareduceB(double area, double area0, int crossings, boolx reverse, boolx sign); void geod_init(struct geod_geodesic* g, double a, double f) { if (!init) Init(); g->a = a; g->f = f; g->f1 = 1 - g->f; g->e2 = g->f * (2 - g->f); g->ep2 = g->e2 / sq(g->f1); /* e2 / (1 - e2) */ g->n = g->f / ( 2 - g->f); g->b = g->a * g->f1; g->c2 = (sq(g->a) + sq(g->b) * (g->e2 == 0 ? 1 : (g->e2 > 0 ? atanh(sqrt(g->e2)) : atan(sqrt(-g->e2))) / sqrt(fabs(g->e2))))/2; /* authalic radius squared */ /* The sig12 threshold for "really short". Using the auxiliary sphere * solution with dnm computed at (bet1 + bet2) / 2, the relative error in the * azimuth consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2. (Error * measured for 1/100 < b/a < 100 and abs(f) >= 1/1000. For a given f and * sig12, the max error occurs for lines near the pole. If the old rule for * computing dnm = (dn1 + dn2)/2 is used, then the error increases by a * factor of 2.) Setting this equal to epsilon gives sig12 = etol2. Here * 0.1 is a safety factor (error decreased by 100) and max(0.001, abs(f)) * stops etol2 getting too large in the nearly spherical case. */ g->etol2 = 0.1 * tol2 / sqrt( fmax(0.001, fabs(g->f)) * fmin(1.0, 1 - g->f/2) / 2 ); A3coeff(g); C3coeff(g); C4coeff(g); } static void geod_lineinit_int(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, double salp1, double calp1, unsigned caps) { double cbet1, sbet1, eps; l->a = g->a; l->f = g->f; l->b = g->b; l->c2 = g->c2; l->f1 = g->f1; /* If caps is 0 assume the standard direct calculation */ l->caps = (caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE) | /* always allow latitude and azimuth and unrolling of longitude */ GEOD_LATITUDE | GEOD_AZIMUTH | GEOD_LONG_UNROLL; l->lat1 = LatFix(lat1); l->lon1 = lon1; l->azi1 = azi1; l->salp1 = salp1; l->calp1 = calp1; sincosdx(AngRound(l->lat1), &sbet1, &cbet1); sbet1 *= l->f1; /* Ensure cbet1 = +epsilon at poles */ norm2(&sbet1, &cbet1); cbet1 = fmax(tiny, cbet1); l->dn1 = sqrt(1 + g->ep2 * sq(sbet1)); /* Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), */ l->salp0 = l->salp1 * cbet1; /* alp0 in [0, pi/2 - |bet1|] */ /* Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following * is slightly better (consider the case salp1 = 0). */ l->calp0 = hypot(l->calp1, l->salp1 * sbet1); /* Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). * sig = 0 is nearest northward crossing of equator. * With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). * With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 * With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 * Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). * With alp0 in (0, pi/2], quadrants for sig and omg coincide. * No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. * With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. */ l->ssig1 = sbet1; l->somg1 = l->salp0 * sbet1; l->csig1 = l->comg1 = sbet1 != 0 || l->calp1 != 0 ? cbet1 * l->calp1 : 1; norm2(&l->ssig1, &l->csig1); /* sig1 in (-pi, pi] */ /* norm2(somg1, comg1); -- don't need to normalize! */ l->k2 = sq(l->calp0) * g->ep2; eps = l->k2 / (2 * (1 + sqrt(1 + l->k2)) + l->k2); if (l->caps & CAP_C1) { double s, c; l->A1m1 = A1m1f(eps); C1f(eps, l->C1a); l->B11 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C1a, nC1); s = sin(l->B11); c = cos(l->B11); /* tau1 = sig1 + B11 */ l->stau1 = l->ssig1 * c + l->csig1 * s; l->ctau1 = l->csig1 * c - l->ssig1 * s; /* Not necessary because C1pa reverts C1a * B11 = -SinCosSeries(TRUE, stau1, ctau1, C1pa, nC1p); */ } if (l->caps & CAP_C1p) C1pf(eps, l->C1pa); if (l->caps & CAP_C2) { l->A2m1 = A2m1f(eps); C2f(eps, l->C2a); l->B21 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C2a, nC2); } if (l->caps & CAP_C3) { C3f(g, eps, l->C3a); l->A3c = -l->f * l->salp0 * A3f(g, eps); l->B31 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C3a, nC3-1); } if (l->caps & CAP_C4) { C4f(g, eps, l->C4a); /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) */ l->A4 = sq(l->a) * l->calp0 * l->salp0 * g->e2; l->B41 = SinCosSeries(FALSE, l->ssig1, l->csig1, l->C4a, nC4); } l->a13 = l->s13 = NaN; } void geod_lineinit(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned caps) { double salp1, calp1; azi1 = AngNormalize(azi1); /* Guard against underflow in salp0 */ sincosdx(AngRound(azi1), &salp1, &calp1); geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); } void geod_gendirectline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned flags, double s12_a12, unsigned caps) { geod_lineinit(l, g, lat1, lon1, azi1, caps); geod_gensetdistance(l, flags, s12_a12); } void geod_directline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double azi1, double s12, unsigned caps) { geod_gendirectline(l, g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, caps); } double geod_genposition(const struct geod_geodesicline* l, unsigned flags, double s12_a12, double* plat2, double* plon2, double* pazi2, double* ps12, double* pm12, double* pM12, double* pM21, double* pS12) { double lat2 = 0, lon2 = 0, azi2 = 0, s12 = 0, m12 = 0, M12 = 0, M21 = 0, S12 = 0; /* Avoid warning about uninitialized B12. */ double sig12, ssig12, csig12, B12 = 0, AB1 = 0; double omg12, lam12, lon12; double ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2, dn2; unsigned outmask = (plat2 ? GEOD_LATITUDE : GEOD_NONE) | (plon2 ? GEOD_LONGITUDE : GEOD_NONE) | (pazi2 ? GEOD_AZIMUTH : GEOD_NONE) | (ps12 ? GEOD_DISTANCE : GEOD_NONE) | (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | (pS12 ? GEOD_AREA : GEOD_NONE); outmask &= l->caps & OUT_ALL; if (!( (flags & GEOD_ARCMODE || ((unsigned)l->caps & ((unsigned)GEOD_DISTANCE_IN & (unsigned)OUT_ALL))) )) /* Impossible distance calculation requested */ return NaN; if (flags & GEOD_ARCMODE) { /* Interpret s12_a12 as spherical arc length */ sig12 = s12_a12 * degree; sincosdx(s12_a12, &ssig12, &csig12); } else { /* Interpret s12_a12 as distance */ double tau12 = s12_a12 / (l->b * (1 + l->A1m1)), s = sin(tau12), c = cos(tau12); /* tau2 = tau1 + tau12 */ B12 = - SinCosSeries(TRUE, l->stau1 * c + l->ctau1 * s, l->ctau1 * c - l->stau1 * s, l->C1pa, nC1p); sig12 = tau12 - (B12 - l->B11); ssig12 = sin(sig12); csig12 = cos(sig12); if (fabs(l->f) > 0.01) { /* Reverted distance series is inaccurate for |f| > 1/100, so correct * sig12 with 1 Newton iteration. The following table shows the * approximate maximum error for a = WGS_a() and various f relative to * GeodesicExact. * erri = the error in the inverse solution (nm) * errd = the error in the direct solution (series only) (nm) * errda = the error in the direct solution (series + 1 Newton) (nm) * * f erri errd errda * -1/5 12e6 1.2e9 69e6 * -1/10 123e3 12e6 765e3 * -1/20 1110 108e3 7155 * -1/50 18.63 200.9 27.12 * -1/100 18.63 23.78 23.37 * -1/150 18.63 21.05 20.26 * 1/150 22.35 24.73 25.83 * 1/100 22.35 25.03 25.31 * 1/50 29.80 231.9 30.44 * 1/20 5376 146e3 10e3 * 1/10 829e3 22e6 1.5e6 * 1/5 157e6 3.8e9 280e6 */ double serr; ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); serr = (1 + l->A1m1) * (sig12 + (B12 - l->B11)) - s12_a12 / l->b; sig12 = sig12 - serr / sqrt(1 + l->k2 * sq(ssig2)); ssig12 = sin(sig12); csig12 = cos(sig12); /* Update B12 below */ } } /* sig2 = sig1 + sig12 */ ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; dn2 = sqrt(1 + l->k2 * sq(ssig2)); if (outmask & (GEOD_DISTANCE | GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { if (flags & GEOD_ARCMODE || fabs(l->f) > 0.01) B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); AB1 = (1 + l->A1m1) * (B12 - l->B11); } /* sin(bet2) = cos(alp0) * sin(sig2) */ sbet2 = l->calp0 * ssig2; /* Alt: cbet2 = hypot(csig2, salp0 * ssig2); */ cbet2 = hypot(l->salp0, l->calp0 * csig2); if (cbet2 == 0) /* I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case */ cbet2 = csig2 = tiny; /* tan(alp0) = cos(sig2)*tan(alp2) */ salp2 = l->salp0; calp2 = l->calp0 * csig2; /* No need to normalize */ if (outmask & GEOD_DISTANCE) s12 = (flags & GEOD_ARCMODE) ? l->b * ((1 + l->A1m1) * sig12 + AB1) : s12_a12; if (outmask & GEOD_LONGITUDE) { double E = copysign(1, l->salp0); /* east or west going? */ /* tan(omg2) = sin(alp0) * tan(sig2) */ somg2 = l->salp0 * ssig2; comg2 = csig2; /* No need to normalize */ /* omg12 = omg2 - omg1 */ omg12 = (flags & GEOD_LONG_UNROLL) ? E * (sig12 - (atan2( ssig2, csig2) - atan2( l->ssig1, l->csig1)) + (atan2(E * somg2, comg2) - atan2(E * l->somg1, l->comg1))) : atan2(somg2 * l->comg1 - comg2 * l->somg1, comg2 * l->comg1 + somg2 * l->somg1); lam12 = omg12 + l->A3c * ( sig12 + (SinCosSeries(TRUE, ssig2, csig2, l->C3a, nC3-1) - l->B31)); lon12 = lam12 / degree; lon2 = (flags & GEOD_LONG_UNROLL) ? l->lon1 + lon12 : AngNormalize(AngNormalize(l->lon1) + AngNormalize(lon12)); } if (outmask & GEOD_LATITUDE) lat2 = atan2dx(sbet2, l->f1 * cbet2); if (outmask & GEOD_AZIMUTH) azi2 = atan2dx(salp2, calp2); if (outmask & (GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { double B22 = SinCosSeries(TRUE, ssig2, csig2, l->C2a, nC2), AB2 = (1 + l->A2m1) * (B22 - l->B21), J12 = (l->A1m1 - l->A2m1) * sig12 + (AB1 - AB2); if (outmask & GEOD_REDUCEDLENGTH) /* Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure * accurate cancellation in the case of coincident points. */ m12 = l->b * ((dn2 * (l->csig1 * ssig2) - l->dn1 * (l->ssig1 * csig2)) - l->csig1 * csig2 * J12); if (outmask & GEOD_GEODESICSCALE) { double t = l->k2 * (ssig2 - l->ssig1) * (ssig2 + l->ssig1) / (l->dn1 + dn2); M12 = csig12 + (t * ssig2 - csig2 * J12) * l->ssig1 / l->dn1; M21 = csig12 - (t * l->ssig1 - l->csig1 * J12) * ssig2 / dn2; } } if (outmask & GEOD_AREA) { double B42 = SinCosSeries(FALSE, ssig2, csig2, l->C4a, nC4); double salp12, calp12; if (l->calp0 == 0 || l->salp0 == 0) { /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ salp12 = salp2 * l->calp1 - calp2 * l->salp1; calp12 = calp2 * l->calp1 + salp2 * l->salp1; } else { /* tan(alp) = tan(alp0) * sec(sig) * tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) * = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) * If csig12 > 0, write * csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) * else * csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 * No need to normalize */ salp12 = l->calp0 * l->salp0 * (csig12 <= 0 ? l->csig1 * (1 - csig12) + ssig12 * l->ssig1 : ssig12 * (l->csig1 * ssig12 / (1 + csig12) + l->ssig1)); calp12 = sq(l->salp0) + sq(l->calp0) * l->csig1 * csig2; } S12 = l->c2 * atan2(salp12, calp12) + l->A4 * (B42 - l->B41); } /* In the pattern * * if ((outmask & GEOD_XX) && pYY) * *pYY = YY; * * the second check "&& pYY" is redundant. It's there to make the CLang * static analyzer happy. */ if ((outmask & GEOD_LATITUDE) && plat2) *plat2 = lat2; if ((outmask & GEOD_LONGITUDE) && plon2) *plon2 = lon2; if ((outmask & GEOD_AZIMUTH) && pazi2) *pazi2 = azi2; if ((outmask & GEOD_DISTANCE) && ps12) *ps12 = s12; if ((outmask & GEOD_REDUCEDLENGTH) && pm12) *pm12 = m12; if (outmask & GEOD_GEODESICSCALE) { if (pM12) *pM12 = M12; if (pM21) *pM21 = M21; } if ((outmask & GEOD_AREA) && pS12) *pS12 = S12; return (flags & GEOD_ARCMODE) ? s12_a12 : sig12 / degree; } void geod_setdistance(struct geod_geodesicline* l, double s13) { l->s13 = s13; l->a13 = geod_genposition(l, GEOD_NOFLAGS, l->s13, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR); } static void geod_setarc(struct geod_geodesicline* l, double a13) { l->a13 = a13; l->s13 = NaN; geod_genposition(l, GEOD_ARCMODE, l->a13, NULLPTR, NULLPTR, NULLPTR, &l->s13, NULLPTR, NULLPTR, NULLPTR, NULLPTR); } void geod_gensetdistance(struct geod_geodesicline* l, unsigned flags, double s13_a13) { (flags & GEOD_ARCMODE) ? geod_setarc(l, s13_a13) : geod_setdistance(l, s13_a13); } void geod_position(const struct geod_geodesicline* l, double s12, double* plat2, double* plon2, double* pazi2) { geod_genposition(l, FALSE, s12, plat2, plon2, pazi2, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR); } double geod_gendirect(const struct geod_geodesic* g, double lat1, double lon1, double azi1, unsigned flags, double s12_a12, double* plat2, double* plon2, double* pazi2, double* ps12, double* pm12, double* pM12, double* pM21, double* pS12) { struct geod_geodesicline l; unsigned outmask = (plat2 ? GEOD_LATITUDE : GEOD_NONE) | (plon2 ? GEOD_LONGITUDE : GEOD_NONE) | (pazi2 ? GEOD_AZIMUTH : GEOD_NONE) | (ps12 ? GEOD_DISTANCE : GEOD_NONE) | (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | (pS12 ? GEOD_AREA : GEOD_NONE); geod_lineinit(&l, g, lat1, lon1, azi1, /* Automatically supply GEOD_DISTANCE_IN if necessary */ outmask | ((flags & GEOD_ARCMODE) ? GEOD_NONE : GEOD_DISTANCE_IN)); return geod_genposition(&l, flags, s12_a12, plat2, plon2, pazi2, ps12, pm12, pM12, pM21, pS12); } void geod_direct(const struct geod_geodesic* g, double lat1, double lon1, double azi1, double s12, double* plat2, double* plon2, double* pazi2) { geod_gendirect(g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, plat2, plon2, pazi2, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR); } static double geod_geninverse_int(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* psalp1, double* pcalp1, double* psalp2, double* pcalp2, double* pm12, double* pM12, double* pM21, double* pS12) { double s12 = 0, m12 = 0, M12 = 0, M21 = 0, S12 = 0; double lon12, lon12s; int latsign, lonsign, swapp; double sbet1, cbet1, sbet2, cbet2, s12x = 0, m12x = 0; double dn1, dn2, lam12, slam12, clam12; double a12 = 0, sig12, calp1 = 0, salp1 = 0, calp2 = 0, salp2 = 0; double Ca[nC]; boolx meridian; /* somg12 == 2 marks that it needs to be calculated */ double omg12 = 0, somg12 = 2, comg12 = 0; unsigned outmask = (ps12 ? GEOD_DISTANCE : GEOD_NONE) | (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | (pS12 ? GEOD_AREA : GEOD_NONE); outmask &= OUT_ALL; /* Compute longitude difference (AngDiff does this carefully). Result is * in [-180, 180] but -180 is only for west-going geodesics. 180 is for * east-going and meridional geodesics. */ lon12 = AngDiff(lon1, lon2, &lon12s); /* Make longitude difference positive. */ lonsign = signbit(lon12) ? -1 : 1; lon12 *= lonsign; lon12s *= lonsign; lam12 = lon12 * degree; /* Calculate sincos of lon12 + error (this applies AngRound internally). */ sincosde(lon12, lon12s, &slam12, &clam12); lon12s = (hd - lon12) - lon12s; /* the supplementary longitude difference */ /* If really close to the equator, treat as on equator. */ lat1 = AngRound(LatFix(lat1)); lat2 = AngRound(LatFix(lat2)); /* Swap points so that point with higher (abs) latitude is point 1 * If one latitude is a nan, then it becomes lat1. */ swapp = fabs(lat1) < fabs(lat2) || lat2 != lat2 ? -1 : 1; if (swapp < 0) { lonsign *= -1; swapx(&lat1, &lat2); } /* Make lat1 <= -0 */ latsign = signbit(lat1) ? 1 : -1; lat1 *= latsign; lat2 *= latsign; /* Now we have * * 0 <= lon12 <= 180 * -90 <= lat1 <= -0 * lat1 <= lat2 <= -lat1 * * longsign, swapp, latsign register the transformation to bring the * coordinates to this canonical form. In all cases, 1 means no change was * made. We make these transformations so that there are few cases to * check, e.g., on verifying quadrants in atan2. In addition, this * enforces some symmetries in the results returned. */ sincosdx(lat1, &sbet1, &cbet1); sbet1 *= g->f1; /* Ensure cbet1 = +epsilon at poles */ norm2(&sbet1, &cbet1); cbet1 = fmax(tiny, cbet1); sincosdx(lat2, &sbet2, &cbet2); sbet2 *= g->f1; /* Ensure cbet2 = +epsilon at poles */ norm2(&sbet2, &cbet2); cbet2 = fmax(tiny, cbet2); /* If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the * |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is * a better measure. This logic is used in assigning calp2 in Lambda12. * Sometimes these quantities vanish and in that case we force bet2 = +/- * bet1 exactly. An example where is is necessary is the inverse problem * 48.522876735459 0 -48.52287673545898293 179.599720456223079643 * which failed with Visual Studio 10 (Release and Debug) */ if (cbet1 < -sbet1) { if (cbet2 == cbet1) sbet2 = copysign(sbet1, sbet2); } else { if (fabs(sbet2) == -sbet1) cbet2 = cbet1; } dn1 = sqrt(1 + g->ep2 * sq(sbet1)); dn2 = sqrt(1 + g->ep2 * sq(sbet2)); meridian = lat1 == -qd || slam12 == 0; if (meridian) { /* Endpoints are on a single full meridian, so the geodesic might lie on * a meridian. */ double ssig1, csig1, ssig2, csig2; calp1 = clam12; salp1 = slam12; /* Head to the target longitude */ calp2 = 1; salp2 = 0; /* At the target we're heading north */ /* tan(bet) = tan(sig) * cos(alp) */ ssig1 = sbet1; csig1 = calp1 * cbet1; ssig2 = sbet2; csig2 = calp2 * cbet2; /* sig12 = sig2 - sig1 */ sig12 = atan2(fmax(0.0, csig1 * ssig2 - ssig1 * csig2) + 0, csig1 * csig2 + ssig1 * ssig2); Lengths(g, g->n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, &s12x, &m12x, NULLPTR, (outmask & GEOD_GEODESICSCALE) ? &M12 : NULLPTR, (outmask & GEOD_GEODESICSCALE) ? &M21 : NULLPTR, Ca); /* Add the check for sig12 since zero length geodesics might yield m12 < * 0. Test case was * * echo 20.001 0 20.001 0 | GeodSolve -i */ if (sig12 < tol2 || m12x >= 0) { /* Need at least 2, to handle 90 0 90 180 */ if (sig12 < 3 * tiny || /* Prevent negative s12 or m12 for short lines */ (sig12 < tol0 && (s12x < 0 || m12x < 0))) sig12 = m12x = s12x = 0; m12x *= g->b; s12x *= g->b; a12 = sig12 / degree; } else /* m12 < 0, i.e., prolate and too close to anti-podal */ meridian = FALSE; } if (!meridian && sbet1 == 0 && /* and sbet2 == 0 */ /* Mimic the way Lambda12 works with calp1 = 0 */ (g->f <= 0 || lon12s >= g->f * hd)) { /* Geodesic runs along equator */ calp1 = calp2 = 0; salp1 = salp2 = 1; s12x = g->a * lam12; sig12 = omg12 = lam12 / g->f1; m12x = g->b * sin(sig12); if (outmask & GEOD_GEODESICSCALE) M12 = M21 = cos(sig12); a12 = lon12 / g->f1; } else if (!meridian) { /* Now point1 and point2 belong within a hemisphere bounded by a * meridian and geodesic is neither meridional or equatorial. */ /* Figure a starting point for Newton's method */ double dnm = 0; sig12 = InverseStart(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, slam12, clam12, &salp1, &calp1, &salp2, &calp2, &dnm, Ca); if (sig12 >= 0) { /* Short lines (InverseStart sets salp2, calp2, dnm) */ s12x = sig12 * g->b * dnm; m12x = sq(dnm) * g->b * sin(sig12 / dnm); if (outmask & GEOD_GEODESICSCALE) M12 = M21 = cos(sig12 / dnm); a12 = sig12 / degree; omg12 = lam12 / (g->f1 * dnm); } else { /* Newton's method. This is a straightforward solution of f(alp1) = * lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one * root in the interval (0, pi) and its derivative is positive at the * root. Thus f(alp) is positive for alp > alp1 and negative for alp < * alp1. During the course of the iteration, a range (alp1a, alp1b) is * maintained which brackets the root and with each evaluation of * f(alp) the range is shrunk, if possible. Newton's method is * restarted whenever the derivative of f is negative (because the new * value of alp1 is then further from the solution) or if the new * estimate of alp1 lies outside (0,pi); in this case, the new starting * guess is taken to be (alp1a + alp1b) / 2. */ double ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, domg12 = 0; unsigned numit = 0; /* Bracketing range */ double salp1a = tiny, calp1a = 1, salp1b = tiny, calp1b = -1; boolx tripn = FALSE; boolx tripb = FALSE; for (;; ++numit) { /* the WGS84 test set: mean = 1.47, sd = 1.25, max = 16 * WGS84 and random input: mean = 2.85, sd = 0.60 */ double dv = 0, v = Lambda12(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, slam12, clam12, &salp2, &calp2, &sig12, &ssig1, &csig1, &ssig2, &csig2, &eps, &domg12, numit < maxit1, &dv, Ca); if (tripb || /* Reversed test to allow escape with NaNs */ !(fabs(v) >= (tripn ? 8 : 1) * tol0) || /* Enough bisections to get accurate result */ numit == maxit2) break; /* Update bracketing values */ if (v > 0 && (numit > maxit1 || calp1/salp1 > calp1b/salp1b)) { salp1b = salp1; calp1b = calp1; } else if (v < 0 && (numit > maxit1 || calp1/salp1 < calp1a/salp1a)) { salp1a = salp1; calp1a = calp1; } if (numit < maxit1 && dv > 0) { double dalp1 = -v/dv; if (fabs(dalp1) < pi) { double sdalp1 = sin(dalp1), cdalp1 = cos(dalp1), nsalp1 = salp1 * cdalp1 + calp1 * sdalp1; if (nsalp1 > 0) { calp1 = calp1 * cdalp1 - salp1 * sdalp1; salp1 = nsalp1; norm2(&salp1, &calp1); /* In some regimes we don't get quadratic convergence because * slope -> 0. So use convergence conditions based on epsilon * instead of sqrt(epsilon). */ tripn = fabs(v) <= 16 * tol0; continue; } } } /* Either dv was not positive or updated value was outside legal * range. Use the midpoint of the bracket as the next estimate. * This mechanism is not needed for the WGS84 ellipsoid, but it does * catch problems with more eccentric ellipsoids. Its efficacy is * such for the WGS84 test set with the starting guess set to alp1 = * 90deg: * the WGS84 test set: mean = 5.21, sd = 3.93, max = 24 * WGS84 and random input: mean = 4.74, sd = 0.99 */ salp1 = (salp1a + salp1b)/2; calp1 = (calp1a + calp1b)/2; norm2(&salp1, &calp1); tripn = FALSE; tripb = (fabs(salp1a - salp1) + (calp1a - calp1) < tolb || fabs(salp1 - salp1b) + (calp1 - calp1b) < tolb); } Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, &s12x, &m12x, NULLPTR, (outmask & GEOD_GEODESICSCALE) ? &M12 : NULLPTR, (outmask & GEOD_GEODESICSCALE) ? &M21 : NULLPTR, Ca); m12x *= g->b; s12x *= g->b; a12 = sig12 / degree; if (outmask & GEOD_AREA) { /* omg12 = lam12 - domg12 */ double sdomg12 = sin(domg12), cdomg12 = cos(domg12); somg12 = slam12 * cdomg12 - clam12 * sdomg12; comg12 = clam12 * cdomg12 + slam12 * sdomg12; } } } if (outmask & GEOD_DISTANCE) s12 = 0 + s12x; /* Convert -0 to 0 */ if (outmask & GEOD_REDUCEDLENGTH) m12 = 0 + m12x; /* Convert -0 to 0 */ if (outmask & GEOD_AREA) { double /* From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) */ salp0 = salp1 * cbet1, calp0 = hypot(calp1, salp1 * sbet1); /* calp0 > 0 */ double alp12; if (calp0 != 0 && salp0 != 0) { double /* From Lambda12: tan(bet) = tan(sig) * cos(alp) */ ssig1 = sbet1, csig1 = calp1 * cbet1, ssig2 = sbet2, csig2 = calp2 * cbet2, k2 = sq(calp0) * g->ep2, eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2), /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0). */ A4 = sq(g->a) * calp0 * salp0 * g->e2; double B41, B42; norm2(&ssig1, &csig1); norm2(&ssig2, &csig2); C4f(g, eps, Ca); B41 = SinCosSeries(FALSE, ssig1, csig1, Ca, nC4); B42 = SinCosSeries(FALSE, ssig2, csig2, Ca, nC4); S12 = A4 * (B42 - B41); } else /* Avoid problems with indeterminate sig1, sig2 on equator */ S12 = 0; if (!meridian && somg12 == 2) { somg12 = sin(omg12); comg12 = cos(omg12); } if (!meridian && /* omg12 < 3/4 * pi */ comg12 > -0.7071 && /* Long difference not too big */ sbet2 - sbet1 < 1.75) { /* Lat difference not too big */ /* Use tan(Gamma/2) = tan(omg12/2) * * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2)) * with tan(x/2) = sin(x)/(1+cos(x)) */ double domg12 = 1 + comg12, dbet1 = 1 + cbet1, dbet2 = 1 + cbet2; alp12 = 2 * atan2( somg12 * ( sbet1 * dbet2 + sbet2 * dbet1 ), domg12 * ( sbet1 * sbet2 + dbet1 * dbet2 ) ); } else { /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ double salp12 = salp2 * calp1 - calp2 * salp1, calp12 = calp2 * calp1 + salp2 * salp1; /* The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz * salp12 = -0 and alp12 = -180. However this depends on the sign * being attached to 0 correctly. The following ensures the correct * behavior. */ if (salp12 == 0 && calp12 < 0) { salp12 = tiny * calp1; calp12 = -1; } alp12 = atan2(salp12, calp12); } S12 += g->c2 * alp12; S12 *= swapp * lonsign * latsign; /* Convert -0 to 0 */ S12 += 0; } /* Convert calp, salp to azimuth accounting for lonsign, swapp, latsign. */ if (swapp < 0) { swapx(&salp1, &salp2); swapx(&calp1, &calp2); if (outmask & GEOD_GEODESICSCALE) swapx(&M12, &M21); } salp1 *= swapp * lonsign; calp1 *= swapp * latsign; salp2 *= swapp * lonsign; calp2 *= swapp * latsign; if (psalp1) *psalp1 = salp1; if (pcalp1) *pcalp1 = calp1; if (psalp2) *psalp2 = salp2; if (pcalp2) *pcalp2 = calp2; if (outmask & GEOD_DISTANCE) *ps12 = s12; if (outmask & GEOD_REDUCEDLENGTH) *pm12 = m12; if (outmask & GEOD_GEODESICSCALE) { if (pM12) *pM12 = M12; if (pM21) *pM21 = M21; } if (outmask & GEOD_AREA) *pS12 = S12; /* Returned value in [0, 180] */ return a12; } double geod_geninverse(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* pazi1, double* pazi2, double* pm12, double* pM12, double* pM21, double* pS12) { double salp1, calp1, salp2, calp2, a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, ps12, &salp1, &calp1, &salp2, &calp2, pm12, pM12, pM21, pS12); if (pazi1) *pazi1 = atan2dx(salp1, calp1); if (pazi2) *pazi2 = atan2dx(salp2, calp2); return a12; } void geod_inverseline(struct geod_geodesicline* l, const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, unsigned caps) { double salp1, calp1, a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, NULLPTR, &salp1, &calp1, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR), azi1 = atan2dx(salp1, calp1); caps = caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE; /* Ensure that a12 can be converted to a distance */ if (caps & ((unsigned)OUT_ALL & (unsigned)GEOD_DISTANCE_IN)) caps |= GEOD_DISTANCE; geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); geod_setarc(l, a12); } void geod_inverse(const struct geod_geodesic* g, double lat1, double lon1, double lat2, double lon2, double* ps12, double* pazi1, double* pazi2) { geod_geninverse(g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, NULLPTR, NULLPTR, NULLPTR, NULLPTR); } double SinCosSeries(boolx sinp, double sinx, double cosx, const double c[], int n) { /* Evaluate * y = sinp ? sum(c[i] * sin( 2*i * x), i, 1, n) : * sum(c[i] * cos((2*i+1) * x), i, 0, n-1) * using Clenshaw summation. N.B. c[0] is unused for sin series * Approx operation count = (n + 5) mult and (2 * n + 2) add */ double ar, y0, y1; c += (n + sinp); /* Point to one beyond last element */ ar = 2 * (cosx - sinx) * (cosx + sinx); /* 2 * cos(2 * x) */ y0 = (n & 1) ? *--c : 0; y1 = 0; /* accumulators for sum */ /* Now n is even */ n /= 2; while (n--) { /* Unroll loop x 2, so accumulators return to their original role */ y1 = ar * y0 - y1 + *--c; y0 = ar * y1 - y0 + *--c; } return sinp ? 2 * sinx * cosx * y0 /* sin(2 * x) * y0 */ : cosx * (y0 - y1); /* cos(x) * (y0 - y1) */ } void Lengths(const struct geod_geodesic* g, double eps, double sig12, double ssig1, double csig1, double dn1, double ssig2, double csig2, double dn2, double cbet1, double cbet2, double* ps12b, double* pm12b, double* pm0, double* pM12, double* pM21, /* Scratch area of the right size */ double Ca[]) { double m0 = 0, J12 = 0, A1 = 0, A2 = 0; double Cb[nC]; /* Return m12b = (reduced length)/b; also calculate s12b = distance/b, * and m0 = coefficient of secular term in expression for reduced length. */ boolx redlp = pm12b || pm0 || pM12 || pM21; if (ps12b || redlp) { A1 = A1m1f(eps); C1f(eps, Ca); if (redlp) { A2 = A2m1f(eps); C2f(eps, Cb); m0 = A1 - A2; A2 = 1 + A2; } A1 = 1 + A1; } if (ps12b) { double B1 = SinCosSeries(TRUE, ssig2, csig2, Ca, nC1) - SinCosSeries(TRUE, ssig1, csig1, Ca, nC1); /* Missing a factor of b */ *ps12b = A1 * (sig12 + B1); if (redlp) { double B2 = SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - SinCosSeries(TRUE, ssig1, csig1, Cb, nC2); J12 = m0 * sig12 + (A1 * B1 - A2 * B2); } } else if (redlp) { /* Assume here that nC1 >= nC2 */ int l; for (l = 1; l <= nC2; ++l) Cb[l] = A1 * Ca[l] - A2 * Cb[l]; J12 = m0 * sig12 + (SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - SinCosSeries(TRUE, ssig1, csig1, Cb, nC2)); } if (pm0) *pm0 = m0; if (pm12b) /* Missing a factor of b. * Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure * accurate cancellation in the case of coincident points. */ *pm12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - csig1 * csig2 * J12; if (pM12 || pM21) { double csig12 = csig1 * csig2 + ssig1 * ssig2; double t = g->ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2); if (pM12) *pM12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1; if (pM21) *pM21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2; } } double Astroid(double x, double y) { /* Solve k^4+2*k^3-(x^2+y^2-1)*k^2-2*y^2*k-y^2 = 0 for positive root k. * This solution is adapted from Geocentric::Reverse. */ double k; double p = sq(x), q = sq(y), r = (p + q - 1) / 6; if ( !(q == 0 && r <= 0) ) { double /* Avoid possible division by zero when r = 0 by multiplying equations * for s and t by r^3 and r, resp. */ S = p * q / 4, /* S = r^3 * s */ r2 = sq(r), r3 = r * r2, /* The discriminant of the quadratic equation for T3. This is zero on * the evolute curve p^(1/3)+q^(1/3) = 1 */ disc = S * (S + 2 * r3); double u = r; double v, uv, w; if (disc >= 0) { double T3 = S + r3, T; /* Pick the sign on the sqrt to maximize abs(T3). This minimizes loss * of precision due to cancellation. The result is unchanged because * of the way the T is used in definition of u. */ T3 += T3 < 0 ? -sqrt(disc) : sqrt(disc); /* T3 = (r * t)^3 */ /* N.B. cbrt always returns the double root. cbrt(-8) = -2. */ T = cbrt(T3); /* T = r * t */ /* T can be zero; but then r2 / T -> 0. */ u += T + (T != 0 ? r2 / T : 0); } else { /* T is complex, but the way u is defined the result is double. */ double ang = atan2(sqrt(-disc), -(S + r3)); /* There are three possible cube roots. We choose the root which * avoids cancellation. Note that disc < 0 implies that r < 0. */ u += 2 * r * cos(ang / 3); } v = sqrt(sq(u) + q); /* guaranteed positive */ /* Avoid loss of accuracy when u < 0. */ uv = u < 0 ? q / (v - u) : u + v; /* u+v, guaranteed positive */ w = (uv - q) / (2 * v); /* positive? */ /* Rearrange expression for k to avoid loss of accuracy due to * subtraction. Division by 0 not possible because uv > 0, w >= 0. */ k = uv / (sqrt(uv + sq(w)) + w); /* guaranteed positive */ } else { /* q == 0 && r <= 0 */ /* y = 0 with |x| <= 1. Handle this case directly. * for y small, positive root is k = abs(y)/sqrt(1-x^2) */ k = 0; } return k; } double InverseStart(const struct geod_geodesic* g, double sbet1, double cbet1, double dn1, double sbet2, double cbet2, double dn2, double lam12, double slam12, double clam12, double* psalp1, double* pcalp1, /* Only updated if return val >= 0 */ double* psalp2, double* pcalp2, /* Only updated for short lines */ double* pdnm, /* Scratch area of the right size */ double Ca[]) { double salp1 = 0, calp1 = 0, salp2 = 0, calp2 = 0, dnm = 0; /* Return a starting point for Newton's method in salp1 and calp1 (function * value is -1). If Newton's method doesn't need to be used, return also * salp2 and calp2 and function value is sig12. */ double sig12 = -1, /* Return value */ /* bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0] */ sbet12 = sbet2 * cbet1 - cbet2 * sbet1, cbet12 = cbet2 * cbet1 + sbet2 * sbet1; double sbet12a; boolx shortline = cbet12 >= 0 && sbet12 < 0.5 && cbet2 * lam12 < 0.5; double somg12, comg12, ssig12, csig12; sbet12a = sbet2 * cbet1 + cbet2 * sbet1; if (shortline) { double sbetm2 = sq(sbet1 + sbet2), omg12; /* sin((bet1+bet2)/2)^2 * = (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2) */ sbetm2 /= sbetm2 + sq(cbet1 + cbet2); dnm = sqrt(1 + g->ep2 * sbetm2); omg12 = lam12 / (g->f1 * dnm); somg12 = sin(omg12); comg12 = cos(omg12); } else { somg12 = slam12; comg12 = clam12; } salp1 = cbet2 * somg12; calp1 = comg12 >= 0 ? sbet12 + cbet2 * sbet1 * sq(somg12) / (1 + comg12) : sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); ssig12 = hypot(salp1, calp1); csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12; if (shortline && ssig12 < g->etol2) { /* really short lines */ salp2 = cbet1 * somg12; calp2 = sbet12 - cbet1 * sbet2 * (comg12 >= 0 ? sq(somg12) / (1 + comg12) : 1 - comg12); norm2(&salp2, &calp2); /* Set return value */ sig12 = atan2(ssig12, csig12); } else if (fabs(g->n) > 0.1 || /* No astroid calc if too eccentric */ csig12 >= 0 || ssig12 >= 6 * fabs(g->n) * pi * sq(cbet1)) { /* Nothing to do, zeroth order spherical approximation is OK */ } else { /* Scale lam12 and bet2 to x, y coordinate system where antipodal point * is at origin and singular point is at y = 0, x = -1. */ double x, y, lamscale, betscale; double lam12x = atan2(-slam12, -clam12); /* lam12 - pi */ if (g->f >= 0) { /* In fact f == 0 does not get here */ /* x = dlong, y = dlat */ { double k2 = sq(sbet1) * g->ep2, eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); lamscale = g->f * cbet1 * A3f(g, eps) * pi; } betscale = lamscale * cbet1; x = lam12x / lamscale; y = sbet12a / betscale; } else { /* f < 0 */ /* x = dlat, y = dlong */ double cbet12a = cbet2 * cbet1 - sbet2 * sbet1, bet12a = atan2(sbet12a, cbet12a); double m12b, m0; /* In the case of lon12 = 180, this repeats a calculation made in * Inverse. */ Lengths(g, g->n, pi + bet12a, sbet1, -cbet1, dn1, sbet2, cbet2, dn2, cbet1, cbet2, NULLPTR, &m12b, &m0, NULLPTR, NULLPTR, Ca); x = -1 + m12b / (cbet1 * cbet2 * m0 * pi); betscale = x < -0.01 ? sbet12a / x : -g->f * sq(cbet1) * pi; lamscale = betscale / cbet1; y = lam12x / lamscale; } if (y > -tol1 && x > -1 - xthresh) { /* strip near cut */ if (g->f >= 0) { salp1 = fmin(1.0, -x); calp1 = - sqrt(1 - sq(salp1)); } else { calp1 = fmax(x > -tol1 ? 0.0 : -1.0, x); salp1 = sqrt(1 - sq(calp1)); } } else { /* Estimate alp1, by solving the astroid problem. * * Could estimate alpha1 = theta + pi/2, directly, i.e., * calp1 = y/k; salp1 = -x/(1+k); for f >= 0 * calp1 = x/(1+k); salp1 = -y/k; for f < 0 (need to check) * * However, it's better to estimate omg12 from astroid and use * spherical formula to compute alp1. This reduces the mean number of * Newton iterations for astroid cases from 2.24 (min 0, max 6) to 2.12 * (min 0 max 5). The changes in the number of iterations are as * follows: * * change percent * 1 5 * 0 78 * -1 16 * -2 0.6 * -3 0.04 * -4 0.002 * * The histogram of iterations is (m = number of iterations estimating * alp1 directly, n = number of iterations estimating via omg12, total * number of trials = 148605): * * iter m n * 0 148 186 * 1 13046 13845 * 2 93315 102225 * 3 36189 32341 * 4 5396 7 * 5 455 1 * 6 56 0 * * Because omg12 is near pi, estimate work with omg12a = pi - omg12 */ double k = Astroid(x, y); double omg12a = lamscale * ( g->f >= 0 ? -x * k/(1 + k) : -y * (1 + k)/k ); somg12 = sin(omg12a); comg12 = -cos(omg12a); /* Update spherical estimate of alp1 using omg12 instead of lam12 */ salp1 = cbet2 * somg12; calp1 = sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); } } /* Sanity check on starting guess. Backwards check allows NaN through. */ if (!(salp1 <= 0)) norm2(&salp1, &calp1); else { salp1 = 1; calp1 = 0; } *psalp1 = salp1; *pcalp1 = calp1; if (shortline) *pdnm = dnm; if (sig12 >= 0) { *psalp2 = salp2; *pcalp2 = calp2; } return sig12; } double Lambda12(const struct geod_geodesic* g, double sbet1, double cbet1, double dn1, double sbet2, double cbet2, double dn2, double salp1, double calp1, double slam120, double clam120, double* psalp2, double* pcalp2, double* psig12, double* pssig1, double* pcsig1, double* pssig2, double* pcsig2, double* peps, double* pdomg12, boolx diffp, double* pdlam12, /* Scratch area of the right size */ double Ca[]) { double salp2 = 0, calp2 = 0, sig12 = 0, ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, domg12 = 0, dlam12 = 0; double salp0, calp0; double somg1, comg1, somg2, comg2, somg12, comg12, lam12; double B312, eta, k2; if (sbet1 == 0 && calp1 == 0) /* Break degeneracy of equatorial line. This case has already been * handled. */ calp1 = -tiny; /* sin(alp1) * cos(bet1) = sin(alp0) */ salp0 = salp1 * cbet1; calp0 = hypot(calp1, salp1 * sbet1); /* calp0 > 0 */ /* tan(bet1) = tan(sig1) * cos(alp1) * tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1)=tan(alp1)*sin(bet1) */ ssig1 = sbet1; somg1 = salp0 * sbet1; csig1 = comg1 = calp1 * cbet1; norm2(&ssig1, &csig1); /* norm2(&somg1, &comg1); -- don't need to normalize! */ /* Enforce symmetries in the case abs(bet2) = -bet1. Need to be careful * about this case, since this can yield singularities in the Newton * iteration. * sin(alp2) * cos(bet2) = sin(alp0) */ salp2 = cbet2 != cbet1 ? salp0 / cbet2 : salp1; /* calp2 = sqrt(1 - sq(salp2)) * = sqrt(sq(calp0) - sq(sbet2)) / cbet2 * and subst for calp0 and rearrange to give (choose positive sqrt * to give alp2 in [0, pi/2]). */ calp2 = cbet2 != cbet1 || fabs(sbet2) != -sbet1 ? sqrt(sq(calp1 * cbet1) + (cbet1 < -sbet1 ? (cbet2 - cbet1) * (cbet1 + cbet2) : (sbet1 - sbet2) * (sbet1 + sbet2))) / cbet2 : fabs(calp1); /* tan(bet2) = tan(sig2) * cos(alp2) * tan(omg2) = sin(alp0) * tan(sig2). */ ssig2 = sbet2; somg2 = salp0 * sbet2; csig2 = comg2 = calp2 * cbet2; norm2(&ssig2, &csig2); /* norm2(&somg2, &comg2); -- don't need to normalize! */ /* sig12 = sig2 - sig1, limit to [0, pi] */ sig12 = atan2(fmax(0.0, csig1 * ssig2 - ssig1 * csig2) + 0, csig1 * csig2 + ssig1 * ssig2); /* omg12 = omg2 - omg1, limit to [0, pi] */ somg12 = fmax(0.0, comg1 * somg2 - somg1 * comg2) + 0; comg12 = comg1 * comg2 + somg1 * somg2; /* eta = omg12 - lam120 */ eta = atan2(somg12 * clam120 - comg12 * slam120, comg12 * clam120 + somg12 * slam120); k2 = sq(calp0) * g->ep2; eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); C3f(g, eps, Ca); B312 = (SinCosSeries(TRUE, ssig2, csig2, Ca, nC3-1) - SinCosSeries(TRUE, ssig1, csig1, Ca, nC3-1)); domg12 = -g->f * A3f(g, eps) * salp0 * (sig12 + B312); lam12 = eta + domg12; if (diffp) { if (calp2 == 0) dlam12 = - 2 * g->f1 * dn1 / sbet1; else { Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2, NULLPTR, &dlam12, NULLPTR, NULLPTR, NULLPTR, Ca); dlam12 *= g->f1 / (calp2 * cbet2); } } *psalp2 = salp2; *pcalp2 = calp2; *psig12 = sig12; *pssig1 = ssig1; *pcsig1 = csig1; *pssig2 = ssig2; *pcsig2 = csig2; *peps = eps; *pdomg12 = domg12; if (diffp) *pdlam12 = dlam12; return lam12; } double A3f(const struct geod_geodesic* g, double eps) { /* Evaluate A3 */ return polyvalx(nA3 - 1, g->A3x, eps); } void C3f(const struct geod_geodesic* g, double eps, double c[]) { /* Evaluate C3 coeffs * Elements c[1] through c[nC3 - 1] are set */ double mult = 1; int o = 0, l; for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ int m = nC3 - l - 1; /* order of polynomial in eps */ mult *= eps; c[l] = mult * polyvalx(m, g->C3x + o, eps); o += m + 1; } } void C4f(const struct geod_geodesic* g, double eps, double c[]) { /* Evaluate C4 coeffs * Elements c[0] through c[nC4 - 1] are set */ double mult = 1; int o = 0, l; for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ int m = nC4 - l - 1; /* order of polynomial in eps */ c[l] = mult * polyvalx(m, g->C4x + o, eps); o += m + 1; mult *= eps; } } /* The scale factor A1-1 = mean value of (d/dsigma)I1 - 1 */ double A1m1f(double eps) { static const double coeff[] = { /* (1-eps)*A1-1, polynomial in eps2 of order 3 */ 1, 4, 64, 0, 256, }; int m = nA1/2; double t = polyvalx(m, coeff, sq(eps)) / coeff[m + 1]; return (t + eps) / (1 - eps); } /* The coefficients C1[l] in the Fourier expansion of B1 */ void C1f(double eps, double c[]) { static const double coeff[] = { /* C1[1]/eps^1, polynomial in eps2 of order 2 */ -1, 6, -16, 32, /* C1[2]/eps^2, polynomial in eps2 of order 2 */ -9, 64, -128, 2048, /* C1[3]/eps^3, polynomial in eps2 of order 1 */ 9, -16, 768, /* C1[4]/eps^4, polynomial in eps2 of order 1 */ 3, -5, 512, /* C1[5]/eps^5, polynomial in eps2 of order 0 */ -7, 1280, /* C1[6]/eps^6, polynomial in eps2 of order 0 */ -7, 2048, }; double eps2 = sq(eps), d = eps; int o = 0, l; for (l = 1; l <= nC1; ++l) { /* l is index of C1p[l] */ int m = (nC1 - l) / 2; /* order of polynomial in eps^2 */ c[l] = d * polyvalx(m, coeff + o, eps2) / coeff[o + m + 1]; o += m + 2; d *= eps; } } /* The coefficients C1p[l] in the Fourier expansion of B1p */ void C1pf(double eps, double c[]) { static const double coeff[] = { /* C1p[1]/eps^1, polynomial in eps2 of order 2 */ 205, -432, 768, 1536, /* C1p[2]/eps^2, polynomial in eps2 of order 2 */ 4005, -4736, 3840, 12288, /* C1p[3]/eps^3, polynomial in eps2 of order 1 */ -225, 116, 384, /* C1p[4]/eps^4, polynomial in eps2 of order 1 */ -7173, 2695, 7680, /* C1p[5]/eps^5, polynomial in eps2 of order 0 */ 3467, 7680, /* C1p[6]/eps^6, polynomial in eps2 of order 0 */ 38081, 61440, }; double eps2 = sq(eps), d = eps; int o = 0, l; for (l = 1; l <= nC1p; ++l) { /* l is index of C1p[l] */ int m = (nC1p - l) / 2; /* order of polynomial in eps^2 */ c[l] = d * polyvalx(m, coeff + o, eps2) / coeff[o + m + 1]; o += m + 2; d *= eps; } } /* The scale factor A2-1 = mean value of (d/dsigma)I2 - 1 */ double A2m1f(double eps) { static const double coeff[] = { /* (eps+1)*A2-1, polynomial in eps2 of order 3 */ -11, -28, -192, 0, 256, }; int m = nA2/2; double t = polyvalx(m, coeff, sq(eps)) / coeff[m + 1]; return (t - eps) / (1 + eps); } /* The coefficients C2[l] in the Fourier expansion of B2 */ void C2f(double eps, double c[]) { static const double coeff[] = { /* C2[1]/eps^1, polynomial in eps2 of order 2 */ 1, 2, 16, 32, /* C2[2]/eps^2, polynomial in eps2 of order 2 */ 35, 64, 384, 2048, /* C2[3]/eps^3, polynomial in eps2 of order 1 */ 15, 80, 768, /* C2[4]/eps^4, polynomial in eps2 of order 1 */ 7, 35, 512, /* C2[5]/eps^5, polynomial in eps2 of order 0 */ 63, 1280, /* C2[6]/eps^6, polynomial in eps2 of order 0 */ 77, 2048, }; double eps2 = sq(eps), d = eps; int o = 0, l; for (l = 1; l <= nC2; ++l) { /* l is index of C2[l] */ int m = (nC2 - l) / 2; /* order of polynomial in eps^2 */ c[l] = d * polyvalx(m, coeff + o, eps2) / coeff[o + m + 1]; o += m + 2; d *= eps; } } /* The scale factor A3 = mean value of (d/dsigma)I3 */ void A3coeff(struct geod_geodesic* g) { static const double coeff[] = { /* A3, coeff of eps^5, polynomial in n of order 0 */ -3, 128, /* A3, coeff of eps^4, polynomial in n of order 1 */ -2, -3, 64, /* A3, coeff of eps^3, polynomial in n of order 2 */ -1, -3, -1, 16, /* A3, coeff of eps^2, polynomial in n of order 2 */ 3, -1, -2, 8, /* A3, coeff of eps^1, polynomial in n of order 1 */ 1, -1, 2, /* A3, coeff of eps^0, polynomial in n of order 0 */ 1, 1, }; int o = 0, k = 0, j; for (j = nA3 - 1; j >= 0; --j) { /* coeff of eps^j */ int m = nA3 - j - 1 < j ? nA3 - j - 1 : j; /* order of polynomial in n */ g->A3x[k++] = polyvalx(m, coeff + o, g->n) / coeff[o + m + 1]; o += m + 2; } } /* The coefficients C3[l] in the Fourier expansion of B3 */ void C3coeff(struct geod_geodesic* g) { static const double coeff[] = { /* C3[1], coeff of eps^5, polynomial in n of order 0 */ 3, 128, /* C3[1], coeff of eps^4, polynomial in n of order 1 */ 2, 5, 128, /* C3[1], coeff of eps^3, polynomial in n of order 2 */ -1, 3, 3, 64, /* C3[1], coeff of eps^2, polynomial in n of order 2 */ -1, 0, 1, 8, /* C3[1], coeff of eps^1, polynomial in n of order 1 */ -1, 1, 4, /* C3[2], coeff of eps^5, polynomial in n of order 0 */ 5, 256, /* C3[2], coeff of eps^4, polynomial in n of order 1 */ 1, 3, 128, /* C3[2], coeff of eps^3, polynomial in n of order 2 */ -3, -2, 3, 64, /* C3[2], coeff of eps^2, polynomial in n of order 2 */ 1, -3, 2, 32, /* C3[3], coeff of eps^5, polynomial in n of order 0 */ 7, 512, /* C3[3], coeff of eps^4, polynomial in n of order 1 */ -10, 9, 384, /* C3[3], coeff of eps^3, polynomial in n of order 2 */ 5, -9, 5, 192, /* C3[4], coeff of eps^5, polynomial in n of order 0 */ 7, 512, /* C3[4], coeff of eps^4, polynomial in n of order 1 */ -14, 7, 512, /* C3[5], coeff of eps^5, polynomial in n of order 0 */ 21, 2560, }; int o = 0, k = 0, l, j; for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ for (j = nC3 - 1; j >= l; --j) { /* coeff of eps^j */ int m = nC3 - j - 1 < j ? nC3 - j - 1 : j; /* order of polynomial in n */ g->C3x[k++] = polyvalx(m, coeff + o, g->n) / coeff[o + m + 1]; o += m + 2; } } } /* The coefficients C4[l] in the Fourier expansion of I4 */ void C4coeff(struct geod_geodesic* g) { static const double coeff[] = { /* C4[0], coeff of eps^5, polynomial in n of order 0 */ 97, 15015, /* C4[0], coeff of eps^4, polynomial in n of order 1 */ 1088, 156, 45045, /* C4[0], coeff of eps^3, polynomial in n of order 2 */ -224, -4784, 1573, 45045, /* C4[0], coeff of eps^2, polynomial in n of order 3 */ -10656, 14144, -4576, -858, 45045, /* C4[0], coeff of eps^1, polynomial in n of order 4 */ 64, 624, -4576, 6864, -3003, 15015, /* C4[0], coeff of eps^0, polynomial in n of order 5 */ 100, 208, 572, 3432, -12012, 30030, 45045, /* C4[1], coeff of eps^5, polynomial in n of order 0 */ 1, 9009, /* C4[1], coeff of eps^4, polynomial in n of order 1 */ -2944, 468, 135135, /* C4[1], coeff of eps^3, polynomial in n of order 2 */ 5792, 1040, -1287, 135135, /* C4[1], coeff of eps^2, polynomial in n of order 3 */ 5952, -11648, 9152, -2574, 135135, /* C4[1], coeff of eps^1, polynomial in n of order 4 */ -64, -624, 4576, -6864, 3003, 135135, /* C4[2], coeff of eps^5, polynomial in n of order 0 */ 8, 10725, /* C4[2], coeff of eps^4, polynomial in n of order 1 */ 1856, -936, 225225, /* C4[2], coeff of eps^3, polynomial in n of order 2 */ -8448, 4992, -1144, 225225, /* C4[2], coeff of eps^2, polynomial in n of order 3 */ -1440, 4160, -4576, 1716, 225225, /* C4[3], coeff of eps^5, polynomial in n of order 0 */ -136, 63063, /* C4[3], coeff of eps^4, polynomial in n of order 1 */ 1024, -208, 105105, /* C4[3], coeff of eps^3, polynomial in n of order 2 */ 3584, -3328, 1144, 315315, /* C4[4], coeff of eps^5, polynomial in n of order 0 */ -128, 135135, /* C4[4], coeff of eps^4, polynomial in n of order 1 */ -2560, 832, 405405, /* C4[5], coeff of eps^5, polynomial in n of order 0 */ 128, 99099, }; int o = 0, k = 0, l, j; for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ for (j = nC4 - 1; j >= l; --j) { /* coeff of eps^j */ int m = nC4 - j - 1; /* order of polynomial in n */ g->C4x[k++] = polyvalx(m, coeff + o, g->n) / coeff[o + m + 1]; o += m + 2; } } } int transit(double lon1, double lon2) { double lon12; /* Return 1 or -1 if crossing prime meridian in east or west direction. * Otherwise return zero. */ /* Compute lon12 the same way as Geodesic::Inverse. */ lon12 = AngDiff(lon1, lon2, NULLPTR); lon1 = AngNormalize(lon1); lon2 = AngNormalize(lon2); return lon12 > 0 && ((lon1 < 0 && lon2 >= 0) || (lon1 > 0 && lon2 == 0)) ? 1 : (lon12 < 0 && lon1 >= 0 && lon2 < 0 ? -1 : 0); } int transitdirect(double lon1, double lon2) { /* Compute exactly the parity of * int(floor(lon2 / 360)) - int(floor(lon1 / 360)) */ lon1 = remainder(lon1, 2.0 * td); lon2 = remainder(lon2, 2.0 * td); return ( (lon2 >= 0 && lon2 < td ? 0 : 1) - (lon1 >= 0 && lon1 < td ? 0 : 1) ); } void accini(double s[]) { /* Initialize an accumulator; this is an array with two elements. */ s[0] = s[1] = 0; } void acccopy(const double s[], double t[]) { /* Copy an accumulator; t = s. */ t[0] = s[0]; t[1] = s[1]; } void accadd(double s[], double y) { /* Add y to an accumulator. */ double u, z = sumx(y, s[1], &u); s[0] = sumx(z, s[0], &s[1]); if (s[0] == 0) s[0] = u; else s[1] = s[1] + u; } double accsum(const double s[], double y) { /* Return accumulator + y (but don't add to accumulator). */ double t[2]; acccopy(s, t); accadd(t, y); return t[0]; } void accneg(double s[]) { /* Negate an accumulator. */ s[0] = -s[0]; s[1] = -s[1]; } void accrem(double s[], double y) { /* Reduce to [-y/2, y/2]. */ s[0] = remainder(s[0], y); accadd(s, 0.0); } void geod_polygon_init(struct geod_polygon* p, boolx polylinep) { p->polyline = (polylinep != 0); geod_polygon_clear(p); } void geod_polygon_clear(struct geod_polygon* p) { p->lat0 = p->lon0 = p->lat = p->lon = NaN; accini(p->P); accini(p->A); p->num = p->crossings = 0; } void geod_polygon_addpoint(const struct geod_geodesic* g, struct geod_polygon* p, double lat, double lon) { if (p->num == 0) { p->lat0 = p->lat = lat; p->lon0 = p->lon = lon; } else { double s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ geod_geninverse(g, p->lat, p->lon, lat, lon, &s12, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, p->polyline ? NULLPTR : &S12); accadd(p->P, s12); if (!p->polyline) { accadd(p->A, S12); p->crossings += transit(p->lon, lon); } p->lat = lat; p->lon = lon; } ++p->num; } void geod_polygon_addedge(const struct geod_geodesic* g, struct geod_polygon* p, double azi, double s) { if (p->num) { /* Do nothing is num is zero */ /* Initialize S12 to stop Visual Studio warning. Initialization of lat and * lon is to make CLang static analyzer happy. */ double lat = 0, lon = 0, S12 = 0; geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, &lat, &lon, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, p->polyline ? NULLPTR : &S12); accadd(p->P, s); if (!p->polyline) { accadd(p->A, S12); p->crossings += transitdirect(p->lon, lon); } p->lat = lat; p->lon = lon; ++p->num; } } unsigned geod_polygon_compute(const struct geod_geodesic* g, const struct geod_polygon* p, boolx reverse, boolx sign, double* pA, double* pP) { double s12, S12, t[2]; if (p->num < 2) { if (pP) *pP = 0; if (!p->polyline && pA) *pA = 0; return p->num; } if (p->polyline) { if (pP) *pP = p->P[0]; return p->num; } geod_geninverse(g, p->lat, p->lon, p->lat0, p->lon0, &s12, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, &S12); if (pP) *pP = accsum(p->P, s12); acccopy(p->A, t); accadd(t, S12); if (pA) *pA = areareduceA(t, 4 * pi * g->c2, p->crossings + transit(p->lon, p->lon0), reverse, sign); return p->num; } unsigned geod_polygon_testpoint(const struct geod_geodesic* g, const struct geod_polygon* p, double lat, double lon, boolx reverse, boolx sign, double* pA, double* pP) { double perimeter, tempsum; int crossings, i; unsigned num = p->num + 1; if (num == 1) { if (pP) *pP = 0; if (!p->polyline && pA) *pA = 0; return num; } perimeter = p->P[0]; tempsum = p->polyline ? 0 : p->A[0]; crossings = p->crossings; for (i = 0; i < (p->polyline ? 1 : 2); ++i) { double s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ geod_geninverse(g, i == 0 ? p->lat : lat, i == 0 ? p->lon : lon, i != 0 ? p->lat0 : lat, i != 0 ? p->lon0 : lon, &s12, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, p->polyline ? NULLPTR : &S12); perimeter += s12; if (!p->polyline) { tempsum += S12; crossings += transit(i == 0 ? p->lon : lon, i != 0 ? p->lon0 : lon); } } if (pP) *pP = perimeter; if (p->polyline) return num; if (pA) *pA = areareduceB(tempsum, 4 * pi * g->c2, crossings, reverse, sign); return num; } unsigned geod_polygon_testedge(const struct geod_geodesic* g, const struct geod_polygon* p, double azi, double s, boolx reverse, boolx sign, double* pA, double* pP) { double perimeter, tempsum; int crossings; unsigned num = p->num + 1; if (num == 1) { /* we don't have a starting point! */ if (pP) *pP = NaN; if (!p->polyline && pA) *pA = NaN; return 0; } perimeter = p->P[0] + s; if (p->polyline) { if (pP) *pP = perimeter; return num; } tempsum = p->A[0]; crossings = p->crossings; { /* Initialization of lat, lon, and S12 is to make CLang static analyzer * happy. */ double lat = 0, lon = 0, s12, S12 = 0; geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, &lat, &lon, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, &S12); tempsum += S12; crossings += transitdirect(p->lon, lon); geod_geninverse(g, lat, lon, p->lat0, p->lon0, &s12, NULLPTR, NULLPTR, NULLPTR, NULLPTR, NULLPTR, &S12); perimeter += s12; tempsum += S12; crossings += transit(lon, p->lon0); } if (pP) *pP = perimeter; if (pA) *pA = areareduceB(tempsum, 4 * pi * g->c2, crossings, reverse, sign); return num; } void geod_polygonarea(const struct geod_geodesic* g, double lats[], double lons[], int n, double* pA, double* pP) { int i; struct geod_polygon p; geod_polygon_init(&p, FALSE); for (i = 0; i < n; ++i) geod_polygon_addpoint(g, &p, lats[i], lons[i]); geod_polygon_compute(g, &p, FALSE, TRUE, pA, pP); } double areareduceA(double area[], double area0, int crossings, boolx reverse, boolx sign) { accrem(area, area0); if (crossings & 1) accadd(area, (area[0] < 0 ? 1 : -1) * area0/2); /* area is with the clockwise sense. If !reverse convert to * counter-clockwise convention. */ if (!reverse) accneg(area); /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ if (sign) { if (area[0] > area0/2) accadd(area, -area0); else if (area[0] <= -area0/2) accadd(area, +area0); } else { if (area[0] >= area0) accadd(area, -area0); else if (area[0] < 0) accadd(area, +area0); } return 0 + area[0]; } double areareduceB(double area, double area0, int crossings, boolx reverse, boolx sign) { area = remainder(area, area0); if (crossings & 1) area += (area < 0 ? 1 : -1) * area0/2; /* area is with the clockwise sense. If !reverse convert to * counter-clockwise convention. */ if (!reverse) area *= -1; /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ if (sign) { if (area > area0/2) area -= area0; else if (area <= -area0/2) area += area0; } else { if (area >= area0) area -= area0; else if (area < 0) area += area0; } return 0 + area; } /** @endcond */ proj-9.8.1/src/info.cpp000664 001750 001750 00000017730 15166171715 014711 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: proj_info() and proj_pj_info() * * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 * ****************************************************************************** * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" #include "filemanager.hpp" /*****************************************************************************/ static char *path_append(char *buf, const char *app, size_t *buf_size) { /****************************************************************************** Helper for proj_info() below. Append app to buf, separated by a semicolon. Also handle allocation of longer buffer if needed. Returns buffer and adjusts *buf_size through provided pointer arg. ******************************************************************************/ char *p; size_t len, applen = 0, buflen = 0; #ifdef _WIN32 const char *delim = ";"; #else const char *delim = ":"; #endif /* Nothing to do? */ if (nullptr == app) return buf; applen = strlen(app); if (0 == applen) return buf; /* Start checking whether buf is long enough */ if (nullptr != buf) buflen = strlen(buf); len = buflen + applen + strlen(delim) + 1; /* "pj_realloc", so to speak */ if (*buf_size < len) { p = static_cast(calloc(2 * len, sizeof(char))); if (nullptr == p) { free(buf); return nullptr; } *buf_size = 2 * len; if (buf != nullptr) strcpy(p, buf); free(buf); buf = p; } assert(buf); /* Only append a delimiter if something's already there */ if (0 != buflen) strcat(buf, delim); strcat(buf, app); return buf; } static const char *empty = {""}; static char version[64] = {""}; static PJ_INFO info = {0, 0, 0, nullptr, nullptr, nullptr, nullptr, 0}; /*****************************************************************************/ PJ_INFO proj_info(void) { /****************************************************************************** Basic info about the current instance of the PROJ.4 library. Returns PJ_INFO struct. ******************************************************************************/ size_t buf_size = 0; char *buf = nullptr; pj_acquire_lock(); info.major = PROJ_VERSION_MAJOR; info.minor = PROJ_VERSION_MINOR; info.patch = PROJ_VERSION_PATCH; /* A normal version string is xx.yy.zz which is 8 characters long and there is room for 64 bytes in the version string. */ snprintf(version, sizeof(version), "%d.%d.%d", info.major, info.minor, info.patch); info.version = version; info.release = pj_get_release(); /* build search path string */ auto ctx = pj_get_default_ctx(); if (ctx->search_paths.empty()) { const auto searchpaths = pj_get_default_searchpaths(ctx); for (const auto &path : searchpaths) { buf = path_append(buf, path.c_str(), &buf_size); } } else { for (const auto &path : ctx->search_paths) { buf = path_append(buf, path.c_str(), &buf_size); } } if (info.searchpath != empty) free(const_cast(info.searchpath)); info.searchpath = buf ? buf : empty; info.paths = ctx->c_compat_paths; info.path_count = static_cast(ctx->search_paths.size()); pj_release_lock(); return info; } /*****************************************************************************/ PJ_PROJ_INFO proj_pj_info(PJ *P) { /****************************************************************************** Basic info about a particular instance of a projection object. Returns PJ_PROJ_INFO struct. ******************************************************************************/ PJ_PROJ_INFO pjinfo; char *def; memset(&pjinfo, 0, sizeof(PJ_PROJ_INFO)); pjinfo.accuracy = -1.0; if (nullptr == P) return pjinfo; /* coordinate operation description */ if (!P->alternativeCoordinateOperations.empty()) { if (P->iCurCoordOp >= 0) { P = P->alternativeCoordinateOperations[P->iCurCoordOp].pj; } else { PJ *candidateOp = nullptr; // If there's just a single coordinate operation which is // instantiable, use it. for (const auto &op : P->alternativeCoordinateOperations) { if (op.isInstantiable()) { if (candidateOp == nullptr) { candidateOp = op.pj; } else { candidateOp = nullptr; break; } } } if (candidateOp) { P = candidateOp; } else { pjinfo.id = "unknown"; pjinfo.description = "unavailable until proj_trans is called"; pjinfo.definition = "unavailable until proj_trans is called"; return pjinfo; } } } /* projection id */ if (pj_param(P->ctx, P->params, "tproj").i) pjinfo.id = pj_param(P->ctx, P->params, "sproj").s; pjinfo.description = P->descr; if (P->iso_obj) { auto identifiedObj = dynamic_cast(P->iso_obj.get()); // cppcheck-suppress knownConditionTrueFalse if (identifiedObj) { pjinfo.description = identifiedObj->nameStr().c_str(); } } // accuracy if (P->iso_obj) { auto conv = dynamic_cast( P->iso_obj.get()); // cppcheck-suppress knownConditionTrueFalse if (conv) { pjinfo.accuracy = 0.0; } else { auto op = dynamic_cast( P->iso_obj.get()); // cppcheck-suppress knownConditionTrueFalse if (op) { const auto &accuracies = op->coordinateOperationAccuracies(); if (!accuracies.empty()) { try { pjinfo.accuracy = std::stod(accuracies[0]->value()); } catch (const std::exception &) { } } } } } /* projection definition */ if (P->def_full) def = P->def_full; else def = pj_get_def(P, 0); /* pj_get_def takes a non-const PJ pointer */ if (nullptr == def) pjinfo.definition = empty; else pjinfo.definition = pj_shrink(def); /* Make proj_destroy clean this up eventually */ P->def_full = def; pjinfo.has_inverse = pj_has_inverse(P); return pjinfo; } proj-9.8.1/src/gauss.cpp000664 001750 001750 00000006647 15166171715 015105 0ustar00eveneven000000 000000 /* ** libproj -- library of cartographic projections ** ** Copyright (c) 2003 Gerald I. Evenden */ /* ** 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 #include #include "proj.h" #include "proj_internal.h" #define MAX_ITER 20 namespace { // anonymous namespace struct GAUSS { double C; double K; double e; double ratexp; }; } // anonymous namespace #define DEL_TOL 1e-14 static double srat(double esinp, double ratexp) { return (pow((1. - esinp) / (1. + esinp), ratexp)); } void *pj_gauss_ini(double e, double phi0, double *chi, double *rc) { double sphi, cphi, es; struct GAUSS *en; if ((en = (struct GAUSS *)malloc(sizeof(struct GAUSS))) == nullptr) return (nullptr); es = e * e; en->e = e; sphi = sin(phi0); cphi = cos(phi0); cphi *= cphi; *rc = sqrt(1. - es) / (1. - es * sphi * sphi); en->C = sqrt(1. + es * cphi * cphi / (1. - es)); if (en->C == 0.0) { free(en); return nullptr; } *chi = asin(sphi / en->C); en->ratexp = 0.5 * en->C * e; double srat_val = srat(en->e * sphi, en->ratexp); if (srat_val == 0.0) { free(en); return nullptr; } if (.5 * phi0 + M_FORTPI < 1e-10) { en->K = 1.0 / srat_val; } else { en->K = tan(.5 * *chi + M_FORTPI) / (pow(tan(.5 * phi0 + M_FORTPI), en->C) * srat_val); } return ((void *)en); } PJ_LP pj_gauss(PJ_CONTEXT *ctx, PJ_LP elp, const void *data) { const struct GAUSS *en = (const struct GAUSS *)data; PJ_LP slp; (void)ctx; slp.phi = 2. * atan(en->K * pow(tan(.5 * elp.phi + M_FORTPI), en->C) * srat(en->e * sin(elp.phi), en->ratexp)) - M_HALFPI; slp.lam = en->C * (elp.lam); return (slp); } PJ_LP pj_inv_gauss(PJ_CONTEXT *ctx, PJ_LP slp, const void *data) { const struct GAUSS *en = (const struct GAUSS *)data; PJ_LP elp; double num; int i; elp.lam = slp.lam / en->C; num = pow(tan(.5 * slp.phi + M_FORTPI) / en->K, 1. / en->C); for (i = MAX_ITER; i; --i) { elp.phi = 2. * atan(num * srat(en->e * sin(slp.phi), -.5 * en->e)) - M_HALFPI; if (fabs(elp.phi - slp.phi) < DEL_TOL) break; slp.phi = elp.phi; } /* convergence failed */ if (!i) proj_context_errno_set( ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return (elp); } proj-9.8.1/src/networkfilemanager.cpp000664 001750 001750 00000312666 15166171715 017650 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to network access and caching * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2019-2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS #if !defined(_WIN32) && !defined(__APPLE__) && !defined(_GNU_SOURCE) // For usleep() on Cygwin #define _GNU_SOURCE #endif #if defined(EMSCRIPTEN_FETCH_ENABLED) #ifndef __EMSCRIPTEN_PTHREADS__ #error "__EMSCRIPTEN_PTHREADS__ not defined. Are you setting -pthread?" #endif #ifdef CURL_ENABLED #error "EMSCRIPTEN_FETCH is not compatible with CURL. Use only one." #endif #define DO_EMSCRIPTEN_FETCH #endif #include #include #include #include #include #include "filemanager.hpp" #include "proj.h" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/lru_cache.hpp" #include "proj_internal.h" #include "sqlite3_utils.hpp" #ifdef CURL_ENABLED #include #include // for sqlite3_snprintf #endif #ifdef DO_EMSCRIPTEN_FETCH #include #include #include // for sqlite3_snprintf #include #endif #include #ifdef _WIN32 #include #else #include #include #endif #if defined(_WIN32) #include #elif defined(__MACH__) && defined(__APPLE__) #include #elif defined(__FreeBSD__) #include #include #endif #include //! @cond Doxygen_Suppress #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) using namespace NS_PROJ::internal; NS_PROJ_START // --------------------------------------------------------------------------- static void sleep_ms(int ms) { #ifdef _WIN32 Sleep(ms); #else usleep(ms * 1000); #endif } // --------------------------------------------------------------------------- constexpr size_t DOWNLOAD_CHUNK_SIZE = 16 * 1024; constexpr int MAX_CHUNKS = 64; struct FileProperties { unsigned long long size = 0; time_t lastChecked = 0; std::string lastModified{}; std::string etag{}; }; class NetworkChunkCache { public: void insert(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx, std::vector &&data); std::shared_ptr> get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx); std::shared_ptr> get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx, FileProperties &props); void clearMemoryCache(); static void clearDiskChunkCache(PJ_CONTEXT *ctx); private: struct Key { std::string url; unsigned long long chunkIdx; Key(const std::string &urlIn, unsigned long long chunkIdxIn) : url(urlIn), chunkIdx(chunkIdxIn) {} bool operator==(const Key &other) const { return url == other.url && chunkIdx == other.chunkIdx; } }; struct KeyHasher { std::size_t operator()(const Key &k) const { return std::hash{}(k.url) ^ (std::hash{}(k.chunkIdx) << 1); } }; lru11::Cache< Key, std::shared_ptr>, std::mutex, std::unordered_map< Key, typename std::list>>>::iterator, KeyHasher>> cache_{MAX_CHUNKS}; }; // --------------------------------------------------------------------------- static NetworkChunkCache gNetworkChunkCache{}; // --------------------------------------------------------------------------- class NetworkFilePropertiesCache { public: void insert(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props); bool tryGet(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props); void clearMemoryCache(); private: lru11::Cache cache_{}; }; // --------------------------------------------------------------------------- static NetworkFilePropertiesCache gNetworkFileProperties{}; // --------------------------------------------------------------------------- class DiskChunkCache { PJ_CONTEXT *ctx_ = nullptr; std::string path_{}; sqlite3 *hDB_ = nullptr; std::unique_ptr vfs_{}; explicit DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path); bool initialize(); void commitAndClose(); bool createDBStructure(); bool checkConsistency(); bool get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id, sqlite3_int64 &prev, sqlite3_int64 &next, sqlite3_int64 &head, sqlite3_int64 &tail); bool update_links_of_prev_and_next_links(sqlite3_int64 prev, sqlite3_int64 next); bool update_linked_chunks(sqlite3_int64 link_id, sqlite3_int64 prev, sqlite3_int64 next); bool update_linked_chunks_head_tail(sqlite3_int64 head, sqlite3_int64 tail); DiskChunkCache(const DiskChunkCache &) = delete; DiskChunkCache &operator=(const DiskChunkCache &) = delete; public: static std::unique_ptr open(PJ_CONTEXT *ctx); ~DiskChunkCache(); sqlite3 *handle() { return hDB_; } std::unique_ptr prepare(const char *sql); bool move_to_head(sqlite3_int64 chunk_id); bool move_to_tail(sqlite3_int64 chunk_id); void closeAndUnlink(); }; // --------------------------------------------------------------------------- static bool pj_context_get_grid_cache_is_enabled(PJ_CONTEXT *ctx) { pj_load_ini(ctx); return ctx->gridChunkCache.enabled; } // --------------------------------------------------------------------------- static long long pj_context_get_grid_cache_max_size(PJ_CONTEXT *ctx) { pj_load_ini(ctx); return ctx->gridChunkCache.max_size; } // --------------------------------------------------------------------------- static int pj_context_get_grid_cache_ttl(PJ_CONTEXT *ctx) { pj_load_ini(ctx); return ctx->gridChunkCache.ttl; } // --------------------------------------------------------------------------- std::unique_ptr DiskChunkCache::open(PJ_CONTEXT *ctx) { if (!pj_context_get_grid_cache_is_enabled(ctx)) { return nullptr; } const auto cachePath = pj_context_get_grid_cache_filename(ctx); if (cachePath.empty()) { return nullptr; } auto diskCache = std::unique_ptr(new DiskChunkCache(ctx, cachePath)); if (!diskCache->initialize()) diskCache.reset(); return diskCache; } // --------------------------------------------------------------------------- DiskChunkCache::DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path) : ctx_(ctx), path_(path) {} // --------------------------------------------------------------------------- bool DiskChunkCache::initialize() { std::string vfsName; if (ctx_->custom_sqlite3_vfs_name.empty()) { vfs_ = SQLite3VFS::create(true, false, false); if (vfs_ == nullptr) { return false; } vfsName = vfs_->name(); } else { vfsName = ctx_->custom_sqlite3_vfs_name; } sqlite3_open_v2(path_.c_str(), &hDB_, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, vfsName.c_str()); if (!hDB_) { pj_log(ctx_, PJ_LOG_ERROR, "Cannot open %s", path_.c_str()); return false; } // Cannot run more than 30 times / a bit more than one second. for (int i = 0;; i++) { int ret = sqlite3_exec(hDB_, "BEGIN EXCLUSIVE", nullptr, nullptr, nullptr); if (ret == SQLITE_OK) { break; } if (ret != SQLITE_BUSY) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); sqlite3_close(hDB_); hDB_ = nullptr; return false; } const char *max_iters = getenv("PROJ_LOCK_MAX_ITERS"); if (i >= (max_iters && max_iters[0] ? atoi(max_iters) : 30)) { // A bit more than 1 second pj_log(ctx_, PJ_LOG_ERROR, "Cannot take exclusive lock on %s", path_.c_str()); sqlite3_close(hDB_); hDB_ = nullptr; return false; } pj_log(ctx_, PJ_LOG_TRACE, "Lock taken on cache. Waiting a bit..."); // Retry every 5 ms for 50 ms, then every 10 ms for 100 ms, then // every 100 ms sleep_ms(i < 10 ? 5 : i < 20 ? 10 : 100); } char **pasResult = nullptr; int nRows = 0; int nCols = 0; sqlite3_get_table(hDB_, "SELECT 1 FROM sqlite_master WHERE name = 'properties'", &pasResult, &nRows, &nCols, nullptr); sqlite3_free_table(pasResult); if (nRows == 0) { if (!createDBStructure()) { sqlite3_close(hDB_); hDB_ = nullptr; return false; } } if (getenv("PROJ_CHECK_CACHE_CONSISTENCY")) { checkConsistency(); } return true; } // --------------------------------------------------------------------------- static const char *cache_db_structure_sql = "CREATE TABLE properties(" " url TEXT PRIMARY KEY NOT NULL," " lastChecked TIMESTAMP NOT NULL," " fileSize INTEGER NOT NULL," " lastModified TEXT," " etag TEXT" ");" "CREATE TABLE downloaded_file_properties(" " url TEXT PRIMARY KEY NOT NULL," " lastChecked TIMESTAMP NOT NULL," " fileSize INTEGER NOT NULL," " lastModified TEXT," " etag TEXT" ");" "CREATE TABLE chunk_data(" " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0)," " data BLOB NOT NULL" ");" "CREATE TABLE chunks(" " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0)," " url TEXT NOT NULL," " offset INTEGER NOT NULL," " data_id INTEGER NOT NULL," " data_size INTEGER NOT NULL," " CONSTRAINT fk_chunks_url FOREIGN KEY (url) REFERENCES properties(url)," " CONSTRAINT fk_chunks_data FOREIGN KEY (data_id) REFERENCES chunk_data(id)" ");" "CREATE INDEX idx_chunks ON chunks(url, offset);" "CREATE TABLE linked_chunks(" " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0)," " chunk_id INTEGER NOT NULL," " prev INTEGER," " next INTEGER," " CONSTRAINT fk_links_chunkid FOREIGN KEY (chunk_id) REFERENCES chunks(id)," " CONSTRAINT fk_links_prev FOREIGN KEY (prev) REFERENCES linked_chunks(id)," " CONSTRAINT fk_links_next FOREIGN KEY (next) REFERENCES linked_chunks(id)" ");" "CREATE INDEX idx_linked_chunks_chunk_id ON linked_chunks(chunk_id);" "CREATE TABLE linked_chunks_head_tail(" " head INTEGER," " tail INTEGER," " CONSTRAINT lht_head FOREIGN KEY (head) REFERENCES linked_chunks(id)," " CONSTRAINT lht_tail FOREIGN KEY (tail) REFERENCES linked_chunks(id)" ");" "INSERT INTO linked_chunks_head_tail VALUES (NULL, NULL);"; bool DiskChunkCache::createDBStructure() { pj_log(ctx_, PJ_LOG_TRACE, "Creating cache DB structure"); if (sqlite3_exec(hDB_, cache_db_structure_sql, nullptr, nullptr, nullptr) != SQLITE_OK) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } return true; } // --------------------------------------------------------------------------- // Used by checkConsistency() and insert() #define INVALIDATED_SQL_LITERAL "'invalidated'" bool DiskChunkCache::checkConsistency() { auto stmt = prepare("SELECT * FROM chunk_data WHERE id NOT IN (SELECT " "data_id FROM chunks)"); if (!stmt) { return false; } if (stmt->execute() != SQLITE_DONE) { fprintf(stderr, "Rows in chunk_data not referenced by chunks.\n"); return false; } stmt = prepare("SELECT * FROM chunks WHERE id NOT IN (SELECT chunk_id FROM " "linked_chunks)"); if (!stmt) { return false; } if (stmt->execute() != SQLITE_DONE) { fprintf(stderr, "Rows in chunks not referenced by linked_chunks.\n"); return false; } stmt = prepare("SELECT * FROM chunks WHERE url <> " INVALIDATED_SQL_LITERAL " AND url " "NOT IN (SELECT url FROM properties)"); if (!stmt) { return false; } if (stmt->execute() != SQLITE_DONE) { fprintf(stderr, "url values in chunks not referenced by properties.\n"); return false; } stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail"); if (!stmt) { return false; } if (stmt->execute() != SQLITE_ROW) { fprintf(stderr, "linked_chunks_head_tail empty.\n"); return false; } const auto head = stmt->getInt64(); const auto tail = stmt->getInt64(); if (stmt->execute() != SQLITE_DONE) { fprintf(stderr, "linked_chunks_head_tail has more than one row.\n"); return false; } stmt = prepare("SELECT COUNT(*) FROM linked_chunks"); if (!stmt) { return false; } if (stmt->execute() != SQLITE_ROW) { fprintf(stderr, "linked_chunks_head_tail empty.\n"); return false; } const auto count_linked_chunks = stmt->getInt64(); if (head) { auto id = head; std::set visitedIds; stmt = prepare("SELECT next FROM linked_chunks WHERE id = ?"); if (!stmt) { return false; } while (true) { visitedIds.insert(id); stmt->reset(); stmt->bindInt64(id); if (stmt->execute() != SQLITE_ROW) { fprintf(stderr, "cannot find linked_chunks.id = %d.\n", static_cast(id)); return false; } auto next = stmt->getInt64(); if (next == 0) { if (id != tail) { fprintf(stderr, "last item when following next is not tail.\n"); return false; } break; } if (visitedIds.find(next) != visitedIds.end()) { fprintf(stderr, "found cycle on linked_chunks.next = %d.\n", static_cast(next)); return false; } id = next; } if (visitedIds.size() != static_cast(count_linked_chunks)) { fprintf(stderr, "ghost items in linked_chunks when following next.\n"); return false; } } else if (count_linked_chunks) { fprintf(stderr, "linked_chunks_head_tail.head = NULL but linked_chunks " "not empty.\n"); return false; } if (tail) { auto id = tail; std::set visitedIds; stmt = prepare("SELECT prev FROM linked_chunks WHERE id = ?"); if (!stmt) { return false; } while (true) { visitedIds.insert(id); stmt->reset(); stmt->bindInt64(id); if (stmt->execute() != SQLITE_ROW) { fprintf(stderr, "cannot find linked_chunks.id = %d.\n", static_cast(id)); return false; } auto prev = stmt->getInt64(); if (prev == 0) { if (id != head) { fprintf(stderr, "last item when following prev is not head.\n"); return false; } break; } if (visitedIds.find(prev) != visitedIds.end()) { fprintf(stderr, "found cycle on linked_chunks.prev = %d.\n", static_cast(prev)); return false; } id = prev; } if (visitedIds.size() != static_cast(count_linked_chunks)) { fprintf(stderr, "ghost items in linked_chunks when following prev.\n"); return false; } } else if (count_linked_chunks) { fprintf(stderr, "linked_chunks_head_tail.tail = NULL but linked_chunks " "not empty.\n"); return false; } fprintf(stderr, "check ok\n"); return true; } // --------------------------------------------------------------------------- void DiskChunkCache::commitAndClose() { if (hDB_) { if (sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr) != SQLITE_OK) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); } sqlite3_close(hDB_); hDB_ = nullptr; } } // --------------------------------------------------------------------------- DiskChunkCache::~DiskChunkCache() { commitAndClose(); } // --------------------------------------------------------------------------- void DiskChunkCache::closeAndUnlink() { commitAndClose(); if (vfs_) { vfs_->raw()->xDelete(vfs_->raw(), path_.c_str(), 0); } } // --------------------------------------------------------------------------- std::unique_ptr DiskChunkCache::prepare(const char *sql) { sqlite3_stmt *hStmt = nullptr; sqlite3_prepare_v2(hDB_, sql, -1, &hStmt, nullptr); if (!hStmt) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return nullptr; } return std::unique_ptr(new SQLiteStatement(hStmt)); } // --------------------------------------------------------------------------- bool DiskChunkCache::get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id, sqlite3_int64 &prev, sqlite3_int64 &next, sqlite3_int64 &head, sqlite3_int64 &tail) { auto stmt = prepare("SELECT id, prev, next FROM linked_chunks WHERE chunk_id = ?"); if (!stmt) return false; stmt->bindInt64(chunk_id); { const auto ret = stmt->execute(); if (ret != SQLITE_ROW) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } } link_id = stmt->getInt64(); prev = stmt->getInt64(); next = stmt->getInt64(); stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail"); { const auto ret = stmt->execute(); if (ret != SQLITE_ROW) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } } head = stmt->getInt64(); tail = stmt->getInt64(); return true; } // --------------------------------------------------------------------------- bool DiskChunkCache::update_links_of_prev_and_next_links(sqlite3_int64 prev, sqlite3_int64 next) { if (prev) { auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?"); if (!stmt) return false; if (next) stmt->bindInt64(next); else stmt->bindNull(); stmt->bindInt64(prev); const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } } if (next) { auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?"); if (!stmt) return false; if (prev) stmt->bindInt64(prev); else stmt->bindNull(); stmt->bindInt64(next); const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } } return true; } // --------------------------------------------------------------------------- bool DiskChunkCache::update_linked_chunks(sqlite3_int64 link_id, sqlite3_int64 prev, sqlite3_int64 next) { auto stmt = prepare("UPDATE linked_chunks SET prev = ?, next = ? WHERE id = ?"); if (!stmt) return false; if (prev) stmt->bindInt64(prev); else stmt->bindNull(); if (next) stmt->bindInt64(next); else stmt->bindNull(); stmt->bindInt64(link_id); const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } return true; } // --------------------------------------------------------------------------- bool DiskChunkCache::update_linked_chunks_head_tail(sqlite3_int64 head, sqlite3_int64 tail) { auto stmt = prepare("UPDATE linked_chunks_head_tail SET head = ?, tail = ?"); if (!stmt) return false; if (head) stmt->bindInt64(head); else stmt->bindNull(); // shouldn't happen normally if (tail) stmt->bindInt64(tail); else stmt->bindNull(); // shouldn't happen normally const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } return true; } // --------------------------------------------------------------------------- bool DiskChunkCache::move_to_head(sqlite3_int64 chunk_id) { sqlite3_int64 link_id = 0; sqlite3_int64 prev = 0; sqlite3_int64 next = 0; sqlite3_int64 head = 0; sqlite3_int64 tail = 0; if (!get_links(chunk_id, link_id, prev, next, head, tail)) { return false; } if (link_id == head) { return true; } if (!update_links_of_prev_and_next_links(prev, next)) { return false; } if (head) { auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?"); if (!stmt) return false; stmt->bindInt64(link_id); stmt->bindInt64(head); const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } } return update_linked_chunks(link_id, 0, head) && update_linked_chunks_head_tail(link_id, (link_id == tail) ? prev : tail); } // --------------------------------------------------------------------------- bool DiskChunkCache::move_to_tail(sqlite3_int64 chunk_id) { sqlite3_int64 link_id = 0; sqlite3_int64 prev = 0; sqlite3_int64 next = 0; sqlite3_int64 head = 0; sqlite3_int64 tail = 0; if (!get_links(chunk_id, link_id, prev, next, head, tail)) { return false; } if (link_id == tail) { return true; } if (!update_links_of_prev_and_next_links(prev, next)) { return false; } if (tail) { auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?"); if (!stmt) return false; stmt->bindInt64(link_id); stmt->bindInt64(tail); const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_)); return false; } } return update_linked_chunks(link_id, tail, 0) && update_linked_chunks_head_tail((link_id == head) ? next : head, link_id); } // --------------------------------------------------------------------------- void NetworkChunkCache::insert(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx, std::vector &&data) { auto dataPtr(std::make_shared>(std::move(data))); cache_.insert(Key(url, chunkIdx), dataPtr); auto diskCache = DiskChunkCache::open(ctx); if (!diskCache) return; auto hDB = diskCache->handle(); // Always insert DOWNLOAD_CHUNK_SIZE bytes to avoid fragmentation std::vector blob(*dataPtr); assert(blob.size() <= DOWNLOAD_CHUNK_SIZE); blob.resize(DOWNLOAD_CHUNK_SIZE); // Check if there is an existing entry for that URL and offset auto stmt = diskCache->prepare( "SELECT id, data_id FROM chunks WHERE url = ? AND offset = ?"); if (!stmt) return; stmt->bindText(url.c_str()); stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE); const auto mainRet = stmt->execute(); if (mainRet == SQLITE_ROW) { const auto chunk_id = stmt->getInt64(); const auto data_id = stmt->getInt64(); stmt = diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?"); if (!stmt) return; stmt->bindBlob(blob.data(), blob.size()); stmt->bindInt64(data_id); { const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } diskCache->move_to_head(chunk_id); return; } else if (mainRet != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } // Lambda to recycle an existing entry that was either invalidated, or // least recently used. const auto reuseExistingEntry = [ctx, &blob, &diskCache, hDB, &url, chunkIdx, &dataPtr](std::unique_ptr &stmtIn) { const auto chunk_id = stmtIn->getInt64(); const auto data_id = stmtIn->getInt64(); if (data_id <= 0) { pj_log(ctx, PJ_LOG_ERROR, "data_id <= 0"); return; } auto l_stmt = diskCache->prepare( "UPDATE chunk_data SET data = ? WHERE id = ?"); if (!l_stmt) return; l_stmt->bindBlob(blob.data(), blob.size()); l_stmt->bindInt64(data_id); { const auto ret2 = l_stmt->execute(); if (ret2 != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } l_stmt = diskCache->prepare("UPDATE chunks SET url = ?, " "offset = ?, data_size = ?, data_id = ? " "WHERE id = ?"); if (!l_stmt) return; l_stmt->bindText(url.c_str()); l_stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE); l_stmt->bindInt64(dataPtr->size()); l_stmt->bindInt64(data_id); l_stmt->bindInt64(chunk_id); { const auto ret2 = l_stmt->execute(); if (ret2 != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } diskCache->move_to_head(chunk_id); }; // Find if there is an invalidated chunk we can reuse stmt = diskCache->prepare( "SELECT id, data_id FROM chunks " "WHERE id = (SELECT tail FROM linked_chunks_head_tail) AND " "url = " INVALIDATED_SQL_LITERAL); if (!stmt) return; { const auto ret = stmt->execute(); if (ret == SQLITE_ROW) { reuseExistingEntry(stmt); return; } else if (ret != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } // Check if we have not reached the max size of the cache stmt = diskCache->prepare("SELECT COUNT(*) FROM chunks"); if (!stmt) return; { const auto ret = stmt->execute(); if (ret != SQLITE_ROW) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } const auto max_size = pj_context_get_grid_cache_max_size(ctx); if (max_size > 0 && static_cast(stmt->getInt64() * DOWNLOAD_CHUNK_SIZE) >= max_size) { stmt = diskCache->prepare( "SELECT id, data_id FROM chunks " "WHERE id = (SELECT tail FROM linked_chunks_head_tail)"); if (!stmt) return; const auto ret = stmt->execute(); if (ret != SQLITE_ROW) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } reuseExistingEntry(stmt); return; } // Otherwise just append a new entry stmt = diskCache->prepare("INSERT INTO chunk_data(data) VALUES (?)"); if (!stmt) return; stmt->bindBlob(blob.data(), blob.size()); { const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } const auto chunk_data_id = sqlite3_last_insert_rowid(hDB); stmt = diskCache->prepare("INSERT INTO chunks(url, offset, data_id, " "data_size) VALUES (?,?,?,?)"); if (!stmt) return; stmt->bindText(url.c_str()); stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE); stmt->bindInt64(chunk_data_id); stmt->bindInt64(dataPtr->size()); { const auto ret = stmt->execute(); if (ret != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } const auto chunk_id = sqlite3_last_insert_rowid(hDB); stmt = diskCache->prepare( "INSERT INTO linked_chunks(chunk_id, prev, next) VALUES (?,NULL,NULL)"); if (!stmt) return; stmt->bindInt64(chunk_id); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } stmt = diskCache->prepare("SELECT head FROM linked_chunks_head_tail"); if (!stmt) return; if (stmt->execute() != SQLITE_ROW) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } if (stmt->getInt64() == 0) { stmt = diskCache->prepare( "UPDATE linked_chunks_head_tail SET head = ?, tail = ?"); if (!stmt) return; stmt->bindInt64(chunk_id); stmt->bindInt64(chunk_id); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } diskCache->move_to_head(chunk_id); } // --------------------------------------------------------------------------- std::shared_ptr> NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx) { std::shared_ptr> ret; if (cache_.tryGet(Key(url, chunkIdx), ret)) { return ret; } auto diskCache = DiskChunkCache::open(ctx); if (!diskCache) return ret; auto hDB = diskCache->handle(); auto stmt = diskCache->prepare( "SELECT chunks.id, chunks.data_size, chunk_data.data FROM chunks " "JOIN chunk_data ON chunks.id = chunk_data.id " "WHERE chunks.url = ? AND chunks.offset = ?"); if (!stmt) return ret; stmt->bindText(url.c_str()); stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE); const auto mainRet = stmt->execute(); if (mainRet == SQLITE_ROW) { const auto chunk_id = stmt->getInt64(); const auto data_size = stmt->getInt64(); int blob_size = 0; const void *blob = stmt->getBlob(blob_size); if (blob_size < data_size) { pj_log(ctx, PJ_LOG_ERROR, "blob_size=%d < data_size for chunk_id=%d", blob_size, static_cast(chunk_id)); return ret; } if (data_size > static_cast(DOWNLOAD_CHUNK_SIZE)) { pj_log(ctx, PJ_LOG_ERROR, "data_size > DOWNLOAD_CHUNK_SIZE"); return ret; } ret.reset(new std::vector()); ret->assign(reinterpret_cast(blob), reinterpret_cast(blob) + static_cast(data_size)); cache_.insert(Key(url, chunkIdx), ret); if (!diskCache->move_to_head(chunk_id)) return ret; } else if (mainRet != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); } return ret; } // --------------------------------------------------------------------------- std::shared_ptr> NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx, FileProperties &props) { if (!gNetworkFileProperties.tryGet(ctx, url, props)) { return nullptr; } return get(ctx, url, chunkIdx); } // --------------------------------------------------------------------------- void NetworkChunkCache::clearMemoryCache() { cache_.clear(); } // --------------------------------------------------------------------------- void NetworkChunkCache::clearDiskChunkCache(PJ_CONTEXT *ctx) { auto diskCache = DiskChunkCache::open(ctx); if (!diskCache) return; diskCache->closeAndUnlink(); } // --------------------------------------------------------------------------- void NetworkFilePropertiesCache::insert(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props) { time(&props.lastChecked); cache_.insert(url, props); auto diskCache = DiskChunkCache::open(ctx); if (!diskCache) return; auto hDB = diskCache->handle(); auto stmt = diskCache->prepare("SELECT fileSize, lastModified, etag " "FROM properties WHERE url = ?"); if (!stmt) return; stmt->bindText(url.c_str()); if (stmt->execute() == SQLITE_ROW) { FileProperties cachedProps; cachedProps.size = stmt->getInt64(); const char *lastModified = stmt->getText(); cachedProps.lastModified = lastModified ? lastModified : std::string(); const char *etag = stmt->getText(); cachedProps.etag = etag ? etag : std::string(); if (props.size != cachedProps.size || props.lastModified != cachedProps.lastModified || props.etag != cachedProps.etag) { // If cached properties don't match recent fresh ones, invalidate // cached chunks stmt = diskCache->prepare("SELECT id FROM chunks WHERE url = ?"); if (!stmt) return; stmt->bindText(url.c_str()); std::vector ids; while (stmt->execute() == SQLITE_ROW) { ids.emplace_back(stmt->getInt64()); stmt->resetResIndex(); } for (const auto id : ids) { diskCache->move_to_tail(id); } stmt = diskCache->prepare( "UPDATE chunks SET url = " INVALIDATED_SQL_LITERAL ", " "offset = -1, data_size = 0 WHERE url = ?"); if (!stmt) return; stmt->bindText(url.c_str()); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } stmt = diskCache->prepare("UPDATE properties SET lastChecked = ?, " "fileSize = ?, lastModified = ?, etag = ? " "WHERE url = ?"); if (!stmt) return; stmt->bindInt64(props.lastChecked); stmt->bindInt64(props.size); if (props.lastModified.empty()) stmt->bindNull(); else stmt->bindText(props.lastModified.c_str()); if (props.etag.empty()) stmt->bindNull(); else stmt->bindText(props.etag.c_str()); stmt->bindText(url.c_str()); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } else { stmt = diskCache->prepare("INSERT INTO properties (url, lastChecked, " "fileSize, lastModified, etag) VALUES " "(?,?,?,?,?)"); if (!stmt) return; stmt->bindText(url.c_str()); stmt->bindInt64(props.lastChecked); stmt->bindInt64(props.size); if (props.lastModified.empty()) stmt->bindNull(); else stmt->bindText(props.lastModified.c_str()); if (props.etag.empty()) stmt->bindNull(); else stmt->bindText(props.etag.c_str()); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return; } } } // --------------------------------------------------------------------------- bool NetworkFilePropertiesCache::tryGet(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props) { if (cache_.tryGet(url, props)) { return true; } auto diskCache = DiskChunkCache::open(ctx); if (!diskCache) return false; auto stmt = diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag " "FROM properties WHERE url = ?"); if (!stmt) return false; stmt->bindText(url.c_str()); if (stmt->execute() != SQLITE_ROW) { return false; } props.lastChecked = static_cast(stmt->getInt64()); props.size = stmt->getInt64(); const char *lastModified = stmt->getText(); props.lastModified = lastModified ? lastModified : std::string(); const char *etag = stmt->getText(); props.etag = etag ? etag : std::string(); const auto ttl = pj_context_get_grid_cache_ttl(ctx); if (ttl > 0) { time_t curTime; time(&curTime); if (curTime > props.lastChecked + ttl) { props = FileProperties(); return false; } } cache_.insert(url, props); return true; } // --------------------------------------------------------------------------- void NetworkFilePropertiesCache::clearMemoryCache() { cache_.clear(); } // --------------------------------------------------------------------------- class NetworkFile : public File { PJ_CONTEXT *m_ctx; std::string m_url; PROJ_NETWORK_HANDLE *m_handle; unsigned long long m_pos = 0; size_t m_nBlocksToDownload = 1; unsigned long long m_lastDownloadedOffset; FileProperties m_props; proj_network_close_cbk_type m_closeCbk; bool m_hasChanged = false; NetworkFile(const NetworkFile &) = delete; NetworkFile &operator=(const NetworkFile &) = delete; protected: NetworkFile(PJ_CONTEXT *ctx, const std::string &url, PROJ_NETWORK_HANDLE *handle, unsigned long long lastDownloadOffset, const FileProperties &props) : File(url), m_ctx(ctx), m_url(url), m_handle(handle), m_lastDownloadedOffset(lastDownloadOffset), m_props(props), m_closeCbk(ctx->networking.close) {} public: ~NetworkFile() override; size_t read(void *buffer, size_t sizeBytes) override; size_t write(const void *, size_t) override { return 0; } bool seek(unsigned long long offset, int whence) override; unsigned long long tell() override; void reassign_context(PJ_CONTEXT *ctx) override; bool hasChanged() const override { return m_hasChanged; } static std::unique_ptr open(PJ_CONTEXT *ctx, const char *filename); static bool get_props_from_headers(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, FileProperties &props); }; // --------------------------------------------------------------------------- bool NetworkFile::get_props_from_headers(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle, FileProperties &props) { const char *contentRange = ctx->networking.get_header_value( ctx, handle, "Content-Range", ctx->networking.user_data); if (contentRange) { const char *slash = strchr(contentRange, '/'); if (slash) { props.size = std::stoull(slash + 1); const char *lastModified = ctx->networking.get_header_value( ctx, handle, "Last-Modified", ctx->networking.user_data); if (lastModified) props.lastModified = lastModified; const char *etag = ctx->networking.get_header_value( ctx, handle, "ETag", ctx->networking.user_data); if (etag) props.etag = etag; return true; } } return false; } // --------------------------------------------------------------------------- std::unique_ptr NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) { FileProperties props; if (gNetworkChunkCache.get(ctx, filename, 0, props)) { return std::unique_ptr(new NetworkFile( ctx, filename, nullptr, std::numeric_limits::max(), props)); } else { std::vector buffer(DOWNLOAD_CHUNK_SIZE); size_t size_read = 0; std::string errorBuffer; errorBuffer.resize(1024); auto handle = ctx->networking.open( ctx, filename, 0, buffer.size(), buffer.data(), &size_read, errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data); if (!handle) { errorBuffer.resize(strlen(errorBuffer.data())); pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename, errorBuffer.c_str()); proj_context_errno_set(ctx, PROJ_ERR_OTHER_NETWORK_ERROR); } else if (get_props_from_headers(ctx, handle, props)) { gNetworkFileProperties.insert(ctx, filename, props); buffer.resize(size_read); gNetworkChunkCache.insert(ctx, filename, 0, std::move(buffer)); return std::unique_ptr( new NetworkFile(ctx, filename, handle, size_read, props)); } else { ctx->networking.close(ctx, handle, ctx->networking.user_data); } return std::unique_ptr(nullptr); } } // --------------------------------------------------------------------------- std::unique_ptr pj_network_file_open(PJ_CONTEXT *ctx, const char *filename) { return NetworkFile::open(ctx, filename); } // --------------------------------------------------------------------------- size_t NetworkFile::read(void *buffer, size_t sizeBytes) { if (sizeBytes == 0) return 0; auto iterOffset = m_pos; while (sizeBytes) { const auto chunkIdxToDownload = iterOffset / DOWNLOAD_CHUNK_SIZE; const auto offsetToDownload = chunkIdxToDownload * DOWNLOAD_CHUNK_SIZE; std::vector region; auto pChunk = gNetworkChunkCache.get(m_ctx, m_url, chunkIdxToDownload); if (pChunk != nullptr) { region = *pChunk; } else { if (offsetToDownload == m_lastDownloadedOffset) { // In case of consecutive reads (of small size), we use a // heuristic that we will read the file sequentially, so // we double the requested size to decrease the number of // client/server roundtrips. if (m_nBlocksToDownload < 100) m_nBlocksToDownload *= 2; } else { // Random reads. Cancel the above heuristics. m_nBlocksToDownload = 1; } // Ensure that we will request at least the number of blocks // to satisfy the remaining buffer size to read. const auto endOffsetToDownload = ((iterOffset + sizeBytes + DOWNLOAD_CHUNK_SIZE - 1) / DOWNLOAD_CHUNK_SIZE) * DOWNLOAD_CHUNK_SIZE; const auto nMinBlocksToDownload = static_cast( (endOffsetToDownload - offsetToDownload) / DOWNLOAD_CHUNK_SIZE); if (m_nBlocksToDownload < nMinBlocksToDownload) m_nBlocksToDownload = nMinBlocksToDownload; // Avoid reading already cached data. // Note: this might get evicted if concurrent reads are done, but // this should not cause bugs. Just missed optimization. for (size_t i = 1; i < m_nBlocksToDownload; i++) { if (gNetworkChunkCache.get(m_ctx, m_url, chunkIdxToDownload + i) != nullptr) { m_nBlocksToDownload = i; break; } } if (m_nBlocksToDownload > MAX_CHUNKS) m_nBlocksToDownload = MAX_CHUNKS; region.resize(m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE); size_t nRead = 0; std::string errorBuffer; errorBuffer.resize(1024); if (!m_handle) { m_handle = m_ctx->networking.open( m_ctx, m_url.c_str(), offsetToDownload, m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, ®ion[0], &nRead, errorBuffer.size(), &errorBuffer[0], m_ctx->networking.user_data); if (!m_handle) { proj_context_errno_set(m_ctx, PROJ_ERR_OTHER_NETWORK_ERROR); return 0; } } else { nRead = m_ctx->networking.read_range( m_ctx, m_handle, offsetToDownload, m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, ®ion[0], errorBuffer.size(), &errorBuffer[0], m_ctx->networking.user_data); } if (nRead == 0) { errorBuffer.resize(strlen(errorBuffer.data())); if (!errorBuffer.empty()) { pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s", m_url.c_str(), errorBuffer.c_str()); } proj_context_errno_set(m_ctx, PROJ_ERR_OTHER_NETWORK_ERROR); return 0; } if (!m_hasChanged) { FileProperties props; if (get_props_from_headers(m_ctx, m_handle, props)) { if (props.size != m_props.size || props.lastModified != m_props.lastModified || props.etag != m_props.etag) { gNetworkFileProperties.insert(m_ctx, m_url, props); gNetworkChunkCache.clearMemoryCache(); m_hasChanged = true; } } } region.resize(nRead); m_lastDownloadedOffset = offsetToDownload + nRead; const auto nChunks = (region.size() + DOWNLOAD_CHUNK_SIZE - 1) / DOWNLOAD_CHUNK_SIZE; for (size_t i = 0; i < nChunks; i++) { std::vector chunk( region.data() + i * DOWNLOAD_CHUNK_SIZE, region.data() + std::min((i + 1) * DOWNLOAD_CHUNK_SIZE, region.size())); gNetworkChunkCache.insert(m_ctx, m_url, chunkIdxToDownload + i, std::move(chunk)); } } const size_t nToCopy = static_cast( std::min(static_cast(sizeBytes), region.size() - (iterOffset - offsetToDownload))); memcpy(buffer, region.data() + iterOffset - offsetToDownload, nToCopy); buffer = static_cast(buffer) + nToCopy; iterOffset += nToCopy; sizeBytes -= nToCopy; if (region.size() < static_cast(DOWNLOAD_CHUNK_SIZE) && sizeBytes != 0) { break; } } size_t nRead = static_cast(iterOffset - m_pos); m_pos = iterOffset; return nRead; } // --------------------------------------------------------------------------- bool NetworkFile::seek(unsigned long long offset, int whence) { if (whence == SEEK_SET) { m_pos = offset; } else if (whence == SEEK_CUR) { m_pos += offset; } else { if (offset != 0) return false; m_pos = m_props.size; } return true; } // --------------------------------------------------------------------------- unsigned long long NetworkFile::tell() { return m_pos; } // --------------------------------------------------------------------------- NetworkFile::~NetworkFile() { if (m_handle) { m_ctx->networking.close(m_ctx, m_handle, m_ctx->networking.user_data); } } // --------------------------------------------------------------------------- void NetworkFile::reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; if (m_closeCbk != m_ctx->networking.close) { pj_log(m_ctx, PJ_LOG_ERROR, "Networking close callback has changed following context " "reassignment ! This is highly suspicious"); } } // --------------------------------------------------------------------------- #ifdef CURL_ENABLED struct CurlFileHandle { std::string m_url; CURL *m_handle; std::string m_headers{}; std::string m_lastval{}; std::string m_useragent{}; char m_szCurlErrBuf[CURL_ERROR_SIZE + 1] = {}; CurlFileHandle(const CurlFileHandle &) = delete; CurlFileHandle &operator=(const CurlFileHandle &) = delete; explicit CurlFileHandle(PJ_CONTEXT *ctx, const char *url, CURL *handle); ~CurlFileHandle(); static PROJ_NETWORK_HANDLE * open(PJ_CONTEXT *, const char *url, unsigned long long offset, size_t size_to_read, void *buffer, size_t *out_size_read, size_t error_string_max_size, char *out_error_string, void *); }; // --------------------------------------------------------------------------- static std::string GetExecutableName() { #if defined(__linux) std::string path; path.resize(1024); const auto ret = readlink("/proc/self/exe", &path[0], path.size()); if (ret > 0) { path.resize(ret); const auto pos = path.rfind('/'); if (pos != std::string::npos) { path = path.substr(pos + 1); } return path; } #elif defined(_WIN32) std::string path; path.resize(1024); if (GetModuleFileNameA(nullptr, &path[0], static_cast(path.size()))) { path.resize(strlen(path.c_str())); const auto pos = path.rfind('\\'); if (pos != std::string::npos) { path = path.substr(pos + 1); } return path; } #elif defined(__MACH__) && defined(__APPLE__) std::string path; path.resize(1024); uint32_t size = static_cast(path.size()); if (_NSGetExecutablePath(&path[0], &size) == 0) { path.resize(strlen(path.c_str())); const auto pos = path.rfind('/'); if (pos != std::string::npos) { path = path.substr(pos + 1); } return path; } #elif defined(__FreeBSD__) int mib[4]; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PATHNAME; mib[3] = -1; std::string path; path.resize(1024); size_t size = path.size(); if (sysctl(mib, 4, &path[0], &size, nullptr, 0) == 0) { path.resize(strlen(path.c_str())); const auto pos = path.rfind('/'); if (pos != std::string::npos) { path = path.substr(pos + 1); } return path; } #endif return std::string(); } // --------------------------------------------------------------------------- static void checkRet(PJ_CONTEXT *ctx, CURLcode code, int line) { if (code != CURLE_OK) { pj_log(ctx, PJ_LOG_ERROR, "curl_easy_setopt at line %d failed", line); } } #define CHECK_RET(ctx, code) checkRet(ctx, code, __LINE__) // --------------------------------------------------------------------------- static std::string pj_context_get_bundle_path(PJ_CONTEXT *ctx) { pj_load_ini(ctx); return ctx->ca_bundle_path; } #if CURL_AT_LEAST_VERSION(7, 71, 0) static bool pj_context_get_native_ca(PJ_CONTEXT *ctx) { pj_load_ini(ctx); return ctx->native_ca; } #endif // --------------------------------------------------------------------------- CurlFileHandle::CurlFileHandle(PJ_CONTEXT *ctx, const char *url, CURL *handle) : m_url(url), m_handle(handle) { CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_URL, m_url.c_str())); if (getenv("PROJ_CURL_VERBOSE")) CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_VERBOSE, 1)); // CURLOPT_SUPPRESS_CONNECT_HEADERS is defined in curl 7.54.0 or newer. #if LIBCURL_VERSION_NUM >= 0x073600 CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L)); #endif // Enable following redirections. Requires libcurl 7.10.1 at least. CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 10)); if (getenv("PROJ_UNSAFE_SSL")) { CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L)); CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L)); } #if defined(SSL_OPTIONS) // https://curl.se/libcurl/c/CURLOPT_SSL_OPTIONS.html auto ssl_options = static_cast(SSL_OPTIONS); #if CURL_AT_LEAST_VERSION(7, 71, 0) if (pj_context_get_native_ca(ctx)) { ssl_options = ssl_options | CURLSSLOPT_NATIVE_CA; } #endif CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_SSL_OPTIONS, ssl_options)); #else #if CURL_AT_LEAST_VERSION(7, 71, 0) if (pj_context_get_native_ca(ctx)) { CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA)); } #endif #endif const auto ca_bundle_path = pj_context_get_bundle_path(ctx); if (!ca_bundle_path.empty()) { CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_CAINFO, ca_bundle_path.c_str())); } CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, m_szCurlErrBuf)); if (getenv("PROJ_NO_USERAGENT") == nullptr) { m_useragent = "PROJ " STR(PROJ_VERSION_MAJOR) "." STR( PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH); const auto exeName = GetExecutableName(); if (!exeName.empty()) { m_useragent = exeName + " using " + m_useragent; } CHECK_RET(ctx, curl_easy_setopt(handle, CURLOPT_USERAGENT, m_useragent.data())); } } // --------------------------------------------------------------------------- CurlFileHandle::~CurlFileHandle() { curl_easy_cleanup(m_handle); } // --------------------------------------------------------------------------- static size_t pj_curl_write_func(void *buffer, size_t count, size_t nmemb, void *req) { const size_t nSize = count * nmemb; auto pStr = static_cast(req); if (pStr->size() + nSize > pStr->capacity()) { // to avoid servers not honouring Range to cause excessive memory // allocation return 0; } pStr->append(static_cast(buffer), nSize); return nmemb; } // --------------------------------------------------------------------------- static double GetNewRetryDelay(int response_code, double dfOldDelay, const char *pszErrBuf, const char *pszCurlError) { if (response_code == 429 || response_code == 500 || (response_code >= 502 && response_code <= 504) || // S3 sends some client timeout errors as 400 Client Error (response_code == 400 && pszErrBuf && strstr(pszErrBuf, "RequestTimeout")) || (pszCurlError && strstr(pszCurlError, "Connection reset by peer")) || (pszCurlError && strstr(pszCurlError, "Connection timed out")) || (pszCurlError && strstr(pszCurlError, "SSL connection timeout"))) { // Use an exponential backoff factor of 2 plus some random jitter // We don't care about cryptographic quality randomness, hence: // coverity[dont_call] return dfOldDelay * (2 + rand() * 0.5 / RAND_MAX); } else { return 0; } } // --------------------------------------------------------------------------- constexpr double MIN_RETRY_DELAY_MS = 500; constexpr double MAX_RETRY_DELAY_MS = 60000; PROJ_NETWORK_HANDLE *CurlFileHandle::open(PJ_CONTEXT *ctx, const char *url, unsigned long long offset, size_t size_to_read, void *buffer, size_t *out_size_read, size_t error_string_max_size, char *out_error_string, void *) { CURL *hCurlHandle = curl_easy_init(); if (!hCurlHandle) return nullptr; auto file = std::unique_ptr( new CurlFileHandle(ctx, url, hCurlHandle)); double oldDelay = MIN_RETRY_DELAY_MS; std::string headers; std::string body; char szBuffer[128]; sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset, offset + size_to_read - 1); while (true) { CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer)); headers.clear(); headers.reserve(16 * 1024); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers)); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, pj_curl_write_func)); body.clear(); body.reserve(size_to_read); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body)); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, pj_curl_write_func)); file->m_szCurlErrBuf[0] = '\0'; curl_easy_perform(hCurlHandle); long response_code = 0; curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr)); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr)); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr)); CHECK_RET( ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr)); if (response_code == 0 || response_code >= 300) { const double delay = GetNewRetryDelay(static_cast(response_code), oldDelay, body.c_str(), file->m_szCurlErrBuf); if (delay != 0 && delay < MAX_RETRY_DELAY_MS) { pj_log(ctx, PJ_LOG_TRACE, "Got a HTTP %ld error. Retrying in %d ms", response_code, static_cast(delay)); sleep_ms(static_cast(delay)); oldDelay = delay; } else { if (out_error_string) { if (file->m_szCurlErrBuf[0]) { snprintf(out_error_string, error_string_max_size, "%s", file->m_szCurlErrBuf); } else { snprintf(out_error_string, error_string_max_size, "HTTP error %ld: %s", response_code, body.c_str()); } } return nullptr; } } else { break; } } if (out_error_string && error_string_max_size) { out_error_string[0] = '\0'; } if (!body.empty()) { memcpy(buffer, body.data(), std::min(size_to_read, body.size())); } *out_size_read = std::min(size_to_read, body.size()); file->m_headers = std::move(headers); return reinterpret_cast(file.release()); } // --------------------------------------------------------------------------- static void pj_curl_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle, void * /*user_data*/) { delete reinterpret_cast(handle); } // --------------------------------------------------------------------------- static size_t pj_curl_read_range(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *raw_handle, unsigned long long offset, size_t size_to_read, void *buffer, size_t error_string_max_size, char *out_error_string, void *) { auto handle = reinterpret_cast(raw_handle); auto hCurlHandle = handle->m_handle; double oldDelay = MIN_RETRY_DELAY_MS; std::string headers; std::string body; char szBuffer[128]; sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset, offset + size_to_read - 1); while (true) { CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer)); headers.clear(); headers.reserve(16 * 1024); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers)); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, pj_curl_write_func)); body.clear(); body.reserve(size_to_read); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body)); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, pj_curl_write_func)); handle->m_szCurlErrBuf[0] = '\0'; curl_easy_perform(hCurlHandle); long response_code = 0; curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code); CHECK_RET(ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr)); CHECK_RET( ctx, curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr)); if (response_code == 0 || response_code >= 300) { const double delay = GetNewRetryDelay(static_cast(response_code), oldDelay, body.c_str(), handle->m_szCurlErrBuf); if (delay != 0 && delay < MAX_RETRY_DELAY_MS) { pj_log(ctx, PJ_LOG_TRACE, "Got a HTTP %ld error. Retrying in %d ms", response_code, static_cast(delay)); sleep_ms(static_cast(delay)); oldDelay = delay; } else { if (out_error_string) { if (handle->m_szCurlErrBuf[0]) { snprintf(out_error_string, error_string_max_size, "%s", handle->m_szCurlErrBuf); } else { snprintf(out_error_string, error_string_max_size, "HTTP error %ld: %s", response_code, body.c_str()); } } return 0; } } else { break; } } if (out_error_string && error_string_max_size) { out_error_string[0] = '\0'; } if (!body.empty()) { memcpy(buffer, body.data(), std::min(size_to_read, body.size())); } handle->m_headers = std::move(headers); return std::min(size_to_read, body.size()); } // --------------------------------------------------------------------------- static const char *pj_curl_get_header_value(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *raw_handle, const char *header_name, void *) { auto handle = reinterpret_cast(raw_handle); auto pos = ci_find(handle->m_headers, header_name); if (pos == std::string::npos) return nullptr; pos += strlen(header_name); const char *c_str = handle->m_headers.c_str(); if (c_str[pos] == ':') pos++; while (c_str[pos] == ' ') pos++; auto posEnd = pos; while (c_str[posEnd] != '\r' && c_str[posEnd] != '\n' && c_str[posEnd] != '\0') posEnd++; handle->m_lastval = handle->m_headers.substr(pos, posEnd - pos); return handle->m_lastval.c_str(); } #elif defined(DO_EMSCRIPTEN_FETCH) constexpr double MIN_RETRY_DELAY_MS = 500; constexpr double MAX_RETRY_DELAY_MS = 60000; struct EmscriptenFileHandle { PJ_CONTEXT *m_ctx; // for logging? TODO std::string m_url; std::unordered_map m_headers; std::string m_lastval{}; std::string m_useragent{}; EmscriptenFileHandle(const EmscriptenFileHandle &) = delete; EmscriptenFileHandle &operator=(const EmscriptenFileHandle &) = delete; explicit EmscriptenFileHandle(PJ_CONTEXT *ctx, const char *url); ~EmscriptenFileHandle(); static PROJ_NETWORK_HANDLE * open(PJ_CONTEXT *, const char *url, unsigned long long offset, size_t size_to_read, void *buffer, size_t *out_size_read, size_t error_string_max_size, char *out_error_string, void *); }; EmscriptenFileHandle::EmscriptenFileHandle(PJ_CONTEXT *ctx, const char *url) : m_ctx(ctx), m_url(url) { pj_log(ctx, PJ_LOG_DEBUG, "EmscriptenFileHandle created with url %s", url); if (getenv("PROJ_NO_USERAGENT") == nullptr) { m_useragent = "PROJ " STR(PROJ_VERSION_MAJOR) "." STR( PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH); const auto exeName = std::string{}; // TODO: GetExecutableName(); if (!exeName.empty()) { m_useragent = exeName + " using " + m_useragent; } } } EmscriptenFileHandle::~EmscriptenFileHandle() {} // Same as in Curl? static double GetNewRetryDelay(int response_code, double dfOldDelay, const char *pszErrBuf, const char *pszCurlError) { if (response_code == 429 || response_code == 500 || (response_code >= 502 && response_code <= 504) || // S3 sends some client timeout errors as 400 Client Error (response_code == 400 && pszErrBuf && strstr(pszErrBuf, "RequestTimeout")) || (pszCurlError && strstr(pszCurlError, "Connection reset by peer")) || (pszCurlError && strstr(pszCurlError, "Connection timed out")) || (pszCurlError && strstr(pszCurlError, "SSL connection timeout"))) { // Use an exponential backoff factor of 2 plus some random jitter // We don't care about cryptographic quality randomness, hence: // coverity[dont_call] return dfOldDelay * (2 + rand() * 0.5 / RAND_MAX); } else { return 0; } } #define DO_TRACE_FETCH 0 #if DO_TRACE_FETCH != 0 #define TRACE_FETCH(data) \ do { \ std::cout << data << std::endl; \ } while (false) #else #define TRACE_FETCH(data) #endif static size_t pj_emscripten_read_range(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *raw_handle, unsigned long long offset, size_t size_to_read, void *buffer, size_t error_string_max_size, char *out_error_string, void *) { auto handle = reinterpret_cast(raw_handle); double oldDelay = MIN_RETRY_DELAY_MS; char szBuffer[128]; sqlite3_snprintf(sizeof(szBuffer), szBuffer, "bytes=%llu-%llu", offset, offset + size_to_read - 1); // To work in the browser, we need to run the fetch part in Web Worker, // otherwise we cannot run it synchronous. (at least with my tests) // It is easier running all PROJ in the Web Worker (that is the test done). // Documentation says compiling with pthread flag is needed. // https://emscripten.org/docs/api_reference/fetch.html#synchronous-fetches // We encapsulate the code related to empscripten_fetch in a lamda. // Some tests running this lambda in a thread were partially successful. size_t real_read = 0; const std::string url = handle->m_url; auto fetching = [&]() { emscripten_fetch_t *fetch = nullptr; while (true) { emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); strcpy(attr.requestMethod, "GET"); const char *requestHeaders[] = {"Range", szBuffer, nullptr}; attr.requestHeaders = requestHeaders; attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_REPLACE; if (fetch) { emscripten_fetch_close(fetch); } TRACE_FETCH("Pre-fetch, url: " << url << ", range: " << szBuffer); fetch = emscripten_fetch(&attr, url.c_str()); // emscripten_fetch_wait(fetch, -1); // This is deprecated TRACE_FETCH("Post-fetch"); if (!fetch) { snprintf(out_error_string, error_string_max_size, "Cannot init emscripten_fetch for url %s", url.c_str()); return; } const auto response_code = fetch->status; TRACE_FETCH("Received HTTP response code: " << response_code); if (response_code == 0 || response_code >= 300) { const double delay = GetNewRetryDelay(static_cast(response_code), oldDelay, fetch->data, fetch->statusText); if (delay != 0 && delay < MAX_RETRY_DELAY_MS) { pj_log(ctx, PJ_LOG_TRACE, "Got a HTTP %ld error. Retrying in %d ms", response_code, static_cast(delay)); TRACE_FETCH("HTTP error " << response_code << ", retrying in " << delay << " ms"); sleep_ms(static_cast(delay)); oldDelay = delay; } else { if (out_error_string) { if (fetch->statusText[0]) { snprintf(out_error_string, error_string_max_size, "%s", fetch->statusText); } else { snprintf(out_error_string, error_string_max_size, "HTTP error %hu: %s", response_code, fetch->data); } } TRACE_FETCH("HTTP error for " << url); emscripten_fetch_close(fetch); return; } } else { break; } } // end of while(true) if (out_error_string && error_string_max_size) { out_error_string[0] = '\0'; } const size_t numBytes = static_cast(fetch->numBytes); TRACE_FETCH("Read bytes: " << numBytes); TRACE_FETCH("Size to read: " << size_to_read); if (fetch->readyState != 4) { std::cout << "fetch ready state (" << fetch->readyState << ") is not 4. That's a problem " << std::endl; return; } real_read = std::min(size_to_read, numBytes); if (numBytes) { memcpy(buffer, fetch->data, real_read); } TRACE_FETCH("Real read: " << real_read); // get the http headers { // https://github.com/emscripten-core/emscripten/blob/56c214a/test/fetch/test_fetch_headers_received.c size_t headersLengthBytes = emscripten_fetch_get_response_headers_length(fetch) + 1; TRACE_FETCH("Headers length: " << headersLengthBytes); char *headerString = (char *)malloc(headersLengthBytes); assert(headerString); emscripten_fetch_get_response_headers(fetch, headerString, headersLengthBytes); TRACE_FETCH("Raw headers:\n" << headerString); char **responseHeaders = emscripten_fetch_unpack_response_headers(headerString); assert(responseHeaders); free(headerString); TRACE_FETCH("Parsed headers:"); int numHeaders = 0; for (; responseHeaders[numHeaders * 2]; ++numHeaders) { // Check both the header and its value are present. assert(responseHeaders[(numHeaders * 2) + 1]); std::string key(responseHeaders[numHeaders * 2]); std::string val(responseHeaders[(numHeaders * 2) + 1]); TRACE_FETCH(" " << key << ": " << val); std::transform(key.begin(), key.end(), key.begin(), ::tolower); handle->m_headers.emplace(key, val); } TRACE_FETCH("Finished receiving " << numHeaders << " headers"); emscripten_fetch_free_unpacked_response_headers(responseHeaders); } TRACE_FETCH("Pre-close"); emscripten_fetch_close(fetch); TRACE_FETCH("Post-close"); return; }; fetching(); TRACE_FETCH("Post-fetching"); if (real_read == 0) { std::cout << "Problems in fetch:" << out_error_string << std::endl; } return real_read; } static const char * pj_emscripten_get_header_value(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *raw_handle, const char *header_name, void *) { TRACE_FETCH("EmscriptenFileHandle::header_name " << header_name); auto handle = reinterpret_cast(raw_handle); std::string key(header_name); std::transform(key.begin(), key.end(), key.begin(), ::tolower); const auto it = handle->m_headers.find(key); if (it != handle->m_headers.end()) { TRACE_FETCH("EmscriptenFileHandle::header_value " << it->second); return it->second.c_str(); } return nullptr; } static void pj_emscripten_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle, void * /*user_data*/) { delete reinterpret_cast(handle); } PROJ_NETWORK_HANDLE *EmscriptenFileHandle::open( PJ_CONTEXT *ctx, const char *url, unsigned long long offset, size_t size_to_read, void *buffer, size_t *out_size_read, size_t error_string_max_size, char *out_error_string, void *) { auto file = std::unique_ptr( new EmscriptenFileHandle(ctx, url)); PROJ_NETWORK_HANDLE *handle = reinterpret_cast(file.get()); *out_size_read = pj_emscripten_read_range(ctx, handle, offset, size_to_read, buffer, error_string_max_size, out_error_string, nullptr); return reinterpret_cast(file.release()); } #else // --------------------------------------------------------------------------- static PROJ_NETWORK_HANDLE * no_op_network_open(PJ_CONTEXT *, const char * /* url */, unsigned long long, /* offset */ size_t, /* size to read */ void *, /* buffer to update with bytes read*/ size_t *, /* output: size actually read */ size_t error_string_max_size, char *out_error_string, void * /*user_data*/) { if (out_error_string) { snprintf(out_error_string, error_string_max_size, "%s", "Network functionality not available"); } return nullptr; } // --------------------------------------------------------------------------- static void no_op_network_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *, void * /*user_data*/) {} #endif // --------------------------------------------------------------------------- void FileManager::fillDefaultNetworkInterface(PJ_CONTEXT *ctx) { #ifdef CURL_ENABLED ctx->networking.open = CurlFileHandle::open; ctx->networking.close = pj_curl_close; ctx->networking.read_range = pj_curl_read_range; ctx->networking.get_header_value = pj_curl_get_header_value; #elif defined(DO_EMSCRIPTEN_FETCH) ctx->networking.open = EmscriptenFileHandle::open; ctx->networking.close = pj_emscripten_close; ctx->networking.read_range = pj_emscripten_read_range; ctx->networking.get_header_value = pj_emscripten_get_header_value; #else ctx->networking.open = no_op_network_open; ctx->networking.close = no_op_network_close; #endif } // --------------------------------------------------------------------------- void FileManager::clearMemoryCache() { gNetworkChunkCache.clearMemoryCache(); gNetworkFileProperties.clearMemoryCache(); } NS_PROJ_END //! @endcond // --------------------------------------------------------------------------- #ifdef WIN32 static const char nfm_dir_chars[] = "/\\"; #else static const char nfm_dir_chars[] = "/"; #endif static bool nfm_is_tilde_slash(const char *name) { return *name == '~' && strchr(nfm_dir_chars, name[1]); } static bool nfm_is_rel_or_absolute_filename(const char *name) { return strchr(nfm_dir_chars, *name) || (*name == '.' && strchr(nfm_dir_chars, name[1])) || (!strncmp(name, "..", 2) && strchr(nfm_dir_chars, name[2])) || (name[0] != '\0' && name[1] == ':' && strchr(nfm_dir_chars, name[2])); } static std::string build_url(PJ_CONTEXT *ctx, const char *name) { if (!nfm_is_tilde_slash(name) && !nfm_is_rel_or_absolute_filename(name) && !starts_with(name, "http://") && !starts_with(name, "https://")) { std::string remote_file(proj_context_get_url_endpoint(ctx)); if (!remote_file.empty()) { if (remote_file.back() != '/') { remote_file += '/'; } remote_file += name; } return remote_file; } return name; } // --------------------------------------------------------------------------- /** Define a custom set of callbacks for network access. * * All callbacks should be provided (non NULL pointers). * * @param ctx PROJ context, or NULL * @param open_cbk Callback to open a remote file given its URL * @param close_cbk Callback to close a remote file. * @param get_header_value_cbk Callback to get HTTP headers * @param read_range_cbk Callback to read a range of bytes inside a remote file. * @param user_data Arbitrary pointer provided by the user, and passed to the * above callbacks. May be NULL. * @return TRUE in case of success. * @since 7.0 */ int proj_context_set_network_callbacks( PJ_CONTEXT *ctx, proj_network_open_cbk_type open_cbk, proj_network_close_cbk_type close_cbk, proj_network_get_header_value_cbk_type get_header_value_cbk, proj_network_read_range_type read_range_cbk, void *user_data) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (!open_cbk || !close_cbk || !get_header_value_cbk || !read_range_cbk) { return false; } ctx->networking.open = open_cbk; ctx->networking.close = close_cbk; ctx->networking.get_header_value = get_header_value_cbk; ctx->networking.read_range = read_range_cbk; ctx->networking.user_data = user_data; return true; } // --------------------------------------------------------------------------- /** Enable or disable network access. * * This overrides the default endpoint in the PROJ configuration file or with * the PROJ_NETWORK environment variable. * * @param ctx PROJ context, or NULL * @param enable TRUE if network access is allowed. * @return TRUE if network access is possible. That is either libcurl is * available, or an alternate interface has been set. * @since 7.0 */ int proj_context_set_enable_network(PJ_CONTEXT *ctx, int enable) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } // Load ini file, now so as to override its network settings pj_load_ini(ctx); ctx->networking.enabled = enable != FALSE; #ifdef CURL_ENABLED return ctx->networking.enabled; #elif defined(DO_EMSCRIPTEN_FETCH) return ctx->networking.enabled; #else return ctx->networking.enabled && ctx->networking.open != NS_PROJ::no_op_network_open; #endif } // --------------------------------------------------------------------------- /** Return if network access is enabled. * * @param ctx PROJ context, or NULL * @return TRUE if network access has been enabled * @since 7.0 */ int proj_context_is_network_enabled(PJ_CONTEXT *ctx) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } pj_load_ini(ctx); return ctx->networking.enabled; } // --------------------------------------------------------------------------- /** Define the URL endpoint to query for remote grids. * * This overrides the default endpoint in the PROJ configuration file or with * the PROJ_NETWORK_ENDPOINT environment variable. * * @param ctx PROJ context, or NULL * @param url Endpoint URL. Must NOT be NULL. * @since 7.0 */ void proj_context_set_url_endpoint(PJ_CONTEXT *ctx, const char *url) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } // Load ini file, now so as to override its network settings pj_load_ini(ctx); ctx->endpoint = url; } // --------------------------------------------------------------------------- /** Enable or disable the local cache of grid chunks * * This overrides the setting in the PROJ configuration file. * * @param ctx PROJ context, or NULL * @param enabled TRUE if the cache is enabled. * @since 7.0 */ void proj_grid_cache_set_enable(PJ_CONTEXT *ctx, int enabled) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } // Load ini file, now so as to override its settings pj_load_ini(ctx); ctx->gridChunkCache.enabled = enabled != FALSE; } // --------------------------------------------------------------------------- /** Override, for the considered context, the path and file of the local * cache of grid chunks. * * @param ctx PROJ context, or NULL * @param fullname Full name to the cache (encoded in UTF-8). If set to NULL, * caching will be disabled. * @since 7.0 */ void proj_grid_cache_set_filename(PJ_CONTEXT *ctx, const char *fullname) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } // Load ini file, now so as to override its settings pj_load_ini(ctx); ctx->gridChunkCache.filename = fullname ? fullname : std::string(); } // --------------------------------------------------------------------------- /** Override, for the considered context, the maximum size of the local * cache of grid chunks. * * @param ctx PROJ context, or NULL * @param max_size_MB Maximum size, in mega-bytes (1024*1024 bytes), or * negative value to set unlimited size. * @since 7.0 */ void proj_grid_cache_set_max_size(PJ_CONTEXT *ctx, int max_size_MB) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } // Load ini file, now so as to override its settings pj_load_ini(ctx); ctx->gridChunkCache.max_size = max_size_MB < 0 ? -1 : static_cast(max_size_MB) * 1024 * 1024; if (max_size_MB == 0) { // For debug purposes only const char *env_var = getenv("PROJ_GRID_CACHE_MAX_SIZE_BYTES"); if (env_var && env_var[0] != '\0') { ctx->gridChunkCache.max_size = atoi(env_var); } } } // --------------------------------------------------------------------------- /** Override, for the considered context, the time-to-live delay for * re-checking if the cached properties of files are still up-to-date. * * @param ctx PROJ context, or NULL * @param ttl_seconds Delay in seconds. Use negative value for no expiration. * @since 7.0 */ void proj_grid_cache_set_ttl(PJ_CONTEXT *ctx, int ttl_seconds) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } // Load ini file, now so as to override its settings pj_load_ini(ctx); ctx->gridChunkCache.ttl = ttl_seconds; } // --------------------------------------------------------------------------- /** Clear the local cache of grid chunks. * * @param ctx PROJ context, or NULL * @since 7.0 */ void proj_grid_cache_clear(PJ_CONTEXT *ctx) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } NS_PROJ::gNetworkChunkCache.clearDiskChunkCache(ctx); } // --------------------------------------------------------------------------- /** Return if a file must be downloaded or is already available in the * PROJ user-writable directory. * * The file will be determinted to have to be downloaded if it does not exist * yet in the user-writable directory, or if it is determined that a more recent * version exists. To determine if a more recent version exists, PROJ will * use the "downloaded_file_properties" table of its grid cache database. * Consequently files manually placed in the user-writable * directory without using this function would be considered as * non-existing/obsolete and would be unconditionally downloaded again. * * This function can only be used if networking is enabled, and either * the default curl network API or a custom one have been installed. * * @param ctx PROJ context, or NULL * @param url_or_filename URL or filename (without directory component) * @param ignore_ttl_setting If set to FALSE, PROJ will only check the * recentness of an already downloaded file, if * the delay between the last time it has been * verified and the current time exceeds the TTL * setting. This can save network accesses. * If set to TRUE, PROJ will unconditionally * check from the server the recentness of the file. * @return TRUE if the file must be downloaded with proj_download_file() * @since 7.0 */ int proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename, int ignore_ttl_setting) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (!proj_context_is_network_enabled(ctx)) { pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled"); return false; } const auto url(build_url(ctx, url_or_filename)); const char *filename = strrchr(url.c_str(), '/'); if (filename == nullptr) return false; const auto localFilename( std::string(proj_context_get_user_writable_directory(ctx, false)) + filename); auto f = NS_PROJ::FileManager::open(ctx, localFilename.c_str(), NS_PROJ::FileAccess::READ_ONLY); if (!f) { return true; } f.reset(); auto diskCache = NS_PROJ::DiskChunkCache::open(ctx); if (!diskCache) return false; auto stmt = diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag " "FROM downloaded_file_properties WHERE url = ?"); if (!stmt) return true; stmt->bindText(url.c_str()); if (stmt->execute() != SQLITE_ROW) { return true; } NS_PROJ::FileProperties cachedProps; cachedProps.lastChecked = static_cast(stmt->getInt64()); cachedProps.size = stmt->getInt64(); const char *lastModified = stmt->getText(); cachedProps.lastModified = lastModified ? lastModified : std::string(); const char *etag = stmt->getText(); cachedProps.etag = etag ? etag : std::string(); if (!ignore_ttl_setting) { const auto ttl = NS_PROJ::pj_context_get_grid_cache_ttl(ctx); if (ttl > 0) { time_t curTime; time(&curTime); if (curTime > cachedProps.lastChecked + ttl) { unsigned char dummy; size_t size_read = 0; std::string errorBuffer; errorBuffer.resize(1024); auto handle = ctx->networking.open( ctx, url.c_str(), 0, 1, &dummy, &size_read, errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data); if (!handle) { errorBuffer.resize(strlen(errorBuffer.data())); pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(), errorBuffer.c_str()); return false; } NS_PROJ::FileProperties props; if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle, props)) { ctx->networking.close(ctx, handle, ctx->networking.user_data); return false; } ctx->networking.close(ctx, handle, ctx->networking.user_data); if (props.size != cachedProps.size || props.lastModified != cachedProps.lastModified || props.etag != cachedProps.etag) { return true; } stmt = diskCache->prepare( "UPDATE downloaded_file_properties SET lastChecked = ? " "WHERE url = ?"); if (!stmt) return false; stmt->bindInt64(curTime); stmt->bindText(url.c_str()); if (stmt->execute() != SQLITE_DONE) { auto hDB = diskCache->handle(); pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return false; } } } } return false; } // --------------------------------------------------------------------------- static NS_PROJ::io::DatabaseContextPtr nfm_getDBcontext(PJ_CONTEXT *ctx) { try { return ctx->get_cpp_context()->getDatabaseContext().as_nullable(); } catch (const std::exception &e) { pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what()); return nullptr; } } // --------------------------------------------------------------------------- /** Download a file in the PROJ user-writable directory. * * The file will only be downloaded if it does not exist yet in the * user-writable directory, or if it is determined that a more recent * version exists. To determine if a more recent version exists, PROJ will * use the "downloaded_file_properties" table of its grid cache database. * Consequently files manually placed in the user-writable * directory without using this function would be considered as * non-existing/obsolete and would be unconditionally downloaded again. * * This function can only be used if networking is enabled, and either * the default curl network API or a custom one have been installed. * * @param ctx PROJ context, or NULL * @param url_or_filename URL or filename (without directory component) * @param ignore_ttl_setting If set to FALSE, PROJ will only check the * recentness of an already downloaded file, if * the delay between the last time it has been * verified and the current time exceeds the TTL * setting. This can save network accesses. * If set to TRUE, PROJ will unconditionally * check from the server the recentness of the file. * @param progress_cbk Progress callback, or NULL. * The passed percentage is in the [0, 1] range. * The progress callback must return TRUE * if download must be continued. * @param user_data User data to provide to the progress callback, or NULL * @return TRUE if the download was successful (or not needed) * @since 7.0 */ int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename, int ignore_ttl_setting, int (*progress_cbk)(PJ_CONTEXT *, double pct, void *user_data), void *user_data) { if (ctx == nullptr) { ctx = pj_get_default_ctx(); } if (!proj_context_is_network_enabled(ctx)) { pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled"); return false; } if (!proj_is_download_needed(ctx, url_or_filename, ignore_ttl_setting)) { return true; } const auto url(build_url(ctx, url_or_filename)); const char *lastSlash = strrchr(url.c_str(), '/'); if (lastSlash == nullptr) return false; const auto localFilename( std::string(proj_context_get_user_writable_directory(ctx, true)) + lastSlash); // Evict potential existing (empty) entry from ctx->lookupedFiles if we // have tried previously from accessing the non-existing local file. // Cf https://github.com/OSGeo/PROJ/issues/4397 { const char *short_filename = lastSlash + 1; auto iter = ctx->lookupedFiles.find(short_filename); if (iter != ctx->lookupedFiles.end()) { ctx->lookupedFiles.erase(iter); } auto dbContext = nfm_getDBcontext(ctx); if (dbContext) { dbContext->invalidateGridInfo(short_filename); } } #ifdef _WIN32 const int nPID = GetCurrentProcessId(); #else const int nPID = getpid(); #endif char szUniqueSuffix[128]; snprintf(szUniqueSuffix, sizeof(szUniqueSuffix), "%d_%p", nPID, static_cast(&url)); const auto localFilenameTmp(localFilename + szUniqueSuffix); auto f = NS_PROJ::FileManager::open(ctx, localFilenameTmp.c_str(), NS_PROJ::FileAccess::CREATE); if (!f) { pj_log(ctx, PJ_LOG_ERROR, "Cannot create %s", localFilenameTmp.c_str()); return false; } constexpr size_t FULL_FILE_CHUNK_SIZE = 1024 * 1024; std::vector buffer(FULL_FILE_CHUNK_SIZE); // For testing purposes only const char *env_var_PROJ_FULL_FILE_CHUNK_SIZE = getenv("PROJ_FULL_FILE_CHUNK_SIZE"); if (env_var_PROJ_FULL_FILE_CHUNK_SIZE && env_var_PROJ_FULL_FILE_CHUNK_SIZE[0] != '\0') { buffer.resize(atoi(env_var_PROJ_FULL_FILE_CHUNK_SIZE)); } size_t size_read = 0; std::string errorBuffer; errorBuffer.resize(1024); auto handle = ctx->networking.open( ctx, url.c_str(), 0, buffer.size(), &buffer[0], &size_read, errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data); if (!handle) { errorBuffer.resize(strlen(errorBuffer.data())); pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(), errorBuffer.c_str()); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } time_t curTime; time(&curTime); NS_PROJ::FileProperties props; if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle, props)) { ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } if (size_read == 0) { pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected"); ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } if (f->write(buffer.data(), size_read) != size_read) { pj_log(ctx, PJ_LOG_ERROR, "Write error"); ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } unsigned long long totalDownloaded = size_read; while (totalDownloaded < props.size) { if (totalDownloaded + buffer.size() > props.size) { buffer.resize(static_cast(props.size - totalDownloaded)); } errorBuffer.resize(1024); size_read = ctx->networking.read_range( ctx, handle, totalDownloaded, buffer.size(), &buffer[0], errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data); if (size_read < buffer.size()) { pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected"); ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } if (f->write(buffer.data(), size_read) != size_read) { pj_log(ctx, PJ_LOG_ERROR, "Write error"); ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } totalDownloaded += size_read; if (progress_cbk && !progress_cbk(ctx, double(totalDownloaded) / props.size, user_data)) { ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str()); return false; } } ctx->networking.close(ctx, handle, ctx->networking.user_data); f.reset(); NS_PROJ::FileManager::unlink(ctx, localFilename.c_str()); if (!NS_PROJ::FileManager::rename(ctx, localFilenameTmp.c_str(), localFilename.c_str())) { pj_log(ctx, PJ_LOG_ERROR, "Cannot rename %s to %s", localFilenameTmp.c_str(), localFilename.c_str()); return false; } auto diskCache = NS_PROJ::DiskChunkCache::open(ctx); if (!diskCache) return false; auto stmt = diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag " "FROM downloaded_file_properties WHERE url = ?"); if (!stmt) return false; stmt->bindText(url.c_str()); props.lastChecked = curTime; auto hDB = diskCache->handle(); if (stmt->execute() == SQLITE_ROW) { stmt = diskCache->prepare( "UPDATE downloaded_file_properties SET lastChecked = ?, " "fileSize = ?, lastModified = ?, etag = ? " "WHERE url = ?"); if (!stmt) return false; stmt->bindInt64(props.lastChecked); stmt->bindInt64(props.size); if (props.lastModified.empty()) stmt->bindNull(); else stmt->bindText(props.lastModified.c_str()); if (props.etag.empty()) stmt->bindNull(); else stmt->bindText(props.etag.c_str()); stmt->bindText(url.c_str()); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return false; } } else { stmt = diskCache->prepare( "INSERT INTO downloaded_file_properties (url, lastChecked, " "fileSize, lastModified, etag) VALUES " "(?,?,?,?,?)"); if (!stmt) return false; stmt->bindText(url.c_str()); stmt->bindInt64(props.lastChecked); stmt->bindInt64(props.size); if (props.lastModified.empty()) stmt->bindNull(); else stmt->bindText(props.lastModified.c_str()); if (props.etag.empty()) stmt->bindNull(); else stmt->bindText(props.etag.c_str()); if (stmt->execute() != SQLITE_DONE) { pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB)); return false; } } return true; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress // --------------------------------------------------------------------------- std::string pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx) { pj_load_ini(ctx); if (!ctx->gridChunkCache.filename.empty()) { return ctx->gridChunkCache.filename; } const std::string path(proj_context_get_user_writable_directory(ctx, true)); ctx->gridChunkCache.filename = path + "/cache.db"; return ctx->gridChunkCache.filename; } //! @endcond proj-9.8.1/src/datum_set.cpp000664 001750 001750 00000014663 15166171715 015745 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Apply datum definition to PJ structure from initialization string. * Author: Frank Warmerdam, warmerda@home.com * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include "proj.h" #include "proj_internal.h" /* SEC_TO_RAD = Pi/180/3600 */ #define SEC_TO_RAD 4.84813681109535993589914102357e-6 /************************************************************************/ /* pj_datum_set() */ /************************************************************************/ int pj_datum_set(PJ_CONTEXT *ctx, paralist *pl, PJ *projdef) { const char *name, *towgs84, *nadgrids; projdef->datum_type = PJD_UNKNOWN; /* -------------------------------------------------------------------- */ /* Is there a datum definition in the parameters list? If so, */ /* add the defining values to the parameter list. Note that */ /* this will append the ellipse definition as well as the */ /* towgs84= and related parameters. It should also be pointed */ /* out that the addition is permanent rather than temporary */ /* like most other keyword expansion so that the ellipse */ /* definition will last into the pj_ell_set() function called */ /* after this one. */ /* -------------------------------------------------------------------- */ if ((name = pj_param(ctx, pl, "sdatum").s) != nullptr) { paralist *curr; const char *s; int i; /* find the end of the list, so we can add to it */ for (curr = pl; curr && curr->next; curr = curr->next) { } /* cannot happen in practice, but makes static analyzers happy */ if (!curr) return -1; /* find the datum definition */ const struct PJ_DATUMS *pj_datums = pj_get_datums_ref(); for (i = 0; (s = pj_datums[i].id) && strcmp(name, s); ++i) { } if (!s) { pj_log(ctx, PJ_LOG_ERROR, _("Unknown value for datum")); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return 1; } if (pj_datums[i].ellipse_id && strlen(pj_datums[i].ellipse_id) > 0) { char entry[100]; strcpy(entry, "ellps="); strncpy(entry + strlen(entry), pj_datums[i].ellipse_id, sizeof(entry) - 1 - strlen(entry)); entry[sizeof(entry) - 1] = '\0'; auto param = pj_mkparam(entry); if (nullptr == param) { proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return 1; } curr->next = param; curr = param; } if (pj_datums[i].defn && strlen(pj_datums[i].defn) > 0) { auto param = pj_mkparam(pj_datums[i].defn); if (nullptr == param) { proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return 1; } curr->next = param; /* curr = param; */ } } /* -------------------------------------------------------------------- */ /* Check for nadgrids parameter. */ /* -------------------------------------------------------------------- */ nadgrids = pj_param(ctx, pl, "snadgrids").s; if (nadgrids != nullptr) { /* We don't actually save the value separately. It will continue to exist int he param list for use in pj_apply_gridshift.c */ projdef->datum_type = PJD_GRIDSHIFT; } /* -------------------------------------------------------------------- */ /* Check for towgs84 parameter. */ /* -------------------------------------------------------------------- */ else if ((towgs84 = pj_param(ctx, pl, "stowgs84").s) != nullptr) { int parm_count = 0; const char *s; memset(projdef->datum_params, 0, sizeof(double) * 7); /* parse out the parameters */ for (s = towgs84; *s != '\0' && parm_count < 7;) { projdef->datum_params[parm_count++] = pj_atof(s); while (*s != '\0' && *s != ',') s++; if (*s == ',') s++; } if (projdef->datum_params[3] != 0.0 || projdef->datum_params[4] != 0.0 || projdef->datum_params[5] != 0.0 || projdef->datum_params[6] != 0.0) { projdef->datum_type = PJD_7PARAM; /* transform from arc seconds to radians */ projdef->datum_params[3] *= SEC_TO_RAD; projdef->datum_params[4] *= SEC_TO_RAD; projdef->datum_params[5] *= SEC_TO_RAD; /* transform from parts per million to scaling factor */ projdef->datum_params[6] = (projdef->datum_params[6] / 1000000.0) + 1; } else projdef->datum_type = PJD_3PARAM; /* Note that pj_init() will later switch datum_type to PJD_WGS84 if shifts are all zero, and ellipsoid is WGS84 or GRS80 */ } return 0; } proj-9.8.1/src/param.cpp000664 001750 001750 00000015354 15166171715 015056 0ustar00eveneven000000 000000 /* put parameters in linked list and retrieve */ #include #include #include #include #include #include "proj.h" #include "proj_internal.h" /* create parameter list entry */ paralist *pj_mkparam(const char *str) { paralist *newitem; if ((newitem = (paralist *)malloc(sizeof(paralist) + strlen(str))) != nullptr) { newitem->used = 0; newitem->next = nullptr; if (*str == '+') ++str; (void)strcpy(newitem->param, str); } return newitem; } /* As pj_mkparam, but payload ends at first whitespace, rather than at end of * */ paralist *pj_mkparam_ws(const char *str, const char **next_str) { paralist *newitem; size_t len = 0; if (nullptr == str) return nullptr; /* Find start and length of string */ while (isspace(*str)) str++; if (*str == '+') str++; bool in_string = false; for (; str[len] != '\0'; len++) { if (in_string) { if (str[len] == '"' && str[len + 1] == '"') { len++; } else if (str[len] == '"') { in_string = false; } } else if (str[len] == '=' && str[len + 1] == '"') { in_string = true; } else if (isspace(str[len])) { break; } } if (next_str) *next_str = str + len; /* Use calloc to automagically 0-terminate the copy */ newitem = (paralist *)calloc(1, sizeof(paralist) + len + 1); if (nullptr == newitem) return nullptr; memcpy(newitem->param, str, len); newitem->used = 0; newitem->next = nullptr; return newitem; } /**************************************************************************************/ paralist *pj_param_exists(paralist *list, const char *parameter) { /*************************************************************************************** Determine whether a given parameter exists in a paralist. If it does, return a pointer to the corresponding list element - otherwise return 0. In support of the pipeline syntax, the search is terminated once a "+step" list element is reached, in which case a 0 is returned, unless the parameter searched for is actually "step", in which case a pointer to the "step" list element is returned. This function is equivalent to the pj_param (...) call with the "opt" argument set to the parameter name preceeeded by a 't'. But by using this one, one avoids writing the code allocating memory for a new copy of parameter name, and prepending the t (for compile time known names, this is obviously not an issue). ***************************************************************************************/ paralist *next = list; const char *c = strchr(parameter, '='); size_t len = strlen(parameter); if (c) len = c - parameter; if (list == nullptr) return nullptr; for (next = list; next; next = next->next) { if (0 == strncmp(parameter, next->param, len) && (next->param[len] == '=' || next->param[len] == 0)) { next->used = 1; return next; } if (0 == strcmp(parameter, "step")) return nullptr; } return nullptr; } /************************************************************************/ /* pj_param() */ /* */ /* Test for presence or get parameter value. The first */ /* character in `opt' is a parameter type which can take the */ /* values: */ /* */ /* `t' - test for presence, return TRUE/FALSE in PROJVALUE.i */ /* `i' - integer value returned in PROJVALUE.i */ /* `d' - simple valued real input returned in PROJVALUE.f */ /* `r' - degrees (DMS translation applied), returned as */ /* radians in PROJVALUE.f */ /* `s' - string returned in PROJVALUE.s */ /* `b' - test for t/T/f/F, return in PROJVALUE.i */ /* */ /* Search is terminated when "step" is found, in which case */ /* 0 is returned, unless "step" was the target searched for. */ /* */ /************************************************************************/ PROJVALUE pj_param(PJ_CONTEXT *ctx, paralist *pl, const char *opt) { int type; unsigned l; PROJVALUE value = {0}; if (ctx == nullptr) ctx = pj_get_default_ctx(); type = *opt++; if (nullptr == strchr("tbirds", type)) { fprintf(stderr, "invalid request to pj_param, fatal\n"); exit(1); } pl = pj_param_exists(pl, opt); if (type == 't') { value.i = pl != nullptr; return value; } /* Not found */ if (nullptr == pl) { /* Return value after the switch, so that the return path is */ /* taken in all cases */ switch (type) { case 'b': case 'i': value.i = 0; break; case 'd': case 'r': value.f = 0.; break; case 's': value.s = nullptr; break; } return value; } /* Found parameter - now find its value */ pl->used |= 1; l = (int)strlen(opt); opt = pl->param + l; if (*opt == '=') ++opt; switch (type) { case 'i': /* integer input */ value.i = atoi(opt); for (const char *ptr = opt; *ptr != '\0'; ++ptr) { if (!(*ptr >= '0' && *ptr <= '9')) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); value.i = 0; } } break; case 'd': /* simple real input */ value.f = pj_atof(opt); break; case 'r': /* degrees input */ value.f = dmstor_ctx(ctx, opt, nullptr); break; case 's': /* char string */ value.s = (char *)opt; break; case 'b': /* boolean */ switch (*opt) { case 'F': case 'f': value.i = 0; break; case '\0': case 'T': case 't': value.i = 1; break; default: proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); value.i = 0; break; } break; } return value; } proj-9.8.1/src/embedded_resources.c000664 001750 001750 00000001073 15166171715 017232 0ustar00eveneven000000 000000 #include #include #include "embedded_resources.h" #if USE_SHARP_EMBED #include "PROJ_DB_SQL_MD5.h" const unsigned char *pj_get_embedded_proj_db(unsigned int *pnSize) { (void)PROJ_DB_SQL_MD5; static const unsigned char proj_db[] = { #embed PROJ_DB }; *pnSize = (unsigned int)sizeof(proj_db); return proj_db; } #else #include "file_embed/proj_db.h" const unsigned char *pj_get_embedded_proj_db(unsigned int *pnSize) { *pnSize = proj_db_size; return proj_db_data; } #endif #include "file_embed/embedded_resources.c" proj-9.8.1/src/create.cpp000664 001750 001750 00000027157 15166171715 015225 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: pj_create_internal() related stuff * * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 * ****************************************************************************** * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include "proj.h" #include "proj_internal.h" #include /*************************************************************************************/ static PJ *skip_prep_fin(PJ *P) { /************************************************************************************** Skip prepare and finalize function for the various "helper operations" added to P when in cs2cs compatibility mode. **************************************************************************************/ P->skip_fwd_prepare = 1; P->skip_fwd_finalize = 1; P->skip_inv_prepare = 1; P->skip_inv_finalize = 1; return P; } /*************************************************************************************/ static int cs2cs_emulation_setup(PJ *P) { /************************************************************************************** If any cs2cs style modifiers are given (axis=..., towgs84=..., ) create the 4D API equivalent operations, so the preparation and finalization steps in the pj_inv/pj_fwd invocators can emulate the behavior of pj_transform and the cs2cs app. Returns 1 on success, 0 on failure **************************************************************************************/ PJ *Q; paralist *p; int do_cart = 0; if (nullptr == P) return 0; /* Don't recurse when calling proj_create (which calls us back) */ if (pj_param_exists(P->params, "break_cs2cs_recursion")) return 1; /* Swap axes? */ p = pj_param_exists(P->params, "axis"); const bool disable_grid_presence_check = pj_param_exists(P->params, "disable_grid_presence_check") != nullptr; /* Don't axisswap if data are already in "enu" order */ if (p && (0 != strcmp("enu", p->param))) { size_t def_size = 100 + strlen(P->axis); char *def = static_cast(malloc(def_size)); if (nullptr == def) return 0; snprintf(def, def_size, "break_cs2cs_recursion proj=axisswap axis=%s", P->axis); Q = pj_create_internal(P->ctx, def); free(def); if (nullptr == Q) return 0; P->axisswap = skip_prep_fin(Q); } /* Geoid grid(s) given? */ p = pj_param_exists(P->params, "geoidgrids"); if (!disable_grid_presence_check && p && strlen(p->param) > strlen("geoidgrids=")) { char *gridnames = p->param + strlen("geoidgrids="); size_t def_size = 100 + 2 * strlen(gridnames); char *def = static_cast(malloc(def_size)); if (nullptr == def) return 0; snprintf(def, def_size, "break_cs2cs_recursion proj=vgridshift grids=%s", pj_double_quote_string_param_if_needed(gridnames).c_str()); Q = pj_create_internal(P->ctx, def); free(def); if (nullptr == Q) return 0; P->vgridshift = skip_prep_fin(Q); } /* Datum shift grid(s) given? */ p = pj_param_exists(P->params, "nadgrids"); if (!disable_grid_presence_check && p && strlen(p->param) > strlen("nadgrids=")) { char *gridnames = p->param + strlen("nadgrids="); size_t def_size = 100 + 2 * strlen(gridnames); char *def = static_cast(malloc(def_size)); if (nullptr == def) return 0; snprintf(def, def_size, "break_cs2cs_recursion proj=hgridshift grids=%s", pj_double_quote_string_param_if_needed(gridnames).c_str()); Q = pj_create_internal(P->ctx, def); free(def); if (nullptr == Q) return 0; P->hgridshift = skip_prep_fin(Q); } /* We ignore helmert if we have grid shift */ p = P->hgridshift ? nullptr : pj_param_exists(P->params, "towgs84"); while (p) { const char *const s = p->param; const double *const d = P->datum_params; /* We ignore null helmert shifts (common in auto-translated resource * files, e.g. epsg) */ if (0 == d[0] && 0 == d[1] && 0 == d[2] && 0 == d[3] && 0 == d[4] && 0 == d[5] && 0 == d[6]) { /* If the current ellipsoid is not WGS84, then make sure the */ /* change in ellipsoid is still done. */ if (!(fabs(P->a_orig - 6378137.0) < 1e-8 && fabs(P->es_orig - 0.0066943799901413) < 1e-15)) { do_cart = 1; } break; } const size_t n = strlen(s); if (n <= 8) /* 8==strlen ("towgs84=") */ return 0; const size_t def_max_size = 100 + n; std::string def; def.reserve(def_max_size); def += "break_cs2cs_recursion proj=helmert exact "; def += s; def += " convention=position_vector"; Q = pj_create_internal(P->ctx, def.c_str()); if (nullptr == Q) return 0; pj_inherit_ellipsoid_def(P, Q); P->helmert = skip_prep_fin(Q); break; } /* We also need cartesian/geographical transformations if we are working in */ /* geocentric/cartesian space or we need to do a Helmert transform. */ if (P->is_geocent || P->helmert || do_cart) { char def[150]; snprintf(def, sizeof(def), "break_cs2cs_recursion proj=cart a=%40.20g es=%40.20g", P->a_orig, P->es_orig); { /* In case the current locale does not use dot but comma as decimal */ /* separator, replace it with dot, so that proj_atof() behaves */ /* correctly. */ /* TODO later: use C++ ostringstream with * imbue(std::locale::classic()) */ /* to be locale unaware */ char *next_pos; for (next_pos = def; (next_pos = strchr(next_pos, ',')) != nullptr; next_pos++) { *next_pos = '.'; } } Q = pj_create_internal(P->ctx, def); if (nullptr == Q) return 0; P->cart = skip_prep_fin(Q); if (!P->is_geocent) { snprintf(def, sizeof(def), "break_cs2cs_recursion proj=cart ellps=WGS84"); Q = pj_create_internal(P->ctx, def); if (nullptr == Q) return 0; P->cart_wgs84 = skip_prep_fin(Q); } } return 1; } /*************************************************************************************/ PJ *pj_create_internal(PJ_CONTEXT *ctx, const char *definition) { /*************************************************************************************/ /************************************************************************************** Create a new PJ object in the context ctx, using the given definition. If ctx==0, the default context is used, if definition==0, or invalid, a null-pointer is returned. The definition may use '+' as argument start indicator, as in "+proj=utm +zone=32", or leave it out, as in "proj=utm zone=32". It may even use free formatting "proj = utm; zone =32 ellps= GRS80". Note that the semicolon separator is allowed, but not required. **************************************************************************************/ char *args, **argv; size_t argc, n; if (nullptr == ctx) ctx = pj_get_default_ctx(); /* Make a copy that we can manipulate */ n = strlen(definition); args = (char *)malloc(n + 1); if (nullptr == args) { proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } strcpy(args, definition); argc = pj_trim_argc(args); if (argc == 0) { free(args); proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } argv = pj_trim_argv(argc, args); if (!argv) { free(args); proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); return nullptr; } PJ *P = pj_create_argv_internal(ctx, (int)argc, argv); free(argv); free(args); return P; } /*************************************************************************************/ PJ *proj_create_argv(PJ_CONTEXT *ctx, int argc, char **argv) { /************************************************************************************** Create a new PJ object in the context ctx, using the given definition argument array argv. If ctx==0, the default context is used, if definition==0, or invalid, a null-pointer is returned. The definition arguments may use '+' as argument start indicator, as in {"+proj=utm", "+zone=32"}, or leave it out, as in {"proj=utm", "zone=32"}. **************************************************************************************/ if (nullptr == ctx) ctx = pj_get_default_ctx(); if (nullptr == argv) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } /* We assume that free format is used, and build a full proj_create * compatible string */ char *c = pj_make_args(argc, argv); if (nullptr == c) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP /* ENOMEM */); return nullptr; } PJ *P = proj_create(ctx, c); free((char *)c); return P; } /*************************************************************************************/ PJ *pj_create_argv_internal(PJ_CONTEXT *ctx, int argc, char **argv) { /************************************************************************************** For use by pipeline init function. **************************************************************************************/ if (nullptr == ctx) ctx = pj_get_default_ctx(); if (nullptr == argv) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); return nullptr; } /* ...and let pj_init_ctx do the hard work */ /* New interface: forbid init=epsg:XXXX syntax by default */ const int allow_init_epsg = proj_context_get_use_proj4_init_rules(ctx, FALSE); PJ *P = pj_init_ctx_with_allow_init_epsg(ctx, argc, argv, allow_init_epsg); /* Support cs2cs-style modifiers */ int ret = cs2cs_emulation_setup(P); if (0 == ret) return proj_destroy(P); return P; } proj-9.8.1/src/wkt2_generated_parser.h000664 001750 001750 00000013422 15166171715 017676 0ustar00eveneven000000 000000 /* A Bison parser, made by GNU Bison 3.5.1. */ /* Bison interface for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* Undocumented macros, especially those whose name start with YY_, are private implementation details. Do not rely on them. */ #ifndef YY_PJ_WKT2_WKT2_GENERATED_PARSER_H_INCLUDED # define YY_PJ_WKT2_WKT2_GENERATED_PARSER_H_INCLUDED /* Debug traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif #if YYDEBUG extern int pj_wkt2_debug; #endif /* Token type. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { END = 0, T_PROJECTION = 258, T_DATUM = 259, T_SPHEROID = 260, T_PRIMEM = 261, T_UNIT = 262, T_AXIS = 263, T_PARAMETER = 264, T_GEODCRS = 265, T_LENGTHUNIT = 266, T_ANGLEUNIT = 267, T_SCALEUNIT = 268, T_TIMEUNIT = 269, T_ELLIPSOID = 270, T_CS = 271, T_ID = 272, T_PROJCRS = 273, T_BASEGEODCRS = 274, T_MERIDIAN = 275, T_BEARING = 276, T_ORDER = 277, T_ANCHOR = 278, T_ANCHOREPOCH = 279, T_CONVERSION = 280, T_METHOD = 281, T_REMARK = 282, T_GEOGCRS = 283, T_BASEGEOGCRS = 284, T_SCOPE = 285, T_AREA = 286, T_BBOX = 287, T_CITATION = 288, T_URI = 289, T_VERTCRS = 290, T_VDATUM = 291, T_GEOIDMODEL = 292, T_COMPOUNDCRS = 293, T_PARAMETERFILE = 294, T_COORDINATEOPERATION = 295, T_SOURCECRS = 296, T_TARGETCRS = 297, T_INTERPOLATIONCRS = 298, T_OPERATIONACCURACY = 299, T_CONCATENATEDOPERATION = 300, T_STEP = 301, T_BOUNDCRS = 302, T_ABRIDGEDTRANSFORMATION = 303, T_DERIVINGCONVERSION = 304, T_TDATUM = 305, T_CALENDAR = 306, T_TIMEORIGIN = 307, T_TIMECRS = 308, T_VERTICALEXTENT = 309, T_TIMEEXTENT = 310, T_USAGE = 311, T_DYNAMIC = 312, T_FRAMEEPOCH = 313, T_MODEL = 314, T_VELOCITYGRID = 315, T_ENSEMBLE = 316, T_MEMBER = 317, T_ENSEMBLEACCURACY = 318, T_DERIVEDPROJCRS = 319, T_BASEPROJCRS = 320, T_EDATUM = 321, T_ENGCRS = 322, T_PDATUM = 323, T_PARAMETRICCRS = 324, T_PARAMETRICUNIT = 325, T_BASEVERTCRS = 326, T_BASEENGCRS = 327, T_BASEPARAMCRS = 328, T_BASETIMECRS = 329, T_EPOCH = 330, T_COORDEPOCH = 331, T_COORDINATEMETADATA = 332, T_POINTMOTIONOPERATION = 333, T_VERSION = 334, T_AXISMINVALUE = 335, T_AXISMAXVALUE = 336, T_RANGEMEANING = 337, T_exact = 338, T_wraparound = 339, T_DEFININGTRANSFORMATION = 340, T_GEODETICCRS = 341, T_GEODETICDATUM = 342, T_PROJECTEDCRS = 343, T_PRIMEMERIDIAN = 344, T_GEOGRAPHICCRS = 345, T_TRF = 346, T_VERTICALCRS = 347, T_VERTICALDATUM = 348, T_VRF = 349, T_TIMEDATUM = 350, T_TEMPORALQUANTITY = 351, T_ENGINEERINGDATUM = 352, T_ENGINEERINGCRS = 353, T_PARAMETRICDATUM = 354, T_AFFINE = 355, T_CARTESIAN = 356, T_CYLINDRICAL = 357, T_ELLIPSOIDAL = 358, T_LINEAR = 359, T_PARAMETRIC = 360, T_POLAR = 361, T_SPHERICAL = 362, T_VERTICAL = 363, T_TEMPORAL = 364, T_TEMPORALCOUNT = 365, T_TEMPORALMEASURE = 366, T_ORDINAL = 367, T_TEMPORALDATETIME = 368, T_NORTH = 369, T_NORTHNORTHEAST = 370, T_NORTHEAST = 371, T_EASTNORTHEAST = 372, T_EAST = 373, T_EASTSOUTHEAST = 374, T_SOUTHEAST = 375, T_SOUTHSOUTHEAST = 376, T_SOUTH = 377, T_SOUTHSOUTHWEST = 378, T_SOUTHWEST = 379, T_WESTSOUTHWEST = 380, T_WEST = 381, T_WESTNORTHWEST = 382, T_NORTHWEST = 383, T_NORTHNORTHWEST = 384, T_UP = 385, T_DOWN = 386, T_GEOCENTRICX = 387, T_GEOCENTRICY = 388, T_GEOCENTRICZ = 389, T_COLUMNPOSITIVE = 390, T_COLUMNNEGATIVE = 391, T_ROWPOSITIVE = 392, T_ROWNEGATIVE = 393, T_DISPLAYRIGHT = 394, T_DISPLAYLEFT = 395, T_DISPLAYUP = 396, T_DISPLAYDOWN = 397, T_FORWARD = 398, T_AFT = 399, T_PORT = 400, T_STARBOARD = 401, T_CLOCKWISE = 402, T_COUNTERCLOCKWISE = 403, T_TOWARDS = 404, T_AWAYFROM = 405, T_FUTURE = 406, T_PAST = 407, T_UNSPECIFIED = 408, T_STRING = 409, T_UNSIGNED_INTEGER_DIFFERENT_ONE_TWO_THREE = 410 }; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef int YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif int pj_wkt2_parse (pj_wkt2_parse_context *context); #endif /* !YY_PJ_WKT2_WKT2_GENERATED_PARSER_H_INCLUDED */ proj-9.8.1/src/strerrno.cpp000664 001750 001750 00000005372 15166171715 015633 0ustar00eveneven000000 000000 /* list of projection system errno values */ #include #include #include #include "proj.h" #include "proj_config.h" #include "proj_internal.h" const char *proj_errno_string(int err) { return proj_context_errno_string(pj_get_default_ctx(), err); } static const struct { int num; const char *str; } error_strings[] = { {PROJ_ERR_INVALID_OP_WRONG_SYNTAX, _("Invalid PROJ string syntax")}, {PROJ_ERR_INVALID_OP_MISSING_ARG, _("Missing argument")}, {PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE, _("Invalid value for an argument")}, {PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS, _("Mutually exclusive arguments")}, {PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID, _("File not found or invalid")}, {PROJ_ERR_COORD_TRANSFM_INVALID_COORD, _("Invalid coordinate")}, {PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN, _("Point outside of projection domain")}, {PROJ_ERR_COORD_TRANSFM_NO_OPERATION, _("No operation matching criteria found for coordinate")}, {PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID, _("Coordinate to transform falls outside grid")}, {PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA, _("Coordinate to transform falls into a grid cell that evaluates to " "nodata")}, {PROJ_ERR_COORD_TRANSFM_NO_CONVERGENCE, _("Iterative method fails to converge on coordinate to transform")}, {PROJ_ERR_COORD_TRANSFM_MISSING_TIME, _("Coordinate to transform lacks time")}, {PROJ_ERR_OTHER_API_MISUSE, _("API misuse")}, {PROJ_ERR_OTHER_NO_INVERSE_OP, _("No inverse operation")}, {PROJ_ERR_OTHER_NETWORK_ERROR, _("Network error when accessing a remote resource")}, }; const char *proj_context_errno_string(PJ_CONTEXT *ctx, int err) { if (ctx == nullptr) ctx = pj_get_default_ctx(); if (0 == err) return nullptr; const char *str = nullptr; for (const auto &num_str_pair : error_strings) { if (err == num_str_pair.num) { str = num_str_pair.str; break; } } if (str == nullptr && err > 0 && (err & PROJ_ERR_INVALID_OP) != 0) { str = _( "Unspecified error related to coordinate operation initialization"); } if (str == nullptr && err > 0 && (err & PROJ_ERR_COORD_TRANSFM) != 0) { str = _("Unspecified error related to coordinate transformation"); } if (str) { ctx->lastFullErrorMessage = str; } else { ctx->lastFullErrorMessage.resize(50); snprintf(&ctx->lastFullErrorMessage[0], ctx->lastFullErrorMessage.size(), _("Unknown error (code %d)"), err); ctx->lastFullErrorMessage.resize( strlen(ctx->lastFullErrorMessage.data())); } return ctx->lastFullErrorMessage.c_str(); } proj-9.8.1/src/transformations/000775 001750 001750 00000000000 15166171735 016475 5ustar00eveneven000000 000000 proj-9.8.1/src/transformations/tinshift.cpp000664 001750 001750 00000011403 15166171715 021026 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to triangulation transformation * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define PROJ_COMPILATION #include "tinshift.hpp" #include "filemanager.hpp" #include "proj_internal.h" PROJ_HEAD(tinshift, "Triangulation based transformation"); using namespace TINSHIFT_NAMESPACE; namespace { struct tinshiftData { std::unique_ptr evaluator{}; tinshiftData() = default; tinshiftData(const tinshiftData &) = delete; tinshiftData &operator=(const tinshiftData &) = delete; }; } // namespace static PJ *pj_tinshift_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; auto Q = static_cast(P->opaque); delete Q; P->opaque = nullptr; return pj_default_destructor(P, errlev); } static void tinshift_forward_4d(PJ_COORD &coo, PJ *P) { auto *Q = (struct tinshiftData *)P->opaque; if (!Q->evaluator->forward(coo.xyz.x, coo.xyz.y, coo.xyz.z, coo.xyz.x, coo.xyz.y, coo.xyz.z)) { coo = proj_coord_error(); } } static void tinshift_reverse_4d(PJ_COORD &coo, PJ *P) { auto *Q = (struct tinshiftData *)P->opaque; if (!Q->evaluator->inverse(coo.xyz.x, coo.xyz.y, coo.xyz.z, coo.xyz.x, coo.xyz.y, coo.xyz.z)) { coo = proj_coord_error(); } } PJ *PJ_TRANSFORMATION(tinshift, 1) { const char *filename = pj_param(P->ctx, P->params, "sfile").s; if (!filename) { proj_log_error(P, _("+file= should be specified.")); return pj_tinshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, filename); if (nullptr == file) { proj_log_error(P, _("Cannot open %s"), filename); return pj_tinshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0, SEEK_END); unsigned long long size = file->tell(); // Arbitrary threshold to avoid ingesting an arbitrarily large JSON file, // that could be a denial of service risk. 100 MB should be sufficiently // large for any valid use ! if (size > 100 * 1024 * 1024) { proj_log_error(P, _("File %s too large"), filename); return pj_tinshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0); std::string jsonStr; try { jsonStr.resize(static_cast(size)); } catch (const std::bad_alloc &) { proj_log_error(P, _("Cannot read %s. Not enough memory"), filename); return pj_tinshift_destructor(P, PROJ_ERR_OTHER); } if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) { proj_log_error(P, _("Cannot read %s"), filename); return pj_tinshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } auto Q = new tinshiftData(); P->opaque = (void *)Q; P->destructor = pj_tinshift_destructor; try { Q->evaluator.reset(new Evaluator(TINShiftFile::parse(jsonStr))); } catch (const std::exception &e) { proj_log_error(P, _("invalid model: %s"), e.what()); return pj_tinshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } P->fwd4d = tinshift_forward_4d; P->inv4d = tinshift_reverse_4d; P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; return P; } proj-9.8.1/src/transformations/xyzgridshift.cpp000664 001750 001750 00000022166 15166171715 021744 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Geocentric translation using a grid * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2019, Even Rouault, * * 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 #include #include #include #include "grids.hpp" #include "proj_internal.h" #include PROJ_HEAD(xyzgridshift, "Geocentric grid shift"); using namespace NS_PROJ; namespace { // anonymous namespace struct xyzgridshiftData { PJ *cart = nullptr; bool grid_ref_is_input = true; ListOfGenericGrids grids{}; bool defer_grid_opening = false; int error_code_in_defer_grid_opening = 0; double multiplier = 1.0; }; } // anonymous namespace // --------------------------------------------------------------------------- static bool get_grid_values(PJ *P, xyzgridshiftData *Q, const PJ_LP &lp, double &dx, double &dy, double &dz) { if (Q->defer_grid_opening) { Q->defer_grid_opening = false; Q->grids = pj_generic_grid_init(P, "grids"); Q->error_code_in_defer_grid_opening = proj_errno(P); } if (Q->error_code_in_defer_grid_opening) { proj_errno_set(P, Q->error_code_in_defer_grid_opening); return false; } GenericShiftGridSet *gridset = nullptr; auto grid = pj_find_generic_grid(Q->grids, lp, gridset); if (!grid) { return false; } if (grid->isNullGrid()) { dx = 0; dy = 0; dz = 0; return true; } const auto samplesPerPixel = grid->samplesPerPixel(); if (samplesPerPixel < 3) { proj_log_error(P, "xyzgridshift: grid has not enough samples"); return false; } int sampleX = 0; int sampleY = 1; int sampleZ = 2; for (int i = 0; i < samplesPerPixel; i++) { const auto desc = grid->description(i); if (desc == "x_translation") { sampleX = i; } else if (desc == "y_translation") { sampleY = i; } else if (desc == "z_translation") { sampleZ = i; } } const auto unit = grid->unit(sampleX); if (!unit.empty() && unit != "metre") { proj_log_error(P, "xyzgridshift: Only unit=metre currently handled"); return false; } bool must_retry = false; if (!pj_bilinear_interpolation_three_samples(P->ctx, grid, lp, sampleX, sampleY, sampleZ, dx, dy, dz, must_retry)) { if (must_retry) return get_grid_values(P, Q, lp, dx, dy, dz); return false; } dx *= Q->multiplier; dy *= Q->multiplier; dz *= Q->multiplier; return true; } // --------------------------------------------------------------------------- #define SQUARE(x) ((x) * (x)) // --------------------------------------------------------------------------- static PJ_COORD iterative_adjustment(PJ *P, xyzgridshiftData *Q, const PJ_COORD &pointInit, double factor) { PJ_COORD point = pointInit; for (int i = 0; i < 10; i++) { PJ_COORD geodetic; geodetic.lpz = pj_inv3d(point.xyz, Q->cart); double dx, dy, dz; if (!get_grid_values(P, Q, geodetic.lp, dx, dy, dz)) { return proj_coord_error(); } dx *= factor; dy *= factor; dz *= factor; const double err = SQUARE((point.xyz.x - pointInit.xyz.x) - dx) + SQUARE((point.xyz.y - pointInit.xyz.y) - dy) + SQUARE((point.xyz.z - pointInit.xyz.z) - dz); point.xyz.x = pointInit.xyz.x + dx; point.xyz.y = pointInit.xyz.y + dy; point.xyz.z = pointInit.xyz.z + dz; if (err < 1e-10) { break; } } return point; } // --------------------------------------------------------------------------- static PJ_COORD direct_adjustment(PJ *P, xyzgridshiftData *Q, PJ_COORD point, double factor) { PJ_COORD geodetic; geodetic.lpz = pj_inv3d(point.xyz, Q->cart); double dx, dy, dz; if (!get_grid_values(P, Q, geodetic.lp, dx, dy, dz)) { return proj_coord_error(); } point.xyz.x += factor * dx; point.xyz.y += factor * dy; point.xyz.z += factor * dz; return point; } // --------------------------------------------------------------------------- static PJ_XYZ pj_xyzgridshift_forward_3d(PJ_LPZ lpz, PJ *P) { auto Q = static_cast(P->opaque); PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; if (Q->grid_ref_is_input) { point = direct_adjustment(P, Q, point, 1.0); } else { point = iterative_adjustment(P, Q, point, 1.0); } return point.xyz; } static PJ_LPZ pj_xyzgridshift_reverse_3d(PJ_XYZ xyz, PJ *P) { auto Q = static_cast(P->opaque); PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; if (Q->grid_ref_is_input) { point = iterative_adjustment(P, Q, point, -1.0); } else { point = direct_adjustment(P, Q, point, -1.0); } return point.lpz; } static PJ *pj_xyzgridshift_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; auto Q = static_cast(P->opaque); if (Q) { if (Q->cart) Q->cart->destructor(Q->cart, errlev); delete Q; } P->opaque = nullptr; return pj_default_destructor(P, errlev); } static void pj_xyzgridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) { auto Q = (struct xyzgridshiftData *)P->opaque; for (auto &grid : Q->grids) { grid->reassign_context(ctx); } } PJ *PJ_TRANSFORMATION(xyzgridshift, 0) { auto Q = new xyzgridshiftData; P->opaque = (void *)Q; P->destructor = pj_xyzgridshift_destructor; P->reassign_context = pj_xyzgridshift_reassign_context; P->fwd4d = nullptr; P->inv4d = nullptr; P->fwd3d = pj_xyzgridshift_forward_3d; P->inv3d = pj_xyzgridshift_reverse_3d; P->fwd = nullptr; P->inv = nullptr; P->left = PJ_IO_UNITS_CARTESIAN; P->right = PJ_IO_UNITS_CARTESIAN; // Pass a dummy ellipsoid definition that will be overridden just afterwards Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart == nullptr) return pj_xyzgridshift_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def(P, Q->cart); const char *grid_ref = pj_param(P->ctx, P->params, "sgrid_ref").s; if (grid_ref) { if (strcmp(grid_ref, "input_crs") == 0) { // default } else if (strcmp(grid_ref, "output_crs") == 0) { // Convention use for example for NTF->RGF93 grid that contains // delta x,y,z from NTF to RGF93, but the grid itself is referenced // in RGF93 Q->grid_ref_is_input = false; } else { proj_log_error(P, _("unusupported value for grid_ref")); return pj_xyzgridshift_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (0 == pj_param(P->ctx, P->params, "tgrids").i) { proj_log_error(P, _("+grids parameter missing.")); return pj_xyzgridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* multiplier for delta x,y,z */ if (pj_param(P->ctx, P->params, "tmultiplier").i) { Q->multiplier = pj_param(P->ctx, P->params, "dmultiplier").f; } if (P->ctx->defer_grid_opening) { Q->defer_grid_opening = true; } else { Q->grids = pj_generic_grid_init(P, "grids"); /* Was gridlist compiled properly? */ if (proj_errno(P)) { proj_log_error(P, _("could not find required grid(s).")); return pj_xyzgridshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } } return P; } proj-9.8.1/src/transformations/defmodel.cpp000664 001750 001750 00000036520 15166171715 020764 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to deformation model * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define PROJ_COMPILATION #include "defmodel.hpp" #include "filemanager.hpp" #include "grids.hpp" #include "proj_internal.h" #include #include #include #include PROJ_HEAD(defmodel, "Deformation model"); using namespace DeformationModel; namespace { struct Grid : public GridPrototype { PJ_CONTEXT *ctx; const NS_PROJ::GenericShiftGrid *realGrid; mutable bool checkedHorizontal = false; mutable bool checkedVertical = false; mutable int sampleX = 0; mutable int sampleY = 1; mutable int sampleZ = 2; Grid(PJ_CONTEXT *ctxIn, const NS_PROJ::GenericShiftGrid *realGridIn) : ctx(ctxIn), realGrid(realGridIn) { minx = realGridIn->extentAndRes().west; miny = realGridIn->extentAndRes().south; resx = realGridIn->extentAndRes().resX; resy = realGridIn->extentAndRes().resY; width = realGridIn->width(); height = realGridIn->height(); } bool checkHorizontal(const std::string &expectedUnit) const { if (!checkedHorizontal) { const auto samplesPerPixel = realGrid->samplesPerPixel(); if (samplesPerPixel < 2) { pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples", realGrid->name().c_str()); return false; } bool foundDescX = false; bool foundDescY = false; bool foundDesc = false; for (int i = 0; i < samplesPerPixel; i++) { const auto desc = realGrid->description(i); if (desc == "east_offset") { sampleX = i; foundDescX = true; } else if (desc == "north_offset") { sampleY = i; foundDescY = true; } if (!desc.empty()) { foundDesc = true; } } if (foundDesc && (!foundDescX || !foundDescY)) { pj_log(ctx, PJ_LOG_ERROR, "grid %s : Found band description, " "but not the ones expected", realGrid->name().c_str()); return false; } const auto unit = realGrid->unit(sampleX); if (!unit.empty() && unit != expectedUnit) { pj_log(ctx, PJ_LOG_ERROR, "grid %s : Only unit=%s " "currently handled for this mode", realGrid->name().c_str(), expectedUnit.c_str()); return false; } checkedHorizontal = true; } return true; } bool getLongLatOffset(int ix, int iy, double &longOffsetRadian, double &latOffsetRadian) const { if (!checkHorizontal(STR_DEGREE)) { return false; } float longOffsetDeg; float latOffsetDeg; if (!realGrid->valueAt(ix, iy, sampleX, longOffsetDeg) || !realGrid->valueAt(ix, iy, sampleY, latOffsetDeg)) { return false; } longOffsetRadian = longOffsetDeg * DEG_TO_RAD; latOffsetRadian = latOffsetDeg * DEG_TO_RAD; return true; } bool getZOffset(int ix, int iy, double &zOffset) const { if (!checkedVertical) { const auto samplesPerPixel = realGrid->samplesPerPixel(); if (samplesPerPixel == 1) { sampleZ = 0; } else if (samplesPerPixel < 3) { pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples", realGrid->name().c_str()); return false; } bool foundDesc = false; bool foundDescZ = false; for (int i = 0; i < samplesPerPixel; i++) { const auto desc = realGrid->description(i); if (desc == "vertical_offset") { sampleZ = i; foundDescZ = true; } if (!desc.empty()) { foundDesc = true; } } if (foundDesc && !foundDescZ) { pj_log(ctx, PJ_LOG_ERROR, "grid %s : Found band description, " "but not the ones expected", realGrid->name().c_str()); return false; } const auto unit = realGrid->unit(sampleZ); if (!unit.empty() && unit != STR_METRE) { pj_log(ctx, PJ_LOG_ERROR, "grid %s : Only unit=metre currently " "handled for this mode", realGrid->name().c_str()); return false; } checkedVertical = true; } float zOffsetFloat = 0.0f; const bool ret = realGrid->valueAt(ix, iy, sampleZ, zOffsetFloat); zOffset = zOffsetFloat; return ret; } bool getEastingNorthingOffset(int ix, int iy, double &eastingOffset, double &northingOffset) const { if (!checkHorizontal(STR_METRE)) { return false; } float eastingOffsetFloat = 0.0f; float northingOffsetFloat = 0.0f; const bool ret = realGrid->valueAt(ix, iy, sampleX, eastingOffsetFloat) && realGrid->valueAt(ix, iy, sampleY, northingOffsetFloat); eastingOffset = eastingOffsetFloat; northingOffset = northingOffsetFloat; return ret; } bool getLongLatZOffset(int ix, int iy, double &longOffsetRadian, double &latOffsetRadian, double &zOffset) const { return getLongLatOffset(ix, iy, longOffsetRadian, latOffsetRadian) && getZOffset(ix, iy, zOffset); } bool getEastingNorthingZOffset(int ix, int iy, double &eastingOffset, double &northingOffset, double &zOffset) const { return getEastingNorthingOffset(ix, iy, eastingOffset, northingOffset) && getZOffset(ix, iy, zOffset); } #ifdef DEBUG_DEFMODEL std::string name() const { return realGrid->name(); } #endif private: Grid(const Grid &) = delete; Grid &operator=(const Grid &) = delete; }; struct GridSet : public GridSetPrototype { PJ_CONTEXT *ctx; std::unique_ptr realGridSet; std::map> mapGrids{}; GridSet(PJ_CONTEXT *ctxIn, std::unique_ptr &&realGridSetIn) : ctx(ctxIn), realGridSet(std::move(realGridSetIn)) {} const Grid *gridAt(double x, double y) { const NS_PROJ::GenericShiftGrid *realGrid = realGridSet->gridAt(x, y); if (!realGrid) { return nullptr; } auto iter = mapGrids.find(realGrid); if (iter == mapGrids.end()) { iter = mapGrids .insert(std::pair>( realGrid, std::unique_ptr(new Grid(ctx, realGrid)))) .first; } return iter->second.get(); } private: GridSet(const GridSet &) = delete; GridSet &operator=(const GridSet &) = delete; }; struct EvaluatorIface : public EvaluatorIfacePrototype { PJ_CONTEXT *ctx; PJ *cart; EvaluatorIface(PJ_CONTEXT *ctxIn, PJ *cartIn) : ctx(ctxIn), cart(cartIn) {} ~EvaluatorIface() { if (cart) cart->destructor(cart, 0); } std::unique_ptr open(const std::string &filename) { auto realGridSet = NS_PROJ::GenericShiftGridSet::open(ctx, filename); if (!realGridSet) { pj_log(ctx, PJ_LOG_ERROR, "cannot open %s", filename.c_str()); return nullptr; } return std::unique_ptr( new GridSet(ctx, std::move(realGridSet))); } bool isGeographicCRS(const std::string &crsDef) { PJ *P = proj_create(ctx, crsDef.c_str()); if (P == nullptr) { return true; // reasonable default value } const auto type = proj_get_type(P); bool ret = (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || type == PJ_TYPE_GEOGRAPHIC_3D_CRS); proj_destroy(P); return ret; } void geographicToGeocentric(double lam, double phi, double height, double a, double b, double /*es*/, double &X, double &Y, double &Z) { (void)a; (void)b; assert(cart->a == a); assert(cart->b == b); PJ_LPZ lpz; lpz.lam = lam; lpz.phi = phi; lpz.z = height; PJ_XYZ xyz = cart->fwd3d(lpz, cart); X = xyz.x; Y = xyz.y; Z = xyz.z; } void geocentricToGeographic(double X, double Y, double Z, double a, double b, double /*es*/, double &lam, double &phi, double &height) { (void)a; (void)b; assert(cart->a == a); assert(cart->b == b); PJ_XYZ xyz; xyz.x = X; xyz.y = Y; xyz.z = Z; PJ_LPZ lpz = cart->inv3d(xyz, cart); lam = lpz.lam; phi = lpz.phi; height = lpz.z; } #ifdef DEBUG_DEFMODEL void log(const std::string &msg) { pj_log(ctx, PJ_LOG_TRACE, "%s", msg.c_str()); } #endif private: EvaluatorIface(const EvaluatorIface &) = delete; EvaluatorIface &operator=(const EvaluatorIface &) = delete; }; struct defmodelData { std::unique_ptr> evaluator{}; EvaluatorIface evaluatorIface; explicit defmodelData(PJ_CONTEXT *ctx, PJ *cart) : evaluatorIface(ctx, cart) {} defmodelData(const defmodelData &) = delete; defmodelData &operator=(const defmodelData &) = delete; }; } // namespace static PJ *destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; auto Q = static_cast(P->opaque); delete Q; P->opaque = nullptr; return pj_default_destructor(P, errlev); } static void forward_4d(PJ_COORD &coo, PJ *P) { auto *Q = (struct defmodelData *)P->opaque; if (coo.xyzt.t == HUGE_VAL) { coo = proj_coord_error(); proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); return; } if (!Q->evaluator->forward(Q->evaluatorIface, coo.xyzt.x, coo.xyzt.y, coo.xyzt.z, coo.xyzt.t, coo.xyzt.x, coo.xyzt.y, coo.xyzt.z)) { coo = proj_coord_error(); } } static void reverse_4d(PJ_COORD &coo, PJ *P) { auto *Q = (struct defmodelData *)P->opaque; if (coo.xyzt.t == HUGE_VAL) { coo = proj_coord_error(); proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); return; } if (!Q->evaluator->inverse(Q->evaluatorIface, coo.xyzt.x, coo.xyzt.y, coo.xyzt.z, coo.xyzt.t, coo.xyzt.x, coo.xyzt.y, coo.xyzt.z)) { coo = proj_coord_error(); } } // Function called by proj_assign_context() when a new context is assigned to // an existing PJ object. Mostly to deal with objects being passed between // threads. static void reassign_context(PJ *P, PJ_CONTEXT *ctx) { auto *Q = (struct defmodelData *)P->opaque; if (Q->evaluatorIface.ctx != ctx) { Q->evaluator->clearGridCache(); Q->evaluatorIface.ctx = ctx; } } PJ *PJ_TRANSFORMATION(defmodel, 1) { // Pass a dummy ellipsoid definition that will be overridden just afterwards auto cart = proj_create(P->ctx, "+proj=cart +a=1"); if (cart == nullptr) return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def(P, cart); auto Q = new defmodelData(P->ctx, cart); P->opaque = (void *)Q; P->destructor = destructor; P->reassign_context = reassign_context; const char *model = pj_param(P->ctx, P->params, "smodel").s; if (!model) { proj_log_error(P, _("+model= should be specified.")); return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, model); if (nullptr == file) { proj_log_error(P, _("Cannot open %s"), model); return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0, SEEK_END); unsigned long long size = file->tell(); // Arbitrary threshold to avoid ingesting an arbitrarily large JSON file, // that could be a denial of service risk. 10 MB should be sufficiently // large for any valid use ! if (size > 10 * 1024 * 1024) { proj_log_error(P, _("File %s too large"), model); return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } file->seek(0); std::string jsonStr; jsonStr.resize(static_cast(size)); if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) { proj_log_error(P, _("Cannot read %s"), model); return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } try { Q->evaluator.reset(new Evaluator( MasterFile::parse(jsonStr), Q->evaluatorIface, P->a, P->b)); } catch (const std::exception &e) { proj_log_error(P, _("invalid model: %s"), e.what()); return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } P->fwd4d = forward_4d; P->inv4d = reverse_4d; if (Q->evaluator->isGeographicCRS()) { P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; } else { P->left = PJ_IO_UNITS_PROJECTED; P->right = PJ_IO_UNITS_PROJECTED; } return P; } proj-9.8.1/src/transformations/defmodel_impl.hpp000664 001750 001750 00000141570 15166171715 022014 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to deformation model * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef DEFORMATON_MODEL_NAMESPACE #error "Should be included only by defmodel.hpp" #endif #include #include namespace DEFORMATON_MODEL_NAMESPACE { // --------------------------------------------------------------------------- static const std::string STR_DEGREE("degree"); static const std::string STR_METRE("metre"); static const std::string STR_ADDITION("addition"); static const std::string STR_GEOCENTRIC("geocentric"); static const std::string STR_BILINEAR("bilinear"); static const std::string STR_GEOCENTRIC_BILINEAR("geocentric_bilinear"); static const std::string STR_NONE("none"); static const std::string STR_HORIZONTAL("horizontal"); static const std::string STR_VERTICAL("vertical"); static const std::string STR_3D("3d"); constexpr double DEFMODEL_PI = 3.14159265358979323846; constexpr double DEG_TO_RAD_CONSTANT = 3.14159265358979323846 / 180.; inline constexpr double DegToRad(double d) { return d * DEG_TO_RAD_CONSTANT; } // --------------------------------------------------------------------------- enum class DisplacementType { NONE, HORIZONTAL, VERTICAL, THREE_D }; // --------------------------------------------------------------------------- /** Internal class to offer caching services over a Grid */ template struct GridEx { const Grid *grid; bool smallResx; // lesser than one degree double sinhalfresx; double coshalfresx; double sinresy; double cosresy; int last_ix0 = -1; int last_iy0 = -1; double dX00 = 0; double dY00 = 0; double dZ00 = 0; double dX01 = 0; double dY01 = 0; double dZ01 = 0; double dX10 = 0; double dY10 = 0; double dZ10 = 0; double dX11 = 0; double dY11 = 0; double dZ11 = 0; double sinphi0 = 0; double cosphi0 = 0; double sinphi1 = 0; double cosphi1 = 0; explicit GridEx(const Grid *gridIn) : grid(gridIn), smallResx(grid->resx < DegToRad(1)), sinhalfresx(sin(grid->resx / 2)), coshalfresx(cos(grid->resx / 2)), sinresy(sin(grid->resy)), cosresy(cos(grid->resy)) {} // Return geocentric offset (dX, dY, dZ) relative to a point // where x0 = -resx / 2 inline void getBilinearGeocentric(int ix0, int iy0, double de00, double dn00, double de01, double dn01, double de10, double dn10, double de11, double dn11, double m00, double m01, double m10, double m11, double &dX, double &dY, double &dZ) { // If interpolating in the same cell as before, then we can skip // the recomputation of dXij, dYij and dZij if (ix0 != last_ix0 || iy0 != last_iy0) { last_ix0 = ix0; if (iy0 != last_iy0) { const double y0 = grid->miny + iy0 * grid->resy; sinphi0 = sin(y0); cosphi0 = cos(y0); // Use trigonometric formulas to avoid new calls to sin/cos sinphi1 = /* sin(y0+grid->resyRad) = */ sinphi0 * cosresy + cosphi0 * sinresy; cosphi1 = /* cos(y0+grid->resyRad) = */ cosphi0 * cosresy - sinphi0 * sinresy; last_iy0 = iy0; } // Using "traditional" formulas to convert from easting, northing // offsets to geocentric offsets const double sinlam00 = -sinhalfresx; const double coslam00 = coshalfresx; const double sinphi00 = sinphi0; const double cosphi00 = cosphi0; const double dn00sinphi00 = dn00 * sinphi00; dX00 = -de00 * sinlam00 - dn00sinphi00 * coslam00; dY00 = de00 * coslam00 - dn00sinphi00 * sinlam00; dZ00 = dn00 * cosphi00; const double sinlam01 = -sinhalfresx; const double coslam01 = coshalfresx; const double sinphi01 = sinphi1; const double cosphi01 = cosphi1; const double dn01sinphi01 = dn01 * sinphi01; dX01 = -de01 * sinlam01 - dn01sinphi01 * coslam01; dY01 = de01 * coslam01 - dn01sinphi01 * sinlam01; dZ01 = dn01 * cosphi01; const double sinlam10 = sinhalfresx; const double coslam10 = coshalfresx; const double sinphi10 = sinphi0; const double cosphi10 = cosphi0; const double dn10sinphi10 = dn10 * sinphi10; dX10 = -de10 * sinlam10 - dn10sinphi10 * coslam10; dY10 = de10 * coslam10 - dn10sinphi10 * sinlam10; dZ10 = dn10 * cosphi10; const double sinlam11 = sinhalfresx; const double coslam11 = coshalfresx; const double sinphi11 = sinphi1; const double cosphi11 = cosphi1; const double dn11sinphi11 = dn11 * sinphi11; dX11 = -de11 * sinlam11 - dn11sinphi11 * coslam11; dY11 = de11 * coslam11 - dn11sinphi11 * sinlam11; dZ11 = dn11 * cosphi11; } dX = m00 * dX00 + m01 * dX01 + m10 * dX10 + m11 * dX11; dY = m00 * dY00 + m01 * dY01 + m10 * dY10 + m11 * dY11; dZ = m00 * dZ00 + m01 * dZ01 + m10 * dZ10 + m11 * dZ11; } }; // --------------------------------------------------------------------------- /** Internal class to offer caching services over a Component */ template struct ComponentEx { const Component &component; const bool isBilinearInterpolation; /* bilinear vs geocentric_bilinear */ const DisplacementType displacementType; // Cache std::unique_ptr gridSet{}; std::map> mapGrids{}; private: mutable double mCachedDt = 0; mutable double mCachedValue = 0; static DisplacementType getDisplacementType(const std::string &s) { if (s == STR_HORIZONTAL) return DisplacementType::HORIZONTAL; if (s == STR_VERTICAL) return DisplacementType::VERTICAL; if (s == STR_3D) return DisplacementType::THREE_D; return DisplacementType::NONE; } public: explicit ComponentEx(const Component &componentIn) : component(componentIn), isBilinearInterpolation( componentIn.spatialModel().interpolationMethod == STR_BILINEAR), displacementType(getDisplacementType(component.displacementType())) {} double evaluateAt(double dt) const { if (dt == mCachedDt) return mCachedValue; mCachedDt = dt; mCachedValue = component.timeFunction()->evaluateAt(dt); return mCachedValue; } void clearGridCache() { gridSet.reset(); mapGrids.clear(); } }; // --------------------------------------------------------------------------- /** Converts a ISO8601 date-time string, formatted as "YYYY-MM-DDTHH:MM:SSZ", * into a decimal year. * Leap years are taken into account, but not leap seconds. */ static double ISO8601ToDecimalYear(const std::string &dt) { int year, month, day, hour, min, sec; if (sscanf(dt.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &year, &month, &day, &hour, &min, &sec) != 6 || year < 1582 || // Start of Gregorian calendar month < 1 || month > 12 || day < 1 || day > 31 || hour < 0 || hour >= 24 || min < 0 || min >= 60 || sec < 0 || sec >= 61) { throw ParsingException("Wrong formatting / invalid date-time for " + dt); } const bool isLeapYear = (((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0); // Given the intended use, we omit leap seconds... const int month_table[2][12] = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; int dayInYear = day - 1; for (int m = 1; m < month; m++) { dayInYear += month_table[isLeapYear ? 1 : 0][m - 1]; } if (day > month_table[isLeapYear ? 1 : 0][month - 1]) { throw ParsingException("Wrong formatting / invalid date-time for " + dt); } return year + (dayInYear * 86400 + hour * 3600 + min * 60 + sec) / (isLeapYear ? 86400. * 366 : 86400. * 365); } // --------------------------------------------------------------------------- Epoch::Epoch(const std::string &dt) : mDt(dt) { if (!dt.empty()) { mDecimalYear = ISO8601ToDecimalYear(dt); } } // --------------------------------------------------------------------------- double Epoch::toDecimalYear() const { return mDecimalYear; } // --------------------------------------------------------------------------- static std::string getString(const json &j, const char *key, bool optional) { if (!j.contains(key)) { if (optional) { return std::string(); } throw ParsingException(std::string("Missing \"") + key + "\" key"); } const json v = j[key]; if (!v.is_string()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a string"); } return v.get(); } static std::string getReqString(const json &j, const char *key) { return getString(j, key, false); } static std::string getOptString(const json &j, const char *key) { return getString(j, key, true); } // --------------------------------------------------------------------------- static double getDouble(const json &j, const char *key, bool optional) { if (!j.contains(key)) { if (optional) { return std::numeric_limits::quiet_NaN(); } throw ParsingException(std::string("Missing \"") + key + "\" key"); } const json v = j[key]; if (!v.is_number()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a number"); } return v.get(); } static double getReqDouble(const json &j, const char *key) { return getDouble(j, key, false); } static double getOptDouble(const json &j, const char *key) { return getDouble(j, key, true); } // --------------------------------------------------------------------------- static json getObjectMember(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } const json obj = j[key]; if (!obj.is_object()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a object"); } return obj; } // --------------------------------------------------------------------------- static json getArrayMember(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } const json obj = j[key]; if (!obj.is_array()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a array"); } return obj; } // --------------------------------------------------------------------------- std::unique_ptr MasterFile::parse(const std::string &text) { std::unique_ptr dmmf(new MasterFile()); json j; try { j = json::parse(text); } catch (const std::exception &e) { throw ParsingException(e.what()); } if (!j.is_object()) { throw ParsingException("Not an object"); } dmmf->mFileType = getReqString(j, "file_type"); dmmf->mFormatVersion = getReqString(j, "format_version"); dmmf->mName = getOptString(j, "name"); dmmf->mVersion = getOptString(j, "version"); dmmf->mLicense = getOptString(j, "license"); dmmf->mDescription = getOptString(j, "description"); dmmf->mPublicationDate = getOptString(j, "publication_date"); if (j.contains("authority")) { const json jAuthority = j["authority"]; if (!jAuthority.is_object()) { throw ParsingException("authority is not a object"); } dmmf->mAuthority.name = getOptString(jAuthority, "name"); dmmf->mAuthority.url = getOptString(jAuthority, "url"); dmmf->mAuthority.address = getOptString(jAuthority, "address"); dmmf->mAuthority.email = getOptString(jAuthority, "email"); } if (j.contains("links")) { const json jLinks = j["links"]; if (!jLinks.is_array()) { throw ParsingException("links is not an array"); } for (const json &jLink : jLinks) { if (!jLink.is_object()) { throw ParsingException("links[] item is not an object"); } Link link; link.href = getOptString(jLink, "href"); link.rel = getOptString(jLink, "rel"); link.type = getOptString(jLink, "type"); link.title = getOptString(jLink, "title"); dmmf->mLinks.emplace_back(std::move(link)); } } dmmf->mSourceCRS = getReqString(j, "source_crs"); dmmf->mTargetCRS = getReqString(j, "target_crs"); dmmf->mDefinitionCRS = getReqString(j, "definition_crs"); if (dmmf->mSourceCRS != dmmf->mDefinitionCRS) { throw ParsingException( "source_crs != definition_crs not currently supported"); } dmmf->mReferenceEpoch = getOptString(j, "reference_epoch"); dmmf->mUncertaintyReferenceEpoch = getOptString(j, "uncertainty_reference_epoch"); dmmf->mHorizontalOffsetUnit = getOptString(j, "horizontal_offset_unit"); if (!dmmf->mHorizontalOffsetUnit.empty() && dmmf->mHorizontalOffsetUnit != STR_METRE && dmmf->mHorizontalOffsetUnit != STR_DEGREE) { throw ParsingException("Unsupported value for horizontal_offset_unit"); } dmmf->mVerticalOffsetUnit = getOptString(j, "vertical_offset_unit"); if (!dmmf->mVerticalOffsetUnit.empty() && dmmf->mVerticalOffsetUnit != STR_METRE) { throw ParsingException("Unsupported value for vertical_offset_unit"); } dmmf->mHorizontalUncertaintyType = getOptString(j, "horizontal_uncertainty_type"); dmmf->mHorizontalUncertaintyUnit = getOptString(j, "horizontal_uncertainty_unit"); dmmf->mVerticalUncertaintyType = getOptString(j, "vertical_uncertainty_type"); dmmf->mVerticalUncertaintyUnit = getOptString(j, "vertical_uncertainty_unit"); dmmf->mHorizontalOffsetMethod = getOptString(j, "horizontal_offset_method"); if (!dmmf->mHorizontalOffsetMethod.empty() && dmmf->mHorizontalOffsetMethod != STR_ADDITION && dmmf->mHorizontalOffsetMethod != STR_GEOCENTRIC) { throw ParsingException( "Unsupported value for horizontal_offset_method"); } dmmf->mSpatialExtent = SpatialExtent::parse(getObjectMember(j, "extent")); const json jTimeExtent = getObjectMember(j, "time_extent"); dmmf->mTimeExtent.first = Epoch(getReqString(jTimeExtent, "first")); dmmf->mTimeExtent.last = Epoch(getReqString(jTimeExtent, "last")); const json jComponents = getArrayMember(j, "components"); for (const json &jComponent : jComponents) { dmmf->mComponents.emplace_back(Component::parse(jComponent)); const auto &comp = dmmf->mComponents.back(); if (comp.displacementType() == STR_HORIZONTAL || comp.displacementType() == STR_3D) { if (dmmf->mHorizontalOffsetUnit.empty()) { throw ParsingException("horizontal_offset_unit should be " "defined as there is a component with " "displacement_type = horizontal/3d"); } if (dmmf->mHorizontalOffsetMethod.empty()) { throw ParsingException("horizontal_offset_method should be " "defined as there is a component with " "displacement_type = horizontal/3d"); } } if (comp.displacementType() == STR_VERTICAL || comp.displacementType() == STR_3D) { if (dmmf->mVerticalOffsetUnit.empty()) { throw ParsingException("vertical_offset_unit should be defined " "as there is a component with " "displacement_type = vertical/3d"); } } if (dmmf->mHorizontalOffsetUnit == STR_DEGREE && comp.spatialModel().interpolationMethod != STR_BILINEAR) { throw ParsingException("horizontal_offset_unit = degree can only " "be used with interpolation_method = " "bilinear"); } } if (dmmf->mHorizontalOffsetUnit == STR_DEGREE && dmmf->mHorizontalOffsetMethod != STR_ADDITION) { throw ParsingException("horizontal_offset_unit = degree can only be " "used with horizontal_offset_method = addition"); } return dmmf; } // --------------------------------------------------------------------------- SpatialExtent SpatialExtent::parse(const json &j) { SpatialExtent ex; const std::string type = getReqString(j, "type"); if (type != "bbox") { throw ParsingException("unsupported type of extent"); } const json jParameter = getObjectMember(j, "parameters"); const json jBbox = getArrayMember(jParameter, "bbox"); if (jBbox.size() != 4) { throw ParsingException("bbox is not an array of 4 numeric elements"); } for (int i = 0; i < 4; i++) { if (!jBbox[i].is_number()) { throw ParsingException( "bbox is not an array of 4 numeric elements"); } } ex.mMinx = jBbox[0].get(); ex.mMiny = jBbox[1].get(); ex.mMaxx = jBbox[2].get(); ex.mMaxy = jBbox[3].get(); ex.mMinxRad = DegToRad(ex.mMinx); ex.mMinyRad = DegToRad(ex.mMiny); ex.mMaxxRad = DegToRad(ex.mMaxx); ex.mMaxyRad = DegToRad(ex.mMaxy); return ex; } // --------------------------------------------------------------------------- Component Component::parse(const json &j) { Component comp; if (!j.is_object()) { throw ParsingException("component is not an object"); } comp.mDescription = getOptString(j, "description"); comp.mSpatialExtent = SpatialExtent::parse(getObjectMember(j, "extent")); comp.mDisplacementType = getReqString(j, "displacement_type"); if (comp.mDisplacementType != STR_NONE && comp.mDisplacementType != STR_HORIZONTAL && comp.mDisplacementType != STR_VERTICAL && comp.mDisplacementType != STR_3D) { throw ParsingException("Unsupported value for displacement_type"); } comp.mUncertaintyType = getReqString(j, "uncertainty_type"); comp.mHorizontalUncertainty = getOptDouble(j, "horizontal_uncertainty"); comp.mVerticalUncertainty = getOptDouble(j, "vertical_uncertainty"); const json jSpatialModel = getObjectMember(j, "spatial_model"); comp.mSpatialModel.type = getReqString(jSpatialModel, "type"); comp.mSpatialModel.interpolationMethod = getReqString(jSpatialModel, "interpolation_method"); if (comp.mSpatialModel.interpolationMethod != STR_BILINEAR && comp.mSpatialModel.interpolationMethod != STR_GEOCENTRIC_BILINEAR) { throw ParsingException("Unsupported value for interpolation_method"); } comp.mSpatialModel.filename = getReqString(jSpatialModel, "filename"); comp.mSpatialModel.md5Checksum = getOptString(jSpatialModel, "md5_checksum"); const json jTimeFunction = getObjectMember(j, "time_function"); std::string timeFunctionType = getReqString(jTimeFunction, "type"); const json jParameters = timeFunctionType == "constant" ? json() : getObjectMember(jTimeFunction, "parameters"); if (timeFunctionType == "constant") { std::unique_ptr tf(new ConstantTimeFunction()); tf->type = std::move(timeFunctionType); comp.mTimeFunction = std::move(tf); } else if (timeFunctionType == "velocity") { std::unique_ptr tf(new VelocityTimeFunction()); tf->type = std::move(timeFunctionType); tf->referenceEpoch = Epoch(getReqString(jParameters, "reference_epoch")); comp.mTimeFunction = std::move(tf); } else if (timeFunctionType == "step") { std::unique_ptr tf(new StepTimeFunction()); tf->type = std::move(timeFunctionType); tf->stepEpoch = Epoch(getReqString(jParameters, "step_epoch")); comp.mTimeFunction = std::move(tf); } else if (timeFunctionType == "reverse_step") { std::unique_ptr tf( new ReverseStepTimeFunction()); tf->type = std::move(timeFunctionType); tf->stepEpoch = Epoch(getReqString(jParameters, "step_epoch")); comp.mTimeFunction = std::move(tf); } else if (timeFunctionType == "piecewise") { std::unique_ptr tf(new PiecewiseTimeFunction()); tf->type = std::move(timeFunctionType); tf->beforeFirst = getReqString(jParameters, "before_first"); if (tf->beforeFirst != "zero" && tf->beforeFirst != "constant" && tf->beforeFirst != "linear") { throw ParsingException("Unsupported value for before_first"); } tf->afterLast = getReqString(jParameters, "after_last"); if (tf->afterLast != "zero" && tf->afterLast != "constant" && tf->afterLast != "linear") { throw ParsingException("Unsupported value for afterLast"); } const json jModel = getArrayMember(jParameters, "model"); for (const json &jModelElt : jModel) { if (!jModelElt.is_object()) { throw ParsingException("model[] element is not an object"); } PiecewiseTimeFunction::EpochScaleFactorTuple tuple; tuple.epoch = Epoch(getReqString(jModelElt, "epoch")); tuple.scaleFactor = getReqDouble(jModelElt, "scale_factor"); tf->model.emplace_back(std::move(tuple)); } comp.mTimeFunction = std::move(tf); } else if (timeFunctionType == "exponential") { std::unique_ptr tf( new ExponentialTimeFunction()); tf->type = std::move(timeFunctionType); tf->referenceEpoch = Epoch(getReqString(jParameters, "reference_epoch")); tf->endEpoch = Epoch(getOptString(jParameters, "end_epoch")); tf->relaxationConstant = getReqDouble(jParameters, "relaxation_constant"); if (tf->relaxationConstant <= 0.0) { throw ParsingException("Invalid value for relaxation_constant"); } tf->beforeScaleFactor = getReqDouble(jParameters, "before_scale_factor"); tf->initialScaleFactor = getReqDouble(jParameters, "initial_scale_factor"); tf->finalScaleFactor = getReqDouble(jParameters, "final_scale_factor"); comp.mTimeFunction = std::move(tf); } else { throw ParsingException("Unsupported type of time function: " + timeFunctionType); } return comp; } // --------------------------------------------------------------------------- double Component::ConstantTimeFunction::evaluateAt(double) const { return 1.0; } // --------------------------------------------------------------------------- double Component::VelocityTimeFunction::evaluateAt(double dt) const { return dt - referenceEpoch.toDecimalYear(); } // --------------------------------------------------------------------------- double Component::StepTimeFunction::evaluateAt(double dt) const { if (dt < stepEpoch.toDecimalYear()) return 0.0; return 1.0; } // --------------------------------------------------------------------------- double Component::ReverseStepTimeFunction::evaluateAt(double dt) const { if (dt < stepEpoch.toDecimalYear()) return -1.0; return 0.0; } // --------------------------------------------------------------------------- double Component::PiecewiseTimeFunction::evaluateAt(double dt) const { if (model.empty()) { return 0.0; } const double dt1 = model[0].epoch.toDecimalYear(); if (dt < dt1) { if (beforeFirst == "zero") return 0.0; if (beforeFirst == "constant" || model.size() == 1) return model[0].scaleFactor; // linear const double f1 = model[0].scaleFactor; const double dt2 = model[1].epoch.toDecimalYear(); const double f2 = model[1].scaleFactor; if (dt1 == dt2) return f1; return (f1 * (dt2 - dt) + f2 * (dt - dt1)) / (dt2 - dt1); } for (size_t i = 1; i < model.size(); i++) { const double dtip1 = model[i].epoch.toDecimalYear(); if (dt < dtip1) { const double dti = model[i - 1].epoch.toDecimalYear(); const double fip1 = model[i].scaleFactor; const double fi = model[i - 1].scaleFactor; return (fi * (dtip1 - dt) + fip1 * (dt - dti)) / (dtip1 - dti); } } if (afterLast == "zero") { return 0.0; } if (afterLast == "constant" || model.size() == 1) return model.back().scaleFactor; // linear const double dtnm1 = model[model.size() - 2].epoch.toDecimalYear(); const double fnm1 = model[model.size() - 2].scaleFactor; const double dtn = model.back().epoch.toDecimalYear(); const double fn = model.back().scaleFactor; if (dtnm1 == dtn) return fn; return (fnm1 * (dtn - dt) + fn * (dt - dtnm1)) / (dtn - dtnm1); } // --------------------------------------------------------------------------- double Component::ExponentialTimeFunction::evaluateAt(double dt) const { const double t0 = referenceEpoch.toDecimalYear(); if (dt < t0) return beforeScaleFactor; if (!endEpoch.toString().empty()) { dt = std::min(dt, endEpoch.toDecimalYear()); } return initialScaleFactor + (finalScaleFactor - initialScaleFactor) * (1.0 - std::exp(-(dt - t0) / relaxationConstant)); } // --------------------------------------------------------------------------- inline void DeltaEastingNorthingToLongLat(double cosphi, double de, double dn, double a, double b, double es, double &dlam, double &dphi) { const double oneMinuX = es * (1 - cosphi * cosphi); const double X = 1 - oneMinuX; const double sqrtX = sqrt(X); #if 0 // With es of Earth, absolute/relative error is at most 2e-8 // const double sqrtX = 1 - 0.5 * oneMinuX * (1 + 0.25 * oneMinuX); #endif dlam = de * sqrtX / (a * cosphi); dphi = dn * a * sqrtX * X / (b * b); } // --------------------------------------------------------------------------- template Evaluator::Evaluator( std::unique_ptr &&model, EvaluatorIface &iface, double a, double b) : mModel(std::move(model)), mA(a), mB(b), mEs(1 - (b * b) / (a * a)), mIsHorizontalUnitDegree(mModel->horizontalOffsetUnit() == STR_DEGREE), mIsAddition(mModel->horizontalOffsetMethod() == STR_ADDITION), mIsGeographicCRS(iface.isGeographicCRS(mModel->definitionCRS())) { if (!mIsGeographicCRS && mIsHorizontalUnitDegree) { throw EvaluatorException( "definition_crs = projected CRS and " "horizontal_offset_unit = degree are incompatible"); } if (!mIsGeographicCRS && !mIsAddition) { throw EvaluatorException( "definition_crs = projected CRS and " "horizontal_offset_method = geocentric are incompatible"); } mComponents.reserve(mModel->components().size()); for (const auto &comp : mModel->components()) { mComponents.emplace_back(std::unique_ptr>( new ComponentEx(comp))); if (!mIsGeographicCRS && !mComponents.back()->isBilinearInterpolation) { throw EvaluatorException( "definition_crs = projected CRS and " "interpolation_method = geocentric_bilinear are incompatible"); } } } // --------------------------------------------------------------------------- template void Evaluator::clearGridCache() { for (auto &comp : mComponents) { comp->clearGridCache(); } } // --------------------------------------------------------------------------- #ifdef DEBUG_DEFMODEL static std::string shortName(const Component &comp) { const auto &desc = comp.description(); return desc.substr(0, desc.find('\n')) + " (" + comp.spatialModel().filename + ")"; } static std::string toString(double val) { char buffer[32]; snprintf(buffer, sizeof(buffer), "%.9g", val); return buffer; } #endif // --------------------------------------------------------------------------- static bool bboxCheck(double &x, double &y, bool forInverseComputation, const double minx, const double miny, const double maxx, const double maxy, const double EPS, const double extraMarginForInverse) { if (x < minx - EPS || x > maxx + EPS || y < miny - EPS || y > maxy + EPS) { if (!forInverseComputation) { return false; } // In case of iterative computation for inverse, allow to be a // slightly bit outside of the grid and clamp to the edges bool xOk = false; if (x >= minx - EPS && x <= maxx + EPS) { xOk = true; } else if (x > minx - extraMarginForInverse && x < minx) { x = minx; xOk = true; } else if (x < maxx + extraMarginForInverse && x > maxx) { x = maxx; xOk = true; } bool yOk = false; if (y >= miny - EPS && y <= maxy + EPS) { yOk = true; } else if (y > miny - extraMarginForInverse && y < miny) { y = miny; yOk = true; } else if (y < maxy + extraMarginForInverse && y > maxy) { y = maxy; yOk = true; } return xOk && yOk; } return true; } // --------------------------------------------------------------------------- template bool Evaluator::forward( EvaluatorIface &iface, double x, double y, double z, double t, bool forInverseComputation, double &x_out, double &y_out, double &z_out) { x_out = x; y_out = y; z_out = z; const double EPS = mIsGeographicCRS ? 1e-10 : 1e-5; // Check against global model spatial extent, potentially wrapping // longitude to match { const auto &extent = mModel->extent(); const double minx = extent.minxNormalized(mIsGeographicCRS); const double maxx = extent.maxxNormalized(mIsGeographicCRS); if (mIsGeographicCRS) { while (x < minx - EPS) { x += 2.0 * DEFMODEL_PI; } while (x > maxx + EPS) { x -= 2.0 * DEFMODEL_PI; } } const double miny = extent.minyNormalized(mIsGeographicCRS); const double maxy = extent.maxyNormalized(mIsGeographicCRS); const double extraMarginForInverse = mIsGeographicCRS ? DegToRad(0.1) : 10000; if (!bboxCheck(x, y, forInverseComputation, minx, miny, maxx, maxy, EPS, extraMarginForInverse)) { #ifdef DEBUG_DEFMODEL iface.log("Calculation point " + toString(x) + "," + toString(y) + " is outside the extents of the deformation model"); #endif return false; } } // Check against global model temporal extent { const auto &timeExtent = mModel->timeExtent(); if (t < timeExtent.first.toDecimalYear() || t > timeExtent.last.toDecimalYear()) { #ifdef DEBUG_DEFMODEL iface.log("Calculation epoch " + toString(t) + " is not valid for the deformation model"); #endif return false; } } // For mIsHorizontalUnitDegree double dlam = 0; double dphi = 0; // For !mIsHorizontalUnitDegree double de = 0; double dn = 0; double dz = 0; bool sincosphiInitialized = false; double sinphi = 0; double cosphi = 0; for (auto &compEx : mComponents) { const auto &comp = compEx->component; if (compEx->displacementType == DisplacementType::NONE) { continue; } const auto &extent = comp.extent(); double xForGrid = x; double yForGrid = y; const double minx = extent.minxNormalized(mIsGeographicCRS); const double maxx = extent.maxxNormalized(mIsGeographicCRS); const double miny = extent.minyNormalized(mIsGeographicCRS); const double maxy = extent.maxyNormalized(mIsGeographicCRS); const double extraMarginForInverse = 0; if (!bboxCheck(xForGrid, yForGrid, forInverseComputation, minx, miny, maxx, maxy, EPS, extraMarginForInverse)) { #ifdef DEBUG_DEFMODEL iface.log( "Skipping component " + shortName(comp) + " due to point being outside of its declared spatial extent."); #endif continue; } xForGrid = std::max(xForGrid, minx); yForGrid = std::max(yForGrid, miny); xForGrid = std::min(xForGrid, maxx); yForGrid = std::min(yForGrid, maxy); const auto tfactor = compEx->evaluateAt(t); if (tfactor == 0.0) { #ifdef DEBUG_DEFMODEL iface.log("Skipping component " + shortName(comp) + " due to time function evaluating to 0."); #endif continue; } #ifdef DEBUG_DEFMODEL iface.log("Entering component " + shortName(comp) + " with time function evaluating to " + toString(tfactor) + "."); #endif if (compEx->gridSet == nullptr) { compEx->gridSet = iface.open(comp.spatialModel().filename); if (compEx->gridSet == nullptr) { return false; } } const Grid *grid = compEx->gridSet->gridAt(xForGrid, yForGrid); if (grid == nullptr) { #ifdef DEBUG_DEFMODEL iface.log("Skipping component " + shortName(comp) + " due to no grid found for this point in the grid set."); #endif continue; } if (grid->width < 2 || grid->height < 2) { return false; } const double ix_d = (xForGrid - grid->minx) / grid->resx; const double iy_d = (yForGrid - grid->miny) / grid->resy; if (ix_d < -EPS || iy_d < -EPS || ix_d + 1 >= grid->width + EPS || iy_d + 1 >= grid->height + EPS) { #ifdef DEBUG_DEFMODEL iface.log("Skipping component " + shortName(comp) + " due to point being outside of actual spatial extent of " "grid " + grid->name() + "."); #endif continue; } const int ix0 = std::min(static_cast(ix_d), grid->width - 2); const int iy0 = std::min(static_cast(iy_d), grid->height - 2); const int ix1 = ix0 + 1; const int iy1 = iy0 + 1; const double frct_x = ix_d - ix0; const double frct_y = iy_d - iy0; const double one_minus_frct_x = 1. - frct_x; const double one_minus_frct_y = 1. - frct_y; const double m00 = one_minus_frct_x * one_minus_frct_y; const double m10 = frct_x * one_minus_frct_y; const double m01 = one_minus_frct_x * frct_y; const double m11 = frct_x * frct_y; if (compEx->displacementType == DisplacementType::VERTICAL) { double dz00 = 0; double dz01 = 0; double dz10 = 0; double dz11 = 0; if (!grid->getZOffset(ix0, iy0, dz00) || !grid->getZOffset(ix1, iy0, dz10) || !grid->getZOffset(ix0, iy1, dz01) || !grid->getZOffset(ix1, iy1, dz11)) { return false; } const double dzInterp = dz00 * m00 + dz01 * m01 + dz10 * m10 + dz11 * m11; #ifdef DEBUG_DEFMODEL iface.log("tfactor * dzInterp = " + toString(tfactor) + " * " + toString(dzInterp) + "."); #endif dz += tfactor * dzInterp; } else if (mIsHorizontalUnitDegree) { double dx00 = 0; double dy00 = 0; double dx01 = 0; double dy01 = 0; double dx10 = 0; double dy10 = 0; double dx11 = 0; double dy11 = 0; if (compEx->displacementType == DisplacementType::HORIZONTAL) { if (!grid->getLongLatOffset(ix0, iy0, dx00, dy00) || !grid->getLongLatOffset(ix1, iy0, dx10, dy10) || !grid->getLongLatOffset(ix0, iy1, dx01, dy01) || !grid->getLongLatOffset(ix1, iy1, dx11, dy11)) { return false; } } else /* if (compEx->displacementType == DisplacementType::THREE_D) */ { double dz00 = 0; double dz01 = 0; double dz10 = 0; double dz11 = 0; if (!grid->getLongLatZOffset(ix0, iy0, dx00, dy00, dz00) || !grid->getLongLatZOffset(ix1, iy0, dx10, dy10, dz10) || !grid->getLongLatZOffset(ix0, iy1, dx01, dy01, dz01) || !grid->getLongLatZOffset(ix1, iy1, dx11, dy11, dz11)) { return false; } const double dzInterp = dz00 * m00 + dz01 * m01 + dz10 * m10 + dz11 * m11; #ifdef DEBUG_DEFMODEL iface.log("tfactor * dzInterp = " + toString(tfactor) + " * " + toString(dzInterp) + "."); #endif dz += tfactor * dzInterp; } const double dlamInterp = dx00 * m00 + dx01 * m01 + dx10 * m10 + dx11 * m11; const double dphiInterp = dy00 * m00 + dy01 * m01 + dy10 * m10 + dy11 * m11; #ifdef DEBUG_DEFMODEL iface.log("tfactor * dlamInterp = " + toString(tfactor) + " * " + toString(dlamInterp) + "."); iface.log("tfactor * dphiInterp = " + toString(tfactor) + " * " + toString(dphiInterp) + "."); #endif dlam += tfactor * dlamInterp; dphi += tfactor * dphiInterp; } else /* horizontal unit is metre */ { double de00 = 0; double dn00 = 0; double de01 = 0; double dn01 = 0; double de10 = 0; double dn10 = 0; double de11 = 0; double dn11 = 0; if (compEx->displacementType == DisplacementType::HORIZONTAL) { if (!grid->getEastingNorthingOffset(ix0, iy0, de00, dn00) || !grid->getEastingNorthingOffset(ix1, iy0, de10, dn10) || !grid->getEastingNorthingOffset(ix0, iy1, de01, dn01) || !grid->getEastingNorthingOffset(ix1, iy1, de11, dn11)) { return false; } } else /* if (compEx->displacementType == DisplacementType::THREE_D) */ { double dz00 = 0; double dz01 = 0; double dz10 = 0; double dz11 = 0; if (!grid->getEastingNorthingZOffset(ix0, iy0, de00, dn00, dz00) || !grid->getEastingNorthingZOffset(ix1, iy0, de10, dn10, dz10) || !grid->getEastingNorthingZOffset(ix0, iy1, de01, dn01, dz01) || !grid->getEastingNorthingZOffset(ix1, iy1, de11, dn11, dz11)) { return false; } const double dzInterp = dz00 * m00 + dz01 * m01 + dz10 * m10 + dz11 * m11; #ifdef DEBUG_DEFMODEL iface.log("tfactor * dzInterp = " + toString(tfactor) + " * " + toString(dzInterp) + "."); #endif dz += tfactor * dzInterp; } if (compEx->isBilinearInterpolation) { const double deInterp = de00 * m00 + de01 * m01 + de10 * m10 + de11 * m11; const double dnInterp = dn00 * m00 + dn01 * m01 + dn10 * m10 + dn11 * m11; #ifdef DEBUG_DEFMODEL iface.log("tfactor * deInterp = " + toString(tfactor) + " * " + toString(deInterp) + "."); iface.log("tfactor * dnInterp = " + toString(tfactor) + " * " + toString(dnInterp) + "."); #endif de += tfactor * deInterp; dn += tfactor * dnInterp; } else /* geocentric_bilinear */ { double dX; double dY; double dZ; auto iter = compEx->mapGrids.find(grid); if (iter == compEx->mapGrids.end()) { GridEx gridWithCache(grid); iter = compEx->mapGrids .insert(std::pair>( grid, std::move(gridWithCache))) .first; } GridEx &gridwithCacheRef = iter->second; gridwithCacheRef.getBilinearGeocentric( ix0, iy0, de00, dn00, de01, dn01, de10, dn10, de11, dn11, m00, m01, m10, m11, dX, dY, dZ); if (!sincosphiInitialized) { sincosphiInitialized = true; sinphi = sin(y); cosphi = cos(y); } const double lam_rel_to_cell_center = (frct_x - 0.5) * grid->resx; // Use small-angle approximation of sin/cos when reasonable // Max abs/rel error on cos is 3.9e-9 and on sin 1.3e-11 const double sinlam = gridwithCacheRef.smallResx ? lam_rel_to_cell_center * (1 - (1. / 6) * (lam_rel_to_cell_center * lam_rel_to_cell_center)) : sin(lam_rel_to_cell_center); const double coslam = gridwithCacheRef.smallResx ? (1 - 0.5 * (lam_rel_to_cell_center * lam_rel_to_cell_center)) : cos(lam_rel_to_cell_center); // Convert back from geocentric deltas to easting, northing // deltas const double deInterp = -dX * sinlam + dY * coslam; const double dnInterp = (-dX * coslam - dY * sinlam) * sinphi + dZ * cosphi; #ifdef DEBUG_DEFMODEL iface.log("After geocentric_bilinear interpolation: tfactor * " "deInterp = " + toString(tfactor) + " * " + toString(deInterp) + "."); iface.log("After geocentric_bilinear interpolation: tfactor * " "dnInterp = " + toString(tfactor) + " * " + toString(dnInterp) + "."); #endif de += tfactor * deInterp; dn += tfactor * dnInterp; } } } // Apply shifts depending on horizontal_offset_unit and // horizontal_offset_method if (mIsHorizontalUnitDegree) { x_out += dlam; y_out += dphi; } else { #ifdef DEBUG_DEFMODEL iface.log("Total sum of de: " + toString(de)); iface.log("Total sum of dn: " + toString(dn)); #endif if (mIsAddition && !mIsGeographicCRS) { x_out += de; y_out += dn; } else if (mIsAddition) { // Simple way of adding the offset if (!sincosphiInitialized) { cosphi = cos(y); } DeltaEastingNorthingToLongLat(cosphi, de, dn, mA, mB, mEs, dlam, dphi); #ifdef DEBUG_DEFMODEL iface.log("Result dlam: " + toString(dlam)); iface.log("Result dphi: " + toString(dphi)); #endif x_out += dlam; y_out += dphi; } else { // Geocentric way of adding the offset if (!sincosphiInitialized) { sinphi = sin(y); cosphi = cos(y); } const double sinlam = sin(x); const double coslam = cos(x); const double dnsinphi = dn * sinphi; const double dX = -de * sinlam - dnsinphi * coslam; const double dY = de * coslam - dnsinphi * sinlam; const double dZ = dn * cosphi; double X; double Y; double Z; iface.geographicToGeocentric(x, y, 0, mA, mB, mEs, X, Y, Z); #ifdef DEBUG_DEFMODEL iface.log("Geocentric coordinate before: " + toString(X) + "," + toString(Y) + "," + toString(Z)); iface.log("Geocentric shift: " + toString(dX) + "," + toString(dY) + "," + toString(dZ)); #endif X += dX; Y += dY; Z += dZ; #ifdef DEBUG_DEFMODEL iface.log("Geocentric coordinate after: " + toString(X) + "," + toString(Y) + "," + toString(Z)); #endif double h_out_ignored; iface.geocentricToGeographic(X, Y, Z, mA, mB, mEs, x_out, y_out, h_out_ignored); } } #ifdef DEBUG_DEFMODEL iface.log("Total sum of dz: " + toString(dz)); #endif z_out += dz; return true; } // --------------------------------------------------------------------------- template bool Evaluator::inverse( EvaluatorIface &iface, double x, double y, double z, double t, double &x_out, double &y_out, double &z_out) { x_out = x; y_out = y; z_out = z; constexpr double EPS_HORIZ = 1e-12; constexpr double EPS_VERT = 1e-3; constexpr bool forInverseComputation = true; for (int i = 0; i < 10; i++) { #ifdef DEBUG_DEFMODEL iface.log("Iteration " + std::to_string(i) + ": before forward: x=" + toString(x_out) + ", y=" + toString(y_out)); #endif double x_new; double y_new; double z_new; if (!forward(iface, x_out, y_out, z_out, t, forInverseComputation, x_new, y_new, z_new)) { return false; } #ifdef DEBUG_DEFMODEL iface.log("After forward: x=" + toString(x_new) + ", y=" + toString(y_new)); #endif const double dx = x_new - x; const double dy = y_new - y; const double dz = z_new - z; x_out -= dx; y_out -= dy; z_out -= dz; if (std::max(std::fabs(dx), std::fabs(dy)) < EPS_HORIZ && std::fabs(dz) < EPS_VERT) { return true; } } return false; } // --------------------------------------------------------------------------- } // namespace DEFORMATON_MODEL_NAMESPACE proj-9.8.1/src/transformations/helmert.cpp000664 001750 001750 00000064575 15166171715 020660 0ustar00eveneven000000 000000 /*********************************************************************** 3-, 4-and 7-parameter shifts, and their 6-, 8- and 14-parameter kinematic counterparts. Thomas Knudsen, 2016-05-24 ************************************************************************ Implements 3(6)-, 4(8) and 7(14)-parameter Helmert transformations for 3D data. Also incorporates Molodensky-Badekas variant of 7-parameter Helmert transformation, where the rotation is not applied regarding the centre of the spheroid, but given a reference point. Primarily useful for implementation of datum shifts in transformation pipelines. ************************************************************************ Thomas Knudsen, thokn@sdfe.dk, 2016-05-24/06-05 Kristian Evers, kreve@sdfe.dk, 2017-05-01 Even Rouault, even.rouault@spatialys.com Last update: 2018-10-26 ************************************************************************ * Copyright (c) 2016, Thomas Knudsen / SDFE * * 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 #include #include "proj_internal.h" PROJ_HEAD(helmert, "3(6)-, 4(8)- and 7(14)-parameter Helmert shift"); PROJ_HEAD(molobadekas, "Molodensky-Badekas transformation"); static PJ_XYZ helmert_forward_3d(PJ_LPZ lpz, PJ *P); static PJ_LPZ helmert_reverse_3d(PJ_XYZ xyz, PJ *P); /***********************************************************************/ namespace { // anonymous namespace struct pj_opaque_helmert { /************************************************************************ Projection specific elements for the "helmert" PJ object ************************************************************************/ PJ_XYZ xyz; PJ_XYZ xyz_0; PJ_XYZ dxyz; PJ_XYZ refp; PJ_OPK opk; PJ_OPK opk_0; PJ_OPK dopk; double scale; double scale_0; double dscale; double theta; double theta_0; double dtheta; double R[3][3]; double t_epoch, t_obs; int no_rotation, exact, fourparam; int is_position_vector; /* 1 = position_vector, 0 = coordinate_frame */ }; } // anonymous namespace /* Make the maths of the rotation operations somewhat more readable and textbook * like */ #define R00 (Q->R[0][0]) #define R01 (Q->R[0][1]) #define R02 (Q->R[0][2]) #define R10 (Q->R[1][0]) #define R11 (Q->R[1][1]) #define R12 (Q->R[1][2]) #define R20 (Q->R[2][0]) #define R21 (Q->R[2][1]) #define R22 (Q->R[2][2]) /**************************************************************************/ static void update_parameters(PJ *P) { /*************************************************************************** Update transformation parameters. --------------------------------- The 14-parameter Helmert transformation is at it's core the same as the 7-parameter transformation, since the transformation parameters are projected forward or backwards in time via the rate of changes of the parameters. The transformation parameters are calculated for a specific epoch before the actual Helmert transformation is carried out. The transformation parameters are updated with the following equation [0]: . P(t) = P(EPOCH) + P * (t - EPOCH) . where EPOCH is the epoch indicated in the above table and P is the rate of that parameter. [0] http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2008_ITRFs.txt *******************************************************************************/ struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; double dt = Q->t_obs - Q->t_epoch; Q->xyz.x = Q->xyz_0.x + Q->dxyz.x * dt; Q->xyz.y = Q->xyz_0.y + Q->dxyz.y * dt; Q->xyz.z = Q->xyz_0.z + Q->dxyz.z * dt; Q->opk.o = Q->opk_0.o + Q->dopk.o * dt; Q->opk.p = Q->opk_0.p + Q->dopk.p * dt; Q->opk.k = Q->opk_0.k + Q->dopk.k * dt; Q->scale = Q->scale_0 + Q->dscale * dt; Q->theta = Q->theta_0 + Q->dtheta * dt; /* debugging output */ if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { proj_log_trace(P, "Transformation parameters for observation " "t_obs=%g (t_epoch=%g):", Q->t_obs, Q->t_epoch); proj_log_trace(P, "x: %g", Q->xyz.x); proj_log_trace(P, "y: %g", Q->xyz.y); proj_log_trace(P, "z: %g", Q->xyz.z); proj_log_trace(P, "s: %g", Q->scale * 1e-6); proj_log_trace(P, "rx: %g", Q->opk.o); proj_log_trace(P, "ry: %g", Q->opk.p); proj_log_trace(P, "rz: %g", Q->opk.k); proj_log_trace(P, "theta: %g", Q->theta); } } /**************************************************************************/ static void build_rot_matrix(PJ *P) { /*************************************************************************** Build rotation matrix. ---------------------- Here we rename rotation indices from omega, phi, kappa (opk), to fi (i.e. phi), theta, psi (ftp), in order to reduce the mental agility needed to implement the expression for the rotation matrix derived over at https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions The relevant section is Euler angles ( z-’-x" intrinsic) -> Rotation matrix By default small angle approximations are used: The matrix elements are approximated by expanding the trigonometric functions to linear order (i.e. cos(x) = 1, sin(x) = x), and discarding products of second order. This was a useful hack when calculating by hand was the only option, but in general, today, should be avoided because: 1. It does not save much computation time, as the rotation matrix is built only once and probably used many times (except when transforming spatio-temporal coordinates). 2. The error induced may be too large for ultra high accuracy applications: the Earth is huge and the linear error is approximately the angular error multiplied by the Earth radius. However, in many cases the approximation is necessary, since it has been used historically: Rotation angles from older published datum shifts may actually be a least squares fit to the linearized rotation approximation, hence not being strictly valid for deriving the exact rotation matrix. In fact, most publicly available transformation parameters are based on the approximate Helmert transform, which is why we use that as the default setting, even though it is more correct to use the exact form of the equations. So in order to fit historically derived coordinates, the access to the approximate rotation matrix is necessary - at least in principle. Also, when using any published datum transformation information, one should always check which convention (exact or approximate rotation matrix) is expected, and whether the induced error for selecting the opposite convention is acceptable (which it often is). Sign conventions ---------------- Take care: Two different sign conventions exist for the rotation terms. Conceptually they relate to whether we rotate the coordinate system or the "position vector" (the vector going from the coordinate system origin to the point being transformed, i.e. the point coordinates interpreted as vector coordinates). Switching between the "position vector" and "coordinate system" conventions is simply a matter of switching the sign of the rotation angles, which algebraically also translates into a transposition of the rotation matrix. Hence, as geodetic constants should preferably be referred to exactly as published, the "convention" option provides the ability to switch between the conventions. ***************************************************************************/ struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; double f, t, p; /* phi/fi , theta, psi */ double cf, ct, cp; /* cos (fi, theta, psi) */ double sf, st, sp; /* sin (fi, theta, psi) */ /* rename (omega, phi, kappa) to (fi, theta, psi) */ f = Q->opk.o; t = Q->opk.p; p = Q->opk.k; /* Those equations are given assuming coordinate frame convention. */ /* For the position vector convention, we transpose the matrix just after. */ if (Q->exact) { cf = cos(f); sf = sin(f); ct = cos(t); st = sin(t); cp = cos(p); sp = sin(p); R00 = ct * cp; R01 = cf * sp + sf * st * cp; R02 = sf * sp - cf * st * cp; R10 = -ct * sp; R11 = cf * cp - sf * st * sp; R12 = sf * cp + cf * st * sp; R20 = st; R21 = -sf * ct; R22 = cf * ct; } else { R00 = 1; R01 = p; R02 = -t; R10 = -p; R11 = 1; R12 = f; R20 = t; R21 = -f; R22 = 1; } /* For comparison: Description from Engsager/Poder implementation in set_dtm_1.c (trlib) DATUM SHIFT: TO = scale * ROTZ * ROTY * ROTX * FROM + TRANSLA ( cz sz 0) (cy 0 -sy) (1 0 0) ROTZ=(-sz cz 0), ROTY=(0 1 0), ROTX=(0 cx sx) ( 0 0 1) (sy 0 cy) (0 -sx cx) trp->r11 = cos_ry*cos_rz; trp->r12 = cos_rx*sin_rz + sin_rx*sin_ry*cos_rz; trp->r13 = sin_rx*sin_rz - cos_rx*sin_ry*cos_rz; trp->r21 = -cos_ry*sin_rz; trp->r22 = cos_rx*cos_rz - sin_rx*sin_ry*sin_rz; trp->r23 = sin_rx*cos_rz + cos_rx*sin_ry*sin_rz; trp->r31 = sin_ry; trp->r32 = -sin_rx*cos_ry; trp->r33 = cos_rx*cos_ry; trp->scale = 1.0 + scale; */ if (Q->is_position_vector) { double r; r = R01; R01 = R10; R10 = r; r = R02; R02 = R20; R20 = r; r = R12; R12 = R21; R21 = r; } /* some debugging output */ if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { proj_log_trace(P, "Rotation Matrix:"); proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R00, R01, R02); proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R10, R11, R12); proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R20, R21, R22); } } /***********************************************************************/ static PJ_XY helmert_forward(PJ_LP lp, PJ *P) { /***********************************************************************/ struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; double x, y, cr, sr; point.lp = lp; cr = cos(Q->theta) * Q->scale; sr = sin(Q->theta) * Q->scale; x = point.xy.x; y = point.xy.y; point.xy.x = cr * x + sr * y + Q->xyz_0.x; point.xy.y = -sr * x + cr * y + Q->xyz_0.y; return point.xy; } /***********************************************************************/ static PJ_LP helmert_reverse(PJ_XY xy, PJ *P) { /***********************************************************************/ struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; double x, y, sr, cr; point.xy = xy; cr = cos(Q->theta) / Q->scale; sr = sin(Q->theta) / Q->scale; x = point.xy.x - Q->xyz_0.x; y = point.xy.y - Q->xyz_0.y; point.xy.x = x * cr - y * sr; point.xy.y = x * sr + y * cr; return point.lp; } /***********************************************************************/ static PJ_XYZ helmert_forward_3d(PJ_LPZ lpz, PJ *P) { /***********************************************************************/ struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; double X, Y, Z, scale; point.lpz = lpz; if (Q->fourparam) { const auto xy = helmert_forward(point.lp, P); point.xy = xy; return point.xyz; } if (Q->no_rotation && Q->scale == 0) { point.xyz.x = lpz.lam + Q->xyz.x; point.xyz.y = lpz.phi + Q->xyz.y; point.xyz.z = lpz.z + Q->xyz.z; return point.xyz; } scale = 1 + Q->scale * 1e-6; X = lpz.lam - Q->refp.x; Y = lpz.phi - Q->refp.y; Z = lpz.z - Q->refp.z; point.xyz.x = scale * (R00 * X + R01 * Y + R02 * Z); point.xyz.y = scale * (R10 * X + R11 * Y + R12 * Z); point.xyz.z = scale * (R20 * X + R21 * Y + R22 * Z); point.xyz.x += Q->xyz.x; /* for Molodensky-Badekas, Q->xyz already incorporates the Q->refp offset */ point.xyz.y += Q->xyz.y; point.xyz.z += Q->xyz.z; return point.xyz; } /***********************************************************************/ static PJ_LPZ helmert_reverse_3d(PJ_XYZ xyz, PJ *P) { /***********************************************************************/ struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; double X, Y, Z, scale; point.xyz = xyz; if (Q->fourparam) { const auto lp = helmert_reverse(point.xy, P); point.lp = lp; return point.lpz; } if (Q->no_rotation && Q->scale == 0) { point.xyz.x = xyz.x - Q->xyz.x; point.xyz.y = xyz.y - Q->xyz.y; point.xyz.z = xyz.z - Q->xyz.z; return point.lpz; } scale = 1 + Q->scale * 1e-6; /* Unscale and deoffset */ X = (xyz.x - Q->xyz.x) / scale; Y = (xyz.y - Q->xyz.y) / scale; Z = (xyz.z - Q->xyz.z) / scale; /* Inverse rotation through transpose multiplication */ point.xyz.x = (R00 * X + R10 * Y + R20 * Z) + Q->refp.x; point.xyz.y = (R01 * X + R11 * Y + R21 * Z) + Q->refp.y; point.xyz.z = (R02 * X + R12 * Y + R22 * Z) + Q->refp.z; return point.lpz; } static void helmert_forward_4d(PJ_COORD &point, PJ *P) { struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; /* We only need to rebuild the rotation matrix if the * observation time is different from the last call */ double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; if (t_obs != Q->t_obs) { Q->t_obs = t_obs; update_parameters(P); build_rot_matrix(P); } // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = helmert_forward_3d(point.lpz, P); point.xyz = xyz; } static void helmert_reverse_4d(PJ_COORD &point, PJ *P) { struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; /* We only need to rebuild the rotation matrix if the * observation time is different from the last call */ double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; if (t_obs != Q->t_obs) { Q->t_obs = t_obs; update_parameters(P); build_rot_matrix(P); } // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = helmert_reverse_3d(point.xyz, P); point.lpz = lpz; } /* Arcsecond to radians */ #define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) static PJ *init_helmert_six_parameters(PJ *P) { struct pj_opaque_helmert *Q = static_cast( calloc(1, sizeof(struct pj_opaque_helmert))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; /* In most cases, we work on 3D cartesian coordinates */ P->left = PJ_IO_UNITS_CARTESIAN; P->right = PJ_IO_UNITS_CARTESIAN; /* Translations */ if (pj_param(P->ctx, P->params, "tx").i) Q->xyz_0.x = pj_param(P->ctx, P->params, "dx").f; if (pj_param(P->ctx, P->params, "ty").i) Q->xyz_0.y = pj_param(P->ctx, P->params, "dy").f; if (pj_param(P->ctx, P->params, "tz").i) Q->xyz_0.z = pj_param(P->ctx, P->params, "dz").f; /* Rotations */ if (pj_param(P->ctx, P->params, "trx").i) Q->opk_0.o = pj_param(P->ctx, P->params, "drx").f * ARCSEC_TO_RAD; if (pj_param(P->ctx, P->params, "try").i) Q->opk_0.p = pj_param(P->ctx, P->params, "dry").f * ARCSEC_TO_RAD; if (pj_param(P->ctx, P->params, "trz").i) Q->opk_0.k = pj_param(P->ctx, P->params, "drz").f * ARCSEC_TO_RAD; /* Use small angle approximations? */ if (pj_param(P->ctx, P->params, "bexact").i) Q->exact = 1; return P; } static PJ *read_convention(PJ *P) { struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; /* In case there are rotational terms, we require an explicit convention * to be provided. */ if (!Q->no_rotation) { const char *convention = pj_param(P->ctx, P->params, "sconvention").s; if (!convention) { proj_log_error(P, _("helmert: missing 'convention' argument")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (strcmp(convention, "position_vector") == 0) { Q->is_position_vector = 1; } else if (strcmp(convention, "coordinate_frame") == 0) { Q->is_position_vector = 0; } else { proj_log_error( P, _("helmert: invalid value for 'convention' argument")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* historically towgs84 in PROJ has always been using position_vector * convention. Accepting coordinate_frame would be confusing. */ if (pj_param_exists(P->params, "towgs84")) { if (!Q->is_position_vector) { proj_log_error(P, _("helmert: towgs84 should only be used with " "convention=position_vector")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } } return P; } /***********************************************************************/ PJ *PJ_TRANSFORMATION(helmert, 0) { /***********************************************************************/ struct pj_opaque_helmert *Q; if (!init_helmert_six_parameters(P)) { return nullptr; } /* In the 2D case, the coordinates are projected */ if (pj_param_exists(P->params, "theta")) { P->left = PJ_IO_UNITS_PROJECTED; P->right = PJ_IO_UNITS_PROJECTED; P->fwd = helmert_forward; P->inv = helmert_reverse; } P->fwd4d = helmert_forward_4d; P->inv4d = helmert_reverse_4d; P->fwd3d = helmert_forward_3d; P->inv3d = helmert_reverse_3d; Q = (struct pj_opaque_helmert *)P->opaque; /* Detect obsolete transpose flag and error out if found */ if (pj_param(P->ctx, P->params, "ttranspose").i) { proj_log_error(P, _("helmert: 'transpose' argument is no longer valid. " "Use convention=position_vector/coordinate_frame")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* Support the classic PROJ towgs84 parameter, but allow later overrides.*/ /* Note that if towgs84 is specified, the datum_params array is set up */ /* for us automagically by the pj_datum_set call in pj_init_ctx */ if (pj_param_exists(P->params, "towgs84")) { Q->xyz_0.x = P->datum_params[0]; Q->xyz_0.y = P->datum_params[1]; Q->xyz_0.z = P->datum_params[2]; Q->opk_0.o = P->datum_params[3]; Q->opk_0.p = P->datum_params[4]; Q->opk_0.k = P->datum_params[5]; /* We must undo conversion to absolute scale from pj_datum_set */ if (0 == P->datum_params[6]) Q->scale_0 = 0; else Q->scale_0 = (P->datum_params[6] - 1) * 1e6; } if (pj_param(P->ctx, P->params, "ttheta").i) { Q->theta_0 = pj_param(P->ctx, P->params, "dtheta").f * ARCSEC_TO_RAD; Q->fourparam = 1; Q->scale_0 = 1.0; /* default scale for the 4-param shift */ } /* Scale */ if (pj_param(P->ctx, P->params, "ts").i) { Q->scale_0 = pj_param(P->ctx, P->params, "ds").f; if (Q->scale_0 <= -1.0e6) { proj_log_error(P, _("helmert: invalid value for s.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (pj_param(P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0) { proj_log_error(P, _("helmert: invalid value for s.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } /* Translation rates */ if (pj_param(P->ctx, P->params, "tdx").i) Q->dxyz.x = pj_param(P->ctx, P->params, "ddx").f; if (pj_param(P->ctx, P->params, "tdy").i) Q->dxyz.y = pj_param(P->ctx, P->params, "ddy").f; if (pj_param(P->ctx, P->params, "tdz").i) Q->dxyz.z = pj_param(P->ctx, P->params, "ddz").f; /* Rotations rates */ if (pj_param(P->ctx, P->params, "tdrx").i) Q->dopk.o = pj_param(P->ctx, P->params, "ddrx").f * ARCSEC_TO_RAD; if (pj_param(P->ctx, P->params, "tdry").i) Q->dopk.p = pj_param(P->ctx, P->params, "ddry").f * ARCSEC_TO_RAD; if (pj_param(P->ctx, P->params, "tdrz").i) Q->dopk.k = pj_param(P->ctx, P->params, "ddrz").f * ARCSEC_TO_RAD; if (pj_param(P->ctx, P->params, "tdtheta").i) Q->dtheta = pj_param(P->ctx, P->params, "ddtheta").f * ARCSEC_TO_RAD; /* Scale rate */ if (pj_param(P->ctx, P->params, "tds").i) Q->dscale = pj_param(P->ctx, P->params, "dds").f; /* Epoch */ if (pj_param(P->ctx, P->params, "tt_epoch").i) Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; Q->xyz = Q->xyz_0; Q->opk = Q->opk_0; Q->scale = Q->scale_0; Q->theta = Q->theta_0; if ((Q->opk.o == 0) && (Q->opk.p == 0) && (Q->opk.k == 0) && (Q->dopk.o == 0) && (Q->dopk.p == 0) && (Q->dopk.k == 0)) { Q->no_rotation = 1; } if (!read_convention(P)) { return nullptr; } /* Let's help with debugging */ if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { proj_log_trace(P, "Helmert parameters:"); proj_log_trace(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz.x, Q->xyz.y, Q->xyz.z); proj_log_trace(P, "rx= %8.5f ry= %8.5f rz= %8.5f", Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); proj_log_trace(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, Q->no_rotation ? "" : Q->is_position_vector ? " convention=position_vector" : " convention=coordinate_frame"); proj_log_trace(P, "dx= %8.5f dy= %8.5f dz= %8.5f", Q->dxyz.x, Q->dxyz.y, Q->dxyz.z); proj_log_trace(P, "drx=%8.5f dry=%8.5f drz=%8.5f", Q->dopk.o, Q->dopk.p, Q->dopk.k); proj_log_trace(P, "ds= %8.5f t_epoch=%8.5f", Q->dscale, Q->t_epoch); } update_parameters(P); build_rot_matrix(P); return P; } /***********************************************************************/ PJ *PJ_TRANSFORMATION(molobadekas, 0) { /***********************************************************************/ struct pj_opaque_helmert *Q; if (!init_helmert_six_parameters(P)) { return nullptr; } P->fwd3d = helmert_forward_3d; P->inv3d = helmert_reverse_3d; Q = (struct pj_opaque_helmert *)P->opaque; /* Scale */ if (pj_param(P->ctx, P->params, "ts").i) { Q->scale_0 = pj_param(P->ctx, P->params, "ds").f; } Q->opk = Q->opk_0; Q->scale = Q->scale_0; if (!read_convention(P)) { return nullptr; } /* Reference point */ if (pj_param(P->ctx, P->params, "tpx").i) Q->refp.x = pj_param(P->ctx, P->params, "dpx").f; if (pj_param(P->ctx, P->params, "tpy").i) Q->refp.y = pj_param(P->ctx, P->params, "dpy").f; if (pj_param(P->ctx, P->params, "tpz").i) Q->refp.z = pj_param(P->ctx, P->params, "dpz").f; /* Let's help with debugging */ if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { proj_log_trace(P, "Molodensky-Badekas parameters:"); proj_log_trace(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz_0.x, Q->xyz_0.y, Q->xyz_0.z); proj_log_trace(P, "rx= %8.5f ry= %8.5f rz= %8.5f", Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); proj_log_trace(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, Q->is_position_vector ? " convention=position_vector" : " convention=coordinate_frame"); proj_log_trace(P, "px= %8.5f py= %8.5f pz= %8.5f", Q->refp.x, Q->refp.y, Q->refp.z); } /* as an optimization, we incorporate the refp in the translation terms */ Q->xyz_0.x += Q->refp.x; Q->xyz_0.y += Q->refp.y; Q->xyz_0.z += Q->refp.z; Q->xyz = Q->xyz_0; build_rot_matrix(P); return P; } proj-9.8.1/src/transformations/deformation.cpp000664 001750 001750 00000034500 15166171715 021510 0ustar00eveneven000000 000000 /*********************************************************************** Kinematic datum shifting utilizing a deformation model Kristian Evers, 2017-10-29 ************************************************************************ Perform datum shifts by means of a deformation/velocity model. X_out = X_in + (T_obs - T_epoch) * DX Y_out = Y_in + (T_obs - T_epoch) * DY Z_out = Z_in + (T_obs - T_epoch) * DZ The deformation operation takes cartesian coordinates as input and returns cartesian coordinates as well. Corrections in the gridded model are in east, north, up (ENU) space. Hence the input coordinates need to be converted to ENU-space when searching for corrections in the grid. The corrections are then converted to cartesian PJ_XYZ-space and applied to the input coordinates (also in cartesian space). A full deformation model is preferably represented as a 3 channel Geodetic TIFF Grid, but was historically described by a set of two grids: One for the horizontal components and one for the vertical component. The east and north components are (were) stored using the CTable/CTable2 format, up component is (was) stored in the GTX format. Both grids are (were) expected to contain grid-values in units of mm/year in ENU-space. ************************************************************************ * Copyright (c) 2017, Kristian Evers * * 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 "grids.hpp" #include "proj.h" #include "proj_internal.h" #include #include #include PROJ_HEAD(deformation, "Kinematic grid shift"); #define TOL 1e-8 #define MAX_ITERATIONS 10 using namespace NS_PROJ; namespace { // anonymous namespace struct deformationData { double dt = 0; double t_epoch = 0; PJ *cart = nullptr; ListOfGenericGrids grids{}; ListOfHGrids hgrids{}; ListOfVGrids vgrids{}; }; } // anonymous namespace // --------------------------------------------------------------------------- static bool pj_deformation_get_grid_values(PJ *P, deformationData *Q, const PJ_LP &lp, double &vx, double &vy, double &vz) { GenericShiftGridSet *gridset = nullptr; auto grid = pj_find_generic_grid(Q->grids, lp, gridset); if (!grid) { return false; } if (grid->isNullGrid()) { vx = 0; vy = 0; vz = 0; return true; } const auto samplesPerPixel = grid->samplesPerPixel(); if (samplesPerPixel < 3) { proj_log_error(P, "grid has not enough samples"); return false; } int sampleE = 0; int sampleN = 1; int sampleU = 2; for (int i = 0; i < samplesPerPixel; i++) { const auto desc = grid->description(i); if (desc == "east_velocity") { sampleE = i; } else if (desc == "north_velocity") { sampleN = i; } else if (desc == "up_velocity") { sampleU = i; } } const auto unit = grid->unit(sampleE); if (!unit.empty() && unit != "millimetres per year") { proj_log_error(P, "Only unit=millimetres per year currently handled"); return false; } bool must_retry = false; if (!pj_bilinear_interpolation_three_samples(P->ctx, grid, lp, sampleE, sampleN, sampleU, vx, vy, vz, must_retry)) { if (must_retry) return pj_deformation_get_grid_values(P, Q, lp, vx, vy, vz); return false; } // divide by 1000 to get m/year vx /= 1000; vy /= 1000; vz /= 1000; return true; } /********************************************************************************/ static PJ_XYZ pj_deformation_get_grid_shift(PJ *P, const PJ_XYZ &cartesian) { /******************************************************************************** Read correction values from grid. The cartesian input coordinates are converted to geodetic coordinates in order look up the correction values in the grid. Once the grid corrections are read we need to convert them from ENU-space to cartesian PJ_XYZ-space. ENU -> PJ_XYZ formula described in: Nørbech, T., et al, 2003(?), "Transformation from a Common Nordic Reference Frame to ETRS89 in Denmark, Finland, Norway, and Sweden – status report" ********************************************************************************/ PJ_COORD geodetic, shift, temp; double sp, cp, sl, cl; int previous_errno = proj_errno_reset(P); auto Q = static_cast(P->opaque); /* cartesian to geodetic */ geodetic.lpz = pj_inv3d(cartesian, Q->cart); /* look up correction values in grids */ if (!Q->grids.empty()) { double vx = 0; double vy = 0; double vz = 0; if (!pj_deformation_get_grid_values(P, Q, geodetic.lp, vx, vy, vz)) { return proj_coord_error().xyz; } shift.xyz.x = vx; shift.xyz.y = vy; shift.xyz.z = vz; } else { shift.lp = pj_hgrid_value(P, Q->hgrids, geodetic.lp); shift.enu.u = pj_vgrid_value(P, Q->vgrids, geodetic.lp, 1.0); if (proj_errno(P) == PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID) proj_log_debug( P, "coordinate (%.3f, %.3f) outside deformation model", proj_todeg(geodetic.lpz.lam), proj_todeg(geodetic.lpz.phi)); /* grid values are stored as mm/yr, we need m/yr */ shift.xyz.x /= 1000; shift.xyz.y /= 1000; shift.xyz.z /= 1000; } /* pre-calc cosines and sines */ sp = sin(geodetic.lpz.phi); cp = cos(geodetic.lpz.phi); sl = sin(geodetic.lpz.lam); cl = cos(geodetic.lpz.lam); /* ENU -> PJ_XYZ */ temp.xyz.x = -sp * cl * shift.enu.n - sl * shift.enu.e + cp * cl * shift.enu.u; temp.xyz.y = -sp * sl * shift.enu.n + cl * shift.enu.e + cp * sl * shift.enu.u; temp.xyz.z = cp * shift.enu.n + sp * shift.enu.u; shift.xyz = temp.xyz; proj_errno_restore(P, previous_errno); return shift.xyz; } /********************************************************************************/ static PJ_XYZ pj_deformation_reverse_shift(PJ *P, const PJ_XYZ &input, double dt) { /******************************************************************************** Iteratively determine the reverse grid shift correction values. *********************************************************************************/ PJ_XYZ out, delta, dif; double z0; int i = MAX_ITERATIONS; delta = pj_deformation_get_grid_shift(P, input); if (delta.x == HUGE_VAL) { return delta; } /* Store the original z shift for later application */ z0 = delta.z; /* When iterating to find the best horizontal coordinate we also carry */ /* along the z-component, since we need it for the cartesian -> geodetic */ /* conversion. The z-component adjustment is overwritten with z0 after */ /* the loop has finished. */ out.x = input.x - dt * delta.x; out.y = input.y - dt * delta.y; out.z = input.z + dt * delta.z; do { delta = pj_deformation_get_grid_shift(P, out); if (delta.x == HUGE_VAL) break; dif.x = out.x + dt * delta.x - input.x; dif.y = out.y + dt * delta.y - input.y; dif.z = out.z - dt * delta.z - input.z; out.x += dif.x; out.y += dif.y; out.z += dif.z; } while (--i && hypot(dif.x, dif.y) > TOL); out.z = input.z - dt * z0; return out; } static PJ_XYZ pj_deformation_forward_3d(PJ_LPZ lpz, PJ *P) { struct deformationData *Q = (struct deformationData *)P->opaque; PJ_COORD out, in; PJ_XYZ shift; in.lpz = lpz; out = in; if (Q->dt == HUGE_VAL) { out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ proj_log_debug(P, "+dt must be specified"); return out.xyz; } shift = pj_deformation_get_grid_shift(P, in.xyz); if (shift.x == HUGE_VAL) { return shift; } out.xyz.x += Q->dt * shift.x; out.xyz.y += Q->dt * shift.y; out.xyz.z += Q->dt * shift.z; return out.xyz; } static void pj_deformation_forward_4d(PJ_COORD &coo, PJ *P) { struct deformationData *Q = (struct deformationData *)P->opaque; double dt; PJ_XYZ shift; if (Q->dt != HUGE_VAL) { dt = Q->dt; } else { if (coo.xyzt.t == HUGE_VAL) { coo = proj_coord_error(); proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); return; } dt = coo.xyzt.t - Q->t_epoch; } shift = pj_deformation_get_grid_shift(P, coo.xyz); coo.xyzt.x += dt * shift.x; coo.xyzt.y += dt * shift.y; coo.xyzt.z += dt * shift.z; } static PJ_LPZ pj_deformation_reverse_3d(PJ_XYZ in, PJ *P) { struct deformationData *Q = (struct deformationData *)P->opaque; PJ_COORD out; out.xyz = in; if (Q->dt == HUGE_VAL) { out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ proj_log_debug(P, "+dt must be specified"); return out.lpz; } out.xyz = pj_deformation_reverse_shift(P, in, Q->dt); return out.lpz; } static void pj_deformation_reverse_4d(PJ_COORD &coo, PJ *P) { struct deformationData *Q = (struct deformationData *)P->opaque; double dt; if (Q->dt != HUGE_VAL) { dt = Q->dt; } else { if (coo.xyzt.t == HUGE_VAL) { coo = proj_coord_error(); proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); return; } dt = coo.xyzt.t - Q->t_epoch; } coo.xyz = pj_deformation_reverse_shift(P, coo.xyz, dt); } static PJ *pj_deformation_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; auto Q = static_cast(P->opaque); if (Q) { if (Q->cart) Q->cart->destructor(Q->cart, errlev); delete Q; } P->opaque = nullptr; return pj_default_destructor(P, errlev); } PJ *PJ_TRANSFORMATION(deformation, 1) { auto Q = new deformationData; P->opaque = (void *)Q; P->destructor = pj_deformation_destructor; // Pass a dummy ellipsoid definition that will be overridden just afterwards Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart == nullptr) return pj_deformation_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def(P, Q->cart); int has_xy_grids = pj_param(P->ctx, P->params, "txy_grids").i; int has_z_grids = pj_param(P->ctx, P->params, "tz_grids").i; int has_grids = pj_param(P->ctx, P->params, "tgrids").i; /* Build gridlists. Both horizontal and vertical grids are mandatory. */ if (!has_grids && (!has_xy_grids || !has_z_grids)) { proj_log_error(P, _("Either +grids or (+xy_grids and +z_grids) should " "be specified.")); return pj_deformation_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (has_grids) { Q->grids = pj_generic_grid_init(P, "grids"); /* Was gridlist compiled properly? */ if (proj_errno(P)) { proj_log_error(P, _("could not find required grid(s).)")); return pj_deformation_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } } else { Q->hgrids = pj_hgrid_init(P, "xy_grids"); if (proj_errno(P)) { proj_log_error(P, _("could not find requested xy_grid(s).")); return pj_deformation_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } Q->vgrids = pj_vgrid_init(P, "z_grids"); if (proj_errno(P)) { proj_log_error(P, _("could not find requested z_grid(s).")); return pj_deformation_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } } Q->dt = HUGE_VAL; if (pj_param(P->ctx, P->params, "tdt").i) { Q->dt = pj_param(P->ctx, P->params, "ddt").f; } if (pj_param_exists(P->params, "t_obs")) { proj_log_error(P, _("+t_obs parameter is deprecated. Use +dt instead.")); return pj_deformation_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->t_epoch = HUGE_VAL; if (pj_param(P->ctx, P->params, "tt_epoch").i) { Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; } if (Q->dt == HUGE_VAL && Q->t_epoch == HUGE_VAL) { proj_log_error(P, _("either +dt or +t_epoch needs to be set.")); return pj_deformation_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (Q->dt != HUGE_VALL && Q->t_epoch != HUGE_VALL) { proj_log_error(P, _("+dt or +t_epoch are mutually exclusive.")); return pj_deformation_destructor( P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); } P->fwd4d = pj_deformation_forward_4d; P->inv4d = pj_deformation_reverse_4d; P->fwd3d = pj_deformation_forward_3d; P->inv3d = pj_deformation_reverse_3d; P->fwd = nullptr; P->inv = nullptr; P->left = PJ_IO_UNITS_CARTESIAN; P->right = PJ_IO_UNITS_CARTESIAN; return P; } #undef TOL #undef MAX_ITERATIONS proj-9.8.1/src/transformations/molodensky.cpp000664 001750 001750 00000025342 15166171715 021371 0ustar00eveneven000000 000000 /*********************************************************************** (Abridged) Molodensky Transform Kristian Evers, 2017-07-07 ************************************************************************ Implements the (abridged) Molodensky transformations for 2D and 3D data. Primarily useful for implementation of datum shifts in transformation pipelines. The code in this file is mostly based on The Standard and Abridged Molodensky Coordinate Transformation Formulae, 2004, R.E. Deakin, http://www.mygeodesy.id.au/documents/Molodensky%20V2.pdf ************************************************************************ * Copyright (c) 2017, Kristian Evers / SDFE * * 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 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(molodensky, "Molodensky transform"); static PJ_XYZ pj_molodensky_forward_3d(PJ_LPZ lpz, PJ *P); static PJ_LPZ pj_molodensky_reverse_3d(PJ_XYZ xyz, PJ *P); namespace { // anonymous namespace struct pj_opaque_molodensky { double dx; double dy; double dz; double da; double df; int abridged; }; } // anonymous namespace static double RN(double a, double es, double phi) { /********************************************************** N(phi) - prime vertical radius of curvature ------------------------------------------- This is basically the same function as in PJ_cart.c should probably be refactored into it's own file at some point. **********************************************************/ double s = sin(phi); if (es == 0) return a; return a / sqrt(1 - es * s * s); } static double RM(double a, double es, double phi) { /********************************************************** M(phi) - Meridian radius of curvature ------------------------------------- Source: E.J Krakiwsky & D.B. Thomson, 1974, GEODETIC POSITION COMPUTATIONS, Fredericton NB, Canada: University of New Brunswick, Department of Geodesy and Geomatics Engineering, Lecture Notes No. 39, 99 pp. http://www2.unb.ca/gge/Pubs/LN39.pdf **********************************************************/ double s = sin(phi); if (es == 0) return a; /* eq. 13a */ if (phi == 0) return a * (1 - es); /* eq. 13b */ if (fabs(phi) == M_PI_2) return a / sqrt(1 - es); /* eq. 13 */ return (a * (1 - es)) / pow(1 - es * s * s, 1.5); } static PJ_LPZ calc_standard_params(PJ_LPZ lpz, PJ *P) { struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *)P->opaque; double dphi, dlam, dh; /* sines and cosines */ double slam = sin(lpz.lam); double clam = cos(lpz.lam); double sphi = sin(lpz.phi); double cphi = cos(lpz.phi); /* ellipsoid parameters and differences */ double f = P->f, a = P->a; double dx = Q->dx, dy = Q->dy, dz = Q->dz; double da = Q->da, df = Q->df; /* ellipsoid radii of curvature */ double rho = RM(a, P->es, lpz.phi); double nu = RN(a, P->es, lpz.phi); /* delta phi */ dphi = (-dx * sphi * clam) - (dy * sphi * slam) + (dz * cphi) + ((nu * P->es * sphi * cphi * da) / a) + (sphi * cphi * (rho / (1 - f) + nu * (1 - f)) * df); const double dphi_denom = rho + lpz.z; if (dphi_denom == 0.0) { lpz.lam = HUGE_VAL; return lpz; } dphi /= dphi_denom; /* delta lambda */ const double dlam_denom = (nu + lpz.z) * cphi; if (dlam_denom == 0.0) { lpz.lam = HUGE_VAL; return lpz; } dlam = (-dx * slam + dy * clam) / dlam_denom; /* delta h */ dh = dx * cphi * clam + dy * cphi * slam + dz * sphi - (a / nu) * da + nu * (1 - f) * sphi * sphi * df; lpz.phi = dphi; lpz.lam = dlam; lpz.z = dh; return lpz; } static PJ_LPZ calc_abridged_params(PJ_LPZ lpz, PJ *P) { struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *)P->opaque; double dphi, dlam, dh; /* sines and cosines */ double slam = sin(lpz.lam); double clam = cos(lpz.lam); double sphi = sin(lpz.phi); double cphi = cos(lpz.phi); /* ellipsoid parameters and differences */ double dx = Q->dx, dy = Q->dy, dz = Q->dz; double da = Q->da, df = Q->df; double adffda = (P->a * df + P->f * da); /* delta phi */ dphi = -dx * sphi * clam - dy * sphi * slam + dz * cphi + adffda * sin(2 * lpz.phi); dphi /= RM(P->a, P->es, lpz.phi); /* delta lambda */ dlam = -dx * slam + dy * clam; const double dlam_denom = RN(P->a, P->es, lpz.phi) * cphi; if (dlam_denom == 0.0) { lpz.lam = HUGE_VAL; return lpz; } dlam /= dlam_denom; /* delta h */ dh = dx * cphi * clam + dy * cphi * slam + dz * sphi - da + adffda * sphi * sphi; /* offset coordinate */ lpz.phi = dphi; lpz.lam = dlam; lpz.z = dh; return lpz; } static PJ_XY pj_molodensky_forward_2d(PJ_LP lp, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lp = lp; // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = pj_molodensky_forward_3d(point.lpz, P); point.xyz = xyz; return point.xy; } static PJ_LP pj_molodensky_reverse_2d(PJ_XY xy, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xy = xy; point.xyz.z = 0; // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = pj_molodensky_reverse_3d(point.xyz, P); point.lpz = lpz; return point.lp; } static PJ_XYZ pj_molodensky_forward_3d(PJ_LPZ lpz, PJ *P) { struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; /* calculate parameters depending on the mode we are in */ if (Q->abridged) { lpz = calc_abridged_params(lpz, P); } else { lpz = calc_standard_params(lpz, P); } if (lpz.lam == HUGE_VAL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xyz; } /* offset coordinate */ point.lpz.phi += lpz.phi; point.lpz.lam += lpz.lam; point.lpz.z += lpz.z; return point.xyz; } static void pj_molodensky_forward_4d(PJ_COORD &obs, PJ *P) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = pj_molodensky_forward_3d(obs.lpz, P); obs.xyz = xyz; } static PJ_LPZ pj_molodensky_reverse_3d(PJ_XYZ xyz, PJ *P) { struct pj_opaque_molodensky *Q = (struct pj_opaque_molodensky *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; PJ_LPZ lpz; /* calculate parameters depending on the mode we are in */ point.xyz = xyz; if (Q->abridged) lpz = calc_abridged_params(point.lpz, P); else lpz = calc_standard_params(point.lpz, P); if (lpz.lam == HUGE_VAL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lpz; } /* offset coordinate */ point.lpz.phi -= lpz.phi; point.lpz.lam -= lpz.lam; point.lpz.z -= lpz.z; return point.lpz; } static void pj_molodensky_reverse_4d(PJ_COORD &obs, PJ *P) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = pj_molodensky_reverse_3d(obs.xyz, P); obs.lpz = lpz; } PJ *PJ_TRANSFORMATION(molodensky, 1) { struct pj_opaque_molodensky *Q = static_cast( calloc(1, sizeof(struct pj_opaque_molodensky))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; P->fwd4d = pj_molodensky_forward_4d; P->inv4d = pj_molodensky_reverse_4d; P->fwd3d = pj_molodensky_forward_3d; P->inv3d = pj_molodensky_reverse_3d; P->fwd = pj_molodensky_forward_2d; P->inv = pj_molodensky_reverse_2d; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; /* read args */ if (!pj_param(P->ctx, P->params, "tdx").i) { proj_log_error(P, _("missing dx")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->dx = pj_param(P->ctx, P->params, "ddx").f; if (!pj_param(P->ctx, P->params, "tdy").i) { proj_log_error(P, _("missing dy")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->dy = pj_param(P->ctx, P->params, "ddy").f; if (!pj_param(P->ctx, P->params, "tdz").i) { proj_log_error(P, _("missing dz")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->dz = pj_param(P->ctx, P->params, "ddz").f; if (!pj_param(P->ctx, P->params, "tda").i) { proj_log_error(P, _("missing da")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->da = pj_param(P->ctx, P->params, "dda").f; if (!pj_param(P->ctx, P->params, "tdf").i) { proj_log_error(P, _("missing df")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->df = pj_param(P->ctx, P->params, "ddf").f; Q->abridged = pj_param(P->ctx, P->params, "tabridged").i; return P; } proj-9.8.1/src/transformations/gridshift.cpp000664 001750 001750 00000113652 15166171715 021172 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Generic grid shifting, in particular Geographic 3D offsets * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2022, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include #include #include #include #include #include "grids.hpp" #include "proj/internal/internal.hpp" #include "proj_internal.h" #include #include #include #include #include PROJ_HEAD(gridshift, "Generic grid shift"); static std::mutex gMutex{}; // Map of (name, isProjected) static std::map gKnownGrids{}; using namespace NS_PROJ; namespace { // anonymous namespace struct IXY { int32_t x, y; inline bool operator!=(const IXY &other) const { return x != other.x || y != other.y; } }; struct GridInfo { int idxSampleX = -1; int idxSampleY = -1; int idxSampleZ = -1; bool eastingNorthingOffset = false; bool bilinearInterpolation = true; std::vector shifts{}; bool swapXYInRes = false; std::vector idxSampleXYZ{-1, -1, -1}; IXY lastIdxXY = IXY{-1, -1}; }; // --------------------------------------------------------------------------- struct gridshiftData { ListOfGenericGrids m_grids{}; bool m_defer_grid_opening = false; int m_error_code_in_defer_grid_opening = 0; bool m_bHasHorizontalOffset = false; bool m_bHasGeographic3DOffset = false; bool m_bHasEllipsoidalHeightOffset = false; bool m_bHasVerticalToVertical = false; bool m_bHasGeographicToVertical = false; bool m_mainGridTypeIsGeographic3DOffset = false; bool m_skip_z_transform = false; std::string m_mainGridType{}; std::string m_auxGridType{}; std::string m_interpolation{}; std::map m_cacheGridInfo{}; //! Offset in X to add in the forward direction, after the correction // has been applied. (and reciprocally to subtract in the inverse direction // before reading the grid). Used typically for the S-JTSK --> S-JTSK/05 // grid double m_offsetX = 0; //! Offset in Y to add in the forward direction, after the correction // has been applied. (and reciprocally to subtract in the inverse direction // before reading the grid). Used typically for the S-JTSK --> S-JTSK/05 // grid double m_offsetY = 0; bool checkGridTypes(PJ *P, bool &isProjectedCoord); bool loadGridsIfNeeded(PJ *P); const GenericShiftGrid *findGrid(const std::string &type, const PJ_XYZ &input, GenericShiftGridSet *&gridSetOut) const; PJ_XYZ grid_interpolate(PJ_CONTEXT *ctx, const std::string &type, PJ_XY xy, const GenericShiftGrid *grid, bool &biquadraticInterpolationOut); PJ_XYZ grid_apply_internal(PJ_CONTEXT *ctx, const std::string &type, bool isVerticalOnly, const PJ_XYZ in, PJ_DIRECTION direction, const GenericShiftGrid *grid, GenericShiftGridSet *gridset, bool &shouldRetry); PJ_XYZ apply(PJ *P, PJ_DIRECTION dir, PJ_XYZ xyz); }; // --------------------------------------------------------------------------- bool gridshiftData::checkGridTypes(PJ *P, bool &isProjectedCoord) { std::string offsetX, offsetY; int gridCount = 0; isProjectedCoord = false; for (const auto &gridset : m_grids) { for (const auto &grid : gridset->grids()) { ++gridCount; const auto &type = grid->metadataItem("TYPE"); if (type == "HORIZONTAL_OFFSET") { m_bHasHorizontalOffset = true; if (offsetX.empty()) { offsetX = grid->metadataItem("constant_offset", 0); } if (offsetY.empty()) { offsetY = grid->metadataItem("constant_offset", 1); } } else if (type == "GEOGRAPHIC_3D_OFFSET") m_bHasGeographic3DOffset = true; else if (type == "ELLIPSOIDAL_HEIGHT_OFFSET") m_bHasEllipsoidalHeightOffset = true; else if (type == "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL") m_bHasVerticalToVertical = true; else if (type == "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL") m_bHasGeographicToVertical = true; else if (type.empty()) { proj_log_error(P, _("Missing TYPE metadata item in grid(s).")); return false; } else { proj_log_error( P, _("Unhandled value for TYPE metadata item in grid(s).")); return false; } isProjectedCoord = !grid->extentAndRes().isGeographic; } } if (!offsetX.empty() || !offsetY.empty()) { if (gridCount > 1) { // Makes life easier... proj_log_error(P, _("Shift offset found in one grid. Only one grid " "with shift offset is supported at a time.")); return false; } try { m_offsetX = NS_PROJ::internal::c_locale_stod(offsetX); } catch (const std::exception &) { proj_log_error(P, _("Invalid offset value")); return false; } try { m_offsetY = NS_PROJ::internal::c_locale_stod(offsetY); } catch (const std::exception &) { proj_log_error(P, _("Invalid offset value")); return false; } } if (((m_bHasEllipsoidalHeightOffset ? 1 : 0) + (m_bHasVerticalToVertical ? 1 : 0) + (m_bHasGeographicToVertical ? 1 : 0)) > 1) { proj_log_error(P, _("Unsupported mix of grid types.")); return false; } if (m_bHasGeographic3DOffset) { m_mainGridTypeIsGeographic3DOffset = true; m_mainGridType = "GEOGRAPHIC_3D_OFFSET"; } else if (!m_bHasHorizontalOffset) { if (m_bHasEllipsoidalHeightOffset) m_mainGridType = "ELLIPSOIDAL_HEIGHT_OFFSET"; else if (m_bHasGeographicToVertical) m_mainGridType = "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL"; else { assert(m_bHasVerticalToVertical); m_mainGridType = "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL"; } } else { assert(m_bHasHorizontalOffset); m_mainGridType = "HORIZONTAL_OFFSET"; } if (m_bHasHorizontalOffset) { if (m_bHasEllipsoidalHeightOffset) m_auxGridType = "ELLIPSOIDAL_HEIGHT_OFFSET"; else if (m_bHasGeographicToVertical) m_auxGridType = "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL"; else if (m_bHasVerticalToVertical) { m_auxGridType = "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL"; } } return true; } // --------------------------------------------------------------------------- const GenericShiftGrid * gridshiftData::findGrid(const std::string &type, const PJ_XYZ &input, GenericShiftGridSet *&gridSetOut) const { for (const auto &gridset : m_grids) { auto grid = gridset->gridAt(type, input.x, input.y); if (grid) { gridSetOut = gridset.get(); return grid; } } return nullptr; } // --------------------------------------------------------------------------- #define REL_TOLERANCE_HGRIDSHIFT 1e-5 PJ_XYZ gridshiftData::grid_interpolate(PJ_CONTEXT *ctx, const std::string &type, PJ_XY xy, const GenericShiftGrid *grid, bool &biquadraticInterpolationOut) { PJ_XYZ val; val.x = val.y = HUGE_VAL; val.z = 0; const bool isProjectedCoord = !grid->extentAndRes().isGeographic; auto iterCache = m_cacheGridInfo.find(grid); if (iterCache == m_cacheGridInfo.end()) { bool eastingNorthingOffset = false; const auto samplesPerPixel = grid->samplesPerPixel(); int idxSampleY = -1; int idxSampleX = -1; int idxSampleZ = -1; for (int i = 0; i < samplesPerPixel; i++) { const auto desc = grid->description(i); if (!isProjectedCoord && desc == "latitude_offset") { idxSampleY = i; const auto unit = grid->unit(idxSampleY); if (!unit.empty() && unit != "arc-second") { pj_log(ctx, PJ_LOG_ERROR, "gridshift: Only unit=arc-second currently handled"); return val; } } else if (!isProjectedCoord && desc == "longitude_offset") { idxSampleX = i; const auto unit = grid->unit(idxSampleX); if (!unit.empty() && unit != "arc-second") { pj_log(ctx, PJ_LOG_ERROR, "gridshift: Only unit=arc-second currently handled"); return val; } } else if (isProjectedCoord && desc == "easting_offset") { eastingNorthingOffset = true; idxSampleX = i; const auto unit = grid->unit(idxSampleX); if (!unit.empty() && unit != "metre") { pj_log(ctx, PJ_LOG_ERROR, "gridshift: Only unit=metre currently handled"); return val; } } else if (isProjectedCoord && desc == "northing_offset") { eastingNorthingOffset = true; idxSampleY = i; const auto unit = grid->unit(idxSampleY); if (!unit.empty() && unit != "metre") { pj_log(ctx, PJ_LOG_ERROR, "gridshift: Only unit=metre currently handled"); return val; } } else if (desc == "ellipsoidal_height_offset" || desc == "geoid_undulation" || desc == "hydroid_height" || desc == "vertical_offset") { idxSampleZ = i; const auto unit = grid->unit(idxSampleZ); if (!unit.empty() && unit != "metre") { pj_log(ctx, PJ_LOG_ERROR, "gridshift: Only unit=metre currently handled"); return val; } } } if (samplesPerPixel >= 2 && idxSampleY < 0 && idxSampleX < 0 && type == "HORIZONTAL_OFFSET") { if (isProjectedCoord) { eastingNorthingOffset = true; idxSampleX = 0; idxSampleY = 1; } else { // X=longitude assumed to be the second component if metadata // lacking idxSampleX = 1; // Y=latitude assumed to be the first component if metadata // lacking idxSampleY = 0; } } if (type == "HORIZONTAL_OFFSET" || type == "GEOGRAPHIC_3D_OFFSET") { if (idxSampleY < 0 || idxSampleX < 0) { pj_log(ctx, PJ_LOG_ERROR, "gridshift: grid has not expected samples"); return val; } } if (type == "ELLIPSOIDAL_HEIGHT_OFFSET" || type == "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL" || type == "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL" || type == "GEOGRAPHIC_3D_OFFSET") { if (idxSampleZ < 0) { pj_log(ctx, PJ_LOG_ERROR, "gridshift: grid has not expected samples"); return val; } } std::string interpolation(m_interpolation); if (interpolation.empty()) interpolation = grid->metadataItem("interpolation_method"); if (interpolation.empty()) interpolation = "bilinear"; if (interpolation != "bilinear" && interpolation != "biquadratic") { pj_log(ctx, PJ_LOG_ERROR, "gridshift: Unsupported interpolation_method in grid"); return val; } GridInfo gridInfo; gridInfo.idxSampleX = idxSampleX; gridInfo.idxSampleY = idxSampleY; gridInfo.idxSampleZ = m_skip_z_transform ? -1 : idxSampleZ; gridInfo.eastingNorthingOffset = eastingNorthingOffset; gridInfo.bilinearInterpolation = (interpolation == "bilinear" || grid->width() < 3 || grid->height() < 3); gridInfo.shifts.resize(3 * 3 * 3); if (idxSampleX == 1 && idxSampleY == 0) { // Little optimization for the common of grids storing shifts in // latitude, longitude, in that order. // We want to request data in the order it is stored in the grid, // which triggers a read optimization. // But we must compensate for that by switching the role of x and y // after computation. gridInfo.swapXYInRes = true; gridInfo.idxSampleXYZ[0] = 0; gridInfo.idxSampleXYZ[1] = 1; } else { gridInfo.idxSampleXYZ[0] = idxSampleX; gridInfo.idxSampleXYZ[1] = idxSampleY; } gridInfo.idxSampleXYZ[2] = idxSampleZ; iterCache = m_cacheGridInfo.emplace(grid, std::move(gridInfo)).first; } // cppcheck-suppress derefInvalidIteratorRedundantCheck GridInfo &gridInfo = iterCache->second; const int idxSampleX = gridInfo.idxSampleX; const int idxSampleY = gridInfo.idxSampleY; const int idxSampleZ = gridInfo.idxSampleZ; const bool bilinearInterpolation = gridInfo.bilinearInterpolation; biquadraticInterpolationOut = !bilinearInterpolation; IXY indxy; const auto &extent = grid->extentAndRes(); double x = (xy.x - extent.west) / extent.resX; indxy.x = std::isnan(x) ? 0 : (int32_t)lround(floor(x)); double y = (xy.y - extent.south) / extent.resY; indxy.y = std::isnan(y) ? 0 : (int32_t)lround(floor(y)); PJ_XY frct; frct.x = x - indxy.x; frct.y = y - indxy.y; int tmpInt; if (indxy.x < 0) { if (indxy.x == -1 && frct.x > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) { ++indxy.x; frct.x = 0.; } else return val; } else if ((tmpInt = indxy.x + 1) >= grid->width()) { if (tmpInt == grid->width() && frct.x < 10 * REL_TOLERANCE_HGRIDSHIFT) { --indxy.x; frct.x = 1.; } else return val; } if (indxy.y < 0) { if (indxy.y == -1 && frct.y > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) { ++indxy.y; frct.y = 0.; } else return val; } else if ((tmpInt = indxy.y + 1) >= grid->height()) { if (tmpInt == grid->height() && frct.y < 10 * REL_TOLERANCE_HGRIDSHIFT) { --indxy.y; frct.y = 1.; } else return val; } bool nodataFound = false; if (bilinearInterpolation) { double m10 = frct.x; double m11 = m10; double m01 = 1. - frct.x; double m00 = m01; m11 *= frct.y; m01 *= frct.y; frct.y = 1. - frct.y; m00 *= frct.y; m10 *= frct.y; if (idxSampleX >= 0 && idxSampleY >= 0) { if (gridInfo.lastIdxXY != indxy) { if (!grid->valuesAt(indxy.x, indxy.y, 2, 2, idxSampleZ >= 0 ? 3 : 2, gridInfo.idxSampleXYZ.data(), gridInfo.shifts.data(), nodataFound) || nodataFound) { return val; } gridInfo.lastIdxXY = indxy; } if (idxSampleZ >= 0) { val.x = (m00 * gridInfo.shifts[0] + m10 * gridInfo.shifts[3] + m01 * gridInfo.shifts[6] + m11 * gridInfo.shifts[9]); val.y = (m00 * gridInfo.shifts[1] + m10 * gridInfo.shifts[4] + m01 * gridInfo.shifts[7] + m11 * gridInfo.shifts[10]); val.z = m00 * gridInfo.shifts[2] + m10 * gridInfo.shifts[5] + m01 * gridInfo.shifts[8] + m11 * gridInfo.shifts[11]; } else { val.x = (m00 * gridInfo.shifts[0] + m10 * gridInfo.shifts[2] + m01 * gridInfo.shifts[4] + m11 * gridInfo.shifts[6]); val.y = (m00 * gridInfo.shifts[1] + m10 * gridInfo.shifts[3] + m01 * gridInfo.shifts[5] + m11 * gridInfo.shifts[7]); } } else { val.x = 0; val.y = 0; if (idxSampleZ >= 0) { if (gridInfo.lastIdxXY != indxy) { if (!grid->valuesAt(indxy.x, indxy.y, 2, 2, 1, &idxSampleZ, gridInfo.shifts.data(), nodataFound) || nodataFound) { return val; } gridInfo.lastIdxXY = indxy; } val.z = m00 * gridInfo.shifts[0] + m10 * gridInfo.shifts[1] + m01 * gridInfo.shifts[2] + m11 * gridInfo.shifts[3]; } } } else // biquadratic { // Cf https://geodesy.noaa.gov/library/pdfs/NOAA_TM_NOS_NGS_0084.pdf // Depending if we are before or after half-pixel, shift the 3x3 window // of interpolation if ((frct.x <= 0.5 && indxy.x > 0) || (indxy.x + 2 == grid->width())) { indxy.x -= 1; frct.x += 1; } if ((frct.y <= 0.5 && indxy.y > 0) || (indxy.y + 2 == grid->height())) { indxy.y -= 1; frct.y += 1; } // Port of qterp() Fortran function from NOAA // xToInterp must be in [0,2] range // f0 must be f(0), f1 must be f(1), f2 must be f(2) // Returns f(xToInterp) interpolated value along the parabolic function const auto quadraticInterpol = [](double xToInterp, double f0, double f1, double f2) { const double df0 = f1 - f0; const double df1 = f2 - f1; const double d2f0 = df1 - df0; return f0 + xToInterp * df0 + 0.5 * xToInterp * (xToInterp - 1.0) * d2f0; }; if (idxSampleX >= 0 && idxSampleY >= 0) { if (gridInfo.lastIdxXY != indxy) { if (!grid->valuesAt(indxy.x, indxy.y, 3, 3, idxSampleZ >= 0 ? 3 : 2, gridInfo.idxSampleXYZ.data(), gridInfo.shifts.data(), nodataFound) || nodataFound) { return val; } gridInfo.lastIdxXY = indxy; } const auto *shifts_ptr = gridInfo.shifts.data(); if (idxSampleZ >= 0) { double xyz_shift[3][4]; for (int j = 0; j <= 2; ++j) { xyz_shift[j][0] = quadraticInterpol( frct.x, shifts_ptr[0], shifts_ptr[3], shifts_ptr[6]); xyz_shift[j][1] = quadraticInterpol( frct.x, shifts_ptr[1], shifts_ptr[4], shifts_ptr[7]); xyz_shift[j][2] = quadraticInterpol( frct.x, shifts_ptr[2], shifts_ptr[5], shifts_ptr[8]); shifts_ptr += 9; } val.x = quadraticInterpol(frct.y, xyz_shift[0][0], xyz_shift[1][0], xyz_shift[2][0]); val.y = quadraticInterpol(frct.y, xyz_shift[0][1], xyz_shift[1][1], xyz_shift[2][1]); val.z = quadraticInterpol(frct.y, xyz_shift[0][2], xyz_shift[1][2], xyz_shift[2][2]); } else { double xy_shift[3][2]; for (int j = 0; j <= 2; ++j) { xy_shift[j][0] = quadraticInterpol( frct.x, shifts_ptr[0], shifts_ptr[2], shifts_ptr[4]); xy_shift[j][1] = quadraticInterpol( frct.x, shifts_ptr[1], shifts_ptr[3], shifts_ptr[5]); shifts_ptr += 6; } val.x = quadraticInterpol(frct.y, xy_shift[0][0], xy_shift[1][0], xy_shift[2][0]); val.y = quadraticInterpol(frct.y, xy_shift[0][1], xy_shift[1][1], xy_shift[2][1]); } } else { val.x = 0; val.y = 0; if (idxSampleZ >= 0) { if (gridInfo.lastIdxXY != indxy) { if (!grid->valuesAt(indxy.x, indxy.y, 3, 3, 1, &idxSampleZ, gridInfo.shifts.data(), nodataFound) || nodataFound) { return val; } gridInfo.lastIdxXY = indxy; } double z_shift[3]; const auto *shifts_ptr = gridInfo.shifts.data(); for (int j = 0; j <= 2; ++j) { z_shift[j] = quadraticInterpol( frct.x, shifts_ptr[0], shifts_ptr[1], shifts_ptr[2]); shifts_ptr += 3; } val.z = quadraticInterpol(frct.y, z_shift[0], z_shift[1], z_shift[2]); } } } if (idxSampleX >= 0 && idxSampleY >= 0 && !gridInfo.eastingNorthingOffset) { constexpr double convFactorXY = 1. / 3600 / 180 * M_PI; val.x *= convFactorXY; val.y *= convFactorXY; } if (gridInfo.swapXYInRes) { std::swap(val.x, val.y); } return val; } // --------------------------------------------------------------------------- static PJ_XY normalizeX(const GenericShiftGrid *grid, const PJ_XYZ in, const NS_PROJ::ExtentAndRes *&extentOut) { PJ_XY normalized; normalized.x = in.x; normalized.y = in.y; extentOut = &(grid->extentAndRes()); if (extentOut->isGeographic) { const double epsilon = (extentOut->resX + extentOut->resY) * REL_TOLERANCE_HGRIDSHIFT; if (normalized.x < extentOut->west - epsilon) normalized.x += 2 * M_PI; else if (normalized.x > extentOut->east + epsilon) normalized.x -= 2 * M_PI; } return normalized; } // --------------------------------------------------------------------------- #define MAX_ITERATIONS 10 #define TOL 1e-12 PJ_XYZ gridshiftData::grid_apply_internal( PJ_CONTEXT *ctx, const std::string &type, bool isVerticalOnly, const PJ_XYZ in, PJ_DIRECTION direction, const GenericShiftGrid *grid, GenericShiftGridSet *gridset, bool &shouldRetry) { shouldRetry = false; if (in.x == HUGE_VAL) return in; /* normalized longitude of input */ const NS_PROJ::ExtentAndRes *extent; PJ_XY normalized_in = normalizeX(grid, in, extent); bool biquadraticInterpolationOut = false; PJ_XYZ shift = grid_interpolate(ctx, type, normalized_in, grid, biquadraticInterpolationOut); if (grid->hasChanged()) { shouldRetry = gridset->reopen(ctx); PJ_XYZ out; out.x = out.y = out.z = HUGE_VAL; return out; } if (shift.x == HUGE_VAL) return shift; if (direction == PJ_FWD) { PJ_XYZ out = in; out.x += shift.x; out.y += shift.y; out.z += shift.z; return out; } if (isVerticalOnly) { PJ_XYZ out = in; out.z -= shift.z; return out; } PJ_XY guess; guess.x = normalized_in.x - shift.x; guess.y = normalized_in.y - shift.y; // NOAA NCAT transformer tool doesn't do iteration in the reverse path. // Do the same (only for biquadratic, although NCAT applies this logic to // bilinear too) // Cf // https://github.com/noaa-ngs/ncat-lib/blob/77bcff1ce4a78fe06d0312102ada008aefcc2c62/src/gov/noaa/ngs/grid/Transformer.java#L374 // When trying to do iterative reverse path with biquadratic, we can // get convergence failures on points that are close to the boundary of // cells or half-cells. For example with // echo -122.4250009683 37.8286740788 0 | bin/cct -I +proj=gridshift // +grids=tests/us_noaa_nadcon5_nad83_1986_nad83_harn_conus_extract_sanfrancisco.tif // +interpolation=biquadratic if (!biquadraticInterpolationOut) { int i = MAX_ITERATIONS; const double toltol = TOL * TOL; PJ_XY diff; do { shift = grid_interpolate(ctx, type, guess, grid, biquadraticInterpolationOut); if (grid->hasChanged()) { shouldRetry = gridset->reopen(ctx); PJ_XYZ out; out.x = out.y = out.z = HUGE_VAL; return out; } /* We can possibly go outside of the initial guessed grid, so try */ /* to fetch a new grid into which iterate... */ if (shift.x == HUGE_VAL) { PJ_XYZ lp; lp.x = guess.x; lp.y = guess.y; auto newGrid = findGrid(type, lp, gridset); if (newGrid == nullptr || newGrid == grid || newGrid->isNullGrid()) break; pj_log(ctx, PJ_LOG_TRACE, "Switching from grid %s to grid %s", grid->name().c_str(), newGrid->name().c_str()); grid = newGrid; normalized_in = normalizeX(grid, in, extent); diff.x = std::numeric_limits::max(); diff.y = std::numeric_limits::max(); continue; } diff.x = guess.x + shift.x - normalized_in.x; diff.y = guess.y + shift.y - normalized_in.y; guess.x -= diff.x; guess.y -= diff.y; } while (--i && (diff.x * diff.x + diff.y * diff.y > toltol)); /* prob. slightly faster than hypot() */ if (i == 0) { pj_log(ctx, PJ_LOG_TRACE, "Inverse grid shift iterator failed to converge."); proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_NO_CONVERGENCE); PJ_XYZ out; out.x = out.y = out.z = HUGE_VAL; return out; } if (shift.x == HUGE_VAL) { pj_log( ctx, PJ_LOG_TRACE, "Inverse grid shift iteration failed, presumably at grid edge. " "Using first approximation."); } } PJ_XYZ out; out.x = extent->isGeographic ? adjlon(guess.x) : guess.x; out.y = guess.y; out.z = in.z - shift.z; return out; } // --------------------------------------------------------------------------- bool gridshiftData::loadGridsIfNeeded(PJ *P) { if (m_error_code_in_defer_grid_opening) { proj_errno_set(P, m_error_code_in_defer_grid_opening); return false; } else if (m_defer_grid_opening) { m_defer_grid_opening = false; m_grids = pj_generic_grid_init(P, "grids"); m_error_code_in_defer_grid_opening = proj_errno(P); if (m_error_code_in_defer_grid_opening) { return false; } bool isProjectedCoord; if (!checkGridTypes(P, isProjectedCoord)) { return false; } } return true; } // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- static const std::string sHORIZONTAL_OFFSET("HORIZONTAL_OFFSET"); PJ_XYZ gridshiftData::apply(PJ *P, PJ_DIRECTION direction, PJ_XYZ xyz) { PJ_XYZ out; out.x = HUGE_VAL; out.y = HUGE_VAL; out.z = HUGE_VAL; std::string &type = m_mainGridType; bool bFoundGeog3DOffset = false; while (true) { GenericShiftGridSet *gridset = nullptr; const GenericShiftGrid *grid = findGrid(type, xyz, gridset); if (!grid) { if (m_mainGridTypeIsGeographic3DOffset && m_bHasHorizontalOffset) { // If we have a mix of grids with GEOGRAPHIC_3D_OFFSET // and HORIZONTAL_OFFSET+ELLIPSOIDAL_HEIGHT_OFFSET type = sHORIZONTAL_OFFSET; grid = findGrid(type, xyz, gridset); } if (!grid) { proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } } else { if (m_mainGridTypeIsGeographic3DOffset) bFoundGeog3DOffset = true; } if (grid->isNullGrid()) { out = xyz; break; } bool shouldRetry = false; out = grid_apply_internal( P->ctx, type, !(m_bHasGeographic3DOffset || m_bHasHorizontalOffset), xyz, direction, grid, gridset, shouldRetry); if (!shouldRetry) { break; } } if (out.x == HUGE_VAL || out.y == HUGE_VAL) { if (proj_context_errno(P->ctx) == 0) { proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); } return out; } // Second pass to apply vertical transformation, if it is in a // separate grid than lat-lon offsets. if (!bFoundGeog3DOffset && !m_auxGridType.empty()) { xyz = out; while (true) { GenericShiftGridSet *gridset = nullptr; const auto grid = findGrid(m_auxGridType, xyz, gridset); if (!grid) { proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } if (grid->isNullGrid()) { break; } bool shouldRetry = false; out = grid_apply_internal(P->ctx, m_auxGridType, true, xyz, direction, grid, gridset, shouldRetry); if (!shouldRetry) { break; } } if (out.x == HUGE_VAL || out.y == HUGE_VAL) { proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); return out; } } return out; } } // anonymous namespace // --------------------------------------------------------------------------- static PJ_XYZ pj_gridshift_forward_3d(PJ_LPZ lpz, PJ *P) { auto Q = static_cast(P->opaque); if (!Q->loadGridsIfNeeded(P)) { return proj_coord_error().xyz; } PJ_XYZ xyz; xyz.x = lpz.lam; xyz.y = lpz.phi; xyz.z = lpz.z; xyz = Q->apply(P, PJ_FWD, xyz); xyz.x += Q->m_offsetX; xyz.y += Q->m_offsetY; return xyz; } // --------------------------------------------------------------------------- static PJ_LPZ pj_gridshift_reverse_3d(PJ_XYZ xyz, PJ *P) { auto Q = static_cast(P->opaque); // Must be done before using m_offsetX ! if (!Q->loadGridsIfNeeded(P)) { return proj_coord_error().lpz; } xyz.x -= Q->m_offsetX; xyz.y -= Q->m_offsetY; PJ_XYZ xyz_out = Q->apply(P, PJ_INV, xyz); PJ_LPZ lpz; lpz.lam = xyz_out.x; lpz.phi = xyz_out.y; lpz.z = xyz_out.z; return lpz; } // --------------------------------------------------------------------------- static PJ *pj_gridshift_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; delete static_cast(P->opaque); P->opaque = nullptr; return pj_default_destructor(P, errlev); } // --------------------------------------------------------------------------- static void pj_gridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) { auto Q = (struct gridshiftData *)P->opaque; for (auto &grid : Q->m_grids) { grid->reassign_context(ctx); } } // --------------------------------------------------------------------------- PJ *PJ_TRANSFORMATION(gridshift, 0) { auto Q = new gridshiftData; P->opaque = (void *)Q; P->destructor = pj_gridshift_destructor; P->reassign_context = pj_gridshift_reassign_context; P->fwd3d = pj_gridshift_forward_3d; P->inv3d = pj_gridshift_reverse_3d; P->fwd = nullptr; P->inv = nullptr; if (0 == pj_param(P->ctx, P->params, "tgrids").i) { proj_log_error(P, _("+grids parameter missing.")); return pj_gridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } bool isKnownGrid = false; bool isProjectedCoord = false; if (!P->ctx->defer_grid_opening || !pj_param(P->ctx, P->params, "tcoord_type").i) { const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; gMutex.lock(); const auto iter = gKnownGrids.find(gridnames); isKnownGrid = iter != gKnownGrids.end(); if (isKnownGrid) { Q->m_defer_grid_opening = true; isProjectedCoord = iter->second; } gMutex.unlock(); } if (P->ctx->defer_grid_opening || isKnownGrid) { Q->m_defer_grid_opening = true; } else { const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; Q->m_grids = pj_generic_grid_init(P, "grids"); /* Was gridlist compiled properly? */ if (proj_errno(P)) { proj_log_error(P, _("could not find required grid(s).")); return pj_gridshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } if (!Q->checkGridTypes(P, isProjectedCoord)) { return pj_gridshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } gMutex.lock(); gKnownGrids[gridnames] = isProjectedCoord; gMutex.unlock(); } if (pj_param(P->ctx, P->params, "tinterpolation").i) { const char *interpolation = pj_param(P->ctx, P->params, "sinterpolation").s; if (strcmp(interpolation, "bilinear") == 0 || strcmp(interpolation, "biquadratic") == 0) { Q->m_interpolation = interpolation; } else { proj_log_error(P, _("Unsupported value for +interpolation.")); return pj_gridshift_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (pj_param(P->ctx, P->params, "tno_z_transform").i) { Q->m_skip_z_transform = true; } // +coord_type not advertised in documentation on purpose for now. // It is probably useless to do it, as the only potential use case of it // would be for PROJ itself when generating pipelines with deferred grid // opening. if (pj_param(P->ctx, P->params, "tcoord_type").i) { // Check the coordinate type (projected/geographic) from the explicit // +coord_type switch. This is mostly only useful in deferred grid // opening, otherwise we have figured it out above in checkGridTypes() const char *coord_type = pj_param(P->ctx, P->params, "scoord_type").s; if (coord_type) { if (strcmp(coord_type, "projected") == 0) { if (!P->ctx->defer_grid_opening && !isProjectedCoord) { proj_log_error(P, _("+coord_type=projected specified, but the " "grid is known to not be projected")); return pj_gridshift_destructor( P, PROJ_ERR_INVALID_OP_MISSING_ARG); } isProjectedCoord = true; } else if (strcmp(coord_type, "geographic") == 0) { if (!P->ctx->defer_grid_opening && isProjectedCoord) { proj_log_error(P, _("+coord_type=geographic specified, but " "the grid is known to be projected")); return pj_gridshift_destructor( P, PROJ_ERR_INVALID_OP_MISSING_ARG); } } else { proj_log_error(P, _("Unsupported value for +coord_type: valid " "values are 'geographic' or 'projected'")); return pj_gridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } } } if (isKnownGrid || pj_param(P->ctx, P->params, "tcoord_type").i) { if (isProjectedCoord) { P->left = PJ_IO_UNITS_PROJECTED; P->right = PJ_IO_UNITS_PROJECTED; } else { P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; } } else { P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; } return P; } // --------------------------------------------------------------------------- void pj_clear_gridshift_knowngrids_cache() { std::lock_guard lock(gMutex); gKnownGrids.clear(); } proj-9.8.1/src/transformations/affine.cpp000664 001750 001750 00000017541 15166171715 020437 0ustar00eveneven000000 000000 /************************************************************************ * Copyright (c) 2018, Even Rouault * * 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 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(affine, "Affine transformation"); PROJ_HEAD(geogoffset, "Geographic Offset"); namespace { // anonymous namespace struct pj_affine_coeffs { double s11; double s12; double s13; double s21; double s22; double s23; double s31; double s32; double s33; double tscale; }; } // anonymous namespace namespace { // anonymous namespace struct pj_opaque_affine { double xoff; double yoff; double zoff; double toff; struct pj_affine_coeffs forward; struct pj_affine_coeffs reverse; }; } // anonymous namespace static void forward_4d(PJ_COORD &coo, PJ *P) { const struct pj_opaque_affine *Q = (const struct pj_opaque_affine *)P->opaque; const struct pj_affine_coeffs *C = &(Q->forward); const double x = coo.xyz.x; const double y = coo.xyz.y; const double z = coo.xyz.z; coo.xyzt.x = Q->xoff + C->s11 * x + C->s12 * y + C->s13 * z; coo.xyzt.y = Q->yoff + C->s21 * x + C->s22 * y + C->s23 * z; coo.xyzt.z = Q->zoff + C->s31 * x + C->s32 * y + C->s33 * z; coo.xyzt.t = Q->toff + C->tscale * coo.xyzt.t; } static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; forward_4d(point, P); return point.xyz; } static PJ_XY forward_2d(PJ_LP lp, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lp = lp; forward_4d(point, P); return point.xy; } static void reverse_4d(PJ_COORD &coo, PJ *P) { const struct pj_opaque_affine *Q = (const struct pj_opaque_affine *)P->opaque; const struct pj_affine_coeffs *C = &(Q->reverse); double x = coo.xyzt.x - Q->xoff; double y = coo.xyzt.y - Q->yoff; double z = coo.xyzt.z - Q->zoff; coo.xyzt.x = C->s11 * x + C->s12 * y + C->s13 * z; coo.xyzt.y = C->s21 * x + C->s22 * y + C->s23 * z; coo.xyzt.z = C->s31 * x + C->s32 * y + C->s33 * z; coo.xyzt.t = C->tscale * (coo.xyzt.t - Q->toff); } static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; reverse_4d(point, P); return point.lpz; } static PJ_LP reverse_2d(PJ_XY xy, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xy = xy; reverse_4d(point, P); return point.lp; } static struct pj_opaque_affine *initQ() { struct pj_opaque_affine *Q = static_cast( calloc(1, sizeof(struct pj_opaque_affine))); if (nullptr == Q) return nullptr; /* default values */ Q->forward.s11 = 1.0; Q->forward.s22 = 1.0; Q->forward.s33 = 1.0; Q->forward.tscale = 1.0; Q->reverse.s11 = 1.0; Q->reverse.s22 = 1.0; Q->reverse.s33 = 1.0; Q->reverse.tscale = 1.0; return Q; } static void computeReverseParameters(PJ *P) { struct pj_opaque_affine *Q = (struct pj_opaque_affine *)P->opaque; /* cf * https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices */ const double a = Q->forward.s11; const double b = Q->forward.s12; const double c = Q->forward.s13; const double d = Q->forward.s21; const double e = Q->forward.s22; const double f = Q->forward.s23; const double g = Q->forward.s31; const double h = Q->forward.s32; const double i = Q->forward.s33; const double A = e * i - f * h; const double B = -(d * i - f * g); const double C = (d * h - e * g); const double D = -(b * i - c * h); const double E = (a * i - c * g); const double F = -(a * h - b * g); const double G = b * f - c * e; const double H = -(a * f - c * d); const double I = a * e - b * d; const double det = a * A + b * B + c * C; if (det == 0.0 || Q->forward.tscale == 0.0) { if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { proj_log_debug(P, "matrix non invertible"); } P->inv4d = nullptr; P->inv3d = nullptr; P->inv = nullptr; } else { Q->reverse.s11 = A / det; Q->reverse.s12 = D / det; Q->reverse.s13 = G / det; Q->reverse.s21 = B / det; Q->reverse.s22 = E / det; Q->reverse.s23 = H / det; Q->reverse.s31 = C / det; Q->reverse.s32 = F / det; Q->reverse.s33 = I / det; Q->reverse.tscale = 1.0 / Q->forward.tscale; } } PJ *PJ_TRANSFORMATION(affine, 0 /* no need for ellipsoid */) { struct pj_opaque_affine *Q = initQ(); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; P->fwd4d = forward_4d; P->inv4d = reverse_4d; P->fwd3d = forward_3d; P->inv3d = reverse_3d; P->fwd = forward_2d; P->inv = reverse_2d; P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; /* read args */ Q->xoff = pj_param(P->ctx, P->params, "dxoff").f; Q->yoff = pj_param(P->ctx, P->params, "dyoff").f; Q->zoff = pj_param(P->ctx, P->params, "dzoff").f; Q->toff = pj_param(P->ctx, P->params, "dtoff").f; if (pj_param(P->ctx, P->params, "ts11").i) { Q->forward.s11 = pj_param(P->ctx, P->params, "ds11").f; } Q->forward.s12 = pj_param(P->ctx, P->params, "ds12").f; Q->forward.s13 = pj_param(P->ctx, P->params, "ds13").f; Q->forward.s21 = pj_param(P->ctx, P->params, "ds21").f; if (pj_param(P->ctx, P->params, "ts22").i) { Q->forward.s22 = pj_param(P->ctx, P->params, "ds22").f; } Q->forward.s23 = pj_param(P->ctx, P->params, "ds23").f; Q->forward.s31 = pj_param(P->ctx, P->params, "ds31").f; Q->forward.s32 = pj_param(P->ctx, P->params, "ds32").f; if (pj_param(P->ctx, P->params, "ts33").i) { Q->forward.s33 = pj_param(P->ctx, P->params, "ds33").f; } if (pj_param(P->ctx, P->params, "ttscale").i) { Q->forward.tscale = pj_param(P->ctx, P->params, "dtscale").f; } computeReverseParameters(P); return P; } /* Arcsecond to radians */ #define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) PJ *PJ_TRANSFORMATION(geogoffset, 0 /* no need for ellipsoid */) { struct pj_opaque_affine *Q = initQ(); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; P->fwd4d = forward_4d; P->inv4d = reverse_4d; P->fwd3d = forward_3d; P->inv3d = reverse_3d; P->fwd = forward_2d; P->inv = reverse_2d; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; /* read args */ Q->xoff = pj_param(P->ctx, P->params, "ddlon").f * ARCSEC_TO_RAD; Q->yoff = pj_param(P->ctx, P->params, "ddlat").f * ARCSEC_TO_RAD; Q->zoff = pj_param(P->ctx, P->params, "ddh").f; return P; } proj-9.8.1/src/transformations/horner.cpp000664 001750 001750 00000060411 15166171715 020476 0ustar00eveneven000000 000000 /*********************************************************************** Interfacing to a classic piece of geodetic software ************************************************************************ gen_pol is a highly efficient, classic implementation of a generic 2D Horner's Scheme polynomial evaluation routine by Knud Poder and Karsten Engsager, originating in the vivid geodetic environment at what was then (1960-ish) the Danish Geodetic Institute. The original Poder/Engsager gen_pol implementation (where the polynomial degree and two sets of polynomial coefficients are packed together in one compound array, handled via a plain double pointer) is compelling and "true to the code history": It has a beautiful classical 1960s ring to it, not unlike the original fft implementations, which revolutionized spectral analysis in twenty lines of code. The Poder coding sound, as classic 1960s as Phil Spector's Wall of Sound, is beautiful and inimitable. On the other hand: For the uninitiated, the gen_pol code is hard to follow, despite being compact. Also, since adding metadata and improving maintainability of the code are among the implied goals of a current SDFE/DTU Space project, the material in this file introduces a version with a more modern (or at least 1990s) look, introducing a "double 2D polynomial" data type, HORNER. Despite introducing a new data type for handling the polynomial coefficients, great care has been taken to keep the coefficient array organization identical to that of gen_pol. Hence, on one hand, the HORNER data type helps improving the long term maintainability of the code by making the data organization more mentally accessible. On the other hand, it allows us to preserve the business end of the original gen_pol implementation - although not including the famous "Poder dual autocheck" in all its enigmatic elegance. ********************************************************************** The material included here was written by Knud Poder, starting around 1960, and Karsten Engsager, starting around 1970. It was originally written in Algol 60, later (1980s) reimplemented in C. The HORNER data type interface, and the organization as a header library was implemented by Thomas Knudsen, starting around 2015. *********************************************************************** * * Copyright (c) 2016, SDFE http://www.sdfe.dk / Thomas Knudsen / Karsten Engsager * * 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 #include #include #include #include #include #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(horner, "Horner polynomial evaluation"); /* make horner.h interface with proj's memory management */ #define horner_dealloc(x) free(x) #define horner_calloc(n, x) calloc(n, x) namespace { // anonymous namespace struct horner { int uneg; /* u axis negated? */ int vneg; /* v axis negated? */ uint32_t order; /* maximum degree of polynomium */ double range; /* radius of the region of validity */ bool has_inv; /* inv parameters are specified */ double inverse_tolerance; /* in the units of the destination coords, specifies when to stop iterating if !has_inv and direction is reverse */ double *fwd_u; /* coefficients for the forward transformations */ double *fwd_v; /* i.e. latitude/longitude to northing/easting */ double *inv_u; /* coefficients for the inverse transformations */ double *inv_v; /* i.e. northing/easting to latitude/longitude */ double *fwd_c; /* coefficients for the complex forward transformations */ double *inv_c; /* coefficients for the complex inverse transformations */ PJ_UV *fwd_origin; /* False longitude/latitude */ PJ_UV *inv_origin; /* False easting/northing */ }; } // anonymous namespace typedef struct horner HORNER; /* e.g. degree = 2: a + bx + cy + dxx + eyy + fxy, i.e. 6 coefficients */ constexpr uint32_t horner_number_of_real_coefficients(uint32_t order) { return (order + 1) * (order + 2) / 2; } constexpr uint32_t horner_number_of_complex_coefficients(uint32_t order) { return 2 * order + 2; } static void horner_free(HORNER *h) { horner_dealloc(h->inv_v); horner_dealloc(h->inv_u); horner_dealloc(h->fwd_v); horner_dealloc(h->fwd_u); horner_dealloc(h->fwd_c); horner_dealloc(h->inv_c); horner_dealloc(h->fwd_origin); horner_dealloc(h->inv_origin); horner_dealloc(h); } static HORNER *horner_alloc(uint32_t order, bool complex_polynomia) { /* uint32_t is unsigned, so we need not check for order > 0 */ bool polynomia_ok = false; HORNER *h = static_cast(horner_calloc(1, sizeof(HORNER))); if (nullptr == h) return nullptr; uint32_t n = complex_polynomia ? horner_number_of_complex_coefficients(order) : horner_number_of_real_coefficients(order); h->order = order; if (complex_polynomia) { h->fwd_c = static_cast(horner_calloc(n, sizeof(double))); h->inv_c = static_cast(horner_calloc(n, sizeof(double))); if (h->fwd_c && h->inv_c) polynomia_ok = true; } else { h->fwd_u = static_cast(horner_calloc(n, sizeof(double))); h->fwd_v = static_cast(horner_calloc(n, sizeof(double))); h->inv_u = static_cast(horner_calloc(n, sizeof(double))); h->inv_v = static_cast(horner_calloc(n, sizeof(double))); if (h->fwd_u && h->fwd_v && h->inv_u && h->inv_v) polynomia_ok = true; } h->fwd_origin = static_cast(horner_calloc(1, sizeof(PJ_UV))); h->inv_origin = static_cast(horner_calloc(1, sizeof(PJ_UV))); if (polynomia_ok && h->fwd_origin && h->inv_origin) return h; /* safe, since all pointers are null-initialized (by calloc) */ horner_free(h); return nullptr; } inline static PJ_UV double_real_horner_eval(uint32_t order, const double *cx, const double *cy, PJ_UV en, uint32_t order_offset = 0) { /* The melody of this block is straight out of the great Engsager/Poder songbook. For numerical stability, the summation is carried out backwards, summing the tiny high order elements first. Double Horner's scheme: N = n*Cy*e -> yout, E = e*Cx*n -> xout */ const double n = en.v; const double e = en.u; const uint32_t sz = horner_number_of_real_coefficients(order); cx += sz; cy += sz; double N = *--cy; double E = *--cx; for (uint32_t r = order; r > order_offset; r--) { double u = *--cy; double v = *--cx; for (uint32_t c = order; c >= r; c--) { u = n * u + *--cy; v = e * v + *--cx; } N = e * N + u; E = n * E + v; } return {E, N}; } inline static double single_real_horner_eval(uint32_t order, const double *cx, double x, uint32_t order_offset = 0) { const uint32_t sz = order + 1; /* Number of coefficients per polynomial */ cx += sz; double u = *--cx; for (uint32_t r = order; r > order_offset; r--) { u = x * u + *--cx; } return u; } inline static PJ_UV complex_horner_eval(uint32_t order, const double *c, PJ_UV en, uint32_t order_offset = 0) { // the coefficients are ordered like this: // (Cn0+i*Ce0, Cn1+i*Ce1, ...) const uint32_t sz = horner_number_of_complex_coefficients(order); const double e = en.u; const double n = en.v; const double *cbeg = c + order_offset * 2; c += sz; double E = *--c; double N = *--c; double w; while (c > cbeg) { w = n * E + e * N + *--c; N = n * N - e * E + *--c; E = w; } return {E, N}; } inline static PJ_UV generate_error_coords() { PJ_UV uv_error; uv_error.u = uv_error.v = HUGE_VAL; return uv_error; } inline static bool coords_out_of_range(PJ *P, const HORNER *transformation, double n, double e) { const double range = transformation->range; if ((fabs(n) > range) || (fabs(e) > range)) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return true; } return false; } static PJ_UV real_default_impl(PJ *P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) { /*********************************************************************** A reimplementation of the classic Engsager/Poder 2D Horner polynomial evaluation engine "gen_pol". This version omits the inimitable Poder "dual autocheck"-machinery, which here is intended to be implemented at a higher level of the library: We separate the polynomial evaluation from the quality control (which, given the limited MTBF for "computing machinery", typical when Knud Poder invented the dual autocheck method, was not defensible at that time). Another difference from the original version is that we return the result on the stack, rather than accepting pointers to result variables as input. This results in code that is easy to read: projected = horner (s34j, 1, geographic); geographic = horner (s34j, -1, projected ); and experiments have shown that on contemporary architectures, the time taken for returning even comparatively large objects on the stack (and the UV is not that large - typically only 16 bytes) is negligibly different from passing two pointers (i.e. typically also 16 bytes) the other way. The polynomium has the form: P = sum (i = [0 : order]) sum (j = [0 : order - i]) pow(par_1, i) * pow(par_2, j) * coef(index(order, i, j)) ***********************************************************************/ assert(direction == PJ_FWD || direction == PJ_INV); double n, e; if (direction == PJ_FWD) { /* forward */ e = position.u - transformation->fwd_origin->u; n = position.v - transformation->fwd_origin->v; } else { /* inverse */ e = position.u - transformation->inv_origin->u; n = position.v - transformation->inv_origin->v; } if (coords_out_of_range(P, transformation, n, e)) { return generate_error_coords(); } const double *tcx = direction == PJ_FWD ? transformation->fwd_u : transformation->inv_u; const double *tcy = direction == PJ_FWD ? transformation->fwd_v : transformation->inv_v; PJ_UV en = {e, n}; position = double_real_horner_eval(transformation->order, tcx, tcy, en); return position; } static PJ_UV real_iterative_inverse_impl(PJ *P, const HORNER *transformation, PJ_UV position) { double n, e; // in this case fwd_origin needs to be added in the end e = position.u; n = position.v; if (coords_out_of_range(P, transformation, n, e)) { return generate_error_coords(); } /* * solve iteratively * * | E | | u00 | | u01 + u02*x + ... ' u10 + u11*x + u20*y + ... * | | x | | | = | | + |-------------------------- ' * --------------------------| | | | N | | v00 | | v10 + v11*y + v20*x * + * ... ' v01 + v02*y + ... | | y | * * | x | | Ma ' Mb |-1 | E-u00 | * | | = |-------- | | | * | y | | Mc ' Md | | N-v00 | */ const uint32_t order = transformation->order; const double tol = transformation->inverse_tolerance; const double de = e - transformation->fwd_u[0]; const double dn = n - transformation->fwd_v[0]; double x0 = 0.0; double y0 = 0.0; int loops = 32; // usually converges really fast (1-2 loops) bool converged = false; while (loops-- > 0 && !converged) { double Ma = 0.0; double Mb = 0.0; double Mc = 0.0; double Md = 0.0; { const double *tcx = transformation->fwd_u; const double *tcy = transformation->fwd_v; PJ_UV x0y0 = {x0, y0}; // sum the i > 0 coefficients PJ_UV Mbc = double_real_horner_eval(order, tcx, tcy, x0y0, 1); Mb = Mbc.u; Mc = Mbc.v; // sum the i = 0, j > 0 coefficients Ma = single_real_horner_eval(order, tcx, x0, 1); Md = single_real_horner_eval(order, tcy, y0, 1); } double idet = 1.0 / (Ma * Md - Mb * Mc); double x = idet * (Md * de - Mb * dn); double y = idet * (Ma * dn - Mc * de); converged = (fabs(x - x0) < tol) && (fabs(y - y0) < tol); x0 = x; y0 = y; } // if loops have been exhausted and we have not converged yet, // we are never going to converge if (!converged) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM); return generate_error_coords(); } else { position.u = x0 + transformation->fwd_origin->u; position.v = y0 + transformation->fwd_origin->v; return position; } } static void horner_forward_4d(PJ_COORD &point, PJ *P) { const HORNER *transformation = reinterpret_cast(P->opaque); point.uv = real_default_impl(P, transformation, PJ_FWD, point.uv); } static void horner_inverse_4d(PJ_COORD &point, PJ *P) { const HORNER *transformation = reinterpret_cast(P->opaque); point.uv = real_default_impl(P, transformation, PJ_INV, point.uv); } static void horner_iterative_inverse_4d(PJ_COORD &point, PJ *P) { const HORNER *transformation = reinterpret_cast(P->opaque); point.uv = real_iterative_inverse_impl(P, transformation, point.uv); } static PJ_UV complex_default_impl(PJ *P, const HORNER *transformation, PJ_DIRECTION direction, PJ_UV position) { /*********************************************************************** A reimplementation of a classic Engsager/Poder Horner complex polynomial evaluation engine. ***********************************************************************/ assert(direction == PJ_FWD || direction == PJ_INV); double n, e; if (direction == PJ_FWD) { /* forward */ e = position.u - transformation->fwd_origin->u; n = position.v - transformation->fwd_origin->v; } else { /* inverse */ e = position.u - transformation->inv_origin->u; n = position.v - transformation->inv_origin->v; } if (transformation->uneg) e = -e; if (transformation->vneg) n = -n; if (coords_out_of_range(P, transformation, n, e)) { return generate_error_coords(); } // coefficient pointers double *cb = direction == PJ_FWD ? transformation->fwd_c : transformation->inv_c; PJ_UV en = {e, n}; position = complex_horner_eval(transformation->order, cb, en); return position; } static PJ_UV complex_iterative_inverse_impl(PJ *P, const HORNER *transformation, PJ_UV position) { double n, e; // in this case fwd_origin and any existing flipping needs to be added in // the end e = position.u; n = position.v; if (coords_out_of_range(P, transformation, n, e)) { return generate_error_coords(); } { // complex real part corresponds to Northing, imag part to Easting const double tol = transformation->inverse_tolerance; const std::complex dZ(n - transformation->fwd_c[0], e - transformation->fwd_c[1]); std::complex w0(0.0, 0.0); int loops = 32; // usually converges really fast (1-2 loops) bool converged = false; while (loops-- > 0 && !converged) { // sum coefficient pointers from back to front until the first // complex pair (fwd_c0+i*fwd_c1) const double *c = transformation->fwd_c; PJ_UV en = {w0.imag(), w0.real()}; en = complex_horner_eval(transformation->order, c, en, 1); std::complex det(en.v, en.u); std::complex w1 = dZ / det; converged = (fabs(w1.real() - w0.real()) < tol) && (fabs(w1.imag() - w0.imag()) < tol); w0 = w1; } // if loops have been exhausted and we have not converged yet, // we are never going to converge if (!converged) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM); position = generate_error_coords(); } else { double E = w0.imag(); double N = w0.real(); if (transformation->uneg) E = -E; if (transformation->vneg) N = -N; position.u = E + transformation->fwd_origin->u; position.v = N + transformation->fwd_origin->v; } return position; } } static void complex_horner_forward_4d(PJ_COORD &point, PJ *P) { const HORNER *transformation = reinterpret_cast(P->opaque); point.uv = complex_default_impl(P, transformation, PJ_FWD, point.uv); } static void complex_horner_inverse_4d(PJ_COORD &point, PJ *P) { const HORNER *transformation = reinterpret_cast(P->opaque); point.uv = complex_default_impl(P, transformation, PJ_INV, point.uv); } static void complex_horner_iterative_inverse_4d(PJ_COORD &point, PJ *P) { const HORNER *transformation = reinterpret_cast(P->opaque); point.uv = complex_iterative_inverse_impl(P, transformation, point.uv); } static PJ *horner_freeup(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); horner_free((HORNER *)P->opaque); P->opaque = nullptr; return pj_default_destructor(P, errlev); } static int parse_coefs(PJ *P, double *coefs, const char *param, int ncoefs) { char *buf, *init, *next = nullptr; int i; size_t buf_size = strlen(param) + 2; buf = static_cast(calloc(buf_size, sizeof(char))); if (nullptr == buf) { proj_log_error(P, "No memory left"); return 0; } snprintf(buf, buf_size, "t%s", param); if (0 == pj_param(P->ctx, P->params, buf).i) { free(buf); return 0; } snprintf(buf, buf_size, "s%s", param); init = pj_param(P->ctx, P->params, buf).s; free(buf); for (i = 0; i < ncoefs; i++) { if (i > 0) { if (next == nullptr || ',' != *next) { proj_log_error(P, "Malformed polynomium set %s. need %d coefs", param, ncoefs); return 0; } init = ++next; } coefs[i] = pj_strtod(init, &next); } return 1; } /*********************************************************************/ PJ *PJ_PROJECTION(horner) { /*********************************************************************/ int degree = 0; HORNER *Q; P->fwd3d = nullptr; P->inv3d = nullptr; P->fwd = nullptr; P->inv = nullptr; P->left = P->right = PJ_IO_UNITS_WHATEVER; P->destructor = horner_freeup; /* Polynomial degree specified? */ if (pj_param(P->ctx, P->params, "tdeg").i) { /* degree specified? */ degree = pj_param(P->ctx, P->params, "ideg").i; if (degree < 0 || degree > 10000) { /* What are reasonable minimum and maximums for degree? */ proj_log_error(P, _("Degree is unreasonable: %d"), degree); return horner_freeup(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else { proj_log_error(P, _("Must specify polynomial degree, (+deg=n)")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } bool complex_polynomia = false; if (pj_param(P->ctx, P->params, "tfwd_c").i || pj_param(P->ctx, P->params, "tinv_c").i) /* complex polynomium? */ complex_polynomia = true; Q = horner_alloc(degree, complex_polynomia); if (Q == nullptr) return horner_freeup(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; bool has_inv = false; if (!complex_polynomia) { has_inv = pj_param_exists(P->params, "inv_u") || pj_param_exists(P->params, "inv_v") || pj_param_exists(P->params, "inv_origin"); } else { has_inv = pj_param_exists(P->params, "inv_c") || pj_param_exists(P->params, "inv_origin"); } Q->has_inv = has_inv; // setup callbacks if (complex_polynomia) { P->fwd4d = complex_horner_forward_4d; P->inv4d = has_inv ? complex_horner_inverse_4d : complex_horner_iterative_inverse_4d; } else { P->fwd4d = horner_forward_4d; P->inv4d = has_inv ? horner_inverse_4d : horner_iterative_inverse_4d; } if (complex_polynomia) { /* Westings and/or southings? */ Q->uneg = pj_param_exists(P->params, "uneg") ? 1 : 0; Q->vneg = pj_param_exists(P->params, "vneg") ? 1 : 0; const int n = static_cast(horner_number_of_complex_coefficients(degree)); if (0 == parse_coefs(P, Q->fwd_c, "fwd_c", n)) { proj_log_error(P, _("missing fwd_c")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (has_inv && 0 == parse_coefs(P, Q->inv_c, "inv_c", n)) { proj_log_error(P, _("missing inv_c")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } } else { const int n = static_cast(horner_number_of_real_coefficients(degree)); if (0 == parse_coefs(P, Q->fwd_u, "fwd_u", n)) { proj_log_error(P, _("missing fwd_u")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (0 == parse_coefs(P, Q->fwd_v, "fwd_v", n)) { proj_log_error(P, _("missing fwd_v")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (has_inv && 0 == parse_coefs(P, Q->inv_u, "inv_u", n)) { proj_log_error(P, _("missing inv_u")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (has_inv && 0 == parse_coefs(P, Q->inv_v, "inv_v", n)) { proj_log_error(P, _("missing inv_v")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } } if (0 == parse_coefs(P, &(Q->fwd_origin->u), "fwd_origin", 2)) { proj_log_error(P, _("missing fwd_origin")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (has_inv && 0 == parse_coefs(P, &(Q->inv_origin->u), "inv_origin", 2)) { proj_log_error(P, _("missing inv_origin")); return horner_freeup(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (0 == parse_coefs(P, &Q->range, "range", 1)) Q->range = 500000; if (0 == parse_coefs(P, &Q->inverse_tolerance, "inv_tolerance", 1)) Q->inverse_tolerance = 0.001; return P; } proj-9.8.1/src/transformations/defmodel_exceptions.hpp000664 001750 001750 00000005663 15166171715 023236 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to deformation model * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef DEFORMATON_MODEL_NAMESPACE #error "Should be included only by defmodel.hpp" #endif #include namespace DEFORMATON_MODEL_NAMESPACE { // --------------------------------------------------------------------------- /** Parsing exception. */ class ParsingException : public std::exception { public: explicit ParsingException(const std::string &msg) : msg_(msg) {} const char *what() const noexcept override; private: std::string msg_; }; const char *ParsingException::what() const noexcept { return msg_.c_str(); } // --------------------------------------------------------------------------- class UnimplementedException : public std::exception { public: explicit UnimplementedException(const std::string &msg) : msg_(msg) {} const char *what() const noexcept override; private: std::string msg_; }; const char *UnimplementedException::what() const noexcept { return msg_.c_str(); } // --------------------------------------------------------------------------- /** Evaluator exception. */ class EvaluatorException : public std::exception { public: explicit EvaluatorException(const std::string &msg) : msg_(msg) {} const char *what() const noexcept override; private: std::string msg_; }; const char *EvaluatorException::what() const noexcept { return msg_.c_str(); } // --------------------------------------------------------------------------- } // namespace DEFORMATON_MODEL_NAMESPACE proj-9.8.1/src/transformations/hgridshift.cpp000664 001750 001750 00000016107 15166171715 021337 0ustar00eveneven000000 000000 #include #include #include #include #include #include "grids.hpp" #include "proj_internal.h" PROJ_HEAD(hgridshift, "Horizontal grid shift"); static std::mutex gMutexHGridShift{}; static std::set gKnownGridsHGridShift{}; using namespace NS_PROJ; namespace { // anonymous namespace struct hgridshiftData { double t_final = 0; double t_epoch = 0; ListOfHGrids grids{}; bool defer_grid_opening = false; int error_code_in_defer_grid_opening = 0; }; } // anonymous namespace static PJ_XYZ pj_hgridshift_forward_3d(PJ_LPZ lpz, PJ *P) { auto Q = static_cast(P->opaque); PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; if (Q->defer_grid_opening) { Q->defer_grid_opening = false; Q->grids = pj_hgrid_init(P, "grids"); Q->error_code_in_defer_grid_opening = proj_errno(P); } if (Q->error_code_in_defer_grid_opening) { proj_errno_set(P, Q->error_code_in_defer_grid_opening); return proj_coord_error().xyz; } if (!Q->grids.empty()) { /* Only try the gridshift if at least one grid is loaded, * otherwise just pass the coordinate through unchanged. */ point.lp = pj_hgrid_apply(P->ctx, Q->grids, point.lp, PJ_FWD); } return point.xyz; } static PJ_LPZ pj_hgridshift_reverse_3d(PJ_XYZ xyz, PJ *P) { auto Q = static_cast(P->opaque); PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; if (Q->defer_grid_opening) { Q->defer_grid_opening = false; Q->grids = pj_hgrid_init(P, "grids"); Q->error_code_in_defer_grid_opening = proj_errno(P); } if (Q->error_code_in_defer_grid_opening) { proj_errno_set(P, Q->error_code_in_defer_grid_opening); return proj_coord_error().lpz; } if (!Q->grids.empty()) { /* Only try the gridshift if at least one grid is loaded, * otherwise just pass the coordinate through unchanged. */ point.lp = pj_hgrid_apply(P->ctx, Q->grids, point.lp, PJ_INV); } return point.lpz; } static void pj_hgridshift_forward_4d(PJ_COORD &coo, PJ *P) { struct hgridshiftData *Q = (struct hgridshiftData *)P->opaque; /* If transformation is not time restricted, we always call it */ if (Q->t_final == 0 || Q->t_epoch == 0) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = pj_hgridshift_forward_3d(coo.lpz, P); coo.xyz = xyz; return; } /* Time restricted - only apply transform if within time bracket */ if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = pj_hgridshift_forward_3d(coo.lpz, P); coo.xyz = xyz; } } static void pj_hgridshift_reverse_4d(PJ_COORD &coo, PJ *P) { struct hgridshiftData *Q = (struct hgridshiftData *)P->opaque; /* If transformation is not time restricted, we always call it */ if (Q->t_final == 0 || Q->t_epoch == 0) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = pj_hgridshift_reverse_3d(coo.xyz, P); coo.lpz = lpz; return; } /* Time restricted - only apply transform if within time bracket */ if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = pj_hgridshift_reverse_3d(coo.xyz, P); coo.lpz = lpz; } } static PJ *pj_hgridshift_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; delete static_cast(P->opaque); P->opaque = nullptr; return pj_default_destructor(P, errlev); } static void pj_hgridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) { auto Q = (struct hgridshiftData *)P->opaque; for (auto &grid : Q->grids) { grid->reassign_context(ctx); } } PJ *PJ_TRANSFORMATION(hgridshift, 0) { auto Q = new hgridshiftData; P->opaque = (void *)Q; P->destructor = pj_hgridshift_destructor; P->reassign_context = pj_hgridshift_reassign_context; P->fwd4d = pj_hgridshift_forward_4d; P->inv4d = pj_hgridshift_reverse_4d; P->fwd3d = pj_hgridshift_forward_3d; P->inv3d = pj_hgridshift_reverse_3d; P->fwd = nullptr; P->inv = nullptr; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; if (0 == pj_param(P->ctx, P->params, "tgrids").i) { proj_log_error(P, _("+grids parameter missing.")); return pj_hgridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* TODO: Refactor into shared function that can be used */ /* by both vgridshift and hgridshift */ if (pj_param(P->ctx, P->params, "tt_final").i) { Q->t_final = pj_param(P->ctx, P->params, "dt_final").f; if (Q->t_final == 0) { /* a number wasn't passed to +t_final, let's see if it was "now" */ /* and set the time accordingly. */ if (!strcmp("now", pj_param(P->ctx, P->params, "st_final").s)) { time_t now; struct tm *date; time(&now); date = localtime(&now); Q->t_final = 1900.0 + date->tm_year + date->tm_yday / 365.0; } } } if (pj_param(P->ctx, P->params, "tt_epoch").i) Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; if (P->ctx->defer_grid_opening) { Q->defer_grid_opening = true; } else { const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; gMutexHGridShift.lock(); const bool isKnownGrid = gKnownGridsHGridShift.find(gridnames) != gKnownGridsHGridShift.end(); gMutexHGridShift.unlock(); if (isKnownGrid) { Q->defer_grid_opening = true; } else { Q->grids = pj_hgrid_init(P, "grids"); /* Was gridlist compiled properly? */ if (proj_errno(P)) { proj_log_error(P, _("could not find required grid(s).")); return pj_hgridshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } gMutexHGridShift.lock(); gKnownGridsHGridShift.insert(gridnames); gMutexHGridShift.unlock(); } } return P; } void pj_clear_hgridshift_knowngrids_cache() { std::lock_guard lock(gMutexHGridShift); gKnownGridsHGridShift.clear(); } proj-9.8.1/src/transformations/vgridshift.cpp000664 001750 001750 00000020562 15166171715 021355 0ustar00eveneven000000 000000 #include #include #include #include #include #include "grids.hpp" #include "proj_internal.h" PROJ_HEAD(vgridshift, "Vertical grid shift"); static std::mutex gMutexVGridShift{}; static std::set gKnownGridsVGridShift{}; using namespace NS_PROJ; namespace { // anonymous namespace struct vgridshiftData { double t_final = 0; double t_epoch = 0; double forward_multiplier = 0; ListOfVGrids grids{}; bool defer_grid_opening = false; int error_code_in_defer_grid_opening = 0; }; } // anonymous namespace static void deal_with_vertcon_gtx_hack(PJ *P) { struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; // The .gtx VERTCON files stored millimeters, but the .tif files // are in metres. if (Q->forward_multiplier != 0.001) { return; } const char *gridname = pj_param(P->ctx, P->params, "sgrids").s; if (!gridname) { return; } if (strcmp(gridname, "vertconw.gtx") != 0 && strcmp(gridname, "vertconc.gtx") != 0 && strcmp(gridname, "vertcone.gtx") != 0) { return; } if (Q->grids.empty()) { return; } const auto &grids = Q->grids[0]->grids(); if (!grids.empty() && grids[0]->name().find(".tif") != std::string::npos) { Q->forward_multiplier = 1.0; } } static PJ_XYZ pj_vgridshift_forward_3d(PJ_LPZ lpz, PJ *P) { struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; if (Q->defer_grid_opening) { Q->defer_grid_opening = false; Q->grids = pj_vgrid_init(P, "grids"); deal_with_vertcon_gtx_hack(P); Q->error_code_in_defer_grid_opening = proj_errno(P); } if (Q->error_code_in_defer_grid_opening) { proj_errno_set(P, Q->error_code_in_defer_grid_opening); return proj_coord_error().xyz; } if (!Q->grids.empty()) { /* Only try the gridshift if at least one grid is loaded, * otherwise just pass the coordinate through unchanged. */ point.xyz.z += pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier); } return point.xyz; } static PJ_LPZ pj_vgridshift_reverse_3d(PJ_XYZ xyz, PJ *P) { struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; if (Q->defer_grid_opening) { Q->defer_grid_opening = false; Q->grids = pj_vgrid_init(P, "grids"); deal_with_vertcon_gtx_hack(P); Q->error_code_in_defer_grid_opening = proj_errno(P); } if (Q->error_code_in_defer_grid_opening) { proj_errno_set(P, Q->error_code_in_defer_grid_opening); return proj_coord_error().lpz; } if (!Q->grids.empty()) { /* Only try the gridshift if at least one grid is loaded, * otherwise just pass the coordinate through unchanged. */ point.xyz.z -= pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier); } return point.lpz; } static void pj_vgridshift_forward_4d(PJ_COORD &coo, PJ *P) { struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; /* If transformation is not time restricted, we always call it */ if (Q->t_final == 0 || Q->t_epoch == 0) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = pj_vgridshift_forward_3d(coo.lpz, P); coo.xyz = xyz; return; } /* Time restricted - only apply transform if within time bracket */ if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto xyz = pj_vgridshift_forward_3d(coo.lpz, P); coo.xyz = xyz; } } static void pj_vgridshift_reverse_4d(PJ_COORD &coo, PJ *P) { struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; /* If transformation is not time restricted, we always call it */ if (Q->t_final == 0 || Q->t_epoch == 0) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = pj_vgridshift_reverse_3d(coo.xyz, P); coo.lpz = lpz; return; } /* Time restricted - only apply transform if within time bracket */ if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) { // Assigning in 2 steps avoids cppcheck warning // "Overlapping read/write of union is undefined behavior" // Cf // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 const auto lpz = pj_vgridshift_reverse_3d(coo.xyz, P); coo.lpz = lpz; } } static PJ *pj_vgridshift_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; delete static_cast(P->opaque); P->opaque = nullptr; return pj_default_destructor(P, errlev); } static void pj_vgridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) { auto Q = (struct vgridshiftData *)P->opaque; for (auto &grid : Q->grids) { grid->reassign_context(ctx); } } PJ *PJ_TRANSFORMATION(vgridshift, 0) { auto Q = new vgridshiftData; P->opaque = (void *)Q; P->destructor = pj_vgridshift_destructor; P->reassign_context = pj_vgridshift_reassign_context; if (!pj_param(P->ctx, P->params, "tgrids").i) { proj_log_error(P, _("+grids parameter missing.")); return pj_vgridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* TODO: Refactor into shared function that can be used */ /* by both vgridshift and hgridshift */ if (pj_param(P->ctx, P->params, "tt_final").i) { Q->t_final = pj_param(P->ctx, P->params, "dt_final").f; if (Q->t_final == 0) { /* a number wasn't passed to +t_final, let's see if it was "now" */ /* and set the time accordingly. */ if (!strcmp("now", pj_param(P->ctx, P->params, "st_final").s)) { time_t now; struct tm *date; time(&now); date = localtime(&now); Q->t_final = 1900.0 + date->tm_year + date->tm_yday / 365.0; } } } if (pj_param(P->ctx, P->params, "tt_epoch").i) Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; /* historical: the forward direction subtracts the grid offset. */ Q->forward_multiplier = -1.0; if (pj_param(P->ctx, P->params, "tmultiplier").i) { Q->forward_multiplier = pj_param(P->ctx, P->params, "dmultiplier").f; } if (P->ctx->defer_grid_opening) { Q->defer_grid_opening = true; } else { const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; gMutexVGridShift.lock(); const bool isKnownGrid = gKnownGridsVGridShift.find(gridnames) != gKnownGridsVGridShift.end(); gMutexVGridShift.unlock(); if (isKnownGrid) { Q->defer_grid_opening = true; } else { /* Build gridlist. P->vgridlist_geoid can be empty if +grids only * ask for optional grids. */ Q->grids = pj_vgrid_init(P, "grids"); /* Was gridlist compiled properly? */ if (proj_errno(P)) { proj_log_error(P, _("could not find required grid(s).")); return pj_vgridshift_destructor( P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); } gMutexVGridShift.lock(); gKnownGridsVGridShift.insert(gridnames); gMutexVGridShift.unlock(); } } P->fwd4d = pj_vgridshift_forward_4d; P->inv4d = pj_vgridshift_reverse_4d; P->fwd3d = pj_vgridshift_forward_3d; P->inv3d = pj_vgridshift_reverse_3d; P->fwd = nullptr; P->inv = nullptr; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; return P; } void pj_clear_vgridshift_knowngrids_cache() { std::lock_guard lock(gMutexVGridShift); gKnownGridsVGridShift.clear(); } proj-9.8.1/src/transformations/tinshift_exceptions.hpp000664 001750 001750 00000004203 15166171715 023274 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to TIN based transformations * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef TINSHIFT_NAMESPACE #error "Should be included only by tinshift.hpp" #endif #include namespace TINSHIFT_NAMESPACE { // --------------------------------------------------------------------------- /** Parsing exception. */ class ParsingException : public std::exception { public: explicit ParsingException(const std::string &msg) : msg_(msg) {} const char *what() const noexcept override; private: std::string msg_; }; const char *ParsingException::what() const noexcept { return msg_.c_str(); } // --------------------------------------------------------------------------- } // namespace TINSHIFT_NAMESPACE proj-9.8.1/src/transformations/tinshift.hpp000664 001750 001750 00000022711 15166171715 021037 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to TIN based transformations * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef TINSHIFT_HPP #define TINSHIFT_HPP #ifdef PROJ_COMPILATION #include "proj/internal/include_nlohmann_json.hpp" #else #include "nlohmann/json.hpp" #endif #include #include #include #include #include #include #include #include #include "quadtree.hpp" #ifndef TINSHIFT_NAMESPACE #define TINSHIFT_NAMESPACE TINShift #endif #include "tinshift_exceptions.hpp" namespace TINSHIFT_NAMESPACE { enum FallbackStrategy { FALLBACK_NONE, FALLBACK_NEAREST_SIDE, FALLBACK_NEAREST_CENTROID, }; using json = nlohmann::json; // --------------------------------------------------------------------------- /** Content of a TINShift file. */ class TINShiftFile { public: /** Parse the provided serialized JSON content and return an object. * * @throws ParsingException in case of error. */ static std::unique_ptr parse(const std::string &text); /** Get file type. Should always be "triangulation_file" */ const std::string &fileType() const { return mFileType; } /** Get the version of the format. At time of writing, only "1.0" is known */ const std::string &formatVersion() const { return mFormatVersion; } /** Get brief descriptive name of the deformation model. */ const std::string &name() const { return mName; } /** Get a string identifying the version of the deformation model. * The format for specifying version is defined by the agency * responsible for the deformation model. */ const std::string &version() const { return mVersion; } /** Get a string identifying the license of the file. * e.g "Create Commons Attribution 4.0 International" */ const std::string &license() const { return mLicense; } /** Get a text description of the model. Intended to be longer than name() */ const std::string &description() const { return mDescription; } /** Get a text description of the model. Intended to be longer than name() */ const std::string &publicationDate() const { return mPublicationDate; } const enum FallbackStrategy &fallbackStrategy() const { return mFallbackStrategy; } /** Basic information on the agency responsible for the model. */ struct Authority { std::string name{}; std::string url{}; std::string address{}; std::string email{}; }; /** Get basic information on the agency responsible for the model. */ const Authority &authority() const { return mAuthority; } /** Hyperlink related to the model. */ struct Link { /** URL holding the information */ std::string href{}; /** Relationship to the dataset. e.g. "about", "source", "license", * "metadata" */ std::string rel{}; /** Mime type */ std::string type{}; /** Description of the link */ std::string title{}; }; /** Get links to related information. */ const std::vector links() const { return mLinks; } /** Get a string identifying the CRS of source coordinates in the * vertices. Typically "EPSG:XXXX". If the transformation is for vertical * component, this should be the code for a compound CRS (can be * EPSG:XXXX+YYYY where XXXX is the code of the horizontal CRS and YYYY * the code of the vertical CRS). * For example, for the KKJ->ETRS89 transformation, this is EPSG:2393 * ("KKJ / Finland Uniform Coordinate System"). * The input coordinates are assumed to * be passed in the "normalized for visualisation" / "GIS friendly" order, * that is longitude, latitude for geographic coordinates and * easting, northing for projected coordinates. * This may be empty for unspecified CRS. */ const std::string &inputCRS() const { return mInputCRS; } /** Get a string identifying the CRS of target coordinates in the * vertices. Typically "EPSG:XXXX". If the transformation is for vertical * component, this should be the code for a compound CRS (can be * EPSG:XXXX+YYYY where XXXX is the code of the horizontal CRS and YYYY * the code of the vertical CRS). * For example, for the KKJ->ETRS89 transformation, this is EPSG:3067 * ("ETRS89 / TM35FIN(E,N)"). * The output coordinates will be * returned in the "normalized for visualisation" / "GIS friendly" order, * that is longitude, latitude for geographic coordinates and * easting, northing for projected coordinates. * This may be empty for unspecified CRS. */ const std::string &outputCRS() const { return mOutputCRS; } /** Return whether horizontal coordinates are transformed. */ bool transformHorizontalComponent() const { return mTransformHorizontalComponent; } /** Return whether vertical coordinates are transformed. */ bool transformVerticalComponent() const { return mTransformVerticalComponent; } /** Indices of vertices of a triangle */ struct VertexIndices { /** Index of first vertex */ unsigned idx1; /** Index of second vertex */ unsigned idx2; /** Index of third vertex */ unsigned idx3; }; /** Return number of elements per vertex of vertices() */ unsigned verticesColumnCount() const { return mVerticesColumnCount; } /** Return description of triangulation vertices. * Each vertex is described by verticesColumnCount() consecutive values. * They are respectively: * - the source X value * - the source Y value * - (if transformHorizontalComponent() is true) the target X value * - (if transformHorizontalComponent() is true) the target Y value * - (if transformVerticalComponent() is true) the delta Z value (to go from * source to target Z) * * X is assumed to be a longitude (in degrees) or easting value. * Y is assumed to be a latitude (in degrees) or northing value. */ const std::vector &vertices() const { return mVertices; } /** Return triangles*/ const std::vector &triangles() const { return mTriangles; } private: TINShiftFile() = default; std::string mFileType{}; std::string mFormatVersion{}; std::string mName{}; std::string mVersion{}; std::string mLicense{}; std::string mDescription{}; std::string mPublicationDate{}; enum FallbackStrategy mFallbackStrategy {}; Authority mAuthority{}; std::vector mLinks{}; std::string mInputCRS{}; std::string mOutputCRS{}; bool mTransformHorizontalComponent = false; bool mTransformVerticalComponent = false; unsigned mVerticesColumnCount = 0; std::vector mVertices{}; std::vector mTriangles{}; }; // --------------------------------------------------------------------------- /** Class to evaluate the transformation of a coordinate */ class Evaluator { public: /** Constructor. */ explicit Evaluator(std::unique_ptr &&fileIn); /** Get file */ const TINShiftFile &file() const { return *(mFile.get()); } /** Evaluate displacement of a position given by (x,y,z,t) and * return it in (x_out,y_out_,z_out). */ bool forward(double x, double y, double z, double &x_out, double &y_out, double &z_out); /** Apply inverse transformation. */ bool inverse(double x, double y, double z, double &x_out, double &y_out, double &z_out); private: std::unique_ptr mFile; // Reused between invocations to save memory allocations std::vector mTriangleIndices{}; std::unique_ptr> mQuadTreeForward{}; std::unique_ptr> mQuadTreeInverse{}; }; // --------------------------------------------------------------------------- } // namespace TINSHIFT_NAMESPACE // --------------------------------------------------------------------------- #include "tinshift_impl.hpp" #endif // TINSHIFT_HPP proj-9.8.1/src/transformations/vertoffset.cpp000664 001750 001750 00000007425 15166171715 021376 0ustar00eveneven000000 000000 /************************************************************************ * Copyright (c) 2022, Even Rouault * * 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 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(vertoffset, "Vertical Offset and Slope") "\n\tTransformation" "\n\tlat_0= lon_0= dh= slope_lat= slope_lon="; namespace { // anonymous namespace struct pj_opaque_vertoffset { double slope_lon; double slope_lat; double zoff; double rho0; double nu0; }; } // anonymous namespace // Cf EPSG Dataset coordinate operation method code 1046 "Vertical Offset and // Slope" static double get_forward_offset(const PJ *P, double phi, double lam) { const struct pj_opaque_vertoffset *Q = (const struct pj_opaque_vertoffset *)P->opaque; return Q->zoff + Q->slope_lat * Q->rho0 * (phi - P->phi0) + Q->slope_lon * Q->nu0 * lam * cos(phi); } static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) { PJ_XYZ xyz; // We need to add lam0 (+lon_0) since it is subtracted in fwd_prepare(), // which is desirable for map projections, but not // for that method which modifies only the Z component. xyz.x = lpz.lam + P->lam0; xyz.y = lpz.phi; xyz.z = lpz.z + get_forward_offset(P, lpz.phi, lpz.lam); return xyz; } static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) { PJ_LPZ lpz; // We need to subtract lam0 (+lon_0) since it is added in inv_finalize(), // which is desirable for map projections, but not // for that method which modifies only the Z component. lpz.lam = xyz.x - P->lam0; lpz.phi = xyz.y; lpz.z = xyz.z - get_forward_offset(P, lpz.phi, lpz.lam); return lpz; } /* Arcsecond to radians */ #define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) PJ *PJ_TRANSFORMATION(vertoffset, 1) { struct pj_opaque_vertoffset *Q = static_cast( calloc(1, sizeof(struct pj_opaque_vertoffset))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = (void *)Q; P->fwd3d = forward_3d; P->inv3d = reverse_3d; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; /* read args */ Q->slope_lon = pj_param(P->ctx, P->params, "dslope_lon").f * ARCSEC_TO_RAD; Q->slope_lat = pj_param(P->ctx, P->params, "dslope_lat").f * ARCSEC_TO_RAD; Q->zoff = pj_param(P->ctx, P->params, "ddh").f; const double sinlat0 = sin(P->phi0); const double oneMinusEsSinlat0Square = 1 - P->es * (sinlat0 * sinlat0); Q->rho0 = P->a * (1 - P->es) / (oneMinusEsSinlat0Square * sqrt(oneMinusEsSinlat0Square)); Q->nu0 = P->a / sqrt(oneMinusEsSinlat0Square); return P; } proj-9.8.1/src/transformations/tinshift_impl.hpp000664 001750 001750 00000067531 15166171715 022071 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to TIN based transformations * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #ifndef TINSHIFT_NAMESPACE #error "Should be included only by tinshift.hpp" #endif #include #include #include namespace TINSHIFT_NAMESPACE { // --------------------------------------------------------------------------- static std::string getString(const json &j, const char *key, bool optional) { if (!j.contains(key)) { if (optional) { return std::string(); } throw ParsingException(std::string("Missing \"") + key + "\" key"); } const json v = j[key]; if (!v.is_string()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a string"); } return v.get(); } static std::string getReqString(const json &j, const char *key) { return getString(j, key, false); } static std::string getOptString(const json &j, const char *key) { return getString(j, key, true); } // --------------------------------------------------------------------------- static json getArrayMember(const json &j, const char *key) { if (!j.contains(key)) { throw ParsingException(std::string("Missing \"") + key + "\" key"); } const json obj = j[key]; if (!obj.is_array()) { throw ParsingException(std::string("The value of \"") + key + "\" should be a array"); } return obj; } // --------------------------------------------------------------------------- std::unique_ptr TINShiftFile::parse(const std::string &text) { std::unique_ptr tinshiftFile(new TINShiftFile()); json j; try { j = json::parse(text); } catch (const std::exception &e) { throw ParsingException(e.what()); } if (!j.is_object()) { throw ParsingException("Not an object"); } tinshiftFile->mFileType = getReqString(j, "file_type"); tinshiftFile->mFormatVersion = getReqString(j, "format_version"); tinshiftFile->mName = getOptString(j, "name"); tinshiftFile->mVersion = getOptString(j, "version"); tinshiftFile->mLicense = getOptString(j, "license"); tinshiftFile->mDescription = getOptString(j, "description"); tinshiftFile->mPublicationDate = getOptString(j, "publication_date"); tinshiftFile->mFallbackStrategy = FALLBACK_NONE; if (j.contains("fallback_strategy")) { if (tinshiftFile->mFormatVersion != "1.1") { throw ParsingException( "fallback_strategy needs format_version 1.1"); } const auto fallback_strategy = getOptString(j, "fallback_strategy"); if (fallback_strategy == "nearest_side") { tinshiftFile->mFallbackStrategy = FALLBACK_NEAREST_SIDE; } else if (fallback_strategy == "nearest_centroid") { tinshiftFile->mFallbackStrategy = FALLBACK_NEAREST_CENTROID; } else if (fallback_strategy == "none") { tinshiftFile->mFallbackStrategy = FALLBACK_NONE; } else { throw ParsingException("invalid fallback_strategy"); } } if (j.contains("authority")) { const json jAuthority = j["authority"]; if (!jAuthority.is_object()) { throw ParsingException("authority is not a object"); } tinshiftFile->mAuthority.name = getOptString(jAuthority, "name"); tinshiftFile->mAuthority.url = getOptString(jAuthority, "url"); tinshiftFile->mAuthority.address = getOptString(jAuthority, "address"); tinshiftFile->mAuthority.email = getOptString(jAuthority, "email"); } if (j.contains("links")) { const json jLinks = j["links"]; if (!jLinks.is_array()) { throw ParsingException("links is not an array"); } for (const json &jLink : jLinks) { if (!jLink.is_object()) { throw ParsingException("links[] item is not an object"); } Link link; link.href = getOptString(jLink, "href"); link.rel = getOptString(jLink, "rel"); link.type = getOptString(jLink, "type"); link.title = getOptString(jLink, "title"); tinshiftFile->mLinks.emplace_back(std::move(link)); } } tinshiftFile->mInputCRS = getOptString(j, "input_crs"); tinshiftFile->mOutputCRS = getOptString(j, "output_crs"); const auto jTransformedComponents = getArrayMember(j, "transformed_components"); for (const json &jComp : jTransformedComponents) { if (!jComp.is_string()) { throw ParsingException( "transformed_components[] item is not a string"); } const auto jCompStr = jComp.get(); if (jCompStr == "horizontal") { tinshiftFile->mTransformHorizontalComponent = true; } else if (jCompStr == "vertical") { tinshiftFile->mTransformVerticalComponent = true; } else { throw ParsingException("transformed_components[] = " + jCompStr + " is not handled"); } } const auto jVerticesColumns = getArrayMember(j, "vertices_columns"); int sourceXCol = -1; int sourceYCol = -1; int sourceZCol = -1; int targetXCol = -1; int targetYCol = -1; int targetZCol = -1; int offsetZCol = -1; for (size_t i = 0; i < jVerticesColumns.size(); ++i) { const json &jColumn = jVerticesColumns[i]; if (!jColumn.is_string()) { throw ParsingException("vertices_columns[] item is not a string"); } const auto jColumnStr = jColumn.get(); if (jColumnStr == "source_x") { sourceXCol = static_cast(i); } else if (jColumnStr == "source_y") { sourceYCol = static_cast(i); } else if (jColumnStr == "source_z") { sourceZCol = static_cast(i); } else if (jColumnStr == "target_x") { targetXCol = static_cast(i); } else if (jColumnStr == "target_y") { targetYCol = static_cast(i); } else if (jColumnStr == "target_z") { targetZCol = static_cast(i); } else if (jColumnStr == "offset_z") { offsetZCol = static_cast(i); } } if (sourceXCol < 0) { throw ParsingException( "source_x must be specified in vertices_columns[]"); } if (sourceYCol < 0) { throw ParsingException( "source_y must be specified in vertices_columns[]"); } if (tinshiftFile->mTransformHorizontalComponent) { if (targetXCol < 0) { throw ParsingException( "target_x must be specified in vertices_columns[]"); } if (targetYCol < 0) { throw ParsingException( "target_y must be specified in vertices_columns[]"); } } if (tinshiftFile->mTransformVerticalComponent) { if (offsetZCol >= 0) { // do nothing } else { if (sourceZCol < 0) { throw ParsingException("source_z or delta_z must be specified " "in vertices_columns[]"); } if (targetZCol < 0) { throw ParsingException( "target_z must be specified in vertices_columns[]"); } } } const auto jTrianglesColumns = getArrayMember(j, "triangles_columns"); int idxVertex1Col = -1; int idxVertex2Col = -1; int idxVertex3Col = -1; for (size_t i = 0; i < jTrianglesColumns.size(); ++i) { const json &jColumn = jTrianglesColumns[i]; if (!jColumn.is_string()) { throw ParsingException("triangles_columns[] item is not a string"); } const auto jColumnStr = jColumn.get(); if (jColumnStr == "idx_vertex1") { idxVertex1Col = static_cast(i); } else if (jColumnStr == "idx_vertex2") { idxVertex2Col = static_cast(i); } else if (jColumnStr == "idx_vertex3") { idxVertex3Col = static_cast(i); } } if (idxVertex1Col < 0) { throw ParsingException( "idx_vertex1 must be specified in triangles_columns[]"); } if (idxVertex2Col < 0) { throw ParsingException( "idx_vertex2 must be specified in triangles_columns[]"); } if (idxVertex3Col < 0) { throw ParsingException( "idx_vertex3 must be specified in triangles_columns[]"); } const auto jVertices = getArrayMember(j, "vertices"); tinshiftFile->mVerticesColumnCount = 2; if (tinshiftFile->mTransformHorizontalComponent) tinshiftFile->mVerticesColumnCount += 2; if (tinshiftFile->mTransformVerticalComponent) tinshiftFile->mVerticesColumnCount += 1; tinshiftFile->mVertices.reserve(tinshiftFile->mVerticesColumnCount * jVertices.size()); for (const auto &jVertex : jVertices) { if (!jVertex.is_array()) { throw ParsingException("vertices[] item is not an array"); } if (jVertex.size() != jVerticesColumns.size()) { throw ParsingException( "vertices[] item has not expected number of elements"); } if (!jVertex[sourceXCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } tinshiftFile->mVertices.push_back(jVertex[sourceXCol].get()); if (!jVertex[sourceYCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } tinshiftFile->mVertices.push_back(jVertex[sourceYCol].get()); if (tinshiftFile->mTransformHorizontalComponent) { if (!jVertex[targetXCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } tinshiftFile->mVertices.push_back( jVertex[targetXCol].get()); if (!jVertex[targetYCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } tinshiftFile->mVertices.push_back( jVertex[targetYCol].get()); } if (tinshiftFile->mTransformVerticalComponent) { if (offsetZCol >= 0) { if (!jVertex[offsetZCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } tinshiftFile->mVertices.push_back( jVertex[offsetZCol].get()); } else { if (!jVertex[sourceZCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } const double sourceZ = jVertex[sourceZCol].get(); if (!jVertex[targetZCol].is_number()) { throw ParsingException("vertices[][] item is not a number"); } const double targetZ = jVertex[targetZCol].get(); tinshiftFile->mVertices.push_back(targetZ - sourceZ); } } } const auto jTriangles = getArrayMember(j, "triangles"); tinshiftFile->mTriangles.reserve(jTriangles.size()); for (const auto &jTriangle : jTriangles) { if (!jTriangle.is_array()) { throw ParsingException("triangles[] item is not an array"); } if (jTriangle.size() != jTrianglesColumns.size()) { throw ParsingException( "triangles[] item has not expected number of elements"); } if (jTriangle[idxVertex1Col].type() != json::value_t::number_unsigned) { throw ParsingException("triangles[][] item is not an integer"); } const unsigned vertex1 = jTriangle[idxVertex1Col].get(); if (vertex1 >= jVertices.size()) { throw ParsingException("Invalid value for a vertex index"); } if (jTriangle[idxVertex2Col].type() != json::value_t::number_unsigned) { throw ParsingException("triangles[][] item is not an integer"); } const unsigned vertex2 = jTriangle[idxVertex2Col].get(); if (vertex2 >= jVertices.size()) { throw ParsingException("Invalid value for a vertex index"); } if (jTriangle[idxVertex3Col].type() != json::value_t::number_unsigned) { throw ParsingException("triangles[][] item is not an integer"); } const unsigned vertex3 = jTriangle[idxVertex3Col].get(); if (vertex3 >= jVertices.size()) { throw ParsingException("Invalid value for a vertex index"); } VertexIndices vi; vi.idx1 = vertex1; vi.idx2 = vertex2; vi.idx3 = vertex3; tinshiftFile->mTriangles.push_back(vi); } return tinshiftFile; } // --------------------------------------------------------------------------- static NS_PROJ::QuadTree::RectObj GetBounds(const TINShiftFile &file, bool forward) { NS_PROJ::QuadTree::RectObj rect; rect.minx = std::numeric_limits::max(); rect.miny = std::numeric_limits::max(); rect.maxx = -std::numeric_limits::max(); rect.maxy = -std::numeric_limits::max(); const auto &vertices = file.vertices(); const unsigned colCount = file.verticesColumnCount(); const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0; const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1; for (size_t i = 0; i + colCount - 1 < vertices.size(); i += colCount) { const double x = vertices[i + idxX]; const double y = vertices[i + idxY]; rect.minx = std::min(rect.minx, x); rect.miny = std::min(rect.miny, y); rect.maxx = std::max(rect.maxx, x); rect.maxy = std::max(rect.maxy, y); } return rect; } // --------------------------------------------------------------------------- static std::unique_ptr> BuildQuadTree(const TINShiftFile &file, bool forward) { auto quadtree = std::unique_ptr>( new NS_PROJ::QuadTree::QuadTree(GetBounds(file, forward))); const auto &triangles = file.triangles(); const auto &vertices = file.vertices(); const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0; const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1; const unsigned colCount = file.verticesColumnCount(); for (size_t i = 0; i < triangles.size(); ++i) { const unsigned i1 = triangles[i].idx1; const unsigned i2 = triangles[i].idx2; const unsigned i3 = triangles[i].idx3; const double x1 = vertices[i1 * colCount + idxX]; const double y1 = vertices[i1 * colCount + idxY]; const double x2 = vertices[i2 * colCount + idxX]; const double y2 = vertices[i2 * colCount + idxY]; const double x3 = vertices[i3 * colCount + idxX]; const double y3 = vertices[i3 * colCount + idxY]; NS_PROJ::QuadTree::RectObj rect; rect.minx = x1; rect.miny = y1; rect.maxx = x1; rect.maxy = y1; rect.minx = std::min(rect.minx, x2); rect.miny = std::min(rect.miny, y2); rect.maxx = std::max(rect.maxx, x2); rect.maxy = std::max(rect.maxy, y2); rect.minx = std::min(rect.minx, x3); rect.miny = std::min(rect.miny, y3); rect.maxx = std::max(rect.maxx, x3); rect.maxy = std::max(rect.maxy, y3); quadtree->insert(static_cast(i), rect); } return quadtree; } // --------------------------------------------------------------------------- Evaluator::Evaluator(std::unique_ptr &&fileIn) : mFile(std::move(fileIn)) {} // --------------------------------------------------------------------------- static inline double sqr(double x) { return x * x; } static inline double squared_distance(double x1, double y1, double x2, double y2) { return sqr(x1 - x2) + sqr(y1 - y2); } static double distance_point_segment(double x, double y, double x1, double y1, double x2, double y2, double dist12) { // squared distance of point x/y to line segment x1/y1 -- x2/y2 double t = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / dist12; if (t <= 0.0) { // closest to x1/y1 return squared_distance(x, y, x1, y1); } if (t >= 1.0) { // closest to y2/y2 return squared_distance(x, y, x2, y2); } // closest to line segment x1/y1 -- x2/y2 return squared_distance(x, y, x1 + t * (x2 - x1), y1 + t * (y2 - y1)); } static const TINShiftFile::VertexIndices * FindTriangle(const TINShiftFile &file, const NS_PROJ::QuadTree::QuadTree &quadtree, std::vector &triangleIndices, double x, double y, bool forward, double &lambda1, double &lambda2, double &lambda3) { #define USE_QUADTREE #ifdef USE_QUADTREE triangleIndices.clear(); quadtree.search(x, y, triangleIndices); #endif const auto &triangles = file.triangles(); const auto &vertices = file.vertices(); constexpr double EPS = 1e-10; const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0; const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1; const unsigned colCount = file.verticesColumnCount(); #ifdef USE_QUADTREE for (unsigned i : triangleIndices) #else for (size_t i = 0; i < triangles.size(); ++i) #endif { const auto &triangle = triangles[i]; const unsigned i1 = triangle.idx1; const unsigned i2 = triangle.idx2; const unsigned i3 = triangle.idx3; const double x1 = vertices[i1 * colCount + idxX]; const double y1 = vertices[i1 * colCount + idxY]; const double x2 = vertices[i2 * colCount + idxX]; const double y2 = vertices[i2 * colCount + idxY]; const double x3 = vertices[i3 * colCount + idxX]; const double y3 = vertices[i3 * colCount + idxY]; const double det_T = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3); lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det_T; lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det_T; if (lambda1 >= -EPS && lambda1 <= 1 + EPS && lambda2 >= -EPS && lambda2 <= 1 + EPS) { lambda3 = 1 - lambda1 - lambda2; if (lambda3 >= 0) { return ▵ } } } if (file.fallbackStrategy() == FALLBACK_NONE) { return nullptr; } // find triangle with the shortest squared distance // // TODO: extend quadtree to support nearest neighbor search double closest_dist = std::numeric_limits::infinity(); double closest_dist2 = std::numeric_limits::infinity(); size_t closest_i = 0; for (size_t i = 0; i < triangles.size(); ++i) { const auto &triangle = triangles[i]; const unsigned i1 = triangle.idx1; const unsigned i2 = triangle.idx2; const unsigned i3 = triangle.idx3; const double x1 = vertices[i1 * colCount + idxX]; const double y1 = vertices[i1 * colCount + idxY]; const double x2 = vertices[i2 * colCount + idxX]; const double y2 = vertices[i2 * colCount + idxY]; const double x3 = vertices[i3 * colCount + idxX]; const double y3 = vertices[i3 * colCount + idxY]; // don't check this triangle if the query point plusminus the // currently closest found distance is outside the triangle's AABB if (x + closest_dist < std::min(x1, std::min(x2, x3)) || x - closest_dist > std::max(x1, std::max(x2, x3)) || y + closest_dist < std::min(y1, std::min(y2, y3)) || y - closest_dist > std::max(y1, std::max(y2, y3))) { continue; } double dist12 = squared_distance(x1, y1, x2, y2); double dist23 = squared_distance(x2, y2, x3, y3); double dist13 = squared_distance(x1, y1, x3, y3); if (dist12 < EPS || dist23 < EPS || dist13 < EPS) { // do not use degenerate triangles continue; } double dist2; if (file.fallbackStrategy() == FALLBACK_NEAREST_SIDE) { // we don't know whether the points of the triangle are given // clockwise or counter-clockwise, so we have to check the distance // of the point to all three sides of the triangle dist2 = distance_point_segment(x, y, x1, y1, x2, y2, dist12); if (dist2 < closest_dist2) { closest_dist2 = dist2; closest_dist = sqrt(dist2); closest_i = i; } dist2 = distance_point_segment(x, y, x2, y2, x3, y3, dist23); if (dist2 < closest_dist2) { closest_dist2 = dist2; closest_dist = sqrt(dist2); closest_i = i; } dist2 = distance_point_segment(x, y, x1, y1, x3, y3, dist13); if (dist2 < closest_dist2) { closest_dist2 = dist2; closest_dist = sqrt(dist2); closest_i = i; } } else if (file.fallbackStrategy() == FALLBACK_NEAREST_CENTROID) { double c_x = (x1 + x2 + x3) / 3.0; double c_y = (y1 + y2 + y3) / 3.0; dist2 = squared_distance(x, y, c_x, c_y); if (dist2 < closest_dist2) { closest_dist2 = dist2; closest_dist = sqrt(dist2); closest_i = i; } } } if (std::isinf(closest_dist)) { // nothing was found due to empty triangle list or only degenerate // triangles return nullptr; } const auto &triangle = triangles[closest_i]; const unsigned i1 = triangle.idx1; const unsigned i2 = triangle.idx2; const unsigned i3 = triangle.idx3; const double x1 = vertices[i1 * colCount + idxX]; const double y1 = vertices[i1 * colCount + idxY]; const double x2 = vertices[i2 * colCount + idxX]; const double y2 = vertices[i2 * colCount + idxY]; const double x3 = vertices[i3 * colCount + idxX]; const double y3 = vertices[i3 * colCount + idxY]; const double det_T = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3); if (std::fabs(det_T) < EPS) { // the nearest triangle is degenerate return nullptr; } lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det_T; lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det_T; lambda3 = 1 - lambda1 - lambda2; return ▵ } // --------------------------------------------------------------------------- bool Evaluator::forward(double x, double y, double z, double &x_out, double &y_out, double &z_out) { if (!mQuadTreeForward) mQuadTreeForward = BuildQuadTree(*(mFile.get()), true); double lambda1 = 0.0; double lambda2 = 0.0; double lambda3 = 0.0; const auto *triangle = FindTriangle(*mFile, *mQuadTreeForward, mTriangleIndices, x, y, true, lambda1, lambda2, lambda3); if (!triangle) return false; const auto &vertices = mFile->vertices(); const unsigned i1 = triangle->idx1; const unsigned i2 = triangle->idx2; const unsigned i3 = triangle->idx3; const unsigned colCount = mFile->verticesColumnCount(); if (mFile->transformHorizontalComponent()) { constexpr unsigned idxTargetColX = 2; constexpr unsigned idxTargetColY = 3; x_out = vertices[i1 * colCount + idxTargetColX] * lambda1 + vertices[i2 * colCount + idxTargetColX] * lambda2 + vertices[i3 * colCount + idxTargetColX] * lambda3; y_out = vertices[i1 * colCount + idxTargetColY] * lambda1 + vertices[i2 * colCount + idxTargetColY] * lambda2 + vertices[i3 * colCount + idxTargetColY] * lambda3; } else { x_out = x; y_out = y; } if (mFile->transformVerticalComponent()) { const int idxCol = mFile->transformHorizontalComponent() ? 4 : 2; z_out = z + (vertices[i1 * colCount + idxCol] * lambda1 + vertices[i2 * colCount + idxCol] * lambda2 + vertices[i3 * colCount + idxCol] * lambda3); } else { z_out = z; } return true; } // --------------------------------------------------------------------------- bool Evaluator::inverse(double x, double y, double z, double &x_out, double &y_out, double &z_out) { NS_PROJ::QuadTree::QuadTree *quadtree; if (!mFile->transformHorizontalComponent() && mFile->transformVerticalComponent()) { if (!mQuadTreeForward) mQuadTreeForward = BuildQuadTree(*(mFile.get()), true); quadtree = mQuadTreeForward.get(); } else { if (!mQuadTreeInverse) mQuadTreeInverse = BuildQuadTree(*(mFile.get()), false); quadtree = mQuadTreeInverse.get(); } double lambda1 = 0.0; double lambda2 = 0.0; double lambda3 = 0.0; const auto *triangle = FindTriangle(*mFile, *quadtree, mTriangleIndices, x, y, false, lambda1, lambda2, lambda3); if (!triangle) return false; const auto &vertices = mFile->vertices(); const unsigned i1 = triangle->idx1; const unsigned i2 = triangle->idx2; const unsigned i3 = triangle->idx3; const unsigned colCount = mFile->verticesColumnCount(); if (mFile->transformHorizontalComponent()) { constexpr unsigned idxTargetColX = 0; constexpr unsigned idxTargetColY = 1; x_out = vertices[i1 * colCount + idxTargetColX] * lambda1 + vertices[i2 * colCount + idxTargetColX] * lambda2 + vertices[i3 * colCount + idxTargetColX] * lambda3; y_out = vertices[i1 * colCount + idxTargetColY] * lambda1 + vertices[i2 * colCount + idxTargetColY] * lambda2 + vertices[i3 * colCount + idxTargetColY] * lambda3; } else { x_out = x; y_out = y; } if (mFile->transformVerticalComponent()) { const int idxCol = mFile->transformHorizontalComponent() ? 4 : 2; z_out = z - (vertices[i1 * colCount + idxCol] * lambda1 + vertices[i2 * colCount + idxCol] * lambda2 + vertices[i3 * colCount + idxCol] * lambda3); } else { z_out = z; } return true; } } // namespace TINSHIFT_NAMESPACE proj-9.8.1/src/transformations/defmodel.hpp000664 001750 001750 00000056205 15166171715 020773 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Functionality related to deformation model * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2020, Even Rouault, * * 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. *****************************************************************************/ /** This file implements the gridded deformation model proposol of * https://docs.google.com/document/d/1wiyrAmzqh8MZlzHSp3wf594Ob_M1LeFtDA5swuzvLZY * It is written in a generic way, independent of the rest of PROJ * infrastructure. * * Verbose debugging info can be turned on by setting the DEBUG_DEFMODEL macro */ #ifndef DEFMODEL_HPP #define DEFMODEL_HPP #ifdef PROJ_COMPILATION #include "proj/internal/include_nlohmann_json.hpp" #else #include "nlohmann/json.hpp" #endif #include #include #include #include #include #include #include #ifndef DEFORMATON_MODEL_NAMESPACE #define DEFORMATON_MODEL_NAMESPACE DeformationModel #endif #include "defmodel_exceptions.hpp" namespace DEFORMATON_MODEL_NAMESPACE { using json = nlohmann::json; // --------------------------------------------------------------------------- /** Spatial extent as a bounding box. */ class SpatialExtent { public: /** Parse the provided object as an extent. * * @throws ParsingException in case of error. */ static SpatialExtent parse(const json &j); double minx() const { return mMinx; } double miny() const { return mMiny; } double maxx() const { return mMaxx; } double maxy() const { return mMaxy; } double minxNormalized(bool bIsGeographic) const { return bIsGeographic ? mMinxRad : mMinx; } double minyNormalized(bool bIsGeographic) const { return bIsGeographic ? mMinyRad : mMiny; } double maxxNormalized(bool bIsGeographic) const { return bIsGeographic ? mMaxxRad : mMaxx; } double maxyNormalized(bool bIsGeographic) const { return bIsGeographic ? mMaxyRad : mMaxy; } protected: friend class MasterFile; friend class Component; SpatialExtent() = default; private: double mMinx = std::numeric_limits::quiet_NaN(); double mMiny = std::numeric_limits::quiet_NaN(); double mMaxx = std::numeric_limits::quiet_NaN(); double mMaxy = std::numeric_limits::quiet_NaN(); double mMinxRad = std::numeric_limits::quiet_NaN(); double mMinyRad = std::numeric_limits::quiet_NaN(); double mMaxxRad = std::numeric_limits::quiet_NaN(); double mMaxyRad = std::numeric_limits::quiet_NaN(); }; // --------------------------------------------------------------------------- /** Epoch */ class Epoch { public: /** Constructor from a ISO 8601 date-time. May throw ParsingException */ explicit Epoch(const std::string &dt = std::string()); /** Return ISO 8601 date-time */ const std::string &toString() const { return mDt; } /** Return decimal year */ double toDecimalYear() const; private: std::string mDt{}; double mDecimalYear = 0; }; // --------------------------------------------------------------------------- /** Component of a deformation model. */ class Component { public: /** Parse the provided object as a component. * * @throws ParsingException in case of error. */ static Component parse(const json &j); /** Get a text description of the component. */ const std::string &description() const { return mDescription; } /** Get the region within which the component is defined. Outside this * region the component evaluates to 0. */ const SpatialExtent &extent() const { return mSpatialExtent; } /** Get the displacement parameters defined by the component, one of * "none", "horizontal", "vertical", and "3d". The "none" option allows * for a component which defines uncertainty with different grids to those * defining displacement. */ const std::string &displacementType() const { return mDisplacementType; } /** Get the uncertainty parameters defined by the component, * one of "none", "horizontal", "vertical", "3d". */ const std::string &uncertaintyType() const { return mUncertaintyType; } /** Get the horizontal uncertainty to use if it is not defined explicitly * in the spatial model. */ double horizontalUncertainty() const { return mHorizontalUncertainty; } /** Get the vertical uncertainty to use if it is not defined explicitly in * the spatial model. */ double verticalUncertainty() const { return mVerticalUncertainty; } struct SpatialModel { /** Specifies the type of the spatial model data file. Initially it * is proposed that only "GeoTIFF" is supported. */ std::string type{}; /** How values in model should be interpolated. This proposal will * support "bilinear" and "geocentric_bilinear". */ std::string interpolationMethod{}; /** Specifies location of the spatial model GeoTIFF file relative to * the master JSON file. */ std::string filename{}; /** A hex encoded MD5 checksum of the grid file that can be used to * validate that it is the correct version of the file. */ std::string md5Checksum{}; }; /** Get the spatial model. */ const SpatialModel &spatialModel() const { return mSpatialModel; } /** Generic type for a type function */ struct TimeFunction { std::string type{}; virtual ~TimeFunction(); virtual double evaluateAt(double dt) const = 0; protected: TimeFunction() = default; }; struct ConstantTimeFunction : public TimeFunction { virtual double evaluateAt(double dt) const override; }; struct VelocityTimeFunction : public TimeFunction { /** Date/time at which the velocity function is zero. */ Epoch referenceEpoch{}; virtual double evaluateAt(double dt) const override; }; struct StepTimeFunction : public TimeFunction { /** Epoch at which the step function transitions from 0 to 1. */ Epoch stepEpoch{}; virtual double evaluateAt(double dt) const override; }; struct ReverseStepTimeFunction : public TimeFunction { /** Epoch at which the reverse step function transitions from 1. to 0 */ Epoch stepEpoch{}; virtual double evaluateAt(double dt) const override; }; struct PiecewiseTimeFunction : public TimeFunction { /** One of "zero", "constant", and "linear", defines the behavior of * the function before the first defined epoch */ std::string beforeFirst{}; /** One of "zero", "constant", and "linear", defines the behavior of * the function after the last defined epoch */ std::string afterLast{}; struct EpochScaleFactorTuple { /** Defines the date/time of the data point. */ Epoch epoch{}; /** Function value at the above epoch */ double scaleFactor = std::numeric_limits::quiet_NaN(); }; /** A sorted array data points each defined by two elements. * The array is sorted in order of increasing epoch. * Note: where the time function includes a step it is represented by * two consecutive data points with the same epoch. The first defines * the scale factor that applies before the epoch and the second the * scale factor that applies after the epoch. */ std::vector model{}; virtual double evaluateAt(double dt) const override; }; struct ExponentialTimeFunction : public TimeFunction { /** The date/time at which the exponential decay starts. */ Epoch referenceEpoch{}; /** The date/time at which the exponential decay ends. */ Epoch endEpoch{}; /** The relaxation constant in years. */ double relaxationConstant = std::numeric_limits::quiet_NaN(); /** The scale factor that applies before the reference epoch. */ double beforeScaleFactor = std::numeric_limits::quiet_NaN(); /** Initial scale factor. */ double initialScaleFactor = std::numeric_limits::quiet_NaN(); /** The scale factor the exponential function approaches. */ double finalScaleFactor = std::numeric_limits::quiet_NaN(); virtual double evaluateAt(double dt) const override; }; /** Get the time function. */ const TimeFunction *timeFunction() const { return mTimeFunction.get(); } private: Component() = default; std::string mDescription{}; SpatialExtent mSpatialExtent{}; std::string mDisplacementType{}; std::string mUncertaintyType{}; double mHorizontalUncertainty = std::numeric_limits::quiet_NaN(); double mVerticalUncertainty = std::numeric_limits::quiet_NaN(); SpatialModel mSpatialModel{}; std::unique_ptr mTimeFunction{}; }; Component::TimeFunction::~TimeFunction() = default; // --------------------------------------------------------------------------- /** Master file of a deformation model. */ class MasterFile { public: /** Parse the provided serialized JSON content and return an object. * * @throws ParsingException in case of error. */ static std::unique_ptr parse(const std::string &text); /** Get file type. Should always be "deformation_model_master_file" */ const std::string &fileType() const { return mFileType; } /** Get the version of the format. At time of writing, only "1.0" is known */ const std::string &formatVersion() const { return mFormatVersion; } /** Get brief descriptive name of the deformation model. */ const std::string &name() const { return mName; } /** Get a string identifying the version of the deformation model. * The format for specifying version is defined by the agency * responsible for the deformation model. */ const std::string &version() const { return mVersion; } /** Get a string identifying the license of the file. * e.g "Create Commons Attribution 4.0 International" */ const std::string &license() const { return mLicense; } /** Get a text description of the model. Intended to be longer than name() */ const std::string &description() const { return mDescription; } /** Get a text description of the model. Intended to be longer than name() */ const std::string &publicationDate() const { return mPublicationDate; } /** Basic information on the agency responsible for the model. */ struct Authority { std::string name{}; std::string url{}; std::string address{}; std::string email{}; }; /** Get basic information on the agency responsible for the model. */ const Authority &authority() const { return mAuthority; } /** Hyperlink related to the model. */ struct Link { /** URL holding the information */ std::string href{}; /** Relationship to the dataset. e.g. "about", "source", "license", * "metadata" */ std::string rel{}; /** Mime type */ std::string type{}; /** Description of the link */ std::string title{}; }; /** Get links to related information. */ const std::vector links() const { return mLinks; } /** Get a string identifying the source CRS. That is the coordinate * reference system to which the deformation model applies. Typically * "EPSG:XXXX" */ const std::string &sourceCRS() const { return mSourceCRS; } /** Get a string identifying the target CRS. That is, for a time * dependent coordinate transformation, the coordinate reference * system resulting from applying the deformation. * Typically "EPSG:XXXX" */ const std::string &targetCRS() const { return mTargetCRS; } /** Get a string identifying the definition CRS. That is, the * coordinate reference system used to define the component spatial * models. Typically "EPSG:XXXX" */ const std::string &definitionCRS() const { return mDefinitionCRS; } /** Get the nominal reference epoch of the deformation model. Formatted * as a ISO-8601 date-time. This is not necessarily used to calculate * the deformation model - each component defines its own time function. */ const std::string &referenceEpoch() const { return mReferenceEpoch; } /** Get the epoch at which the uncertainties of the deformation model * are calculated. Formatted as a ISO-8601 date-time. */ const std::string &uncertaintyReferenceEpoch() const { return mUncertaintyReferenceEpoch; } /** Unit of horizontal offsets. Only "metre" and "degree" are supported. */ const std::string &horizontalOffsetUnit() const { return mHorizontalOffsetUnit; } /** Unit of vertical offsets. Only "metre" is supported. */ const std::string &verticalOffsetUnit() const { return mVerticalOffsetUnit; } /** Type of horizontal uncertainty. e.g "circular 95% confidence limit" */ const std::string &horizontalUncertaintyType() const { return mHorizontalUncertaintyType; } /** Unit of horizontal uncertainty. Only "metre" is supported. */ const std::string &horizontalUncertaintyUnit() const { return mHorizontalUncertaintyUnit; } /** Type of vertical uncertainty. e.g "circular 95% confidence limit" */ const std::string &verticalUncertaintyType() const { return mVerticalUncertaintyType; } /** Unit of vertical uncertainty. Only "metre" is supported. */ const std::string &verticalUncertaintyUnit() const { return mVerticalUncertaintyUnit; } /** Defines how the horizontal offsets are applied to geographic * coordinates. Only "addition" and "geocentric" are supported */ const std::string &horizontalOffsetMethod() const { return mHorizontalOffsetMethod; } /** Get the region within which the deformation model is defined. * It cannot be calculated outside this region */ const SpatialExtent &extent() const { return mSpatialExtent; } /** Defines the range of times for which the model is valid, specified * by a first and a last value. The deformation model is undefined for * dates outside this range. */ struct TimeExtent { Epoch first{}; Epoch last{}; }; /** Get the range of times for which the model is valid. */ const TimeExtent &timeExtent() const { return mTimeExtent; } /** Get an array of the components comprising the deformation model. */ const std::vector &components() const { return mComponents; } private: MasterFile() = default; std::string mFileType{}; std::string mFormatVersion{}; std::string mName{}; std::string mVersion{}; std::string mLicense{}; std::string mDescription{}; std::string mPublicationDate{}; Authority mAuthority{}; std::vector mLinks{}; std::string mSourceCRS{}; std::string mTargetCRS{}; std::string mDefinitionCRS{}; std::string mReferenceEpoch{}; std::string mUncertaintyReferenceEpoch{}; std::string mHorizontalOffsetUnit{}; std::string mVerticalOffsetUnit{}; std::string mHorizontalUncertaintyType{}; std::string mHorizontalUncertaintyUnit{}; std::string mVerticalUncertaintyType{}; std::string mVerticalUncertaintyUnit{}; std::string mHorizontalOffsetMethod{}; SpatialExtent mSpatialExtent{}; TimeExtent mTimeExtent{}; std::vector mComponents{}; }; // --------------------------------------------------------------------------- /** Prototype for a Grid used by GridSet. Intended to be implemented * by user code */ struct GridPrototype { double minx = 0; double miny = 0; double resx = 0; double resy = 0; int width = 0; int height = 0; // cppcheck-suppress functionStatic bool getLongLatOffset(int /*ix*/, int /*iy*/, double & /*longOffsetRadian*/, double & /*latOffsetRadian*/) const { throw UnimplementedException("getLongLatOffset unimplemented"); } // cppcheck-suppress functionStatic bool getZOffset(int /*ix*/, int /*iy*/, double & /*zOffset*/) const { throw UnimplementedException("getZOffset unimplemented"); } // cppcheck-suppress functionStatic bool getEastingNorthingOffset(int /*ix*/, int /*iy*/, double & /*eastingOffset*/, double & /*northingOffset*/) const { throw UnimplementedException("getEastingNorthingOffset unimplemented"); } // cppcheck-suppress functionStatic bool getLongLatZOffset(int /*ix*/, int /*iy*/, double & /*longOffsetRadian*/, double & /*latOffsetRadian*/, double & /*zOffset*/) const { throw UnimplementedException("getLongLatZOffset unimplemented"); #if 0 return getLongLatOffset(ix, iy, longOffsetRadian, latOffsetRadian) && getZOffset(ix, iy, zOffset); #endif } // cppcheck-suppress functionStatic bool getEastingNorthingZOffset(int /*ix*/, int /*iy*/, double & /*eastingOffset*/, double & /*northingOffset*/, double & /*zOffset*/) const { throw UnimplementedException("getEastingNorthingOffset unimplemented"); #if 0 return getEastingNorthingOffset(ix, iy, eastingOffset, northingOffset) && getZOffset(ix, iy, zOffset); #endif } #ifdef DEBUG_DEFMODEL std::string name() const { throw UnimplementedException("name() unimplemented"); } #endif }; // --------------------------------------------------------------------------- /** Prototype for a GridSet used by EvaluatorIface. Intended to be implemented * by user code */ template struct GridSetPrototype { // The return pointer should remain "stable" over time for a given grid // of a GridSet. // cppcheck-suppress functionStatic const Grid *gridAt(double /*x */, double /* y */) { throw UnimplementedException("gridAt unimplemented"); } }; // --------------------------------------------------------------------------- /** Prototype for a EvaluatorIface used by Evaluator. Intended to be implemented * by user code */ template > struct EvaluatorIfacePrototype { std::unique_ptr open(const std::string & /* filename*/) { throw UnimplementedException("open unimplemented"); } // cppcheck-suppress functionStatic void geographicToGeocentric(double /* lam */, double /* phi */, double /* height*/, double /* a */, double /* b */, double /*es*/, double & /* X */, double & /* Y */, double & /* Z */) { throw UnimplementedException("geographicToGeocentric unimplemented"); } // cppcheck-suppress functionStatic void geocentricToGeographic(double /* X */, double /* Y */, double /* Z */, double /* a */, double /* b */, double /*es*/, double & /* lam */, double & /* phi */, double & /* height*/) { throw UnimplementedException("geocentricToGeographic unimplemented"); } // cppcheck-suppress functionStatic bool isGeographicCRS(const std::string & /* crsDef */) { throw UnimplementedException("isGeographicCRS unimplemented"); } #ifdef DEBUG_DEFMODEL void log(const std::string & /* msg */) { throw UnimplementedException("log unimplemented"); } #endif }; // --------------------------------------------------------------------------- /** Internal class to offer caching services over a Component */ template struct ComponentEx; // --------------------------------------------------------------------------- /** Class to evaluate the transformation of a coordinate */ template , class EvaluatorIface = EvaluatorIfacePrototype<>> class Evaluator { public: /** Constructor. May throw EvaluatorException */ explicit Evaluator(std::unique_ptr &&model, EvaluatorIface &iface, double a, double b); /** Evaluate displacement of a position given by (x,y,z,t) and * return it in (x_out,y_out_,z_out). * For geographic CRS (only supported at that time), x must be a * longitude, and y a latitude. */ bool forward(EvaluatorIface &iface, double x, double y, double z, double t, double &x_out, double &y_out, double &z_out) { return forward(iface, x, y, z, t, false, x_out, y_out, z_out); } /** Apply inverse transformation. */ bool inverse(EvaluatorIface &iface, double x, double y, double z, double t, double &x_out, double &y_out, double &z_out); /** Clear grid cache */ void clearGridCache(); /** Return whether the definition CRS is a geographic CRS */ bool isGeographicCRS() const { return mIsGeographicCRS; } private: std::unique_ptr mModel; const double mA; const double mB; const double mEs; const bool mIsHorizontalUnitDegree; /* degree vs metre */ const bool mIsAddition; /* addition vs geocentric */ const bool mIsGeographicCRS; bool forward(EvaluatorIface &iface, double x, double y, double z, double t, bool forInverseComputation, double &x_out, double &y_out, double &z_out); std::vector>> mComponents{}; }; // --------------------------------------------------------------------------- } // namespace DEFORMATON_MODEL_NAMESPACE // --------------------------------------------------------------------------- #include "defmodel_impl.hpp" #endif // DEFMODEL_HPP proj-9.8.1/src/wkt1_generated_parser.c000664 001750 001750 00000176431 15166171715 017702 0ustar00eveneven000000 000000 /* A Bison parser, made by GNU Bison 3.5.1. */ /* Bison implementation for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Undocumented macros, especially those whose name start with YY_, are private implementation details. Do not rely on them. */ /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ #define YYBISON_VERSION "3.5.1" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 1 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* Substitute the variable and function names. */ #define yyparse pj_wkt1_parse #define yylex pj_wkt1_lex #define yyerror pj_wkt1_error #define yydebug pj_wkt1_debug /* #define yynerrs pj_wkt1_nerrs */ /* First part of user prologue. */ /****************************************************************************** * Project: PROJ * Purpose: WKT1 parser grammar * Author: Even Rouault, * ****************************************************************************** * Copyright (c) 2013-2018 Even Rouault, * * 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 "wkt1_parser.h" # ifndef YY_CAST # ifdef __cplusplus # define YY_CAST(Type, Val) static_cast (Val) # define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) # else # define YY_CAST(Type, Val) ((Type) (Val)) # define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) # endif # endif # ifndef YY_NULLPTR # if defined __cplusplus # if 201103L <= __cplusplus # define YY_NULLPTR nullptr # else # define YY_NULLPTR 0 # endif # else # define YY_NULLPTR ((void*)0) # endif # endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE # undef YYERROR_VERBOSE # define YYERROR_VERBOSE 1 #else # define YYERROR_VERBOSE 1 #endif /* Use api.header.include to #include this header instead of duplicating it here. */ #ifndef YY_PJ_WKT1_WKT1_GENERATED_PARSER_H_INCLUDED # define YY_PJ_WKT1_WKT1_GENERATED_PARSER_H_INCLUDED /* Debug traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif #if YYDEBUG extern int pj_wkt1_debug; #endif /* Token type. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { END = 0, T_PARAM_MT = 258, T_CONCAT_MT = 259, T_INVERSE_MT = 260, T_PASSTHROUGH_MT = 261, T_PROJCS = 262, T_PROJECTION = 263, T_GEOGCS = 264, T_DATUM = 265, T_SPHEROID = 266, T_PRIMEM = 267, T_UNIT = 268, T_LINUNIT = 269, T_GEOCCS = 270, T_AUTHORITY = 271, T_VERT_CS = 272, T_VERTCS = 273, T_VERT_DATUM = 274, T_VDATUM = 275, T_COMPD_CS = 276, T_AXIS = 277, T_TOWGS84 = 278, T_FITTED_CS = 279, T_LOCAL_CS = 280, T_LOCAL_DATUM = 281, T_PARAMETER = 282, T_EXTENSION = 283, T_STRING = 284, T_NUMBER = 285, T_IDENTIFIER = 286 }; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef int YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif int pj_wkt1_parse (pj_wkt1_parse_context *context); #endif /* !YY_PJ_WKT1_WKT1_GENERATED_PARSER_H_INCLUDED */ #ifdef short # undef short #endif /* On compilers that do not define __PTRDIFF_MAX__ etc., make sure and (if available) are included so that the code can choose integer types of a good width. */ #ifndef __PTRDIFF_MAX__ # include /* INFRINGES ON USER NAME SPACE */ # if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YY_STDINT_H # endif #endif /* Narrow types that promote to a signed type and that can represent a signed or unsigned integer of at least N bits. In tables they can save space and decrease cache pressure. Promoting to a signed type helps avoid bugs in integer arithmetic. */ #ifdef __INT_LEAST8_MAX__ typedef __INT_LEAST8_TYPE__ yytype_int8; #elif defined YY_STDINT_H typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif #ifdef __INT_LEAST16_MAX__ typedef __INT_LEAST16_TYPE__ yytype_int16; #elif defined YY_STDINT_H typedef int_least16_t yytype_int16; #else typedef short yytype_int16; #endif #if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ typedef __UINT_LEAST8_TYPE__ yytype_uint8; #elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ && UINT_LEAST8_MAX <= INT_MAX) typedef uint_least8_t yytype_uint8; #elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX typedef unsigned char yytype_uint8; #else typedef short yytype_uint8; #endif #if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ typedef __UINT_LEAST16_TYPE__ yytype_uint16; #elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ && UINT_LEAST16_MAX <= INT_MAX) typedef uint_least16_t yytype_uint16; #elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX typedef unsigned short yytype_uint16; #else typedef int yytype_uint16; #endif #ifndef YYPTRDIFF_T # if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ # define YYPTRDIFF_T __PTRDIFF_TYPE__ # define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ # elif defined PTRDIFF_MAX # ifndef ptrdiff_t # include /* INFRINGES ON USER NAME SPACE */ # endif # define YYPTRDIFF_T ptrdiff_t # define YYPTRDIFF_MAXIMUM PTRDIFF_MAX # else # define YYPTRDIFF_T long # define YYPTRDIFF_MAXIMUM LONG_MAX # endif #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif #define YYSIZE_MAXIMUM \ YY_CAST (YYPTRDIFF_T, \ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ ? YYPTRDIFF_MAXIMUM \ : YY_CAST (YYSIZE_T, -1))) #define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) /* Stored state numbers (used for stacks). */ typedef yytype_int16 yy_state_t; /* State numbers in computations. */ typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif #ifndef YY_ATTRIBUTE_PURE # if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) # else # define YY_ATTRIBUTE_PURE # endif #endif #ifndef YY_ATTRIBUTE_UNUSED # if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else # define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YYUSE(E) ((void) (E)) #else # define YYUSE(E) /* empty */ #endif #if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif #if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ # define YY_IGNORE_USELESS_CAST_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") # define YY_IGNORE_USELESS_CAST_END \ _Pragma ("GCC diagnostic pop") #endif #ifndef YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_END #endif #define YY_ASSERT(E) ((void) (0 && (E))) #if ! defined yyoverflow || YYERROR_VERBOSE /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ /* Use EXIT_SUCCESS as a witness for stdlib.h. */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's 'empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* ! defined yyoverflow || YYERROR_VERBOSE */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif # endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 32 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 255 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 37 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 72 /* YYNRULES -- Number of rules. */ #define YYNRULES 114 /* YYNSTATES -- Number of states. */ #define YYNSTATES 289 #define YYUNDEFTOK 2 #define YYMAXUTOK 286 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ (0 <= (YYX) && (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ static const yytype_int8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 33, 35, 2, 2, 36, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 32, 2, 34, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }; #if YYDEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { 0, 81, 81, 93, 93, 96, 99, 99, 102, 102, 102, 102, 105, 108, 110, 111, 114, 116, 117, 120, 123, 127, 132, 132, 132, 132, 132, 132, 135, 136, 139, 139, 143, 147, 148, 151, 152, 154, 155, 156, 157, 159, 160, 163, 166, 169, 174, 176, 177, 178, 179, 180, 183, 185, 186, 187, 188, 191, 195, 202, 202, 205, 208, 211, 214, 217, 220, 223, 226, 227, 228, 229, 232, 235, 238, 239, 242, 244, 245, 246, 249, 251, 251, 254, 256, 257, 258, 261, 264, 267, 272, 272, 274, 277, 282, 285, 287, 290, 293, 296, 299, 302, 305, 308, 311, 314, 317, 320, 323, 326, 328, 330, 331, 332, 335 }; #endif #if YYDEBUG || YYERROR_VERBOSE || 1 /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "\"end of string\"", "error", "$undefined", "\"PARAM_MT\"", "\"CONCAT_MT\"", "\"INVERSE_MT\"", "\"PASSTHROUGH_MT\"", "\"PROJCS\"", "\"PROJECTION\"", "\"GEOGCS\"", "\"DATUM\"", "\"SPHEROID\"", "\"PRIMEM\"", "\"UNIT\"", "\"LINUNIT\"", "\"GEOCCS\"", "\"AUTHORITY\"", "\"VERT_CS\"", "\"VERTCS\"", "\"VERT_DATUM\"", "\"VDATUM\"", "\"COMPD_CS\"", "\"AXIS\"", "\"TOWGS84\"", "\"FITTED_CS\"", "\"LOCAL_CS\"", "\"LOCAL_DATUM\"", "\"PARAMETER\"", "\"EXTENSION\"", "\"string\"", "\"number\"", "\"identifier\"", "'['", "'('", "']'", "')'", "','", "$accept", "input", "begin_node", "begin_node_name", "end_node", "math_transform", "param_mt", "parameter", "opt_parameter_list", "concat_mt", "opt_math_transform_list", "inv_mt", "passthrough_mt", "integer", "coordinate_system", "horz_cs_with_opt_esri_vertcs", "horz_cs", "projected_cs", "opt_parameter_list_linear_unit", "parameter_list_linear_unit", "opt_twin_axis_extension_authority", "opt_authority", "extension", "projection", "geographic_cs", "linunit", "opt_linunit_or_twin_axis_extension_authority", "datum", "opt_towgs84_authority_extension", "spheroid", "semi_major_axis", "inverse_flattening", "prime_meridian", "longitude", "angular_unit", "linear_unit", "unit", "conversion_factor", "geocentric_cs", "opt_three_axis_extension_authority", "three_axis", "authority", "vert_cs", "esri_vert_cs", "opt_axis_authority", "vert_datum", "vdatum_or_datum", "vdatum", "opt_extension_authority", "datum_type", "compd_cs", "head_cs", "tail_cs", "twin_axis", "axis", "towgs84", "towgs84_parameters", "three_parameters", "seven_parameters", "dx", "dy", "dz", "ex", "ey", "ez", "ppm", "fitted_cs", "to_base", "base_cs", "local_cs", "opt_axis_list_authority", "local_datum", YY_NULLPTR }; #endif # ifdef YYPRINT /* YYTOKNUM[NUM] -- (External) token number corresponding to the (internal) symbol number NUM (which must be that of a token). */ static const yytype_int16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 91, 40, 93, 41, 44 }; # endif #define YYPACT_NINF (-131) #define yypact_value_is_default(Yyn) \ ((Yyn) == YYPACT_NINF) #define YYTABLE_NINF (-1) #define yytable_value_is_error(Yyn) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ static const yytype_int16 yypact[] = { 97, 33, 33, 33, 33, 33, 33, 33, 33, 10, -131, -131, 2, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, 40, 12, 38, 47, 69, 93, 95, 96, 89, -131, 117, -131, 102, 126, 126, 123, 1, 22, 135, -131, -131, 119, -131, -131, 107, 33, 110, 111, 33, 113, 33, -131, 114, -131, -131, 115, 33, 33, 33, 33, -131, -131, -131, -131, -131, 118, 33, 121, 150, 125, 147, 147, 127, 149, 55, 6, 91, 128, 135, 135, 136, 97, 129, 149, 33, 131, 157, 33, 134, 137, 141, 33, 138, -131, -131, 33, 139, 55, -131, -131, -131, -131, 140, 145, 55, 142, 55, -131, 143, -131, 55, 141, 144, 151, 6, 33, 152, 153, 149, 149, -131, 140, 154, 18, 55, 155, 6, -131, 19, 55, 128, -131, 135, 55, -131, 135, -131, 151, 159, 161, 55, 156, 158, 75, 55, 163, 160, -131, 162, 55, 165, 33, 33, -131, 151, -131, 169, -131, -131, 33, 151, -131, -131, -131, 142, -131, 55, 55, 164, -131, -131, 65, 55, 171, 33, 151, -131, 140, -131, -131, 151, 14, 55, 65, 55, -131, -131, 151, 166, 167, -131, 55, 170, -131, -131, -131, -131, 18, 55, 151, -131, 140, 172, -131, -131, 173, 175, -131, -131, 55, 33, 151, 151, -131, 140, -131, 151, 140, -131, 174, -131, 55, 178, 181, -131, 184, -131, 164, -131, -131, -131, 159, 98, -131, 55, -131, -131, 179, -131, 180, -131, -131, -131, -131, -131, 159, -131, 55, 55, 55, -131, -131, -131, -131, 151, -131, 187, 165, 182, -131, -131, -131, 55, -131, 183, 151, 159, -131, 190, 55, -131, -131, 185, -131, 192, -131, 188, 193, -131, 189, 196, -131, 191, 198, -131, -131 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ static const yytype_int8 yydefact[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 22, 28, 31, 30, 23, 24, 75, 25, 26, 27, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 110, 29, 0, 0, 0, 0, 0, 0, 0, 82, 0, 81, 89, 0, 0, 0, 0, 0, 107, 8, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 64, 83, 0, 0, 0, 34, 33, 90, 91, 84, 0, 0, 17, 0, 21, 0, 108, 0, 0, 0, 41, 0, 0, 53, 0, 0, 0, 87, 84, 0, 0, 0, 0, 0, 76, 0, 0, 14, 12, 0, 0, 19, 0, 106, 41, 0, 0, 0, 37, 0, 0, 0, 0, 47, 63, 68, 0, 0, 0, 0, 79, 41, 74, 0, 35, 36, 0, 41, 86, 88, 15, 17, 16, 0, 0, 111, 42, 44, 0, 0, 0, 0, 41, 56, 84, 52, 62, 41, 0, 0, 0, 0, 80, 66, 41, 0, 0, 78, 0, 0, 85, 18, 20, 114, 0, 0, 41, 40, 84, 0, 32, 58, 0, 0, 55, 54, 0, 0, 41, 41, 51, 84, 45, 41, 84, 71, 0, 67, 0, 0, 0, 13, 0, 112, 111, 109, 39, 38, 0, 0, 99, 0, 96, 95, 0, 61, 0, 50, 48, 49, 70, 69, 0, 65, 0, 0, 0, 113, 92, 60, 59, 41, 94, 0, 0, 0, 73, 93, 43, 0, 100, 0, 41, 0, 57, 0, 0, 72, 101, 97, 46, 0, 102, 0, 0, 103, 0, 0, 104, 0, 0, 105, 98 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { -131, -131, -47, -2, -68, -58, -131, 79, 53, -131, 62, -131, -131, -131, 130, -131, 194, -131, 116, 101, -131, -120, -130, -131, -27, -131, -131, 16, -131, -131, -131, -131, 168, -131, -131, -51, -60, -29, -131, -131, -131, -124, 176, 199, -131, -131, -131, -131, -107, 122, -131, -131, -131, 51, -114, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, -131, 7, -131 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int16 yydefgoto[] = { -1, 9, 23, 24, 45, 63, 64, 99, 107, 65, 136, 66, 67, 111, 10, 11, 12, 13, 100, 101, 175, 143, 163, 88, 14, 215, 185, 49, 147, 119, 208, 257, 91, 183, 149, 102, 96, 190, 15, 187, 220, 156, 16, 17, 127, 52, 55, 56, 132, 124, 18, 58, 105, 204, 205, 180, 237, 238, 239, 240, 267, 275, 279, 282, 285, 288, 19, 68, 113, 20, 201, 70 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_int16 yytable[] = { 25, 26, 27, 28, 29, 30, 31, 164, 47, 97, 32, 48, 157, 81, 82, 83, 178, 152, 172, 94, 170, 53, 179, 108, 109, 95, 115, 171, 213, 1, 154, 2, 130, 98, 154, 154, 155, 193, 33, 134, 155, 137, 162, 196, 202, 139, 72, 162, 35, 75, 203, 77, 103, 50, 214, 54, 219, 80, 210, 158, 216, 150, 221, 212, 165, 21, 22, 85, 168, 34, 224, 151, 222, 211, 36, 173, 229, 167, 161, 181, 169, 154, 232, 37, 188, 116, 230, 155, 120, 42, 43, 154, 125, 162, 243, 244, 128, 233, 177, 246, 2, 198, 199, 162, 1, 38, 2, 206, 4, 5, 245, 2, 3, 247, 4, 5, 145, 218, 6, 223, 254, 7, 8, 42, 43, 44, 227, 255, 256, 39, 209, 40, 41, 231, 261, 5, 48, 265, 59, 60, 61, 62, 51, 71, 241, 69, 73, 74, 272, 76, 78, 79, 191, 192, 84, 273, 249, 86, 87, 90, 195, 89, 94, 93, 106, 114, 110, 117, 118, 258, 121, 123, 98, 122, 126, 129, 131, 154, 135, 138, 141, 155, 262, 263, 264, 133, 166, 142, 146, 148, 153, 159, 174, 182, 176, 189, 184, 270, 186, 194, 200, 207, 225, 226, 276, 236, 228, 250, 234, 235, 248, 242, 251, 252, 112, 259, 260, 266, 269, 271, 274, 277, 278, 281, 280, 283, 284, 286, 287, 197, 160, 268, 46, 144, 57, 217, 140, 253, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104 }; static const yytype_int16 yycheck[] = { 2, 3, 4, 5, 6, 7, 8, 131, 35, 77, 0, 10, 126, 60, 61, 62, 146, 124, 142, 13, 140, 20, 146, 81, 82, 76, 86, 141, 14, 7, 16, 9, 100, 27, 16, 16, 22, 157, 36, 107, 22, 109, 28, 163, 174, 113, 48, 28, 36, 51, 174, 53, 79, 37, 184, 39, 186, 59, 178, 127, 184, 121, 186, 183, 132, 32, 33, 69, 136, 29, 190, 122, 186, 180, 36, 143, 200, 135, 129, 147, 138, 16, 202, 36, 152, 87, 200, 22, 90, 34, 35, 16, 94, 28, 214, 215, 98, 204, 23, 219, 9, 169, 170, 28, 7, 36, 9, 175, 17, 18, 217, 9, 15, 220, 17, 18, 118, 185, 21, 187, 234, 24, 25, 34, 35, 36, 194, 29, 30, 36, 177, 36, 36, 201, 248, 18, 10, 257, 3, 4, 5, 6, 19, 36, 212, 26, 36, 36, 268, 36, 36, 36, 154, 155, 36, 269, 224, 36, 8, 12, 162, 36, 13, 36, 36, 36, 30, 36, 11, 237, 36, 30, 27, 36, 36, 36, 36, 16, 36, 36, 36, 22, 250, 251, 252, 106, 133, 36, 36, 36, 36, 36, 36, 30, 36, 30, 36, 265, 36, 30, 36, 30, 36, 36, 272, 30, 36, 29, 36, 36, 36, 213, 31, 29, 84, 36, 36, 30, 36, 36, 30, 36, 30, 30, 36, 36, 30, 36, 30, 167, 129, 260, 33, 117, 40, 184, 114, 230, -1, -1, -1, -1, 74, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 79 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_int8 yystos[] = { 0, 7, 9, 15, 17, 18, 21, 24, 25, 38, 51, 52, 53, 54, 61, 75, 79, 80, 87, 103, 106, 32, 33, 39, 40, 40, 40, 40, 40, 40, 40, 40, 0, 36, 29, 36, 36, 36, 36, 36, 36, 36, 34, 35, 36, 41, 80, 61, 10, 64, 64, 19, 82, 20, 64, 83, 84, 53, 88, 3, 4, 5, 6, 42, 43, 46, 48, 49, 104, 26, 108, 36, 40, 36, 36, 40, 36, 40, 36, 36, 40, 39, 39, 39, 36, 40, 36, 8, 60, 36, 12, 69, 69, 36, 13, 72, 73, 41, 27, 44, 55, 56, 72, 61, 79, 89, 36, 45, 42, 42, 30, 50, 51, 105, 36, 73, 40, 36, 11, 66, 40, 36, 36, 30, 86, 40, 36, 81, 40, 36, 41, 36, 85, 44, 41, 36, 47, 41, 36, 41, 86, 36, 36, 58, 55, 40, 36, 65, 36, 71, 73, 72, 85, 36, 16, 22, 78, 91, 41, 36, 56, 72, 28, 59, 78, 41, 45, 42, 41, 42, 58, 91, 78, 41, 36, 57, 36, 23, 59, 78, 92, 41, 30, 70, 36, 63, 36, 76, 41, 30, 74, 40, 40, 58, 30, 40, 58, 47, 41, 41, 36, 107, 59, 78, 90, 91, 41, 30, 67, 39, 58, 85, 58, 14, 59, 62, 78, 90, 41, 59, 77, 78, 91, 41, 58, 36, 36, 41, 36, 78, 91, 41, 58, 85, 36, 36, 30, 93, 94, 95, 96, 41, 40, 58, 58, 85, 58, 85, 36, 41, 29, 31, 29, 107, 91, 29, 30, 68, 41, 36, 36, 91, 41, 41, 41, 58, 30, 97, 74, 36, 41, 36, 58, 91, 30, 98, 41, 36, 30, 99, 36, 30, 100, 36, 30, 101, 36, 30, 102 }; /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_int8 yyr1[] = { 0, 37, 38, 39, 39, 40, 41, 41, 42, 42, 42, 42, 43, 44, 45, 45, 46, 47, 47, 48, 49, 50, 51, 51, 51, 51, 51, 51, 52, 52, 53, 53, 54, 55, 55, 56, 56, 57, 57, 57, 57, 58, 58, 59, 60, 61, 62, 63, 63, 63, 63, 63, 64, 65, 65, 65, 65, 66, 67, 68, 68, 69, 70, 71, 72, 73, 74, 75, 76, 76, 76, 76, 77, 78, 79, 79, 80, 81, 81, 81, 82, 83, 83, 84, 85, 85, 85, 86, 87, 88, 89, 89, 90, 91, 92, 93, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 106, 107, 107, 107, 108 }; /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ static const yytype_int8 yyr2[] = { 0, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 4, 5, 0, 3, 5, 0, 3, 4, 6, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 10, 1, 1, 3, 3, 0, 3, 3, 2, 0, 2, 5, 4, 10, 6, 0, 3, 3, 3, 2, 6, 0, 3, 3, 2, 8, 1, 1, 1, 6, 1, 1, 1, 6, 1, 10, 0, 3, 3, 2, 5, 5, 8, 1, 7, 0, 3, 2, 6, 1, 1, 3, 0, 3, 2, 1, 8, 1, 1, 1, 3, 5, 4, 1, 1, 5, 13, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 10, 3, 0, 2, 3, 6 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYEMPTY (-2) #define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY) \ { \ yychar = (Token); \ yylval = (Value); \ YYPOPSTACK (yylen); \ yystate = *yyssp; \ goto yybackup; \ } \ else \ { \ yyerror (context, YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (0) /* Error token number */ #define YYTERROR 1 #define YYERRCODE 256 /* Enable debugging if requested. */ #if YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (0) /* This macro is provided for backward compatibility. */ #ifndef YY_LOCATION_PRINT # define YY_LOCATION_PRINT(File, Loc) ((void) 0) #endif # define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Type, Value, context); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ static void yy_symbol_value_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, pj_wkt1_parse_context *context) { FILE *yyoutput = yyo; YYUSE (yyoutput); YYUSE (context); if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) YYPRINT (yyo, yytoknum[yytype], *yyvaluep); # endif YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ static void yy_symbol_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, pj_wkt1_parse_context *context) { YYFPRINTF (yyo, "%s %s (", yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); yy_symbol_value_print (yyo, yytype, yyvaluep, context); YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, int yyrule, pj_wkt1_parse_context *context) { int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, yystos[+yyssp[yyi + 1 - yynrhs]], &yyvsp[(yyi + 1) - (yynrhs)] , context); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyssp, yyvsp, Rule, context); \ } while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ # define YYDPRINTF(Args) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif #if YYERROR_VERBOSE # ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) # else /* Return the length of YYSTR. */ static YYPTRDIFF_T yystrlen (const char *yystr) { YYPTRDIFF_T yylen; for (yylen = 0; yystr && yystr[yylen]; yylen++) continue; return yylen; } # endif # endif # ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ static char * yystpcpy (char *yydest, const char *yysrc) { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif # endif # ifndef yytnamerr /* Copy to YYRES the contents of YYSTR after stripping away unnecessary quotes and backslashes, so that it's suitable for yyerror. The heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ static YYPTRDIFF_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { YYPTRDIFF_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { case '\'': case ',': goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; else goto append; append: default: if (yyres) yyres[yyn] = *yyp; yyn++; break; case '"': if (yyres) yyres[yyn] = '\0'; return yyn; } do_not_strip_quotes: ; } if (yyres) return (YYPTRDIFF_T)(yystpcpy (yyres, yystr) - yyres); else return yystrlen (yystr); } # endif /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message about the unexpected token YYTOKEN for the state stack whose top is YYSSP. Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return 2 if the required number of bytes is too large to store. */ static int yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, yy_state_t *yyssp, int yytoken) { enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; /* Internationalized format string. */ const char *yyformat = YY_NULLPTR; /* Arguments of yyformat: reported tokens (one for the "unexpected", one per "expected"). */ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; /* Actual size of YYARG. */ int yycount = 0; /* Cumulated lengths of YYARG. */ YYPTRDIFF_T yysize = 0; /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected tokens because there are none. - The only way there can be no lookahead present (in yychar) is if this state is a consistent state with a default action. Thus, detecting the absence of a lookahead is sufficient to determine that there is no unexpected or expected token to report. In that case, just report a simple "syntax error". - Don't assume there isn't a lookahead just because this state is a consistent state with a default action. There might have been a previous inconsistent state, consistent state with a non-default action, or user semantic action that manipulated yychar. - Of course, the expected token list depends on states to have correct lookahead information, and it depends on the parser not to perform extra reductions after fetching a lookahead from the scanner and before detecting a syntax error. Thus, state merging (from LALR or IELR) and default reductions corrupt the expected token list. However, the list is correct for canonical LR with one exception: it will still contain any token that will not be accepted due to an error action in a later state. */ if (yytoken != YYEMPTY) { int yyn = yypact[+*yyssp]; YYPTRDIFF_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); yysize = yysize0; yyarg[yycount++] = yytname[yytoken]; if (!yypact_value_is_default (yyn)) { /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for this state because they are default actions. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yyx; for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR && !yytable_value_is_error (yytable[yyx + yyn])) { if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) { yycount = 1; yysize = yysize0; break; } yyarg[yycount++] = yytname[yyx]; { YYPTRDIFF_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return 2; } } } } switch (yycount) { # define YYCASE_(N, S) \ case N: \ yyformat = S; \ break default: /* Avoid compiler warnings. */ YYCASE_(0, YY_("syntax error")); YYCASE_(1, YY_("syntax error, unexpected %s")); YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); # undef YYCASE_ } { /* Don't count the "%s"s in the final size, but reserve room for the terminator. */ YYPTRDIFF_T yysize1 = yysize + (yystrlen (yyformat) - 2 * yycount) + 1; if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return 2; } if (*yymsg_alloc < yysize) { *yymsg_alloc = 2 * yysize; if (! (yysize <= *yymsg_alloc && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; return 1; } /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ { char *yyp = *yymsg; int yyi = 0; while ((*yyp = *yyformat) != '\0') if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) { yyp += yytnamerr (yyp, yyarg[yyi++]); yyformat += 2; } else { ++yyp; ++yyformat; } } return 0; } #endif /* YYERROR_VERBOSE */ /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, pj_wkt1_parse_context *context) { YYUSE (yyvaluep); YYUSE (context); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*----------. | yyparse. | `----------*/ int yyparse (pj_wkt1_parse_context *context) { /* The lookahead symbol. */ int yychar; /* The semantic value of the lookahead symbol. */ /* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ YY_INITIAL_VALUE (static YYSTYPE yyval_default;) YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Number of syntax errors so far. */ /* int yynerrs; */ yy_state_fast_t yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* The stacks and their tools: 'yyss': related to states. 'yyvs': related to semantic values. Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ yy_state_t yyssa[YYINITDEPTH]; yy_state_t *yyss; yy_state_t *yyssp; /* The semantic value stack. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs; YYSTYPE *yyvsp; YYPTRDIFF_T yystacksize; int yyn; int yyresult; /* Lookahead token as an internal (translated) token number. */ int yytoken = 0; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; #if YYERROR_VERBOSE /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; #endif #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; yyssp = yyss = yyssa; yyvsp = yyvs = yyvsa; yystacksize = YYINITDEPTH; YYDPRINTF ((stderr, "Starting parse\n")); yystate = 0; yyerrstatus = 0; /* yynerrs = 0; */ yychar = YYEMPTY; /* Cause a token to be read. */ goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; /*--------------------------------------------------------------------. | yysetstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: YYDPRINTF ((stderr, "Entering state %d\n", yystate)); YY_ASSERT (0 <= yystate && yystate < YYNSTATES); YY_IGNORE_USELESS_CAST_BEGIN *yyssp = YY_CAST (yy_state_t, yystate); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE goto yyexhaustedlab; #else { /* Get the current used size of the three stacks, in elements. */ YYPTRDIFF_T yysize = (YYPTRDIFF_T)(yyssp - yyss + 1); # if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * YYSIZEOF (*yyssp), &yyvs1, yysize * YYSIZEOF (*yyvsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; } # else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yy_state_t *yyss1 = yyss; union yyalloc *yyptr = YY_CAST (union yyalloc *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; YY_IGNORE_USELESS_CAST_BEGIN YYDPRINTF ((stderr, "Stack size increased to %ld\n", YY_CAST (long, yystacksize))); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token: ")); yychar = yylex (&yylval, context); } if (yychar <= YYEOF) { yychar = yytoken = YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yytable_value_is_error (yyn)) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Discard the shifted token. */ yychar = YYEMPTY; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; YY_REDUCE_PRINT (yyn); switch (yyn) { default: break; } /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, but that translation would be missed if the semantic action invokes YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an incorrect destructor might then be invoked immediately. In the case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ { const int yylhs = yyr1[yyn] - YYNTOKENS; const int yyi = yypgoto[yylhs] + *yyssp; yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp ? yytable[yyi] : yydefgoto[yylhs]); } goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { /* ++yynerrs; */ #if ! YYERROR_VERBOSE yyerror (context, YY_("syntax error")); #else # define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ yyssp, yytoken) { char const *yymsgp = YY_("syntax error"); int yysyntax_error_status; yysyntax_error_status = YYSYNTAX_ERROR; if (yysyntax_error_status == 0) yymsgp = yymsg; else if (yysyntax_error_status == 1) { if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = YY_CAST (char *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); if (!yymsg) { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; yysyntax_error_status = 2; } else { yysyntax_error_status = YYSYNTAX_ERROR; yymsgp = yymsg; } } yyerror (context, yymsgp); if (yysyntax_error_status == 2) goto yyexhaustedlab; } # undef YYSYNTAX_ERROR #endif } if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval, context); yychar = YYEMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ #if 0 yyerrorlab: /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (0) YYERROR; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ #endif yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { yyn += YYTERROR; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yydestruct ("Error: popping", yystos[yystate], yyvsp, context); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; #if !defined yyoverflow || YYERROR_VERBOSE /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ yyexhaustedlab: yyerror (context, YY_("memory exhausted")); yyresult = 2; /* Fall through. */ #endif /*-----------------------------------------------------. | yyreturn -- parsing is finished, return the result. | `-----------------------------------------------------*/ yyreturn: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = YYTRANSLATE (yychar); yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval, context); } /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", yystos[+*yyssp], yyvsp, context); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif #if YYERROR_VERBOSE if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif return yyresult; } proj-9.8.1/src/mlfn.cpp000664 001750 001750 00000001752 15166171715 014707 0ustar00eveneven000000 000000 #include "proj_internal.h" #include /* meridional distance for ellipsoid and inverse using 6th-order expansion in ** the third flattening n. This gives full double precision accuracy for |f| ** <= 1/150. */ double *pj_enfn(double n) { int Lmax = int(AuxLat::ORDER); // 2*Lmax for the Fourier coeffs for each direction of conversion + 1 for // overall multiplier. double *en; en = (double *)malloc((2 * Lmax + 1) * sizeof(double)); if (nullptr == en) return nullptr; en[0] = pj_rectifying_radius(n); pj_auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::RECTIFYING, en + 1); pj_auxlat_coeffs(n, AuxLat::RECTIFYING, AuxLat::GEOGRAPHIC, en + 1 + Lmax); return en; } double pj_mlfn(double phi, double sphi, double cphi, const double *en) { return en[0] * pj_auxlat_convert(phi, sphi, cphi, en + 1); } double pj_inv_mlfn(double mu, const double *en) { int Lmax = int(AuxLat::ORDER); return pj_auxlat_convert(mu / en[0], en + 1 + Lmax); } proj-9.8.1/src/initcache.cpp000664 001750 001750 00000013561 15166171715 015703 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: init file definition cache. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2009, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include "proj.h" #include "proj_internal.h" static int cache_count = 0; static int cache_alloc = 0; static char **cache_key = nullptr; static paralist **cache_paralist = nullptr; /************************************************************************/ /* pj_clone_paralist() */ /* */ /* Allocate a copy of a parameter list. */ /************************************************************************/ paralist *pj_clone_paralist(const paralist *list) { paralist *list_copy = nullptr, *next_copy = nullptr; for (; list != nullptr; list = list->next) { paralist *newitem = (paralist *)malloc(sizeof(paralist) + strlen(list->param)); assert(newitem); newitem->used = 0; newitem->next = nullptr; strcpy(newitem->param, list->param); if (next_copy) next_copy->next = newitem; else list_copy = newitem; next_copy = newitem; } return list_copy; } /************************************************************************/ /* pj_clear_initcache() */ /* */ /* Clear out all memory held in the init file cache. */ /************************************************************************/ void pj_clear_initcache() { if (cache_alloc > 0) { int i; pj_acquire_lock(); for (i = 0; i < cache_count; i++) { paralist *n, *t = cache_paralist[i]; free(cache_key[i]); /* free parameter list elements */ for (; t != nullptr; t = n) { n = t->next; free(t); } } free(cache_key); free(cache_paralist); cache_count = 0; cache_alloc = 0; cache_key = nullptr; cache_paralist = nullptr; pj_release_lock(); } } /************************************************************************/ /* pj_search_initcache() */ /* */ /* Search for a matching definition in the init cache. */ /************************************************************************/ paralist *pj_search_initcache(const char *filekey) { int i; paralist *result = nullptr; pj_acquire_lock(); for (i = 0; result == nullptr && i < cache_count; i++) { if (strcmp(filekey, cache_key[i]) == 0) { result = pj_clone_paralist(cache_paralist[i]); } } pj_release_lock(); return result; } /************************************************************************/ /* pj_insert_initcache() */ /* */ /* Insert a paralist definition in the init file cache. */ /************************************************************************/ void pj_insert_initcache(const char *filekey, const paralist *list) { pj_acquire_lock(); /* ** Grow list if required. */ if (cache_count == cache_alloc) { char **cache_key_new; paralist **cache_paralist_new; cache_alloc = cache_alloc * 2 + 15; cache_key_new = (char **)malloc(sizeof(char *) * cache_alloc); assert(cache_key_new); if (cache_key && cache_count) { memcpy(cache_key_new, cache_key, sizeof(char *) * cache_count); } free(cache_key); cache_key = cache_key_new; cache_paralist_new = (paralist **)malloc(sizeof(paralist *) * cache_alloc); assert(cache_paralist_new); if (cache_paralist && cache_count) { memcpy(cache_paralist_new, cache_paralist, sizeof(paralist *) * cache_count); } free(cache_paralist); cache_paralist = cache_paralist_new; } /* ** Duplicate the filekey and paralist, and insert in cache. */ cache_key[cache_count] = (char *)malloc(strlen(filekey) + 1); assert(cache_key[cache_count]); strcpy(cache_key[cache_count], filekey); cache_paralist[cache_count] = pj_clone_paralist(list); cache_count++; pj_release_lock(); } proj-9.8.1/src/pj_list.h000664 001750 001750 00000017331 15166171715 015064 0ustar00eveneven000000 000000 #ifdef DO_PJ_LIST_ID static const char PJ_LIST_H_ID[] = "@(#)pj_list.h 4.5 95/08/09 GIE REL"; #endif /* Full list of current projections for Tue Jan 11 12:27:04 EST 1994 ** ** Copy this file and retain only appropriate lines for subset list */ PROJ_HEAD(adams_hemi, "Adams Hemisphere in a Square") PROJ_HEAD(adams_ws1, "Adams World in a Square I") PROJ_HEAD(adams_ws2, "Adams World in a Square II") PROJ_HEAD(aea, "Albers Equal Area") PROJ_HEAD(aeqd, "Azimuthal Equidistant") PROJ_HEAD(affine, "Affine transformation") PROJ_HEAD(airy, "Airy") PROJ_HEAD(aitoff, "Aitoff") PROJ_HEAD(alsk, "Modified Stereographic of Alaska") PROJ_HEAD(apian, "Apian Globular I") PROJ_HEAD(august, "August Epicycloidal") PROJ_HEAD(axisswap, "Axis ordering") PROJ_HEAD(bacon, "Bacon Globular") PROJ_HEAD(bertin1953, "Bertin 1953") PROJ_HEAD(bipc, "Bipolar conic of western hemisphere") PROJ_HEAD(boggs, "Boggs Eumorphic") PROJ_HEAD(bonne, "Bonne (Werner lat_1=90)") PROJ_HEAD(calcofi, "Cal Coop Ocean Fish Invest Lines/Stations") PROJ_HEAD(cart, "Geodetic/cartesian conversions") PROJ_HEAD(cass, "Cassini") PROJ_HEAD(cc, "Central Cylindrical") PROJ_HEAD(ccon, "Central Conic") PROJ_HEAD(cea, "Equal Area Cylindrical") PROJ_HEAD(chamb, "Chamberlin Trimetric") PROJ_HEAD(collg, "Collignon") PROJ_HEAD(col_urban, "Colombia Urban") PROJ_HEAD(comill, "Compact Miller") PROJ_HEAD(crast, "Craster Parabolic (Putnins P4)") PROJ_HEAD(defmodel, "Deformation model") PROJ_HEAD(deformation, "Kinematic grid shift") PROJ_HEAD(denoy, "Denoyer Semi-Elliptical") PROJ_HEAD(airocean, "Airocean Fuller") PROJ_HEAD(eck1, "Eckert I") PROJ_HEAD(eck2, "Eckert II") PROJ_HEAD(eck3, "Eckert III") PROJ_HEAD(eck4, "Eckert IV") PROJ_HEAD(eck5, "Eckert V") PROJ_HEAD(eck6, "Eckert VI") PROJ_HEAD(eqearth, "Equal Earth") PROJ_HEAD(eqc, "Equidistant Cylindrical (Plate Carree)") PROJ_HEAD(eqdc, "Equidistant Conic") PROJ_HEAD(euler, "Euler") PROJ_HEAD(etmerc, "Extended Transverse Mercator") PROJ_HEAD(fahey, "Fahey") PROJ_HEAD(fouc, "Foucaut") PROJ_HEAD(fouc_s, "Foucaut Sinusoidal") PROJ_HEAD(gall, "Gall (Gall Stereographic)") PROJ_HEAD(geoc, "Geocentric Latitude") PROJ_HEAD(geocent, "Geocentric") PROJ_HEAD(geogoffset, "Geographic Offset") PROJ_HEAD(geos, "Geostationary Satellite View") PROJ_HEAD(gins8, "Ginsburg VIII (TsNIIGAiK)") PROJ_HEAD(gn_sinu, "General Sinusoidal Series") PROJ_HEAD(gnom, "Gnomonic") PROJ_HEAD(goode, "Goode Homolosine") PROJ_HEAD(gridshift, "Generic grid shift") PROJ_HEAD(gs48, "Modified Stereographic of 48 U.S.") PROJ_HEAD(gs50, "Modified Stereographic of 50 U.S.") PROJ_HEAD(guyou, "Guyou") PROJ_HEAD(hammer, "Hammer & Eckert-Greifendorff") PROJ_HEAD(hatano, "Hatano Asymmetrical Equal Area") PROJ_HEAD(healpix, "HEALPix") PROJ_HEAD(rhealpix, "rHEALPix") PROJ_HEAD(helmert, "3- and 7-parameter Helmert shift") PROJ_HEAD(hgridshift, "Horizontal grid shift") PROJ_HEAD(horner, "Horner polynomial evaluation") PROJ_HEAD(igh, "Interrupted Goode Homolosine") PROJ_HEAD(igh_o, "Interrupted Goode Homolosine Oceanic View") PROJ_HEAD(imoll, "Interrupted Mollweide") PROJ_HEAD(imoll_o, "Interrupted Mollweide Oceanic View") PROJ_HEAD(imw_p, "International Map of the World Polyconic") PROJ_HEAD(isea, "Icosahedral Snyder Equal Area") PROJ_HEAD(kav5, "Kavrayskiy V") PROJ_HEAD(kav7, "Kavrayskiy VII") PROJ_HEAD(krovak, "Krovak") PROJ_HEAD(labrd, "Laborde") PROJ_HEAD(laea, "Lambert Azimuthal Equal Area") PROJ_HEAD(lagrng, "Lagrange") PROJ_HEAD(larr, "Larrivee") PROJ_HEAD(lask, "Laskowski") PROJ_HEAD(lonlat, "Lat/long (Geodetic)") PROJ_HEAD(latlon, "Lat/long (Geodetic alias)") PROJ_HEAD(latlong, "Lat/long (Geodetic alias)") PROJ_HEAD(longlat, "Lat/long (Geodetic alias)") PROJ_HEAD(lcc, "Lambert Conformal Conic") PROJ_HEAD(lcca, "Lambert Conformal Conic Alternative") PROJ_HEAD(leac, "Lambert Equal Area Conic") PROJ_HEAD(lee_os, "Lee Oblated Stereographic") PROJ_HEAD(loxim, "Loximuthal") PROJ_HEAD(lsat, "Space oblique for LANDSAT") PROJ_HEAD(mbt_s, "McBryde-Thomas Flat-Polar Sine") PROJ_HEAD(mbt_fps, "McBryde-Thomas Flat-Pole Sine (No. 2)") PROJ_HEAD(mbtfpp, "McBride-Thomas Flat-Polar Parabolic") PROJ_HEAD(mbtfpq, "McBryde-Thomas Flat-Polar Quartic") PROJ_HEAD(mbtfps, "McBryde-Thomas Flat-Polar Sinusoidal") PROJ_HEAD(merc, "Mercator") PROJ_HEAD(mil_os, "Miller Oblated Stereographic") PROJ_HEAD(mill, "Miller Cylindrical") PROJ_HEAD(misrsom, "Space oblique for MISR") PROJ_HEAD(mod_krovak, "Modified Krovak") PROJ_HEAD(moll, "Mollweide") PROJ_HEAD(molobadekas, "Molodensky-Badekas transform") /* implemented in PJ_helmert.c */ PROJ_HEAD(molodensky, "Molodensky transform") PROJ_HEAD(murd1, "Murdoch I") PROJ_HEAD(murd2, "Murdoch II") PROJ_HEAD(murd3, "Murdoch III") PROJ_HEAD(natearth, "Natural Earth") PROJ_HEAD(natearth2, "Natural Earth II") PROJ_HEAD(nell, "Nell") PROJ_HEAD(nell_h, "Nell-Hammer") PROJ_HEAD(nicol, "Nicolosi Globular") PROJ_HEAD(nsper, "Near-sided perspective") PROJ_HEAD(nzmg, "New Zealand Map Grid") PROJ_HEAD(noop, "No operation") PROJ_HEAD(ob_tran, "General Oblique Transformation") PROJ_HEAD(ocea, "Oblique Cylindrical Equal Area") PROJ_HEAD(oea, "Oblated Equal Area") PROJ_HEAD(omerc, "Oblique Mercator") PROJ_HEAD(ortel, "Ortelius Oval") PROJ_HEAD(ortho, "Orthographic") PROJ_HEAD(pconic, "Perspective Conic") PROJ_HEAD(patterson, "Patterson Cylindrical") PROJ_HEAD(peirce_q, "Peirce Quincuncial") PROJ_HEAD(pipeline, "Transformation pipeline manager") PROJ_HEAD(poly, "Polyconic (American)") PROJ_HEAD(pop, "Retrieve coordinate value from pipeline stack") PROJ_HEAD(push, "Save coordinate value on pipeline stack") PROJ_HEAD(putp1, "Putnins P1") PROJ_HEAD(putp2, "Putnins P2") PROJ_HEAD(putp3, "Putnins P3") PROJ_HEAD(putp3p, "Putnins P3'") PROJ_HEAD(putp4p, "Putnins P4'") PROJ_HEAD(putp5, "Putnins P5") PROJ_HEAD(putp5p, "Putnins P5'") PROJ_HEAD(putp6, "Putnins P6") PROJ_HEAD(putp6p, "Putnins P6'") PROJ_HEAD(qua_aut, "Quartic Authalic") PROJ_HEAD(qsc, "Quadrilateralized Spherical Cube") PROJ_HEAD(robin, "Robinson") PROJ_HEAD(rouss, "Roussilhe Stereographic") PROJ_HEAD(rpoly, "Rectangular Polyconic") PROJ_HEAD(s2, "S2") PROJ_HEAD(sch, "Spherical Cross-track Height") PROJ_HEAD(set, "Set coordinate value") PROJ_HEAD(sinu, "Sinusoidal (Sanson-Flamsteed)") PROJ_HEAD(som, "Space Oblique Mercator") PROJ_HEAD(somerc, "Swiss. Obl. Mercator") PROJ_HEAD(spilhaus, "Spilhaus") PROJ_HEAD(stere, "Stereographic") PROJ_HEAD(sterea, "Oblique Stereographic Alternative") PROJ_HEAD(gstmerc, "Gauss-Schreiber Transverse Mercator (aka Gauss-Laborde Reunion)") PROJ_HEAD(tcc, "Transverse Central Cylindrical") PROJ_HEAD(tcea, "Transverse Cylindrical Equal Area") PROJ_HEAD(times, "Times Projection") PROJ_HEAD(tinshift, "Triangulation based transformation") PROJ_HEAD(tissot, "Tissot Conic") PROJ_HEAD(tmerc, "Transverse Mercator") PROJ_HEAD(tobmerc, "Tobler-Mercator") PROJ_HEAD(topocentric, "Geocentric/Topocentric conversion") PROJ_HEAD(tpeqd, "Two Point Equidistant") PROJ_HEAD(tpers, "Tilted perspective") PROJ_HEAD(unitconvert, "Unit conversion") PROJ_HEAD(ups, "Universal Polar Stereographic") PROJ_HEAD(urm5, "Urmaev V") PROJ_HEAD(urmfps, "Urmaev Flat-Polar Sinusoidal") PROJ_HEAD(utm, "Universal Transverse Mercator (UTM)") PROJ_HEAD(vandg, "van der Grinten (I)") PROJ_HEAD(vandg2, "van der Grinten II") PROJ_HEAD(vandg3, "van der Grinten III") PROJ_HEAD(vandg4, "van der Grinten IV") PROJ_HEAD(vertoffset, "Vertical Offset and Slope") PROJ_HEAD(vitk1, "Vitkovsky I") PROJ_HEAD(vgridshift, "Vertical grid shift") PROJ_HEAD(wag1, "Wagner I (Kavrayskiy VI)") PROJ_HEAD(wag2, "Wagner II") PROJ_HEAD(wag3, "Wagner III") PROJ_HEAD(wag4, "Wagner IV") PROJ_HEAD(wag5, "Wagner V") PROJ_HEAD(wag6, "Wagner VI") PROJ_HEAD(wag7, "Wagner VII") PROJ_HEAD(webmerc, "Web Mercator / Pseudo Mercator") PROJ_HEAD(weren, "Werenskiold I") PROJ_HEAD(wink1, "Winkel I") PROJ_HEAD(wink2, "Winkel II") PROJ_HEAD(wintri, "Winkel Tripel") PROJ_HEAD(xyzgridshift, "XYZ grid shift") proj-9.8.1/src/dmstor.cpp000664 001750 001750 00000007466 15166171715 015273 0ustar00eveneven000000 000000 /* Convert DMS string to radians */ #include #include #include #include #include "proj.h" #include "proj_internal.h" static double proj_strtod(char *nptr, char **endptr); /* following should be sufficient for all but the ridiculous */ #define MAX_WORK 64 static const char *sym = "NnEeSsWw"; static const double vm[] = {DEG_TO_RAD, .0002908882086657216, .0000048481368110953599}; /* byte sequence for Degree Sign U+00B0 in UTF-8. */ static constexpr char DEG_SIGN1 = '\xc2'; static constexpr char DEG_SIGN2 = '\xb0'; double dmstor(const char *is, char **rs) { return dmstor_ctx(pj_get_default_ctx(), is, rs); } double dmstor_ctx(PJ_CONTEXT *ctx, const char *is, char **rs) { int n, nl; char *s, work[MAX_WORK]; const char *p; double v, tv; if (rs) *rs = (char *)is; /* copy string into work space */ while (isspace(*is)) ++is; n = MAX_WORK; s = work; p = (char *)is; /* * Copy characters into work until we hit a non-printable character or run * out of space in the buffer. Make a special exception for the bytes of * the Degree Sign in UTF-8. * * It is possible that a really odd input (like lots of leading zeros) * could be truncated in copying into work. But ... */ while ((isgraph(static_cast(*p)) || *p == DEG_SIGN1 || *p == DEG_SIGN2) && --n) *s++ = *p++; *s = '\0'; int sign = *(s = work); if (sign == '+' || sign == '-') s++; else sign = '+'; v = 0.; for (nl = 0; nl < 3; nl = n + 1) { if (!(isdigit(*s) || *s == '.')) break; if ((tv = proj_strtod(s, &s)) == HUGE_VAL) return tv; int adv = 1; if (*s == 'D' || *s == 'd' || *s == DEG_SIGN2) { /* * Accept \xb0 as a single-byte degree symbol. This byte is the * degree symbol in various single-byte encodings: multiple ISO * 8859 parts, several Windows code pages and others. */ n = 0; } else if (*s == '\'') { n = 1; } else if (*s == '"') { n = 2; } else if (s[0] == DEG_SIGN1 && s[1] == DEG_SIGN2) { /* degree symbol in UTF-8 */ n = 0; adv = 2; } else if (*s == 'r' || *s == 'R') { if (nl) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return HUGE_VAL; } ++s; v = tv; n = 4; continue; } else { v += tv * vm[nl]; n = 4; continue; } if (n < nl) { proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); return HUGE_VAL; } v += tv * vm[n]; s += adv; } /* postfix sign */ if (*s && (p = strchr(sym, *s))) { sign = (p - sym) >= 4 ? '-' : '+'; ++s; } if (sign == '-') v = -v; if (rs) /* return point of next char after valid string */ *rs = (char *)is + (s - work); return v; } static double proj_strtod(char *nptr, char **endptr) { char c, *cp = nptr; double result; /* * Scan for characters which cause problems with VC++ strtod() */ while ((c = *cp) != '\0') { if (c == 'd' || c == 'D') { /* * Found one, so NUL it out, call strtod(), * then restore it and return */ *cp = '\0'; result = strtod(nptr, endptr); *cp = c; return result; } ++cp; } /* no offending characters, just handle normally */ return pj_strtod(nptr, endptr); } proj-9.8.1/src/deriv.cpp000664 001750 001750 00000002323 15166171715 015057 0ustar00eveneven000000 000000 /* dervative of (*P->fwd) projection */ #include #include "proj.h" #include "proj_internal.h" int pj_deriv(PJ_LP lp, double h, const PJ *P, struct DERIVS *der) { PJ_XY t; /* get rid of constness until we can do it for real */ PJ *Q = (PJ *)P; if (nullptr == Q->fwd) return 1; lp.lam += h; lp.phi += h; if (fabs(lp.phi) > M_HALFPI) return 1; h += h; t = (*Q->fwd)(lp, Q); if (t.x == HUGE_VAL) return 1; der->x_l = t.x; der->y_p = t.y; der->x_p = t.x; der->y_l = t.y; lp.phi -= h; if (fabs(lp.phi) > M_HALFPI) return 1; t = (*Q->fwd)(lp, Q); if (t.x == HUGE_VAL) return 1; der->x_l += t.x; der->y_p -= t.y; der->x_p -= t.x; der->y_l += t.y; lp.lam -= h; t = (*Q->fwd)(lp, Q); if (t.x == HUGE_VAL) return 1; der->x_l -= t.x; der->y_p -= t.y; der->x_p -= t.x; der->y_l -= t.y; lp.phi += h; t = (*Q->fwd)(lp, Q); if (t.x == HUGE_VAL) return 1; der->x_l -= t.x; der->y_p += t.y; der->x_p += t.x; der->y_l -= t.y; h += h; der->x_l /= h; der->y_p /= h; der->x_p /= h; der->y_l /= h; return 0; } proj-9.8.1/src/release.cpp000664 001750 001750 00000000610 15166171715 015363 0ustar00eveneven000000 000000 /* <<< Release Notice for library >>> */ #include "proj.h" #include "proj_internal.h" #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) char const pj_release[] = "Rel. " STR(PROJ_VERSION_MAJOR) "." STR( PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH) ", " "April 10th, 2026"; const char *pj_get_release() { return pj_release; } proj-9.8.1/src/check_md5sum.cmake000664 001750 001750 00000000436 15166171715 016616 0ustar00eveneven000000 000000 file(READ "${IN_FILE}" CONTENTS) string(MD5 MD5SUM "${CONTENTS}") if(NOT("${MD5SUM}" STREQUAL "${EXPECTED_MD5SUM}")) message(FATAL_ERROR "File ${IN_FILE} has been modified. target ${TARGET} should be manually run. And lib_proj.cmake should be updated with \"${MD5SUM}\"") endif() proj-9.8.1/src/tests/000775 001750 001750 00000000000 15166171735 014406 5ustar00eveneven000000 000000 proj-9.8.1/src/tests/CMakeLists.txt000664 001750 001750 00000001054 15166171715 017144 0ustar00eveneven000000 000000 # Build PROJ test programs and run tests (only invoked if BUILD_TESTING) add_executable(geodtest geodtest.c) target_link_libraries(geodtest ${PROJ_LIBRARIES}) if(HAVE_LIBM) target_link_libraries(geodtest m) endif() add_executable(geodsigntest geodsigntest.c) # geodsigntest includes geodesic.c directly so it doesn't need to link # against the library. if(HAVE_LIBM) target_link_libraries(geodsigntest m) endif() # Do not install, instead run tests add_test(NAME geodesic-test COMMAND geodtest) add_test(NAME geodesic-signtest COMMAND geodsigntest) proj-9.8.1/src/tests/geodsigntest.c000664 001750 001750 00000030153 15166171715 017251 0ustar00eveneven000000 000000 /** * \file geodsigntest.c * \brief Test treatment of +/-0 and +/-180 * * Copyright (c) Charles Karney (2022) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #include #include #include /* Include the source file for the library directly so we can access the * internal (static) functions. */ #include "geodesic.c" /* Define function names with the "geod_" prefix. */ #define geod_Init Init #define geod_sum sumx #define geod_AngNormalize AngNormalize #define geod_AngDiff AngDiff #define geod_AngRound AngRound #define geod_sincosd sincosdx #define geod_atan2d atan2dx typedef double T; #if !defined(__cplusplus) #define nullptr 0 #endif #if !defined(OLD_BUGGY_REMQUO) /* * glibc prior to version 2.22 had a bug in remquo. This was reported in 2014 * and fixed in 2015. See * https://sourceware.org/bugzilla/show_bug.cgi?id=17569 * * The bug causes some of the tests here to fail. The failures aren't terribly * serious (just a loss of accuracy). If you're still using the buggy glibc, * then define OLD_BUGGY_REMQUO to be 1. */ #define OLD_BUGGY_REMQUO 0 #endif static const T wgs84_a = 6378137, wgs84_f = 1/298.257223563; /* WGS84 */ static int equiv(T x, T y) { return ( (isnan(x) && isnan(y)) || (x == y && signbit(x) == signbit(y)) ) ? 0 : 1; } static int checkEquals(T x, T y, T d) { if (fabs(x - y) <= d) return 0; printf("checkEquals fails: %.7g != %.7g +/- %.7g\n", x, y, d); return 1; } /* use "do { } while (false)" idiom so it can be punctuated like a * statement. */ #define check(expr, r) do { \ T s = (T)(r), t = expr; \ if (equiv(s, t)) { \ printf("Line %d : %s != %s (%g)\n", \ __LINE__, #expr, #r, t); \ ++n; \ } \ } while (0) #define checksincosd(x, s, c) do { \ T sx, cx; \ geod_sincosd(x, &sx, &cx); \ if (equiv(s, sx)) { \ printf("Line %d: sin(%g) != %g (%g)\n", \ __LINE__, x, s, sx); \ ++n; \ } \ if (equiv(c, cx)) { \ printf("Line %d: cos(%g) != %g (%g)\n", \ __LINE__, x, c, cx); \ ++n; \ } \ } while (0) int main(void) { T inf = INFINITY, nan = NAN, eps = DBL_EPSILON, e; int n = 0; geod_Init(); check( geod_AngRound(-eps/32), -eps/32); check( geod_AngRound(-eps/64), -0.0 ); check( geod_AngRound(- 0.0 ), -0.0 ); check( geod_AngRound( 0.0 ), +0.0 ); check( geod_AngRound( eps/64), +0.0 ); check( geod_AngRound( eps/32), +eps/32); check( geod_AngRound((1-2*eps)/64), (1-2*eps)/64); check( geod_AngRound((1-eps )/64), 1.0 /64); check( geod_AngRound((1-eps/2)/64), 1.0 /64); check( geod_AngRound((1-eps/4)/64), 1.0 /64); check( geod_AngRound( 1.0 /64), 1.0 /64); check( geod_AngRound((1+eps/2)/64), 1.0 /64); check( geod_AngRound((1+eps )/64), 1.0 /64); check( geod_AngRound((1+2*eps)/64), (1+2*eps)/64); check( geod_AngRound((1-eps )/32), (1-eps )/32); check( geod_AngRound((1-eps/2)/32), 1.0 /32); check( geod_AngRound((1-eps/4)/32), 1.0 /32); check( geod_AngRound( 1.0 /32), 1.0 /32); check( geod_AngRound((1+eps/2)/32), 1.0 /32); check( geod_AngRound((1+eps )/32), (1+eps )/32); check( geod_AngRound((1-eps )/16), (1-eps )/16); check( geod_AngRound((1-eps/2)/16), (1-eps/2)/16); check( geod_AngRound((1-eps/4)/16), 1.0 /16); check( geod_AngRound( 1.0 /16), 1.0 /16); check( geod_AngRound((1+eps/4)/16), 1.0 /16); check( geod_AngRound((1+eps/2)/16), 1.0 /16); check( geod_AngRound((1+eps )/16), (1+eps )/16); check( geod_AngRound((1-eps )/ 8), (1-eps )/ 8); check( geod_AngRound((1-eps/2)/ 8), (1-eps/2)/ 8); check( geod_AngRound((1-eps/4)/ 8), 1.0 / 8); check( geod_AngRound((1+eps/2)/ 8), 1.0 / 8); check( geod_AngRound((1+eps )/ 8), (1+eps )/ 8); check( geod_AngRound( 1-eps ), 1-eps ); check( geod_AngRound( 1-eps/2 ), 1-eps/2 ); check( geod_AngRound( 1-eps/4 ), 1 ); check( geod_AngRound( 1.0 ), 1 ); check( geod_AngRound( 1+eps/4 ), 1 ); check( geod_AngRound( 1+eps/2 ), 1 ); check( geod_AngRound( 1+eps ), 1+ eps ); check( geod_AngRound( 90.0-64*eps), 90-64*eps ); check( geod_AngRound( 90.0-32*eps), 90 ); check( geod_AngRound( 90.0 ), 90 ); checksincosd(- inf, nan, nan); #if !OLD_BUGGY_REMQUO checksincosd(-810.0, -1.0, +0.0); #endif checksincosd(-720.0, -0.0, +1.0); checksincosd(-630.0, +1.0, +0.0); checksincosd(-540.0, -0.0, -1.0); checksincosd(-450.0, -1.0, +0.0); checksincosd(-360.0, -0.0, +1.0); checksincosd(-270.0, +1.0, +0.0); checksincosd(-180.0, -0.0, -1.0); checksincosd(- 90.0, -1.0, +0.0); checksincosd(- 0.0, -0.0, +1.0); checksincosd(+ 0.0, +0.0, +1.0); checksincosd(+ 90.0, +1.0, +0.0); checksincosd(+180.0, +0.0, -1.0); checksincosd(+270.0, -1.0, +0.0); checksincosd(+360.0, +0.0, +1.0); checksincosd(+450.0, +1.0, +0.0); checksincosd(+540.0, +0.0, -1.0); checksincosd(+630.0, -1.0, +0.0); checksincosd(+720.0, +0.0, +1.0); #if !OLD_BUGGY_REMQUO checksincosd(+810.0, +1.0, +0.0); #endif checksincosd(+ inf, nan, nan); checksincosd( nan, nan, nan); #if !OLD_BUGGY_REMQUO { T s1, c1, s2, c2, s3, c3; geod_sincosd( 9.0, &s1, &c1); geod_sincosd( 81.0, &s2, &c2); geod_sincosd(-123456789.0, &s3, &c3); if ( equiv(s1, c2) + equiv(s1, s3) + equiv(c1, s2) + equiv(c1, -c3) ) { printf("Line %d : sincos accuracy fail\n", __LINE__); ++n; } } #endif check( geod_atan2d(+0.0 , -0.0 ), +180 ); check( geod_atan2d(-0.0 , -0.0 ), -180 ); check( geod_atan2d(+0.0 , +0.0 ), +0.0 ); check( geod_atan2d(-0.0 , +0.0 ), -0.0 ); check( geod_atan2d(+0.0 , -1.0 ), +180 ); check( geod_atan2d(-0.0 , -1.0 ), -180 ); check( geod_atan2d(+0.0 , +1.0 ), +0.0 ); check( geod_atan2d(-0.0 , +1.0 ), -0.0 ); check( geod_atan2d(-1.0 , +0.0 ), -90 ); check( geod_atan2d(-1.0 , -0.0 ), -90 ); check( geod_atan2d(+1.0 , +0.0 ), +90 ); check( geod_atan2d(+1.0 , -0.0 ), +90 ); check( geod_atan2d(+1.0 , -inf), +180 ); check( geod_atan2d(-1.0 , -inf), -180 ); check( geod_atan2d(+1.0 , +inf), +0.0 ); check( geod_atan2d(-1.0 , +inf), -0.0 ); check( geod_atan2d( +inf, +1.0 ), +90 ); check( geod_atan2d( +inf, -1.0 ), +90 ); check( geod_atan2d( -inf, +1.0 ), -90 ); check( geod_atan2d( -inf, -1.0 ), -90 ); check( geod_atan2d( +inf, -inf), +135 ); check( geod_atan2d( -inf, -inf), -135 ); check( geod_atan2d( +inf, +inf), +45 ); check( geod_atan2d( -inf, +inf), -45 ); check( geod_atan2d( nan, +1.0 ), nan ); check( geod_atan2d(+1.0 , nan), nan ); { T s = 7e-16; if ( equiv( geod_atan2d(s, -1.0), 180 - geod_atan2d(s, 1.0) ) ) { printf("Line %d : atan2d accuracy fail\n", __LINE__); ++n; } } check( geod_sum(+9.0, -9.0, &e), +0.0 ); check( geod_sum(-9.0, +9.0, &e), +0.0 ); check( geod_sum(-0.0, +0.0, &e), +0.0 ); check( geod_sum(+0.0, -0.0, &e), +0.0 ); check( geod_sum(-0.0, -0.0, &e), -0.0 ); check( geod_sum(+0.0, +0.0, &e), +0.0 ); check( geod_AngNormalize(-900.0), -180 ); check( geod_AngNormalize(-720.0), -0.0 ); check( geod_AngNormalize(-540.0), -180 ); check( geod_AngNormalize(-360.0), -0.0 ); check( geod_AngNormalize(-180.0), -180 ); check( geod_AngNormalize( -0.0), -0.0 ); check( geod_AngNormalize( +0.0), +0.0 ); check( geod_AngNormalize( 180.0), +180 ); check( geod_AngNormalize( 360.0), +0.0 ); check( geod_AngNormalize( 540.0), +180 ); check( geod_AngNormalize( 720.0), +0.0 ); check( geod_AngNormalize( 900.0), +180 ); check( geod_AngDiff(+ 0.0, + 0.0, &e), +0.0 ); check( geod_AngDiff(+ 0.0, - 0.0, &e), -0.0 ); check( geod_AngDiff(- 0.0, + 0.0, &e), +0.0 ); check( geod_AngDiff(- 0.0, - 0.0, &e), +0.0 ); check( geod_AngDiff(+ 5.0, +365.0, &e), +0.0 ); check( geod_AngDiff(+365.0, + 5.0, &e), -0.0 ); check( geod_AngDiff(+ 5.0, +185.0, &e), +180.0 ); check( geod_AngDiff(+185.0, + 5.0, &e), -180.0 ); check( geod_AngDiff( +eps , +180.0, &e), +180.0 ); check( geod_AngDiff( -eps , +180.0, &e), -180.0 ); check( geod_AngDiff( +eps , -180.0, &e), +180.0 ); check( geod_AngDiff( -eps , -180.0, &e), -180.0 ); { T x = 138 + 128 * eps, y = -164; if ( equiv( geod_AngDiff(x, y, &e), 58 - 128 * eps ) ) { printf("Line %d : AngDiff accuracy fail\n", __LINE__); ++n; } } { /* azimuth of geodesic line with points on equator determined by signs of * latitude * lat1 lat2 azi1/2 */ T C[2][3] = { { +0.0, -0.0, 180 }, { -0.0, +0.0, 0 } }; struct geod_geodesic g; geod_init(&g, wgs84_a, wgs84_f); T azi1, azi2; int i = 0; for (int k = 0; k < 2; ++k) { geod_inverse(&g, C[k][0], 0.0, C[k][1], 0.0, nullptr, &azi1, &azi2); if ( equiv(azi1, C[k][2]) + equiv(azi2, C[k][2]) ) ++i; } if (i) { printf("Line %d: inverse coincident points on equator fail\n", __LINE__); ++n; } } { /* Does the nearly antipodal equatorial solution go north or south? * lat1 lat2 azi1 azi2 */ T C[2][4] = { { +0.0, +0.0, 56, 124}, { -0.0, -0.0, 124, 56} }; struct geod_geodesic g; geod_init(&g, wgs84_a, wgs84_f); T azi1, azi2; int i = 0; for (int k = 0; k < 2; ++k) { geod_inverse(&g, C[k][0], 0.0, C[k][1], 179.5, nullptr, &azi1, &azi2); i += checkEquals(azi1, C[k][2], 1) + checkEquals(azi2, C[k][3], 1); } if (i) { printf("Line %d: inverse nearly antipodal points on equator fail\n", __LINE__);; ++n; } } { /* How does the exact antipodal equatorial path go N/S + E/W * lat1 lat2 lon2 azi1 azi2 */ T C[4][5] = { { +0.0, +0.0, +180, +0.0, +180}, { -0.0, -0.0, +180, +180, +0.0}, { +0.0, +0.0, -180, -0.0, -180}, { -0.0, -0.0, -180, -180, -0.0} }; struct geod_geodesic g; geod_init(&g, wgs84_a, wgs84_f); T azi1, azi2; int i = 0; for (int k = 0; k < 4; ++k) { geod_inverse(&g, C[k][0], 0.0, C[k][1], C[k][2], nullptr, &azi1, &azi2); if ( equiv(azi1, C[k][3]) + equiv(azi2, C[k][4]) ) ++i; } if (i) { printf("Line %d: inverse antipodal points on equator fail\n", __LINE__); ++n; } } { /* Antipodal points on the equator with prolate ellipsoid * lon2 azi1/2 */ T C[2][2] = { { +180, +90 }, { -180, -90 } }; struct geod_geodesic g; geod_init(&g, 6.4e6, -1/300.0); T azi1, azi2; int i = 0; for (int k = 0; k < 2; ++k) { geod_inverse(&g, 0.0, 0.0, 0.0, C[k][0], nullptr, &azi1, &azi2); if ( equiv(azi1, C[k][1]) + equiv(azi2, C[k][1]) ) ++i; } if (i) { printf("Line %d: inverse antipodal points on equator, prolate, fail\n", __LINE__); ++n; } } { /* azimuths = +/-0 and +/-180 for the direct problem * azi1, lon2, azi2 */ T C[4][3] = { { +0.0, +180, +180 }, { -0.0, -180, -180 }, { +180 , +180, +0.0 }, { -180 , -180, -0.0 } }; struct geod_geodesic g; geod_init(&g, wgs84_a, wgs84_f); T lon2, azi2; int i = 0; for (int k = 0; k < 4; ++k) { geod_gendirect(&g, 0.0, 0.0, C[k][0], GEOD_LONG_UNROLL, 15e6, nullptr, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); if ( equiv(lon2, C[k][1]) + equiv(azi2, C[k][2]) ) ++i; } if (i) { printf("Line %d: direct azi1 = +/-0 +/-180, fail\n", __LINE__); ++n; } } if (n) { printf("%d %s%s\n", n, "failure", (n > 1 ? "s" : "")); return 1; } } proj-9.8.1/src/tests/geodtest.c000664 001750 001750 00000136212 15166171715 016373 0ustar00eveneven000000 000000 /** * \file geodtest.c * \brief Test suite for the geodesic routines in C * * Run these tests by configuring with cmake and running "make test". * * Copyright (c) Charles Karney (2015-2025) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #include "geodesic.h" #include #include #if defined(_MSC_VER) /* Squelch warnings about assignment within conditional expression */ # pragma warning (disable: 4706) #endif #if !defined(__cplusplus) #define nullptr 0 #endif static const double wgs84_a = 6378137, wgs84_f = 1/298.257223563; /* WGS84 */ static int checkEquals(double x, double y, double d) { if (fabs(x - y) <= d) return 0; printf("checkEquals fails: %.7g != %.7g +/- %.7g\n", x, y, d); return 1; } static int checkNaN(double x) { /* cppcheck-suppress duplicateExpression */ if (isnan(x)) return 0; printf("checkNaN fails: %.7g\n", x); return 1; } static const int ncases = 20; static const double testcases[20][12] = { {35.60777, -139.44815, 111.098748429560326, -11.17491, -69.95921, 129.289270889708762, 8935244.5604818305, 80.50729714281974, 6273170.2055303837, 0.16606318447386067, 0.16479116945612937, 12841384694976.432}, {55.52454, 106.05087, 22.020059880982801, 77.03196, 197.18234, 109.112041110671519, 4105086.1713924406, 36.892740690445894, 3828869.3344387607, 0.80076349608092607, 0.80101006984201008, 61674961290615.615}, {-21.97856, 142.59065, -32.44456876433189, 41.84138, 98.56635, -41.84359951440466, 8394328.894657671, 75.62930491011522, 6161154.5773110616, 0.24816339233950381, 0.24930251203627892, -6637997720646.717}, {-66.99028, 112.2363, 173.73491240878403, -12.70631, 285.90344, 2.512956620913668, 11150344.2312080241, 100.278634181155759, 6289939.5670446687, -0.17199490274700385, -0.17722569526345708, -121287239862139.744}, {-17.42761, 173.34268, -159.033557661192928, -15.84784, 5.93557, -20.787484651536988, 16076603.1631180673, 144.640108810286253, 3732902.1583877189, -0.81273638700070476, -0.81299800519154474, 97825992354058.708}, {32.84994, 48.28919, 150.492927788121982, -56.28556, 202.29132, 48.113449399816759, 16727068.9438164461, 150.565799985466607, 3147838.1910180939, -0.87334918086923126, -0.86505036767110637, -72445258525585.010}, {6.96833, 52.74123, 92.581585386317712, -7.39675, 206.17291, 90.721692165923907, 17102477.2496958388, 154.147366239113561, 2772035.6169917581, -0.89991282520302447, -0.89986892177110739, -1311796973197.995}, {-50.56724, -16.30485, -105.439679907590164, -33.56571, -94.97412, -47.348547835650331, 6455670.5118668696, 58.083719495371259, 5409150.7979815838, 0.53053508035997263, 0.52988722644436602, 41071447902810.047}, {-58.93002, -8.90775, 140.965397902500679, -8.91104, 133.13503, 19.255429433416599, 11756066.0219864627, 105.755691241406877, 6151101.2270708536, -0.26548622269867183, -0.27068483874510741, -86143460552774.735}, {-68.82867, -74.28391, 93.774347763114881, -50.63005, -8.36685, 34.65564085411343, 3956936.926063544, 35.572254987389284, 3708890.9544062657, 0.81443963736383502, 0.81420859815358342, -41845309450093.787}, {-10.62672, -32.0898, -86.426713286747751, 5.883, -134.31681, -80.473780971034875, 11470869.3864563009, 103.387395634504061, 6184411.6622659713, -0.23138683500430237, -0.23155097622286792, 4198803992123.548}, {-21.76221, 166.90563, 29.319421206936428, 48.72884, 213.97627, 43.508671946410168, 9098627.3986554915, 81.963476716121964, 6299240.9166992283, 0.13965943368590333, 0.14152969707656796, 10024709850277.476}, {-19.79938, -174.47484, 71.167275780171533, -11.99349, -154.35109, 65.589099775199228, 2319004.8601169389, 20.896611684802389, 2267960.8703918325, 0.93427001867125849, 0.93424887135032789, -3935477535005.785}, {-11.95887, -116.94513, 92.712619830452549, 4.57352, 7.16501, 78.64960934409585, 13834722.5801401374, 124.688684161089762, 5228093.177931598, -0.56879356755666463, -0.56918731952397221, -9919582785894.853}, {-87.85331, 85.66836, -65.120313040242748, 66.48646, 16.09921, -4.888658719272296, 17286615.3147144645, 155.58592449699137, 2635887.4729110181, -0.90697975771398578, -0.91095608883042767, 42667211366919.534}, {1.74708, 128.32011, -101.584843631173858, -11.16617, 11.87109, -86.325793296437476, 12942901.1241347408, 116.650512484301857, 5682744.8413270572, -0.44857868222697644, -0.44824490340007729, 10763055294345.653}, {-25.72959, -144.90758, -153.647468693117198, -57.70581, -269.17879, -48.343983158876487, 9413446.7452453107, 84.664533838404295, 6356176.6898881281, 0.09492245755254703, 0.09737058264766572, 74515122850712.444}, {-41.22777, 122.32875, 14.285113402275739, -7.57291, 130.37946, 10.805303085187369, 3812686.035106021, 34.34330804743883, 3588703.8812128856, 0.82605222593217889, 0.82572158200920196, -2456961531057.857}, {11.01307, 138.25278, 79.43682622782374, 6.62726, 247.05981, 103.708090215522657, 11911190.819018408, 107.341669954114577, 6070904.722786735, -0.29767608923657404, -0.29785143390252321, 17121631423099.696}, {-29.47124, 95.14681, -163.779130441688382, -27.46601, -69.15955, -15.909335945554969, 13487015.8381145492, 121.294026715742277, 5481428.9945736388, -0.51527225545373252, -0.51556587964721788, 104679964020340.318}}; static int testinverse(void) { double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; double azi1a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; struct geod_geodesic g; int i, result = 0; geod_init(&g, wgs84_a, wgs84_f); for (i = 0; i < ncases; ++i) { lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; a12a = geod_geninverse(&g, lat1, lon1, lat2, lon2, &s12a, &azi1a, &azi2a, &m12a, &M12a, &M21a, &S12a); result += checkEquals(azi1, azi1a, 1e-13); result += checkEquals(azi2, azi2a, 1e-13); result += checkEquals(s12, s12a, 1e-8); result += checkEquals(a12, a12a, 1e-13); result += checkEquals(m12, m12a, 1e-8); result += checkEquals(M12, M12a, 1e-15); result += checkEquals(M21, M21a, 1e-15); result += checkEquals(S12, S12a, 0.1); } return result; } static int testdirect(void) { double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; double lat2a, lon2a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; struct geod_geodesic g; int i, result = 0; unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); for (i = 0; i < ncases; ++i) { lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, s12, &lat2a, &lon2a, &azi2a, &s12a, &m12a, &M12a, &M21a, &S12a); result += checkEquals(lat2, lat2a, 1e-13); result += checkEquals(lon2, lon2a, 1e-13); result += checkEquals(azi2, azi2a, 1e-13); result += checkEquals(s12, s12a, 0); result += checkEquals(a12, a12a, 1e-13); result += checkEquals(m12, m12a, 1e-8); result += checkEquals(M12, M12a, 1e-15); result += checkEquals(M21, M21a, 1e-15); result += checkEquals(S12, S12a, 0.1); } return result; } static int testarcdirect(void) { double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; double lat2a, lon2a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; struct geod_geodesic g; int i, result = 0; unsigned flags = GEOD_ARCMODE | GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); for (i = 0; i < ncases; ++i) { lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, a12, &lat2a, &lon2a, &azi2a, &s12a, &m12a, &M12a, &M21a, &S12a); result += checkEquals(lat2, lat2a, 1e-13); result += checkEquals(lon2, lon2a, 1e-13); result += checkEquals(azi2, azi2a, 1e-13); result += checkEquals(s12, s12a, 1e-8); result += checkEquals(a12, a12a, 0); result += checkEquals(s12, s12a, 1e-8); result += checkEquals(m12, m12a, 1e-8); result += checkEquals(M12, M12a, 1e-15); result += checkEquals(M21, M21a, 1e-15); result += checkEquals(S12, S12a, 0.1); } return result; } static int GeodSolve0(void) { double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 40.6, -73.8, 49.01666667, 2.55, &s12, &azi1, &azi2); result += checkEquals(azi1, 53.47022, 0.5e-5); result += checkEquals(azi2, 111.59367, 0.5e-5); result += checkEquals(s12, 5853226, 0.5); return result; } static int GeodSolve1(void) { double lat2, lon2, azi2; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_direct(&g, 40.63972222, -73.77888889, 53.5, 5850e3, &lat2, &lon2, &azi2); result += checkEquals(lat2, 49.01467, 0.5e-5); result += checkEquals(lon2, 2.56106, 0.5e-5); result += checkEquals(azi2, 111.62947, 0.5e-5); return result; } static int GeodSolve2(void) { /* Check fix for antipodal prolate bug found 2010-09-04 */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, 6.4e6, -1/150.0); geod_inverse(&g, 0.07476, 0, -0.07476, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00078, 0.5e-5); result += checkEquals(azi2, 90.00078, 0.5e-5); result += checkEquals(s12, 20106193, 0.5); geod_inverse(&g, 0.1, 0, -0.1, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00105, 0.5e-5); result += checkEquals(azi2, 90.00105, 0.5e-5); result += checkEquals(s12, 20106193, 0.5); return result; } static int GeodSolve4(void) { /* Check fix for short line bug found 2010-05-21 */ double s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 36.493349428792, 0, 36.49334942879201, .0000008, &s12, nullptr, nullptr); result += checkEquals(s12, 0.072, 0.5e-3); return result; } static int GeodSolve5(void) { /* Check fix for point2=pole bug found 2010-05-03 */ double lat2, lon2, azi2; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_direct(&g, 0.01777745589997, 30, 0, 10e6, &lat2, &lon2, &azi2); result += checkEquals(lat2, 90, 0.5e-5); if (lon2 < 0) { result += checkEquals(lon2, -150, 0.5e-5); result += checkEquals(fabs(azi2), 180, 0.5e-5); } else { result += checkEquals(lon2, 30, 0.5e-5); result += checkEquals(azi2, 0, 0.5e-5); } return result; } static int GeodSolve6(void) { /* Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4 * x86 -O3). Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1). */ double s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 88.202499451857, 0, -88.202499451857, 179.981022032992859592, &s12, nullptr, nullptr); result += checkEquals(s12, 20003898.214, 0.5e-3); geod_inverse(&g, 89.262080389218, 0, -89.262080389218, 179.992207982775375662, &s12, nullptr, nullptr); result += checkEquals(s12, 20003925.854, 0.5e-3); geod_inverse(&g, 89.333123580033, 0, -89.333123580032997687, 179.99295812360148422, &s12, nullptr, nullptr); result += checkEquals(s12, 20003926.881, 0.5e-3); return result; } static int GeodSolve9(void) { /* Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3) */ double s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 56.320923501171, 0, -56.320923501171, 179.664747671772880215, &s12, nullptr, nullptr); result += checkEquals(s12, 19993558.287, 0.5e-3); return result; } static int GeodSolve10(void) { /* Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio * 10 rel + debug) */ double s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 52.784459512564, 0, -52.784459512563990912, 179.634407464943777557, &s12, nullptr, nullptr); result += checkEquals(s12, 19991596.095, 0.5e-3); return result; } static int GeodSolve11(void) { /* Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio * 10 rel + debug) */ double s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 48.522876735459, 0, -48.52287673545898293, 179.599720456223079643, &s12, nullptr, nullptr); result += checkEquals(s12, 19989144.774, 0.5e-3); return result; } static int GeodSolve12(void) { /* Check fix for inverse geodesics on extreme prolate/oblate * ellipsoids Reported 2012-08-29 Stefan Guenther * ; fixed 2012-10-07 */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, 89.8, -1.83); geod_inverse(&g, 0, 0, -10, 160, &s12, &azi1, &azi2); result += checkEquals(azi1, 120.27, 1e-2); result += checkEquals(azi2, 105.15, 1e-2); result += checkEquals(s12, 266.7, 1e-1); return result; } static int GeodSolve14(void) { /* Check fix for inverse ignoring lon12 = nan */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 0, 0, 1, nan("0"), &s12, &azi1, &azi2); result += checkNaN(azi1); result += checkNaN(azi2); result += checkNaN(s12); return result; } static int GeodSolve15(void) { /* Initial implementation of Math::eatanhe was wrong for e^2 < 0. This * checks that this is fixed. */ double S12; struct geod_geodesic g; int result = 0; geod_init(&g, 6.4e6, -1/150.0); geod_gendirect(&g, 1, 2, 3, 0, 4, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); result += checkEquals(S12, 23700, 0.5); return result; } static int GeodSolve17(void) { /* Check fix for LONG_UNROLL bug found on 2015-05-07 */ double lat2, lon2, azi2; struct geod_geodesic g; struct geod_geodesicline l; int result = 0; unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); geod_gendirect(&g, 40, -75, -10, flags, 2e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, -39, 1); result += checkEquals(lon2, -254, 1); result += checkEquals(azi2, -170, 1); geod_lineinit(&l, &g, 40, -75, -10, 0); geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, -39, 1); result += checkEquals(lon2, -254, 1); result += checkEquals(azi2, -170, 1); geod_direct(&g, 40, -75, -10, 2e7, &lat2, &lon2, &azi2); result += checkEquals(lat2, -39, 1); result += checkEquals(lon2, 105, 1); result += checkEquals(azi2, -170, 1); geod_position(&l, 2e7, &lat2, &lon2, &azi2); result += checkEquals(lat2, -39, 1); result += checkEquals(lon2, 105, 1); result += checkEquals(azi2, -170, 1); return result; } static int GeodSolve26(void) { /* Check 0/0 problem with area calculation on sphere 2015-09-08 */ double S12; struct geod_geodesic g; int result = 0; geod_init(&g, 6.4e6, 0); geod_geninverse(&g, 1, 2, 3, 4, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); result += checkEquals(S12, 49911046115.0, 0.5); return result; } static int GeodSolve28(void) { /* Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in * Java implementation fixed on 2015-05-19). */ double a12; struct geod_geodesic g; int result = 0; geod_init(&g, 6.4e6, 0.1); a12 = geod_gendirect(&g, 1, 2, 10, 0, 5e6, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(a12, 48.55570690, 0.5e-8); return result; } static int GeodSolve33(void) { /* Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave -- * sind(-0.0) = +0.0 -- and in some version of Visual Studio -- * fmod(-0.0, 360.0) = +0.0. */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00000, 0.5e-5); result += checkEquals(azi2, 90.00000, 0.5e-5); result += checkEquals(s12, 19926189, 0.5); geod_inverse(&g, 0, 0, 0, 179.5, &s12, &azi1, &azi2); result += checkEquals(azi1, 55.96650, 0.5e-5); result += checkEquals(azi2, 124.03350, 0.5e-5); result += checkEquals(s12, 19980862, 0.5); geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 0.00000, 0.5e-5); result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); result += checkEquals(s12, 20003931, 0.5); geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 0.00000, 0.5e-5); result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); result += checkEquals(s12, 19893357, 0.5); geod_init(&g, 6.4e6, 0); geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00000, 0.5e-5); result += checkEquals(azi2, 90.00000, 0.5e-5); result += checkEquals(s12, 19994492, 0.5); geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 0.00000, 0.5e-5); result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); result += checkEquals(s12, 20106193, 0.5); geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 0.00000, 0.5e-5); result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); result += checkEquals(s12, 19994492, 0.5); geod_init(&g, 6.4e6, -1/300.0); geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00000, 0.5e-5); result += checkEquals(azi2, 90.00000, 0.5e-5); result += checkEquals(s12, 19994492, 0.5); geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00000, 0.5e-5); result += checkEquals(azi2, 90.00000, 0.5e-5); result += checkEquals(s12, 20106193, 0.5); geod_inverse(&g, 0, 0, 0.5, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 33.02493, 0.5e-5); result += checkEquals(azi2, 146.97364, 0.5e-5); result += checkEquals(s12, 20082617, 0.5); geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 0.00000, 0.5e-5); result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); result += checkEquals(s12, 20027270, 0.5); return result; } static int GeodSolve55(void) { /* Check fix for nan + point on equator or pole not returning all nans in * Geodesic::Inverse, found 2015-09-23. */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, nan("0"), 0, 0, 90, &s12, &azi1, &azi2); result += checkNaN(azi1); result += checkNaN(azi2); result += checkNaN(s12); geod_inverse(&g, nan("0"), 0, 90, 9, &s12, &azi1, &azi2); result += checkNaN(azi1); result += checkNaN(azi2); result += checkNaN(s12); return result; } static int GeodSolve59(void) { /* Check for points close with longitudes close to 180 deg apart. */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 5, 0.00000000000001, 10, 180, &s12, &azi1, &azi2); result += checkEquals(azi1, 0.000000000000035, 1.5e-14); result += checkEquals(azi2, 179.99999999999996, 1.5e-14); result += checkEquals(s12, 18345191.174332713, 5e-9); return result; } static int GeodSolve61(void) { /* Make sure small negative azimuths are west-going */ double lat2, lon2, azi2; struct geod_geodesic g; struct geod_geodesicline l; int result = 0; unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); geod_gendirect(&g, 45, 0, -0.000000000000000003, flags, 1e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, 45.30632, 0.5e-5); result += checkEquals(lon2, -180, 0.5e-5); result += checkEquals(fabs(azi2), 180, 0.5e-5); geod_inverseline(&l, &g, 45, 0, 80, -0.000000000000000003, 0); geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, 45.30632, 0.5e-5); result += checkEquals(lon2, -180, 0.5e-5); result += checkEquals(fabs(azi2), 180, 0.5e-5); return result; } static int GeodSolve65(void) { /* Check for bug in east-going check in GeodesicLine (needed to check for * sign of 0) and sign error in area calculation due to a bogus override of * the code for alp12. Found/fixed on 2015-12-19. */ double lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; struct geod_geodesic g; struct geod_geodesicline l; int result = 0; unsigned flags = GEOD_LONG_UNROLL, caps = GEOD_ALL; geod_init(&g, wgs84_a, wgs84_f); geod_inverseline(&l, &g, 30, -0.000000000000000001, -31, 180, caps); a12 = geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); result += checkEquals(lat2, -60.23169, 0.5e-5); result += checkEquals(lon2, -0.00000, 0.5e-5); result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); result += checkEquals(s12, 10000000, 0.5); result += checkEquals(a12, 90.06544, 0.5e-5); result += checkEquals(m12, 6363636, 0.5); result += checkEquals(M12, -0.0012834, 0.5e-7); result += checkEquals(M21, 0.0013749, 0.5e-7); result += checkEquals(S12, 0, 0.5); a12 = geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); result += checkEquals(lat2, -30.03547, 0.5e-5); result += checkEquals(lon2, -180.00000, 0.5e-5); result += checkEquals(azi2, -0.00000, 0.5e-5); result += checkEquals(s12, 20000000, 0.5); result += checkEquals(a12, 179.96459, 0.5e-5); result += checkEquals(m12, 54342, 0.5); result += checkEquals(M12, -1.0045592, 0.5e-7); result += checkEquals(M21, -0.9954339, 0.5e-7); result += checkEquals(S12, 127516405431022.0, 0.5); return result; } static int GeodSolve67(void) { /* Check for InverseLine if line is slightly west of S and that s13 is * correctly set. */ double lat2, lon2, azi2; struct geod_geodesic g; struct geod_geodesicline l; int result = 0; unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); geod_inverseline(&l, &g, -5, -0.000000000000002, -10, 180, 0); geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, 4.96445, 0.5e-5); result += checkEquals(lon2, -180.00000, 0.5e-5); result += checkEquals(azi2, -0.00000, 0.5e-5); geod_genposition(&l, flags, 0.5 * l.s13, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, -87.52461, 0.5e-5); result += checkEquals(lon2, -0.00000, 0.5e-5); result += checkEquals(azi2, -180.00000, 0.5e-5); return result; } static int GeodSolve71(void) { /* Check that DirectLine sets s13. */ double lat2, lon2, azi2; struct geod_geodesic g; struct geod_geodesicline l; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_directline(&l, &g, 1, 2, 45, 1e7, 0); geod_position(&l, 0.5 * l.s13, &lat2, &lon2, &azi2); result += checkEquals(lat2, 30.92625, 0.5e-5); result += checkEquals(lon2, 37.54640, 0.5e-5); result += checkEquals(azi2, 55.43104, 0.5e-5); return result; } static int GeodSolve73(void) { /* Check for backwards from the pole bug reported by Anon on 2016-02-13. * This only affected the Java implementation. It was introduced in Java * version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17. * Also the + sign on azi2 is a check on the normalizing of azimuths * (converting -0.0 to +0.0). */ double lat2, lon2, azi2; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_direct(&g, 90, 10, 180, -1e6, &lat2, &lon2, &azi2); result += checkEquals(lat2, 81.04623, 0.5e-5); result += checkEquals(lon2, -170, 0.5e-5); result += azi2 == 0 ? 0 : 1; result += 1/azi2 > 0 ? 0 : 1; /* Check that azi2 = +0.0 not -0.0 */ return result; } static void planimeter(const struct geod_geodesic* g, double points[][2], int N, double* perimeter, double* area) { struct geod_polygon p; int i; geod_polygon_init(&p, 0); for (i = 0; i < N; ++i) geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); geod_polygon_compute(g, &p, 0, 1, area, perimeter); } static void polylength(const struct geod_geodesic* g, double points[][2], int N, double* perimeter) { struct geod_polygon p; int i; geod_polygon_init(&p, 1); for (i = 0; i < N; ++i) geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); geod_polygon_compute(g, &p, 0, 1, nullptr, perimeter); } static int GeodSolve74(void) { /* Check fix for inaccurate areas, bug introduced in v1.46, fixed * 2015-10-16. */ double a12, s12, azi1, azi2, m12, M12, M21, S12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); a12 = geod_geninverse(&g, 54.1589, 15.3872, 54.1591, 15.3877, &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); result += checkEquals(azi1, 55.723110355, 5e-9); result += checkEquals(azi2, 55.723515675, 5e-9); result += checkEquals(s12, 39.527686385, 5e-9); result += checkEquals(a12, 0.000355495, 5e-9); result += checkEquals(m12, 39.527686385, 5e-9); result += checkEquals(M12, 0.999999995, 5e-9); result += checkEquals(M21, 0.999999995, 5e-9); result += checkEquals(S12, 286698586.30197, 5e-4); return result; } static int GeodSolve76(void) { /* The distance from Wellington and Salamanca (a classic failure of * Vincenty) */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, -(41+19/60.0), 174+49/60.0, 40+58/60.0, -(5+30/60.0), &s12, &azi1, &azi2); result += checkEquals(azi1, 160.39137649664, 0.5e-11); result += checkEquals(azi2, 19.50042925176, 0.5e-11); result += checkEquals(s12, 19960543.857179, 0.5e-6); return result; } static int GeodSolve78(void) { /* An example where the NGS calculator fails to converge */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 27.2, 0.0, -27.1, 179.5, &s12, &azi1, &azi2); result += checkEquals(azi1, 45.82468716758, 0.5e-11); result += checkEquals(azi2, 134.22776532670, 0.5e-11); result += checkEquals(s12, 19974354.765767, 0.5e-6); return result; } static int GeodSolve80(void) { /* Some tests to add code coverage: computing scale in special cases + zero * length geodesic (includes GeodSolve80 - GeodSolve83) + using an incapable * line. */ double a12, s12, azi1, azi2, m12, M12, M21, S12; struct geod_geodesic g; struct geod_geodesicline l; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_geninverse(&g, 0, 0, 0, 90, nullptr, nullptr, nullptr, nullptr, &M12, &M21, nullptr); result += checkEquals(M12, -0.00528427534, 0.5e-10); result += checkEquals(M21, -0.00528427534, 0.5e-10); geod_geninverse(&g, 0, 0, 1e-6, 1e-6, nullptr, nullptr, nullptr, nullptr, &M12, &M21, nullptr); result += checkEquals(M12, 1, 0.5e-10); result += checkEquals(M21, 1, 0.5e-10); a12 = geod_geninverse(&g, 20.001, 0, 20.001, 0, &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); result += checkEquals(a12, 0, 1e-13); result += checkEquals(s12, 0, 1e-8); result += checkEquals(azi1, 180, 1e-13); result += checkEquals(azi2, 180, 1e-13); result += checkEquals(m12, 0, 1e-8); result += checkEquals(M12, 1, 1e-15); result += checkEquals(M21, 1, 1e-15); result += checkEquals(S12, 0, 1e-10); result += 1/a12 > 0 ? 0 : 1; result += 1/s12 > 0 ? 0 : 1; result += 1/m12 > 0 ? 0 : 1; a12 = geod_geninverse(&g, 90, 0, 90, 180, &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); result += checkEquals(a12, 0, 1e-13); result += checkEquals(s12, 0, 1e-8); result += checkEquals(azi1, 0, 1e-13); result += checkEquals(azi2, 180, 1e-13); result += checkEquals(m12, 0, 1e-8); result += checkEquals(M12, 1, 1e-15); result += checkEquals(M21, 1, 1e-15); result += checkEquals(S12, 127516405431022.0, 0.5); /* An incapable line which can't take distance as input */ geod_lineinit(&l, &g, 1, 2, 90, GEOD_LATITUDE); a12 = geod_genposition(&l, 0, 1000, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkNaN(a12); return result; } static int GeodSolve84(void) { /* Tests for python implementation to check fix for range errors with * {fmod,sin,cos}(inf) (includes GeodSolve84 - GeodSolve86). */ double lat2, lon2, azi2, inf; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); { /* a round about way to set inf = 0 */ geod_direct(&g, 0, 0, 90, 0, &inf, nullptr, nullptr); /* so that this doesn't give a compiler time error on Windows */ inf = 1.0/inf; } geod_direct(&g, 0, 0, 90, inf, &lat2, &lon2, &azi2); result += checkNaN(lat2); result += checkNaN(lon2); result += checkNaN(azi2); geod_direct(&g, 0, 0, 90, nan("0"), &lat2, &lon2, &azi2); result += checkNaN(lat2); result += checkNaN(lon2); result += checkNaN(azi2); geod_direct(&g, 0, 0, inf, 1000, &lat2, &lon2, &azi2); result += checkNaN(lat2); result += checkNaN(lon2); result += checkNaN(azi2); geod_direct(&g, 0, 0, nan("0"), 1000, &lat2, &lon2, &azi2); result += checkNaN(lat2); result += checkNaN(lon2); result += checkNaN(azi2); geod_direct(&g, 0, inf, 90, 1000, &lat2, &lon2, &azi2); result += lat2 == 0 ? 0 : 1; result += checkNaN(lon2); result += azi2 == 90 ? 0 : 1; geod_direct(&g, 0, nan("0"), 90, 1000, &lat2, &lon2, &azi2); result += lat2 == 0 ? 0 : 1; result += checkNaN(lon2); result += azi2 == 90 ? 0 : 1; geod_direct(&g, inf, 0, 90, 1000, &lat2, &lon2, &azi2); result += checkNaN(lat2); result += checkNaN(lon2); result += checkNaN(azi2); geod_direct(&g, nan("0"), 0, 90, 1000, &lat2, &lon2, &azi2); result += checkNaN(lat2); result += checkNaN(lon2); result += checkNaN(azi2); return result; } static int GeodSolve92(void) { /* Check fix for inaccurate hypot with python 3.[89]. Problem reported * by agdhruv https://github.com/geopy/geopy/issues/466 ; see * https://bugs.python.org/issue43088 */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 37.757540000000006, -122.47018, 37.75754, -122.470177, &s12, &azi1, &azi2); result += checkEquals(azi1, 89.99999923, 1e-7 ); result += checkEquals(azi2, 90.00000106, 1e-7 ); result += checkEquals(s12, 0.264, 0.5e-3); return result; } static int GeodSolve94(void) { /* Check fix for lat2 = nan being treated as lat2 = 0 (bug found * 2021-07-26) */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 0, 0, nan("0"), 90, &s12, &azi1, &azi2); result += checkNaN(azi1); result += checkNaN(azi2); result += checkNaN(s12); return result; } static int GeodSolve96(void) { /* Failure with long doubles found with test case from Nowak + Nowak Da * Costa (2022). Problem was using somg12 > 1 as a test that it needed * to be set when roundoff could result in somg12 slightly bigger that 1. * Found + fixed 2022-03-30. */ double S12; struct geod_geodesic g; int result = 0; geod_init(&g, 6378137, 1/298.257222101); geod_geninverse(&g, 0, 0, 60.0832522871723, 89.8492185074635, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); result += checkEquals(S12, 42426932221845, 0.5); return result; } static int GeodSolve99(void) { /* Test case https://github.com/geographiclib/geographiclib-js/issues/3 * Problem was that output of sincosd(+/-45) was inconsistent because of * directed rounding by Javascript's Math.round. C implementation was OK. */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 45.0, 0.0, -45.0, 179.572719, &s12, &azi1, &azi2); result += checkEquals(azi1, 90.00000028, 1e-8 ); result += checkEquals(azi2, 90.00000028, 1e-8 ); result += checkEquals(s12, 19987083.007, 0.5e-3); return result; } static int GeodSolve100(void) { /* Check fix for meridional failure for a strongly prolate ellipsoid. * This was caused by assuming that sig12 < 1 guarantees the meridional * geodesic is shortest (even though m12 < 0). Counter example is tested * here. Bug is not present for f >= -2, b < 3*a. For f = -2.1 the * inverse calculation for 30.61 0 30.61 180 exhibits the bug. */ double azi1, azi2, s12; struct geod_geodesic g; int result = 0; geod_init(&g, 1e6, -3); geod_inverse(&g, 30.0, 0.0, 30.0, 180.0, &s12, &azi1, &azi2); /* Sloppy bounds checking because series solution is inaccurate for * ellipsoids this eccentric. */ result += checkEquals(azi1, 22.368806, 1.0 ); result += checkEquals(azi2, 157.631194, 1.0 ); result += checkEquals(s12, 1074081.6, 1e3 ); return result; } static int Planimeter0(void) { /* Check fix for pole-encircling bug found 2011-03-16 */ double pa[4][2] = {{89, 0}, {89, 90}, {89, 180}, {89, 270}}; double pb[4][2] = {{-89, 0}, {-89, 90}, {-89, 180}, {-89, 270}}; double pc[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; double pd[3][2] = {{90, 0}, {0, 0}, {0, 90}}; struct geod_geodesic g; double perimeter, area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); planimeter(&g, pa, 4, &perimeter, &area); result += checkEquals(perimeter, 631819.8745, 1e-4); result += checkEquals(area, 24952305678.0, 1); planimeter(&g, pb, 4, &perimeter, &area); result += checkEquals(perimeter, 631819.8745, 1e-4); result += checkEquals(area, -24952305678.0, 1); planimeter(&g, pc, 4, &perimeter, &area); result += checkEquals(perimeter, 627598.2731, 1e-4); result += checkEquals(area, 24619419146.0, 1); planimeter(&g, pd, 3, &perimeter, &area); result += checkEquals(perimeter, 30022685, 1); result += checkEquals(area, 63758202715511.0, 1); polylength(&g, pd, 3, &perimeter); result += checkEquals(perimeter, 20020719, 1); return result; } static int Planimeter5(void) { /* Check fix for Planimeter pole crossing bug found 2011-06-24 */ double points[3][2] = {{89, 0.1}, {89, 90.1}, {89, -179.9}}; struct geod_geodesic g; double perimeter, area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); planimeter(&g, points, 3, &perimeter, &area); result += checkEquals(perimeter, 539297, 1); result += checkEquals(area, 12476152838.5, 1); return result; } static int Planimeter6(void) { /* Check fix for Planimeter lon12 rounding bug found 2012-12-03 */ double pa[3][2] = {{9, -0.00000000000001}, {9, 180}, {9, 0}}; double pb[3][2] = {{9, 0.00000000000001}, {9, 0}, {9, 180}}; double pc[3][2] = {{9, 0.00000000000001}, {9, 180}, {9, 0}}; double pd[3][2] = {{9, -0.00000000000001}, {9, 0}, {9, 180}}; struct geod_geodesic g; double perimeter, area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); planimeter(&g, pa, 3, &perimeter, &area); result += checkEquals(perimeter, 36026861, 1); result += checkEquals(area, 0, 1); planimeter(&g, pb, 3, &perimeter, &area); result += checkEquals(perimeter, 36026861, 1); result += checkEquals(area, 0, 1); planimeter(&g, pc, 3, &perimeter, &area); result += checkEquals(perimeter, 36026861, 1); result += checkEquals(area, 0, 1); planimeter(&g, pd, 3, &perimeter, &area); result += checkEquals(perimeter, 36026861, 1); result += checkEquals(area, 0, 1); return result; } static int Planimeter12(void) { /* Area of arctic circle (not really -- adjunct to rhumb-area test) */ double points[3][2] = {{66.562222222, 0}, {66.562222222, 180}, {66.562222222, 360}}; struct geod_geodesic g; double perimeter, area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); planimeter(&g, points, 3, &perimeter, &area); result += checkEquals(perimeter, 10465729, 1); result += checkEquals(area, 0, 1); return result; } static int Planimeter12r(void) { /* Area of arctic circle (not really -- adjunct to rhumb-area test) */ double points[3][2] = {{66.562222222, -0}, {66.562222222, -180}, {66.562222222, -360}}; struct geod_geodesic g; double perimeter, area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); planimeter(&g, points, 3, &perimeter, &area); result += checkEquals(perimeter, 10465729, 1); result += checkEquals(area, 0, 1); return result; } static int Planimeter13(void) { /* Check encircling pole twice */ double points[6][2] = {{89,-360}, {89,-240}, {89,-120}, {89,0}, {89,120}, {89,240}}; struct geod_geodesic g; double perimeter, area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); planimeter(&g, points, 6, &perimeter, &area); result += checkEquals(perimeter, 1160741, 1); result += checkEquals(area, 32415230256.0, 1); return result; } static int Planimeter15(void) { /* Coverage tests, includes Planimeter15 - Planimeter18 (combinations of * reverse and sign) + calls to testpoint, testedge, geod_polygonarea. */ struct geod_geodesic g; struct geod_polygon p; double lat[] = {2, 1, 3}, lon[] = {1, 2, 3}; double area, s12, azi1; double r = 18454562325.45119, a0 = 510065621724088.5093; /* ellipsoid area */ int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_polygon_init(&p, 0); geod_polygon_addpoint(&g, &p, lat[0], lon[0]); geod_polygon_addpoint(&g, &p, lat[1], lon[1]); geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 1, &area, nullptr); result += checkEquals(area, r, 0.5); geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 0, &area, nullptr); result += checkEquals(area, r, 0.5); geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 1, &area, nullptr); result += checkEquals(area, -r, 0.5); geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 0, &area, nullptr); result += checkEquals(area, a0-r, 0.5); geod_inverse(&g, lat[1], lon[1], lat[2], lon[2], &s12, &azi1, nullptr); geod_polygon_testedge(&g, &p, azi1, s12, 0, 1, &area, nullptr); result += checkEquals(area, r, 0.5); geod_polygon_testedge(&g, &p, azi1, s12, 0, 0, &area, nullptr); result += checkEquals(area, r, 0.5); geod_polygon_testedge(&g, &p, azi1, s12, 1, 1, &area, nullptr); result += checkEquals(area, -r, 0.5); geod_polygon_testedge(&g, &p, azi1, s12, 1, 0, &area, nullptr); result += checkEquals(area, a0-r, 0.5); geod_polygon_addpoint(&g, &p, lat[2], lon[2]); geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); result += checkEquals(area, r, 0.5); geod_polygon_compute(&g, &p, 0, 0, &area, nullptr); result += checkEquals(area, r, 0.5); geod_polygon_compute(&g, &p, 1, 1, &area, nullptr); result += checkEquals(area, -r, 0.5); geod_polygon_compute(&g, &p, 1, 0, &area, nullptr); result += checkEquals(area, a0-r, 0.5); geod_polygonarea(&g, lat, lon, 3, &area, nullptr); result += checkEquals(area, r, 0.5); return result; } static int Planimeter19(void) { /* Coverage tests, includes Planimeter19 - Planimeter20 (degenerate * polygons) + extra cases. */ struct geod_geodesic g; struct geod_polygon p; double area, perim; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_polygon_init(&p, 0); geod_polygon_compute(&g, &p, 0, 1, &area, &perim); result += area == 0 ? 0 : 1; result += perim == 0 ? 0 : 1; geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, &area, &perim); result += area == 0 ? 0 : 1; result += perim == 0 ? 0 : 1; geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, &area, &perim); result += checkNaN(area); result += checkNaN(perim); geod_polygon_addpoint(&g, &p, 1, 1); geod_polygon_compute(&g, &p, 0, 1, &area, &perim); result += area == 0 ? 0 : 1; result += perim == 0 ? 0 : 1; geod_polygon_init(&p, 1); geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); result += checkNaN(perim); geod_polygon_addpoint(&g, &p, 1, 1); geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; geod_polygon_addpoint(&g, &p, 1, 1); geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); result += checkEquals(perim, 1000, 1e-10); geod_polygon_testpoint(&g, &p, 2, 2, 0, 1, nullptr, &perim); result += checkEquals(perim, 156876.149, 0.5e-3); return result; } static int Planimeter21(void) { /* Some tests to add code coverage: multiple circlings of pole (includes * Planimeter21 - Planimeter28) + invocations via testpoint and testedge. */ struct geod_geodesic g; struct geod_polygon p; double area, lat = 45, a = 39.2144607176828184218, s = 8420705.40957178156285, r = 39433884866571.4277, /* Area for one circuit */ a0 = 510065621724088.5093; /* Ellipsoid area */ int result = 0, i; geod_init(&g, wgs84_a, wgs84_f); geod_polygon_init(&p, 0); geod_polygon_addpoint(&g, &p, lat, 60); geod_polygon_addpoint(&g, &p, lat, 180); geod_polygon_addpoint(&g, &p, lat, -60); geod_polygon_addpoint(&g, &p, lat, 60); geod_polygon_addpoint(&g, &p, lat, 180); geod_polygon_addpoint(&g, &p, lat, -60); for (i = 3; i <= 4; ++i) { geod_polygon_addpoint(&g, &p, lat, 60); geod_polygon_addpoint(&g, &p, lat, 180); geod_polygon_testpoint(&g, &p, lat, -60, 0, 1, &area, nullptr); result += checkEquals(area, i*r, 0.5); geod_polygon_testpoint(&g, &p, lat, -60, 0, 0, &area, nullptr); result += checkEquals(area, i*r, 0.5); geod_polygon_testpoint(&g, &p, lat, -60, 1, 1, &area, nullptr); result += checkEquals(area, -i*r, 0.5); geod_polygon_testpoint(&g, &p, lat, -60, 1, 0, &area, nullptr); result += checkEquals(area, -i*r + a0, 0.5); geod_polygon_testedge(&g, &p, a, s, 0, 1, &area, nullptr); result += checkEquals(area, i*r, 0.5); geod_polygon_testedge(&g, &p, a, s, 0, 0, &area, nullptr); result += checkEquals(area, i*r, 0.5); geod_polygon_testedge(&g, &p, a, s, 1, 1, &area, nullptr); result += checkEquals(area, -i*r, 0.5); geod_polygon_testedge(&g, &p, a, s, 1, 0, &area, nullptr); result += checkEquals(area, -i*r + a0, 0.5); geod_polygon_addpoint(&g, &p, lat, -60); geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); result += checkEquals(area, i*r, 0.5); geod_polygon_compute(&g, &p, 0, 0, &area, nullptr); result += checkEquals(area, i*r, 0.5); geod_polygon_compute(&g, &p, 1, 1, &area, nullptr); result += checkEquals(area, -i*r, 0.5); geod_polygon_compute(&g, &p, 1, 0, &area, nullptr); result += checkEquals(area, -i*r + a0, 0.5); } return result; } static int Planimeter29(void) { /* Check fix to transitdirect vs transit zero handling inconsistency */ struct geod_geodesic g; struct geod_polygon p; double area; int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_polygon_init(&p, 0); geod_polygon_addpoint(&g, &p, 0, 0); geod_polygon_addedge(&g, &p, 90, 1000); geod_polygon_addedge(&g, &p, 0, 1000); geod_polygon_addedge(&g, &p, -90, 1000); geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); /* The area should be 1e6. Prior to the fix it was 1e6 - A/2, where * A = ellipsoid area. */ result += checkEquals(area, 1000000.0, 0.01); return result; } int main(void) { int n = 0, i; if ((i = testinverse())) {++n; printf("testinverse fail: %d\n", i);} if ((i = testdirect())) {++n; printf("testdirect fail: %d\n", i);} if ((i = testarcdirect())) {++n; printf("testarcdirect fail: %d\n", i);} if ((i = GeodSolve0())) {++n; printf("GeodSolve0 fail: %d\n", i);} if ((i = GeodSolve1())) {++n; printf("GeodSolve1 fail: %d\n", i);} if ((i = GeodSolve2())) {++n; printf("GeodSolve2 fail: %d\n", i);} if ((i = GeodSolve4())) {++n; printf("GeodSolve4 fail: %d\n", i);} if ((i = GeodSolve5())) {++n; printf("GeodSolve5 fail: %d\n", i);} if ((i = GeodSolve6())) {++n; printf("GeodSolve6 fail: %d\n", i);} if ((i = GeodSolve9())) {++n; printf("GeodSolve9 fail: %d\n", i);} if ((i = GeodSolve10())) {++n; printf("GeodSolve10 fail: %d\n", i);} if ((i = GeodSolve11())) {++n; printf("GeodSolve11 fail: %d\n", i);} if ((i = GeodSolve12())) {++n; printf("GeodSolve12 fail: %d\n", i);} if ((i = GeodSolve14())) {++n; printf("GeodSolve14 fail: %d\n", i);} if ((i = GeodSolve15())) {++n; printf("GeodSolve15 fail: %d\n", i);} if ((i = GeodSolve17())) {++n; printf("GeodSolve17 fail: %d\n", i);} if ((i = GeodSolve26())) {++n; printf("GeodSolve26 fail: %d\n", i);} if ((i = GeodSolve28())) {++n; printf("GeodSolve28 fail: %d\n", i);} if ((i = GeodSolve33())) {++n; printf("GeodSolve33 fail: %d\n", i);} if ((i = GeodSolve55())) {++n; printf("GeodSolve55 fail: %d\n", i);} if ((i = GeodSolve59())) {++n; printf("GeodSolve59 fail: %d\n", i);} if ((i = GeodSolve61())) {++n; printf("GeodSolve61 fail: %d\n", i);} if ((i = GeodSolve65())) {++n; printf("GeodSolve65 fail: %d\n", i);} if ((i = GeodSolve67())) {++n; printf("GeodSolve67 fail: %d\n", i);} if ((i = GeodSolve71())) {++n; printf("GeodSolve71 fail: %d\n", i);} if ((i = GeodSolve73())) {++n; printf("GeodSolve73 fail: %d\n", i);} if ((i = GeodSolve74())) {++n; printf("GeodSolve74 fail: %d\n", i);} if ((i = GeodSolve76())) {++n; printf("GeodSolve76 fail: %d\n", i);} if ((i = GeodSolve78())) {++n; printf("GeodSolve78 fail: %d\n", i);} if ((i = GeodSolve80())) {++n; printf("GeodSolve80 fail: %d\n", i);} if ((i = GeodSolve84())) {++n; printf("GeodSolve84 fail: %d\n", i);} if ((i = GeodSolve92())) {++n; printf("GeodSolve92 fail: %d\n", i);} if ((i = GeodSolve94())) {++n; printf("GeodSolve94 fail: %d\n", i);} if ((i = GeodSolve96())) {++n; printf("GeodSolve96 fail: %d\n", i);} if ((i = GeodSolve99())) {++n; printf("GeodSolve99 fail: %d\n", i);} if ((i = GeodSolve100())) {++n; printf("GeodSolve100 fail: %d\n", i);} if ((i = Planimeter0())) {++n; printf("Planimeter0 fail: %d\n", i);} if ((i = Planimeter5())) {++n; printf("Planimeter5 fail: %d\n", i);} if ((i = Planimeter6())) {++n; printf("Planimeter6 fail: %d\n", i);} if ((i = Planimeter12())) {++n; printf("Planimeter12 fail: %d\n", i);} if ((i = Planimeter12r())) {++n; printf("Planimeter12r fail: %d\n", i);} if ((i = Planimeter13())) {++n; printf("Planimeter13 fail: %d\n", i);} if ((i = Planimeter15())) {++n; printf("Planimeter15 fail: %d\n", i);} if ((i = Planimeter19())) {++n; printf("Planimeter19 fail: %d\n", i);} if ((i = Planimeter21())) {++n; printf("Planimeter21 fail: %d\n", i);} if ((i = Planimeter29())) {++n; printf("Planimeter29 fail: %d\n", i);} return n; } proj-9.8.1/src/tests/multistresstest.cpp000664 001750 001750 00000032441 15166171715 020412 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ.4 * Purpose: Mainline program to stress test multithreaded PROJ.4 processing. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2010, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include #include "proj.h" #ifdef _WIN32 #include #else #include #include #endif #define num_threads 10 static int num_iterations = 1000000; static int reinit_every_iteration = 0; typedef struct { const char *src_def; const char *dst_def; PJ_COORD src; PJ_COORD dst; int dst_error; int skip; } TestItem; static TestItem test_list[] = { {"+proj=utm +zone=11 +datum=WGS84", "+proj=latlong +datum=WGS84", proj_coord(150000.0, 3000000.0, 0.0, 0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=utm +zone=11 +datum=NAD83", "+proj=latlong +datum=NAD27", proj_coord(150000.0, 3000000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=utm +zone=11 +datum=NAD83", "+proj=latlong +nadgrids=@null +ellps=WGS84", proj_coord(150000.0, 3000000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=utm +zone=11 +datum=WGS84", "+proj=merc +datum=potsdam", proj_coord(150000.0, 3000000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=latlong +nadgrids=nzgd2kgrid0005.gsb", "+proj=latlong +datum=WGS84", proj_coord(150000.0, 3000000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=latlong +nadgrids=nzgd2kgrid0005.gsb", "+proj=latlong +datum=WGS84", proj_coord(170, -40, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=latlong +ellps=GRS80 +towgs84=2,3,5", "+proj=latlong +ellps=intl +towgs84=10,12,15", proj_coord(170, -40, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=eqc +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", "+proj=stere +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=cea +lat_ts=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", "+proj=merc +lon_0=12 +k=0.999 +x_0=100000 +y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=bonne +lat_1=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", "+proj=cass +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=nzmg +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", "+proj=gnom +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=ortho +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", "+proj=laea +lat_0=11 +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=aeqd +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", "+proj=eqdc +lat_1=20 +lat_2=5 +lat_0=11 +lon_0=12 +x_0=100000 " "+y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+proj=mill +lat_0=11 +lon_0=12 +y_0=200000 +datum=WGS84 ", "+proj=moll +lon_0=12 +x_0=100000 +y_0=200000 +datum=WGS84 ", proj_coord(150000.0, 250000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {"+init=epsg:3309", "+init=epsg:4326", proj_coord(150000.0, 30000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}, {/* Bad projection (invalid ellipsoid parameter +R_A=0) */ "+proj=utm +zone=11 +datum=WGS84", "+proj=merc +datum=potsdam +R_A=0", proj_coord(150000.0, 3000000.0, 0.0, 0.0), proj_coord(0.0, 0.0, 0.0, 0.0), 0, 0}}; static volatile int active_thread_count = 0; /************************************************************************/ /* TestThread() */ /************************************************************************/ static void TestThread() { int i, test_count = sizeof(test_list) / sizeof(TestItem); int repeat_count = num_iterations; int i_iter; /* -------------------------------------------------------------------- */ /* Initialize coordinate system definitions. */ /* -------------------------------------------------------------------- */ PJ **pj_list; PJ_CONTEXT *ctx = proj_context_create(); pj_list = (PJ **)calloc(test_count, sizeof(PJ *)); if (!reinit_every_iteration) { for (i = 0; i < test_count; i++) { TestItem *test = test_list + i; pj_list[i] = proj_create_crs_to_crs(ctx, test->src_def, test->dst_def, nullptr); } } /* -------------------------------------------------------------------- */ /* Perform tests - over and over. */ /* -------------------------------------------------------------------- */ for (i_iter = 0; i_iter < repeat_count; i_iter++) { for (i = 0; i < test_count; i++) { TestItem *test = test_list + i; if (reinit_every_iteration) { proj_context_use_proj4_init_rules(nullptr, true); pj_list[i] = proj_create_crs_to_crs(ctx, test->src_def, test->dst_def, nullptr); { int skipTest = (pj_list[i] == nullptr); if (skipTest != test->skip) fprintf( stderr, "Threaded projection initialization does not match " "unthreaded initialization\n"); if (skipTest) { proj_destroy(pj_list[i]); continue; } } } if (test->skip) continue; PJ_COORD out = proj_trans(pj_list[i], PJ_FWD, test->src); int error = proj_errno(pj_list[i]); if (error != test->dst_error) { fprintf(stderr, "Got error %d, expected %d\n", error, test->dst_error); } proj_errno_reset(pj_list[i]); if (out.xyz.x != test->dst.xyz.x || out.xyz.y != test->dst.xyz.y || out.xyz.z != test->dst.xyz.z) // if( x != test->dst_x || y != test->dst_y || z != test->dst_z ) { fprintf(stderr, "Got %.15g,%.15g,%.15g\n" "Expected %.15g,%.15g,%.15g\n" "Diff %.15g,%.15g,%.15g\n", out.xyz.x, out.xyz.y, out.xyz.z, test->dst.xyz.x, test->dst.xyz.y, test->dst.xyz.z, out.xyz.x - test->dst.xyz.x, out.xyz.y - test->dst.xyz.y, out.xyz.z - test->dst.xyz.z); } if (reinit_every_iteration) { proj_destroy(pj_list[i]); } } } /* -------------------------------------------------------------------- */ /* Cleanup */ /* -------------------------------------------------------------------- */ if (!reinit_every_iteration) { for (i = 0; i < test_count; i++) { proj_destroy(pj_list[i]); } } free(pj_list); proj_context_destroy(ctx); printf("%d iterations of the %d tests complete in thread X\n", repeat_count, test_count); active_thread_count--; } #ifdef _WIN32 /************************************************************************/ /* WinTestThread() */ /************************************************************************/ static DWORD WINAPI WinTestThread(LPVOID lpParameter) { TestThread(); return 0; } #else /************************************************************************/ /* PosixTestThread() */ /************************************************************************/ static void *PosixTestThread(void *pData) { (void)pData; TestThread(); return nullptr; } #endif /************************************************************************/ /* main() */ /************************************************************************/ #ifdef _WIN32 static DWORD WINAPI do_main(LPVOID unused) #else static int do_main(void) #endif { /* -------------------------------------------------------------------- */ /* Our first pass is to establish the correct answers for all */ /* the tests. */ /* -------------------------------------------------------------------- */ int i, test_count = sizeof(test_list) / sizeof(TestItem); for (i = 0; i < test_count; i++) { TestItem *test = test_list + i; PJ *pj; proj_context_use_proj4_init_rules(nullptr, true); pj = proj_create_crs_to_crs(nullptr, test->src_def, test->dst_def, nullptr); if (pj == nullptr) { printf("Unable to translate:\n%s\n or\n%s\n", test->src_def, test->dst_def); test->skip = 1; proj_destroy(pj); continue; } PJ_COORD out = proj_trans(pj, PJ_FWD, test->src); test->dst = out; test->dst_error = proj_errno(pj); proj_destroy(pj); test->skip = 0; #ifdef nodef printf("Test %d - output %.14g,%.14g,%g\n", i, test->dst.xyz.x, test->dst.xyz.y, test->dst.xyz.z); #endif } printf("%d tests initialized.\n", test_count); /* -------------------------------------------------------------------- */ /* Now launch a bunch of threads to repeat the tests. */ /* -------------------------------------------------------------------- */ #ifdef _WIN32 { // Scoped to workaround lack of c99 support in VS HANDLE ahThread[num_threads]; for (i = 0; i < num_threads; i++) { active_thread_count++; ahThread[i] = CreateThread(NULL, 0, WinTestThread, NULL, 0, NULL); if (ahThread[i] == 0) { printf("Thread creation failed."); return 1; } } printf("%d test threads launched.\n", num_threads); WaitForMultipleObjects(num_threads, ahThread, TRUE, INFINITE); } #else { pthread_t ahThread[num_threads]; pthread_attr_t hThreadAttr; pthread_attr_init(&hThreadAttr); pthread_attr_setdetachstate(&hThreadAttr, PTHREAD_CREATE_DETACHED); for (i = 0; i < num_threads; i++) { active_thread_count++; pthread_create(&(ahThread[i]), &hThreadAttr, PosixTestThread, nullptr); } printf("%d test threads launched.\n", num_threads); while (active_thread_count > 0) sleep(1); } #endif printf("all tests complete.\n"); return 0; } int main(int argc, char **argv) { int i; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "-reinit") == 0) reinit_every_iteration = 1; else if (strcmp(argv[i], "-num_iterations") == 0 && i + 1 < argc) { num_iterations = atoi(argv[i + 1]); i++; } } #ifdef _WIN32 /* This is an incredible weirdness but with mingw cross-compiler */ /* 1. - b/a; where double a = 6378206.4; and double b = 6356583.8; */ /* does not evaluate the same in the main thread or in a thread forked */ /* by CreateThread(), so run the main in a thread... */ { HANDLE thread = CreateThread(NULL, 0, do_main, NULL, 0, NULL); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); } #else do_main(); #endif return 0; } proj-9.8.1/src/pipeline.cpp000664 001750 001750 00000063036 15166171715 015563 0ustar00eveneven000000 000000 /******************************************************************************* Transformation pipeline manager Thomas Knudsen, 2016-05-20/2016-11-20 ******************************************************************************** Geodetic transformations are typically organized in a number of steps. For example, a datum shift could be carried out through these steps: 1. Convert (latitude, longitude, ellipsoidal height) to 3D geocentric cartesian coordinates (X, Y, Z) 2. Transform the (X, Y, Z) coordinates to the new datum, using a 7 parameter Helmert transformation. 3. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) If the height system used is orthometric, rather than ellipsoidal, another step is needed at each end of the process: 1. Add the local geoid undulation (N) to the orthometric height to obtain the ellipsoidal (i.e. geometric) height. 2. Convert (latitude, longitude, ellipsoidal height) to 3D geocentric cartesian coordinates (X, Y, Z) 3. Transform the (X, Y, Z) coordinates to the new datum, using a 7 parameter Helmert transformation. 4. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) 5. Subtract the local geoid undulation (N) from the ellipsoidal height to obtain the orthometric height. Additional steps can be added for e.g. change of vertical datum, so the list can grow fairly long. None of the steps are, however, particularly complex, and data flow is strictly from top to bottom. Hence, in principle, the first example above could be implemented using Unix pipelines: cat my_coordinates | geographic_to_xyz | helmert | xyz_to_geographic > my_transformed_coordinates in the grand tradition of Software Tools [1]. The proj pipeline driver implements a similar concept: Stringing together a number of steps, feeding the output of one step to the input of the next. It is a very powerful concept, that increases the range of relevance of the proj.4 system substantially. It is, however, not a particularly intrusive addition to the PROJ.4 code base: The implementation is by and large completed by adding an extra projection called "pipeline" (i.e. this file), which handles all business, and a small amount of added functionality in the pj_init code, implementing support for multilevel, embedded pipelines. Syntactically, the pipeline system introduces the "+step" keyword (which indicates the start of each transformation step), and reintroduces the +inv keyword (indicating that a given transformation step should run in reverse, i.e. forward, when the pipeline is executed in inverse direction, and vice versa). Hence, the first transformation example above, can be implemented as: +proj=pipeline +step proj=cart +step proj=helmert +step proj=cart +inv Where indicate the Helmert arguments: 3 translations (+x=..., +y=..., +z=...), 3 rotations (+rx=..., +ry=..., +rz=...) and a scale factor (+s=...). Following geodetic conventions, the rotations are given in arcseconds, and the scale factor is given as parts-per-million. [1] B. W. Kernighan & P. J. Plauger: Software tools. Reading, Massachusetts, Addison-Wesley, 1976, 338 pp. ******************************************************************************** Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 ******************************************************************************** * Copyright (c) 2016, 2017, 2018 Thomas Knudsen / SDFE * * 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 #include #include #include #include #include "geodesic.h" #include "proj.h" #include "proj_internal.h" PROJ_HEAD(pipeline, "Transformation pipeline manager"); PROJ_HEAD(pop, "Retrieve coordinate value from pipeline stack"); PROJ_HEAD(push, "Save coordinate value on pipeline stack"); /* Projection specific elements for the PJ object */ namespace { // anonymous namespace struct Step { PJ *pj = nullptr; bool omit_fwd = false; bool omit_inv = false; Step(PJ *pjIn, bool omitFwdIn, bool omitInvIn) : pj(pjIn), omit_fwd(omitFwdIn), omit_inv(omitInvIn) {} Step(Step &&other) : pj(std::move(other.pj)), omit_fwd(other.omit_fwd), omit_inv(other.omit_inv) { other.pj = nullptr; } Step(const Step &) = delete; Step &operator=(const Step &) = delete; ~Step() { proj_destroy(pj); } }; struct Pipeline { char **argv = nullptr; char **current_argv = nullptr; std::vector steps{}; std::stack stack[4]; }; struct PushPop { bool v1; bool v2; bool v3; bool v4; }; } // anonymous namespace static void pipeline_forward_4d(PJ_COORD &point, PJ *P); static void pipeline_reverse_4d(PJ_COORD &point, PJ *P); static PJ_XYZ pipeline_forward_3d(PJ_LPZ lpz, PJ *P); static PJ_LPZ pipeline_reverse_3d(PJ_XYZ xyz, PJ *P); static PJ_XY pipeline_forward(PJ_LP lp, PJ *P); static PJ_LP pipeline_reverse(PJ_XY xy, PJ *P); static void pipeline_reassign_context(PJ *P, PJ_CONTEXT *ctx) { auto pipeline = static_cast(P->opaque); for (auto &step : pipeline->steps) proj_assign_context(step.pj, ctx); } static void pipeline_forward_4d(PJ_COORD &point, PJ *P) { auto pipeline = static_cast(P->opaque); for (auto &step : pipeline->steps) { if (!step.omit_fwd) { if (!step.pj->inverted) pj_fwd4d(point, step.pj); else pj_inv4d(point, step.pj); if (point.xyzt.x == HUGE_VAL) { break; } } } } static void pipeline_reverse_4d(PJ_COORD &point, PJ *P) { auto pipeline = static_cast(P->opaque); for (auto iterStep = pipeline->steps.rbegin(); iterStep != pipeline->steps.rend(); ++iterStep) { const auto &step = *iterStep; if (!step.omit_inv) { if (step.pj->inverted) pj_fwd4d(point, step.pj); else pj_inv4d(point, step.pj); if (point.xyzt.x == HUGE_VAL) { break; } } } } static PJ_XYZ pipeline_forward_3d(PJ_LPZ lpz, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; auto pipeline = static_cast(P->opaque); for (auto &step : pipeline->steps) { if (!step.omit_fwd) { point = pj_approx_3D_trans(step.pj, PJ_FWD, point); if (point.xyzt.x == HUGE_VAL) { break; } } } return point.xyz; } static PJ_LPZ pipeline_reverse_3d(PJ_XYZ xyz, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; auto pipeline = static_cast(P->opaque); for (auto iterStep = pipeline->steps.rbegin(); iterStep != pipeline->steps.rend(); ++iterStep) { const auto &step = *iterStep; if (!step.omit_inv) { point = proj_trans(step.pj, PJ_INV, point); if (point.xyzt.x == HUGE_VAL) { break; } } } return point.lpz; } static PJ_XY pipeline_forward(PJ_LP lp, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lp = lp; auto pipeline = static_cast(P->opaque); for (auto &step : pipeline->steps) { if (!step.omit_fwd) { point = pj_approx_2D_trans(step.pj, PJ_FWD, point); if (point.xyzt.x == HUGE_VAL) { break; } } } return point.xy; } static PJ_LP pipeline_reverse(PJ_XY xy, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xy = xy; auto pipeline = static_cast(P->opaque); for (auto iterStep = pipeline->steps.rbegin(); iterStep != pipeline->steps.rend(); ++iterStep) { const auto &step = *iterStep; if (!step.omit_inv) { point = pj_approx_2D_trans(step.pj, PJ_INV, point); if (point.xyzt.x == HUGE_VAL) { break; } } } return point.lp; } static PJ *destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); auto pipeline = static_cast(P->opaque); free(pipeline->argv); free(pipeline->current_argv); delete pipeline; P->opaque = nullptr; return pj_default_destructor(P, errlev); } /* count the number of args in pipeline definition, and mark all args as used */ static size_t argc_params(paralist *params) { size_t argc = 0; for (; params != nullptr; params = params->next) { argc++; params->used = 1; } return ++argc; /* one extra for the sentinel */ } /* Sentinel for argument list */ static const char *argv_sentinel = "step"; /* turn paralist into argc/argv style argument list */ static char **argv_params(paralist *params, size_t argc) { char **argv; size_t i = 0; argv = static_cast(calloc(argc, sizeof(char *))); if (nullptr == argv) return nullptr; for (; params != nullptr; params = params->next) argv[i++] = params->param; argv[i++] = const_cast(argv_sentinel); return argv; } /* Being the special operator that the pipeline is, we have to handle the */ /* ellipsoid differently than usual. In general, the pipeline operation does */ /* not need an ellipsoid, but in some cases it is beneficial nonetheless. */ /* Unfortunately we can't use the normal ellipsoid setter in pj_init, since */ /* it adds a +ellps parameter to the global args if nothing else is specified*/ /* This is problematic since that ellipsoid spec is then passed on to the */ /* pipeline children. This is rarely what we want, so here we implement our */ /* own logic instead. If an ellipsoid is set in the global args, it is used */ /* as the pipeline ellipsoid. Otherwise we use GRS80 parameters as default. */ /* At last we calculate the rest of the ellipsoid parameters and */ /* re-initialize P->geod. */ static void set_ellipsoid(PJ *P) { paralist *cur, *attachment; int err = proj_errno_reset(P); /* Break the linked list after the global args */ attachment = nullptr; for (cur = P->params; cur != nullptr; cur = cur->next) /* cur->next will always be non 0 given argv_sentinel presence, */ /* but this is far from being obvious for a static analyzer */ if (cur->next != nullptr && strcmp(argv_sentinel, cur->next->param) == 0) { attachment = cur->next; cur->next = nullptr; break; } /* Check if there's any ellipsoid specification in the global params. */ /* If not, use GRS80 as default */ if (0 != pj_ellipsoid(P)) { P->a = 6378137.0; P->f = 1.0 / 298.257222101; P->es = 2 * P->f - P->f * P->f; /* reset an "unerror": In this special use case, the errno is */ /* not an error signal, but just a reply from pj_ellipsoid, */ /* telling us that "No - there was no ellipsoid definition in */ /* the PJ you provided". */ proj_errno_reset(P); } P->a_orig = P->a; P->es_orig = P->es; if (pj_calc_ellipsoid_params(P, P->a, P->es) == 0) geod_init(P->geod, P->a, P->f); /* Re-attach the dangling list */ /* Note: cur will always be non 0 given argv_sentinel presence, */ /* but this is far from being obvious for a static analyzer */ if (cur != nullptr) cur->next = attachment; proj_errno_restore(P, err); } PJ *OPERATION(pipeline, 0) { int i, nsteps = 0, argc; int i_pipeline = -1, i_first_step = -1, i_current_step; char **argv, **current_argv; if (P->ctx->pipelineInitRecursiongCounter == 5) { // Can happen for a string like: // proj=pipeline step "x="""," u=" proj=pipeline step ste=""[" u=" // proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" // proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" // proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" // proj=pipeline step ste="[" u=" proj=pipeline p step ste="[" u=" // proj=pipeline step ste="[" u=" proj=pipeline step ste="[" u=" // proj=pipeline step ste="[" u=" proj=pipeline step ""x=""""""""""" // Probably an issue with the quoting handling code // But doesn't hurt to add an extra safety check proj_log_error(P, _("Pipeline: too deep recursion")); return destructor( P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: nested pipelines */ } P->fwd4d = pipeline_forward_4d; P->inv4d = pipeline_reverse_4d; P->fwd3d = pipeline_forward_3d; P->inv3d = pipeline_reverse_3d; P->fwd = pipeline_forward; P->inv = pipeline_reverse; P->destructor = destructor; P->reassign_context = pipeline_reassign_context; /* Currently, the pipeline driver is a raw bit mover, enabling other * operations */ /* to collaborate efficiently. All prep/fin stuff is done at the step * levels. */ P->skip_fwd_prepare = 1; P->skip_fwd_finalize = 1; P->skip_inv_prepare = 1; P->skip_inv_finalize = 1; P->opaque = new (std::nothrow) Pipeline(); if (nullptr == P->opaque) return destructor(P, PROJ_ERR_INVALID_OP /* ENOMEM */); argc = (int)argc_params(P->params); auto pipeline = static_cast(P->opaque); pipeline->argv = argv = argv_params(P->params, argc); if (nullptr == argv) return destructor(P, PROJ_ERR_INVALID_OP /* ENOMEM */); pipeline->current_argv = current_argv = static_cast(calloc(argc, sizeof(char *))); if (nullptr == current_argv) return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* Do some syntactical sanity checking */ for (i = 0; i < argc && argv[i] != nullptr; i++) { if (0 == strcmp(argv_sentinel, argv[i])) { if (-1 == i_pipeline) { proj_log_error(P, _("Pipeline: +step before +proj=pipeline")); return destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } if (0 == nsteps) i_first_step = i; nsteps++; continue; } if (0 == strcmp("proj=pipeline", argv[i])) { if (-1 != i_pipeline) { proj_log_error(P, _("Pipeline: Nesting only allowed when child " "pipelines are wrapped in '+init's")); return destructor( P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: nested pipelines */ } i_pipeline = i; } else if (0 == nsteps && 0 == strncmp(argv[i], "proj=", 5)) { // Non-sensical to have proj= in the general pipeline parameters. // Would not be a big issue in itself, but this makes bad // performance in parsing hostile pipelines more likely, such as the // one of // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=41290 proj_log_error( P, _("Pipeline: proj= operator before first step not allowed")); return destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } else if (0 == nsteps && 0 == strncmp(argv[i], "o_proj=", 7)) { // Same as above. proj_log_error( P, _("Pipeline: o_proj= operator before first step not allowed")); return destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } } nsteps--; /* Last instance of +step is just a sentinel */ if (-1 == i_pipeline) return destructor( P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: no pipeline def */ if (0 == nsteps) return destructor( P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); /* ERROR: no pipeline def */ set_ellipsoid(P); /* Now loop over all steps, building a new set of arguments for each init */ i_current_step = i_first_step; for (i = 0; i < nsteps; i++) { int j; int current_argc = 0; int err; PJ *next_step = nullptr; /* Build a set of setup args for the current step */ proj_log_trace(P, "Pipeline: Building arg list for step no. %d", i); /* First add the step specific args */ for (j = i_current_step + 1; 0 != strcmp("step", argv[j]); j++) current_argv[current_argc++] = argv[j]; i_current_step = j; /* Then add the global args */ for (j = i_pipeline + 1; 0 != strcmp("step", argv[j]); j++) current_argv[current_argc++] = argv[j]; proj_log_trace(P, "Pipeline: init - %s, %d", current_argv[0], current_argc); for (j = 1; j < current_argc; j++) proj_log_trace(P, " %s", current_argv[j]); err = proj_errno_reset(P); P->ctx->pipelineInitRecursiongCounter++; next_step = pj_create_argv_internal(P->ctx, current_argc, current_argv); P->ctx->pipelineInitRecursiongCounter--; proj_log_trace(P, "Pipeline: Step %d (%s) at %p", i, current_argv[0], next_step); if (nullptr == next_step) { /* The step init failed, but possibly without setting errno. If so, * we say "malformed" */ int err_to_report = proj_errno(P); if (0 == err_to_report) err_to_report = PROJ_ERR_INVALID_OP_WRONG_SYNTAX; proj_log_error(P, _("Pipeline: Bad step definition: %s (%s)"), current_argv[0], proj_context_errno_string(P->ctx, err_to_report)); return destructor(P, err_to_report); /* ERROR: bad pipeline def */ } next_step->parent = P; proj_errno_restore(P, err); /* Is this step inverted? */ for (j = 0; j < current_argc; j++) { if (0 == strcmp("inv", current_argv[j])) { /* if +inv exists in both global and local args the forward * operation should be used */ next_step->inverted = next_step->inverted == 0 ? 1 : 0; } } bool omit_fwd = pj_param(P->ctx, next_step->params, "bomit_fwd").i != 0; bool omit_inv = pj_param(P->ctx, next_step->params, "bomit_inv").i != 0; pipeline->steps.emplace_back(next_step, omit_fwd, omit_inv); proj_log_trace(P, "Pipeline at [%p]: step at [%p] (%s) done", P, next_step, current_argv[0]); } /* Require a forward path through the pipeline */ for (auto &step : pipeline->steps) { PJ *Q = step.pj; if (step.omit_fwd) { continue; } if (Q->inverted) { if (Q->inv || Q->inv3d || Q->inv4d) { continue; } proj_log_error( P, _("Pipeline: Inverse operation for %s is not available"), Q->short_name); return destructor(P, PROJ_ERR_OTHER_NO_INVERSE_OP); } else { if (Q->fwd || Q->fwd3d || Q->fwd4d) { continue; } proj_log_error( P, _("Pipeline: Forward operation for %s is not available"), Q->short_name); return destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } } /* determine if an inverse operation is possible */ for (auto &step : pipeline->steps) { PJ *Q = step.pj; if (step.omit_inv || pj_has_inverse(Q)) { continue; } else { P->inv = nullptr; P->inv3d = nullptr; P->inv4d = nullptr; break; } } /* Replace PJ_IO_UNITS_WHATEVER with input/output units of neighbouring * steps where */ /* it make sense. It does in most cases but not always, for instance */ /* proj=pipeline step proj=unitconvert xy_in=deg xy_out=rad step ... */ /* where the left-hand side units of the first step shouldn't be changed to * RADIANS */ /* as it will result in deg->rad conversions in cs2cs and other * applications. */ for (i = nsteps - 2; i >= 0; --i) { auto pj = pipeline->steps[i].pj; if (pj_left(pj) == PJ_IO_UNITS_WHATEVER && pj_right(pj) == PJ_IO_UNITS_WHATEVER) { const auto right_pj = pipeline->steps[i + 1].pj; const auto right_pj_left = pj_left(right_pj); const auto right_pj_right = pj_right(right_pj); if (right_pj_left != right_pj_right || right_pj_left != PJ_IO_UNITS_WHATEVER) { pj->left = right_pj_left; pj->right = right_pj_left; } } } for (i = 1; i < nsteps; i++) { auto pj = pipeline->steps[i].pj; if (pj_left(pj) == PJ_IO_UNITS_WHATEVER && pj_right(pj) == PJ_IO_UNITS_WHATEVER) { const auto left_pj = pipeline->steps[i - 1].pj; const auto left_pj_left = pj_left(left_pj); const auto left_pj_right = pj_right(left_pj); if (left_pj_left != left_pj_right || left_pj_right != PJ_IO_UNITS_WHATEVER) { pj->left = left_pj_right; pj->right = left_pj_right; } } } /* Check that units between each steps match each other, fail if they don't */ for (i = 0; i + 1 < nsteps; i++) { enum pj_io_units curr_step_output = pj_right(pipeline->steps[i].pj); enum pj_io_units next_step_input = pj_left(pipeline->steps[i + 1].pj); if (curr_step_output == PJ_IO_UNITS_WHATEVER || next_step_input == PJ_IO_UNITS_WHATEVER) continue; if (curr_step_output != next_step_input) { proj_log_error( P, _("Pipeline: Mismatched units between step %d and %d"), i + 1, i + 2); return destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); } } proj_log_trace( P, "Pipeline: %d steps built. Determining i/o characteristics", nsteps); /* Determine forward input (= reverse output) data type */ P->left = pj_left(pipeline->steps.front().pj); /* Now, correspondingly determine forward output (= reverse input) data type */ P->right = pj_right(pipeline->steps.back().pj); return P; } static void push(PJ_COORD &point, PJ *P) { if (P->parent == nullptr) return; struct Pipeline *pipeline = static_cast(P->parent->opaque); struct PushPop *pushpop = static_cast(P->opaque); if (pushpop->v1) pipeline->stack[0].push(point.v[0]); if (pushpop->v2) pipeline->stack[1].push(point.v[1]); if (pushpop->v3) pipeline->stack[2].push(point.v[2]); if (pushpop->v4) pipeline->stack[3].push(point.v[3]); } static void pop(PJ_COORD &point, PJ *P) { if (P->parent == nullptr) return; struct Pipeline *pipeline = static_cast(P->parent->opaque); struct PushPop *pushpop = static_cast(P->opaque); if (pushpop->v1 && !pipeline->stack[0].empty()) { point.v[0] = pipeline->stack[0].top(); pipeline->stack[0].pop(); } if (pushpop->v2 && !pipeline->stack[1].empty()) { point.v[1] = pipeline->stack[1].top(); pipeline->stack[1].pop(); } if (pushpop->v3 && !pipeline->stack[2].empty()) { point.v[2] = pipeline->stack[2].top(); pipeline->stack[2].pop(); } if (pushpop->v4 && !pipeline->stack[3].empty()) { point.v[3] = pipeline->stack[3].top(); pipeline->stack[3].pop(); } } static PJ *setup_pushpop(PJ *P) { auto pushpop = static_cast(calloc(1, sizeof(struct PushPop))); P->opaque = pushpop; if (nullptr == P->opaque) return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if (pj_param_exists(P->params, "v_1")) pushpop->v1 = true; if (pj_param_exists(P->params, "v_2")) pushpop->v2 = true; if (pj_param_exists(P->params, "v_3")) pushpop->v3 = true; if (pj_param_exists(P->params, "v_4")) pushpop->v4 = true; P->left = PJ_IO_UNITS_WHATEVER; P->right = PJ_IO_UNITS_WHATEVER; return P; } PJ *OPERATION(push, 0) { P->fwd4d = push; P->inv4d = pop; return setup_pushpop(P); } PJ *OPERATION(pop, 0) { P->inv4d = push; P->fwd4d = pop; return setup_pushpop(P); } proj-9.8.1/src/projections/000775 001750 001750 00000000000 15166171735 015603 5ustar00eveneven000000 000000 proj-9.8.1/src/projections/putp6.cpp000664 001750 001750 00000005215 15166171715 017366 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_putp6 { double C_x, C_y, A, B, D; }; } // anonymous namespace PROJ_HEAD(putp6, "Putnins P6") "\n\tPCyl, Sph"; PROJ_HEAD(putp6p, "Putnins P6'") "\n\tPCyl, Sph"; #define EPS 1e-10 #define NITER 10 #define CON_POLE 1.732050807568877 /* sqrt(3) */ static PJ_XY putp6_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_putp6 *Q = static_cast(P->opaque); int i; const double p = Q->B * sin(lp.phi); lp.phi *= 1.10265779; for (i = NITER; i; --i) { const double r = sqrt(1. + lp.phi * lp.phi); const double V = ((Q->A - r) * lp.phi - log(lp.phi + r) - p) / (Q->A - 2. * r); lp.phi -= V; if (fabs(V) < EPS) break; } double sqrt_1_plus_phi2; if (!i) { // Note: it seems this case is rarely reached as from experimenting, // i seems to be >= 6 lp.phi = p < 0. ? -CON_POLE : CON_POLE; // the formula of the else case would also work, but this makes // some cppcheck versions happier. sqrt_1_plus_phi2 = 2; } else { sqrt_1_plus_phi2 = sqrt(1. + lp.phi * lp.phi); } xy.x = Q->C_x * lp.lam * (Q->D - sqrt_1_plus_phi2); xy.y = Q->C_y * lp.phi; return xy; } static PJ_LP putp6_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_putp6 *Q = static_cast(P->opaque); double r; lp.phi = xy.y / Q->C_y; r = sqrt(1. + lp.phi * lp.phi); lp.lam = xy.x / (Q->C_x * (Q->D - r)); lp.phi = aasin(P->ctx, ((Q->A - r) * lp.phi - log(lp.phi + r)) / Q->B); return lp; } PJ *PJ_PROJECTION(putp6) { struct pj_putp6 *Q = static_cast(calloc(1, sizeof(struct pj_putp6))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.01346; Q->C_y = 0.91910; Q->A = 4.; Q->B = 2.1471437182129378784; Q->D = 2.; P->es = 0.; P->inv = putp6_s_inverse; P->fwd = putp6_s_forward; return P; } PJ *PJ_PROJECTION(putp6p) { struct pj_putp6 *Q = static_cast(calloc(1, sizeof(struct pj_putp6))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.44329; Q->C_y = 0.80404; Q->A = 6.; Q->B = 5.61125; Q->D = 3.; P->es = 0.; P->inv = putp6_s_inverse; P->fwd = putp6_s_forward; return P; } #undef EPS #undef NITER #undef CON_POLE proj-9.8.1/src/projections/eqdc.cpp000664 001750 001750 00000011153 15166171715 017222 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" #include namespace { // anonymous namespace struct pj_eqdc_data { double phi1; double phi2; double n; double rho; double rho0; double c; double *en; int ellips; }; } // anonymous namespace PROJ_HEAD(eqdc, "Equidistant Conic") "\n\tConic, Sph&Ell\n\tlat_1= lat_2="; #define EPS10 1.e-10 static PJ_XY eqdc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_eqdc_data *Q = static_cast(P->opaque); Q->rho = Q->c - (Q->ellips ? pj_mlfn(lp.phi, sin(lp.phi), cos(lp.phi), Q->en) : lp.phi); const double lam_mul_n = lp.lam * Q->n; xy.x = Q->rho * sin(lam_mul_n); xy.y = Q->rho0 - Q->rho * cos(lam_mul_n); return xy; } static PJ_LP eqdc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_eqdc_data *Q = static_cast(P->opaque); if ((Q->rho = hypot(xy.x, xy.y = Q->rho0 - xy.y)) != 0.0) { if (Q->n < 0.) { Q->rho = -Q->rho; xy.x = -xy.x; xy.y = -xy.y; } lp.phi = Q->c - Q->rho; if (Q->ellips) lp.phi = pj_inv_mlfn(lp.phi, Q->en); lp.lam = atan2(xy.x, xy.y) / Q->n; } else { lp.lam = 0.; lp.phi = Q->n > 0. ? M_HALFPI : -M_HALFPI; } return lp; } static PJ *pj_eqdc_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(eqdc) { double cosphi, sinphi; int secant; struct pj_eqdc_data *Q = static_cast( calloc(1, sizeof(struct pj_eqdc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_eqdc_destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; if (fabs(Q->phi1) > M_HALFPI) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be <= 90°")); return pj_eqdc_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(Q->phi2) > M_HALFPI) { proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be <= 90°")); return pj_eqdc_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(Q->phi1 + Q->phi2) < EPS10) { proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + " "lat_2| should be > 0")); return pj_eqdc_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (!(Q->en = pj_enfn(P->n))) return pj_eqdc_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); sinphi = sin(Q->phi1); Q->n = sinphi; cosphi = cos(Q->phi1); secant = fabs(Q->phi1 - Q->phi2) >= EPS10; Q->ellips = (P->es > 0.); if (Q->ellips) { double ml1, m1; m1 = pj_msfn(sinphi, cosphi, P->es); ml1 = pj_mlfn(Q->phi1, sinphi, cosphi, Q->en); if (secant) { /* secant cone */ sinphi = sin(Q->phi2); cosphi = cos(Q->phi2); const double ml2 = pj_mlfn(Q->phi2, sinphi, cosphi, Q->en); if (ml1 == ml2) { proj_log_error(P, _("Eccentricity too close to 1")); return pj_eqdc_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->n = (m1 - pj_msfn(sinphi, cosphi, P->es)) / (ml2 - ml1); if (Q->n == 0) { // Not quite, but es is very close to 1... proj_log_error(P, _("Invalid value for eccentricity")); return pj_eqdc_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->c = ml1 + m1 / Q->n; Q->rho0 = Q->c - pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); } else { if (secant) Q->n = (cosphi - cos(Q->phi2)) / (Q->phi2 - Q->phi1); if (Q->n == 0) { proj_log_error(P, _("Invalid value for lat_1 and lat_2: lat_1 + " "lat_2 should be > 0")); return pj_eqdc_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->c = Q->phi1 + cos(Q->phi1) / Q->n; Q->rho0 = Q->c - P->phi0; } P->inv = eqdc_e_inverse; P->fwd = eqdc_e_forward; return P; } #undef EPS10 proj-9.8.1/src/projections/lcc.cpp000664 001750 001750 00000013111 15166171715 017043 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(lcc, "Lambert Conformal Conic") "\n\tConic, Sph&Ell\n\tlat_1= and lat_2= or lat_0, k_0="; #define EPS10 1.e-10 namespace { // anonymous namespace struct pj_lcc_data { double phi1; double phi2; double n; double rho0; double c; }; } // anonymous namespace static PJ_XY lcc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0., 0.}; struct pj_lcc_data *Q = static_cast(P->opaque); double rho; if (fabs(fabs(lp.phi) - M_HALFPI) < EPS10) { if ((lp.phi * Q->n) <= 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } rho = 0.; } else { rho = Q->c * (P->es != 0. ? pow(pj_tsfn(lp.phi, sin(lp.phi), P->e), Q->n) : pow(tan(M_FORTPI + .5 * lp.phi), -Q->n)); } lp.lam *= Q->n; xy.x = P->k0 * (rho * sin(lp.lam)); xy.y = P->k0 * (Q->rho0 - rho * cos(lp.lam)); return xy; } static PJ_LP lcc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0., 0.}; struct pj_lcc_data *Q = static_cast(P->opaque); double rho; xy.x /= P->k0; xy.y /= P->k0; xy.y = Q->rho0 - xy.y; rho = hypot(xy.x, xy.y); if (rho != 0.) { if (Q->n < 0.) { rho = -rho; xy.x = -xy.x; xy.y = -xy.y; } if (P->es != 0.) { lp.phi = pj_phi2(P->ctx, pow(rho / Q->c, 1. / Q->n), P->e); if (lp.phi == HUGE_VAL) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } } else lp.phi = 2. * atan(pow(Q->c / rho, 1. / Q->n)) - M_HALFPI; lp.lam = atan2(xy.x, xy.y) / Q->n; } else { lp.lam = 0.; lp.phi = Q->n > 0. ? M_HALFPI : -M_HALFPI; } return lp; } PJ *PJ_PROJECTION(lcc) { double cosphi, sinphi; int secant; struct pj_lcc_data *Q = static_cast( calloc(1, sizeof(struct pj_lcc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; if (pj_param(P->ctx, P->params, "tlat_2").i) Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; else { Q->phi2 = Q->phi1; if (!pj_param(P->ctx, P->params, "tlat_0").i) P->phi0 = Q->phi1; } if (fabs(Q->phi1 + Q->phi2) < EPS10) { proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + " "lat_2| should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->n = sinphi = sin(Q->phi1); cosphi = cos(Q->phi1); if (fabs(cosphi) < EPS10 || fabs(Q->phi1) >= M_PI_2) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(cos(Q->phi2)) < EPS10 || fabs(Q->phi2) >= M_PI_2) { proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } secant = fabs(Q->phi1 - Q->phi2) >= EPS10; if (P->es != 0.) { double ml1, m1; m1 = pj_msfn(sinphi, cosphi, P->es); ml1 = pj_tsfn(Q->phi1, sinphi, P->e); if (secant) { /* secant cone */ sinphi = sin(Q->phi2); Q->n = log(m1 / pj_msfn(sinphi, cos(Q->phi2), P->es)); if (Q->n == 0) { // Not quite, but es is very close to 1... proj_log_error(P, _("Invalid value for eccentricity")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } const double ml2 = pj_tsfn(Q->phi2, sinphi, P->e); const double denom = log(ml1 / ml2); if (denom == 0) { // Not quite, but es is very close to 1... proj_log_error(P, _("Invalid value for eccentricity")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->n /= denom; } Q->rho0 = m1 * pow(ml1, -Q->n) / Q->n; Q->c = Q->rho0; Q->rho0 *= (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. : pow(pj_tsfn(P->phi0, sin(P->phi0), P->e), Q->n); } else { if (secant) Q->n = log(cosphi / cos(Q->phi2)) / log(tan(M_FORTPI + .5 * Q->phi2) / tan(M_FORTPI + .5 * Q->phi1)); if (Q->n == 0) { // Likely reason is that phi1 / phi2 are too close to zero. // Can be reproduced with +proj=lcc +a=1 +lat_2=.0000001 proj_log_error( P, _("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| " "should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->c = cosphi * pow(tan(M_FORTPI + .5 * Q->phi1), Q->n) / Q->n; Q->rho0 = (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) ? 0. : Q->c * pow(tan(M_FORTPI + .5 * P->phi0), -Q->n); } P->inv = lcc_e_inverse; P->fwd = lcc_e_forward; return P; } #undef EPS10 proj-9.8.1/src/projections/tpeqd.cpp000664 001750 001750 00000010335 15166171715 017424 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(tpeqd, "Two Point Equidistant") "\n\tMisc Sph\n\tlat_1= lon_1= lat_2= lon_2="; namespace { // anonymous namespace struct pj_tpeqd { double cp1, sp1, cp2, sp2, ccs, cs, sc, r2z0, z02, dlam2; double hz0, thz0, rhshz0, ca, sa, lp, lamc; }; } // anonymous namespace static PJ_XY tpeqd_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_tpeqd *Q = static_cast(P->opaque); double t, z1, z2, dl1, dl2, sp, cp; sp = sin(lp.phi); cp = cos(lp.phi); z1 = aacos(P->ctx, Q->sp1 * sp + Q->cp1 * cp * cos(dl1 = lp.lam + Q->dlam2)); z2 = aacos(P->ctx, Q->sp2 * sp + Q->cp2 * cp * cos(dl2 = lp.lam - Q->dlam2)); z1 *= z1; z2 *= z2; t = z1 - z2; xy.x = Q->r2z0 * t; t = Q->z02 - t; xy.y = Q->r2z0 * asqrt(4. * Q->z02 * z2 - t * t); if ((Q->ccs * sp - cp * (Q->cs * sin(dl1) - Q->sc * sin(dl2))) < 0.) xy.y = -xy.y; return xy; } static PJ_LP tpeqd_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_tpeqd *Q = static_cast(P->opaque); double cz1, cz2, s, d, cp, sp; cz1 = cos(hypot(xy.y, xy.x + Q->hz0)); cz2 = cos(hypot(xy.y, xy.x - Q->hz0)); s = cz1 + cz2; d = cz1 - cz2; lp.lam = -atan2(d, (s * Q->thz0)); lp.phi = aacos(P->ctx, hypot(Q->thz0 * s, d) * Q->rhshz0); if (xy.y < 0.) lp.phi = -lp.phi; /* lam--phi now in system relative to P1--P2 base equator */ sp = sin(lp.phi); cp = cos(lp.phi); lp.lam -= Q->lp; s = cos(lp.lam); lp.phi = aasin(P->ctx, Q->sa * sp + Q->ca * cp * s); lp.lam = atan2(cp * sin(lp.lam), Q->sa * cp * s - Q->ca * sp) + Q->lamc; return lp; } PJ *PJ_PROJECTION(tpeqd) { double lam_1, lam_2, phi_1, phi_2, A12; struct pj_tpeqd *Q = static_cast(calloc(1, sizeof(struct pj_tpeqd))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* get control point locations */ phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; lam_1 = pj_param(P->ctx, P->params, "rlon_1").f; phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; lam_2 = pj_param(P->ctx, P->params, "rlon_2").f; if (phi_1 == phi_2 && lam_1 == lam_2) { proj_log_error(P, _("Invalid value for lat_1/lon_1/lat_2/lon_2: the 2 " "points should be distinct.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->lam0 = adjlon(0.5 * (lam_1 + lam_2)); Q->dlam2 = adjlon(lam_2 - lam_1); Q->cp1 = cos(phi_1); Q->cp2 = cos(phi_2); Q->sp1 = sin(phi_1); Q->sp2 = sin(phi_2); Q->cs = Q->cp1 * Q->sp2; Q->sc = Q->sp1 * Q->cp2; Q->ccs = Q->cp1 * Q->cp2 * sin(Q->dlam2); const auto SQ = [](double x) { return x * x; }; // Numerically stable formula for computing the central angle from // (phi_1, lam_1) to (phi_2, lam_2). // Special case of Vincenty formula on the sphere. // https://en.wikipedia.org/wiki/Great-circle_distance#Computational_formulae const double cs_minus_sc_cos_dlam = Q->cs - Q->sc * cos(Q->dlam2); Q->z02 = atan2(sqrt(SQ(Q->cp2 * sin(Q->dlam2)) + SQ(cs_minus_sc_cos_dlam)), Q->sp1 * Q->sp2 + Q->cp1 * Q->cp2 * cos(Q->dlam2)); if (Q->z02 == 0.0) { // Actually happens when both lat_1 = lat_2 and |lat_1| = 90 proj_log_error(P, _("Invalid value for lat_1 and lat_2: their absolute " "value should be < 90°.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->hz0 = .5 * Q->z02; A12 = atan2(Q->cp2 * sin(Q->dlam2), cs_minus_sc_cos_dlam); const double pp = aasin(P->ctx, Q->cp1 * sin(A12)); Q->ca = cos(pp); Q->sa = sin(pp); Q->lp = adjlon(atan2(Q->cp1 * cos(A12), Q->sp1) - Q->hz0); Q->dlam2 *= .5; Q->lamc = M_HALFPI - atan2(sin(A12) * Q->sp1, cos(A12)) - Q->dlam2; Q->thz0 = tan(Q->hz0); Q->rhshz0 = .5 / sin(Q->hz0); Q->r2z0 = 0.5 / Q->z02; Q->z02 *= Q->z02; P->inv = tpeqd_s_inverse; P->fwd = tpeqd_s_forward; P->es = 0.; return P; } proj-9.8.1/src/projections/ortho.cpp000664 001750 001750 00000026610 15166171715 017445 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(ortho, "Orthographic") "\n\tAzi, Sph&Ell"; namespace pj_ortho_ns { enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; } namespace { // anonymous namespace struct pj_ortho_data { double sinph0; double cosph0; double nu0; double y_shift; double y_scale; enum pj_ortho_ns::Mode mode; double sinalpha; double cosalpha; }; } // anonymous namespace #define EPS10 1.e-10 static PJ_XY forward_error(PJ *P, PJ_LP lp, PJ_XY xy) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); proj_log_trace(P, "Coordinate (%.3f, %.3f) is on the unprojected hemisphere", proj_todeg(lp.lam), proj_todeg(lp.phi)); return xy; } static PJ_XY ortho_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy; struct pj_ortho_data *Q = static_cast(P->opaque); double coslam, cosphi, sinphi; xy.x = HUGE_VAL; xy.y = HUGE_VAL; cosphi = cos(lp.phi); coslam = cos(lp.lam); switch (Q->mode) { case pj_ortho_ns::EQUIT: if (cosphi * coslam < -EPS10) return forward_error(P, lp, xy); xy.y = sin(lp.phi); break; case pj_ortho_ns::OBLIQ: sinphi = sin(lp.phi); // Is the point visible from the projection plane ? // From // https://lists.osgeo.org/pipermail/proj/2020-September/009831.html // this is the dot product of the normal of the ellipsoid at the center // of the projection and at the point considered for projection. // [cos(phi)*cos(lambda), cos(phi)*sin(lambda), sin(phi)] // Also from Snyder's Map Projection - A working manual, equation (5-3), // page 149 if (Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam < -EPS10) return forward_error(P, lp, xy); xy.y = Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam; break; case pj_ortho_ns::N_POLE: coslam = -coslam; PROJ_FALLTHROUGH; case pj_ortho_ns::S_POLE: if (fabs(lp.phi - P->phi0) - EPS10 > M_HALFPI) return forward_error(P, lp, xy); xy.y = cosphi * coslam; break; } xy.x = cosphi * sin(lp.lam); const double xp = xy.x; const double yp = xy.y; xy.x = (xp * Q->cosalpha - yp * Q->sinalpha) * P->k0; xy.y = (xp * Q->sinalpha + yp * Q->cosalpha) * P->k0; return xy; } static PJ_LP ortho_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp; struct pj_ortho_data *Q = static_cast(P->opaque); double sinc; lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; const double xf = xy.x; const double yf = xy.y; xy.x = (Q->cosalpha * xf + Q->sinalpha * yf) / P->k0; xy.y = (-Q->sinalpha * xf + Q->cosalpha * yf) / P->k0; const double rh = hypot(xy.x, xy.y); sinc = rh; if (sinc > 1.) { if ((sinc - 1.) > EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } sinc = 1.; } const double cosc = sqrt(1. - sinc * sinc); /* in this range OK */ if (fabs(rh) <= EPS10) { lp.phi = P->phi0; lp.lam = 0.0; } else { switch (Q->mode) { case pj_ortho_ns::N_POLE: xy.y = -xy.y; lp.phi = acos(sinc); break; case pj_ortho_ns::S_POLE: lp.phi = -acos(sinc); break; case pj_ortho_ns::EQUIT: lp.phi = xy.y * sinc / rh; xy.x *= sinc; xy.y = cosc * rh; goto sinchk; case pj_ortho_ns::OBLIQ: lp.phi = cosc * Q->sinph0 + xy.y * sinc * Q->cosph0 / rh; xy.y = (cosc - Q->sinph0 * lp.phi) * rh; xy.x *= sinc * Q->cosph0; sinchk: if (fabs(lp.phi) >= 1.) lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; else lp.phi = asin(lp.phi); break; } lp.lam = (xy.y == 0. && (Q->mode == pj_ortho_ns::OBLIQ || Q->mode == pj_ortho_ns::EQUIT)) ? (xy.x == 0. ? 0. : xy.x < 0. ? -M_HALFPI : M_HALFPI) : atan2(xy.x, xy.y); } return lp; } static PJ_XY ortho_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy; struct pj_ortho_data *Q = static_cast(P->opaque); // From EPSG guidance note 7.2, March 2020, §3.3.5 Orthographic const double cosphi = cos(lp.phi); const double sinphi = sin(lp.phi); const double coslam = cos(lp.lam); const double sinlam = sin(lp.lam); // Is the point visible from the projection plane ? // Same condition as in spherical case if (Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam < -EPS10) { xy.x = HUGE_VAL; xy.y = HUGE_VAL; return forward_error(P, lp, xy); } const double nu = 1.0 / sqrt(1.0 - P->es * sinphi * sinphi); const double xp = nu * cosphi * sinlam; const double yp = nu * (sinphi * Q->cosph0 - cosphi * Q->sinph0 * coslam) + P->es * (Q->nu0 * Q->sinph0 - nu * sinphi) * Q->cosph0; xy.x = (Q->cosalpha * xp - Q->sinalpha * yp) * P->k0; xy.y = (Q->sinalpha * xp + Q->cosalpha * yp) * P->k0; return xy; } static PJ_LP ortho_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp; struct pj_ortho_data *Q = static_cast(P->opaque); const auto SQ = [](double x) { return x * x; }; const double xf = xy.x; const double yf = xy.y; xy.x = (Q->cosalpha * xf + Q->sinalpha * yf) / P->k0; xy.y = (-Q->sinalpha * xf + Q->cosalpha * yf) / P->k0; if (Q->mode == pj_ortho_ns::N_POLE || Q->mode == pj_ortho_ns::S_POLE) { // Polar case. Forward case equations can be simplified as: // xy.x = nu * cosphi * sinlam // xy.y = nu * -cosphi * coslam * sign(phi0) // ==> lam = atan2(xy.x, -xy.y * sign(phi0)) // ==> xy.x^2 + xy.y^2 = nu^2 * cosphi^2 // rh^2 = cosphi^2 / (1 - es * sinphi^2) // ==> cosphi^2 = rh^2 * (1 - es) / (1 - es * rh^2) const double rh2 = SQ(xy.x) + SQ(xy.y); if (rh2 >= 1. - 1e-15) { if ((rh2 - 1.) > EPS10) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; } lp.phi = 0; } else { lp.phi = acos(sqrt(rh2 * P->one_es / (1 - P->es * rh2))) * (Q->mode == pj_ortho_ns::N_POLE ? 1 : -1); } lp.lam = atan2(xy.x, xy.y * (Q->mode == pj_ortho_ns::N_POLE ? -1 : 1)); return lp; } if (Q->mode == pj_ortho_ns::EQUIT) { // Equatorial case. Forward case equations can be simplified as: // xy.x = nu * cosphi * sinlam // xy.y = nu * sinphi * (1 - P->es) // x^2 * (1 - es * sinphi^2) = (1 - sinphi^2) * sinlam^2 // y^2 / ((1 - es)^2 + y^2 * es) = sinphi^2 // Equation of the ellipse if (SQ(xy.x) + SQ(xy.y * (P->a / P->b)) > 1 + 1e-11) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; } const double sinphi2 = xy.y == 0 ? 0 : 1.0 / (SQ((1 - P->es) / xy.y) + P->es); if (sinphi2 > 1 - 1e-11) { lp.phi = M_PI_2 * (xy.y > 0 ? 1 : -1); lp.lam = 0; return lp; } lp.phi = asin(sqrt(sinphi2)) * (xy.y > 0 ? 1 : -1); const double sinlam = xy.x * sqrt((1 - P->es * sinphi2) / (1 - sinphi2)); if (fabs(sinlam) - 1 > -1e-15) lp.lam = M_PI_2 * (xy.x > 0 ? 1 : -1); else lp.lam = asin(sinlam); return lp; } // Using Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam == 0 (visibity // condition of the forward case) in the forward equations, and a lot of // substitution games... PJ_XY xy_recentered; xy_recentered.x = xy.x; xy_recentered.y = (xy.y - Q->y_shift) / Q->y_scale; if (SQ(xy.x) + SQ(xy_recentered.y) > 1 + 1e-11) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; } // From EPSG guidance note 7.2, March 2020, §3.3.5 Orthographic // It suggests as initial guess: // lp.lam = 0; // lp.phi = P->phi0; // But for poles, this will not converge well. Better use: lp = ortho_s_inverse(xy_recentered, P); for (int i = 0; i < 20; i++) { const double cosphi = cos(lp.phi); const double sinphi = sin(lp.phi); const double coslam = cos(lp.lam); const double sinlam = sin(lp.lam); const double one_minus_es_sinphi2 = 1.0 - P->es * sinphi * sinphi; const double nu = 1.0 / sqrt(one_minus_es_sinphi2); PJ_XY xy_new; xy_new.x = nu * cosphi * sinlam; xy_new.y = nu * (sinphi * Q->cosph0 - cosphi * Q->sinph0 * coslam) + P->es * (Q->nu0 * Q->sinph0 - nu * sinphi) * Q->cosph0; const double rho = (1.0 - P->es) * nu / one_minus_es_sinphi2; const double J11 = -rho * sinphi * sinlam; const double J12 = nu * cosphi * coslam; const double J21 = rho * (cosphi * Q->cosph0 + sinphi * Q->sinph0 * coslam); const double J22 = nu * Q->sinph0 * cosphi * sinlam; const double D = J11 * J22 - J12 * J21; const double dx = xy.x - xy_new.x; const double dy = xy.y - xy_new.y; const double dphi = (J22 * dx - J12 * dy) / D; const double dlam = (-J21 * dx + J11 * dy) / D; lp.phi += dphi; if (lp.phi > M_PI_2) { lp.phi = M_PI_2 - (lp.phi - M_PI_2); lp.lam = adjlon(lp.lam + M_PI); } else if (lp.phi < -M_PI_2) { lp.phi = -M_PI_2 + (-M_PI_2 - lp.phi); lp.lam = adjlon(lp.lam + M_PI); } lp.lam += dlam; if (fabs(dphi) < 1e-12 && fabs(dlam) < 1e-12) { return lp; } } proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } PJ *PJ_PROJECTION(ortho) { struct pj_ortho_data *Q = static_cast( calloc(1, sizeof(struct pj_ortho_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->sinph0 = sin(P->phi0); Q->cosph0 = cos(P->phi0); if (fabs(fabs(P->phi0) - M_HALFPI) <= EPS10) Q->mode = P->phi0 < 0. ? pj_ortho_ns::S_POLE : pj_ortho_ns::N_POLE; else if (fabs(P->phi0) > EPS10) { Q->mode = pj_ortho_ns::OBLIQ; } else Q->mode = pj_ortho_ns::EQUIT; if (P->es == 0) { P->inv = ortho_s_inverse; P->fwd = ortho_s_forward; } else { Q->nu0 = 1.0 / sqrt(1.0 - P->es * Q->sinph0 * Q->sinph0); Q->y_shift = P->es * Q->nu0 * Q->sinph0 * Q->cosph0; Q->y_scale = 1.0 / sqrt(1.0 - P->es * Q->cosph0 * Q->cosph0); P->inv = ortho_e_inverse; P->fwd = ortho_e_forward; } const double alpha = pj_param(P->ctx, P->params, "ralpha").f; Q->sinalpha = sin(alpha); Q->cosalpha = cos(alpha); return P; } #undef EPS10 proj-9.8.1/src/projections/spilhaus.cpp000664 001750 001750 00000012301 15166171715 020132 0ustar00eveneven000000 000000 /* * Implementation of the Spilhaus projections based on Adams World in a Square * II. * * Explained in https://github.com/OSGeo/PROJ/issues/1851 * * Copyright (c) 2025 Javier Jimenez Shaw * * Related material * ---------------- * * Map Projections - A Working Manual. 1987. John P. Snyder * Sections 3 and 5. * https://doi.org/10.3133/pp1395 * Online at https://neacsu.net/docs/geodesy/snyder/2-general/ */ #include #include #include "proj.h" #include "proj_internal.h" C_NAMESPACE PJ *pj_adams_ws2(PJ *); PROJ_HEAD(spilhaus, "Spilhaus") "\n\tSph&Ell"; namespace { // anonymous namespace struct pj_spilhaus_data { double cosalpha; double sinalpha; double beta; double lambda_0; double conformal_distortion; double cosrot; double sinrot; PJ *adams_ws2; }; } // anonymous namespace static PJ_XY spilhaus_forward(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; struct pj_spilhaus_data *Q = static_cast(P->opaque); const double phi_c = pj_conformal_lat(lp.phi, P); const double cosphi_c = cos(phi_c); const double sinphi_c = sin(phi_c); const double coslam = cos(lp.lam - Q->lambda_0); const double sinlam = sin(lp.lam - Q->lambda_0); PJ_LP lpadams; // Snyder, A working manual, formula 5-7 lpadams.phi = aasin(P->ctx, Q->sinalpha * sinphi_c - Q->cosalpha * cosphi_c * coslam); // Snyder, A working manual, formula 5-8b lpadams.lam = (Q->beta + atan2(cosphi_c * sinlam, (Q->sinalpha * cosphi_c * coslam + Q->cosalpha * sinphi_c))); while (lpadams.lam > M_PI) lpadams.lam -= M_PI * 2; while (lpadams.lam < -M_PI) lpadams.lam += M_PI * 2; PJ_XY xyadams = Q->adams_ws2->fwd(lpadams, Q->adams_ws2); const double factor = Q->conformal_distortion * P->k0; xy.x = -(xyadams.x * Q->cosrot + xyadams.y * Q->sinrot) * factor; xy.y = -(xyadams.x * -Q->sinrot + xyadams.y * Q->cosrot) * factor; return xy; } static PJ_LP spilhaus_inverse(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; struct pj_spilhaus_data *Q = static_cast(P->opaque); PJ_XY xyadams; const double factor = 1.0 / (Q->conformal_distortion * P->k0); xyadams.x = -(xy.x * Q->cosrot + xy.y * -Q->sinrot) * factor; xyadams.y = -(xy.x * Q->sinrot + xy.y * Q->cosrot) * factor; PJ_LP lpadams = Q->adams_ws2->inv(xyadams, Q->adams_ws2); const double cosphi_s = cos(lpadams.phi); const double sinphi_s = sin(lpadams.phi); const double coslam_s = cos(lpadams.lam - Q->beta); const double sinlam_s = sin(lpadams.lam - Q->beta); // conformal latitude lp.phi = aasin(P->ctx, Q->sinalpha * sinphi_s + Q->cosalpha * cosphi_s * coslam_s); lp.lam = Q->lambda_0 + aatan2(cosphi_s * sinlam_s, Q->sinalpha * cosphi_s * coslam_s - Q->cosalpha * sinphi_s); lp.phi = pj_conformal_lat_inverse(lp.phi, P); return lp; } static PJ *spilhaus_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); proj_destroy(static_cast(P->opaque)->adams_ws2); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(spilhaus) { struct pj_spilhaus_data *Q = static_cast( calloc(1, sizeof(struct pj_spilhaus_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = spilhaus_destructor; Q->adams_ws2 = pj_adams_ws2(nullptr); if (Q->adams_ws2 == nullptr) return spilhaus_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->adams_ws2->ctx = P->ctx; Q->adams_ws2->e = 0; Q->adams_ws2 = pj_adams_ws2(Q->adams_ws2); if (Q->adams_ws2 == nullptr) return spilhaus_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); auto param_rad = [&P](const std::string &name, double def) { return pj_param(P->ctx, P->params, ("t" + name).c_str()).i ? pj_param(P->ctx, P->params, ("r" + name).c_str()).f : def * DEG_TO_RAD; }; // Values from https://github.com/OSGeo/PROJ/issues/1851 if (!pj_param(P->ctx, P->params, "tlon_0").i) { P->lam0 = 66.94970198 * DEG_TO_RAD; } if (!pj_param(P->ctx, P->params, "tlat_0").i) { P->phi0 = -49.56371678 * DEG_TO_RAD; } const double azimuth = param_rad("azi", 40.17823482); const double rotation = param_rad("rot", 45); Q->cosrot = cos(rotation); Q->sinrot = sin(rotation); const double conformal_lat_center = pj_conformal_lat(P->phi0, P); Q->sinalpha = -cos(conformal_lat_center) * cos(azimuth); Q->cosalpha = sqrt(1 - Q->sinalpha * Q->sinalpha); Q->lambda_0 = atan2(tan(azimuth), -sin(conformal_lat_center)); Q->beta = M_PI + atan2(-sin(azimuth), -tan(conformal_lat_center)); Q->conformal_distortion = cos(P->phi0) / sqrt(1 - P->es * sin(P->phi0) * sin(P->phi0)) / cos(conformal_lat_center); P->fwd = spilhaus_forward; P->inv = spilhaus_inverse; return P; } proj-9.8.1/src/projections/cc.cpp000664 001750 001750 00000001342 15166171715 016672 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(cc, "Central Cylindrical") "\n\tCyl, Sph"; #define EPS10 1.e-10 static PJ_XY cc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = lp.lam; xy.y = tan(lp.phi); return xy; } static PJ_LP cc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = atan(xy.y); lp.lam = xy.x; return lp; } PJ *PJ_PROJECTION(cc) { P->es = 0.; P->inv = cc_s_inverse; P->fwd = cc_s_forward; return P; } proj-9.8.1/src/projections/wink2.cpp000664 001750 001750 00000003227 15166171715 017343 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(wink2, "Winkel II") "\n\tPCyl, Sph\n\tlat_1="; namespace { // anonymous namespace struct pj_wink2_data { double cosphi1; }; } // anonymous namespace #define MAX_ITER 10 #define LOOP_TOL 1e-7 static PJ_XY wink2_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; int i; xy.y = lp.phi * M_TWO_D_PI; const double k = M_PI * sin(lp.phi); lp.phi *= 1.8; for (i = MAX_ITER; i; --i) { const double V = (lp.phi + sin(lp.phi) - k) / (1. + cos(lp.phi)); lp.phi -= V; if (fabs(V) < LOOP_TOL) break; } if (!i) lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; else lp.phi *= 0.5; xy.x = 0.5 * lp.lam * (cos(lp.phi) + static_cast(P->opaque)->cosphi1); xy.y = M_FORTPI * (sin(lp.phi) + xy.y); return xy; } static PJ_LP wink2_s_inverse(PJ_XY xy, PJ *P) { PJ_LP lpInit; lpInit.phi = xy.y; lpInit.lam = xy.x; constexpr double deltaXYTolerance = 1e-10; return pj_generic_inverse_2d(xy, P, lpInit, deltaXYTolerance); } PJ *PJ_PROJECTION(wink2) { struct pj_wink2_data *Q = static_cast( calloc(1, sizeof(struct pj_wink2_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; static_cast(P->opaque)->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f); P->es = 0.; P->fwd = wink2_s_forward; P->inv = wink2_s_inverse; return P; } #undef MAX_ITER #undef LOOP_TOL proj-9.8.1/src/projections/goode.cpp000664 001750 001750 00000004414 15166171715 017405 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(goode, "Goode Homolosine") "\n\tPCyl, Sph"; #define Y_COR 0.05280 #define PHI_LIM 0.71093078197902358062 C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *); namespace { // anonymous namespace struct pj_goode_data { PJ *sinu; PJ *moll; }; } // anonymous namespace static PJ_XY goode_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy; struct pj_goode_data *Q = static_cast(P->opaque); if (fabs(lp.phi) <= PHI_LIM) xy = Q->sinu->fwd(lp, Q->sinu); else { xy = Q->moll->fwd(lp, Q->moll); xy.y -= lp.phi >= 0.0 ? Y_COR : -Y_COR; } return xy; } static PJ_LP goode_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp; struct pj_goode_data *Q = static_cast(P->opaque); if (fabs(xy.y) <= PHI_LIM) lp = Q->sinu->inv(xy, Q->sinu); else { xy.y += xy.y >= 0.0 ? Y_COR : -Y_COR; lp = Q->moll->inv(xy, Q->moll); } return lp; } static PJ *goode_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); proj_destroy(static_cast(P->opaque)->sinu); proj_destroy(static_cast(P->opaque)->moll); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(goode) { struct pj_goode_data *Q = static_cast( calloc(1, sizeof(struct pj_goode_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = goode_destructor; P->es = 0.; Q->sinu = pj_sinu(nullptr); Q->moll = pj_moll(nullptr); if (Q->sinu == nullptr || Q->moll == nullptr) return goode_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->sinu->es = 0.; Q->sinu->ctx = P->ctx; Q->moll->ctx = P->ctx; Q->sinu = pj_sinu(Q->sinu); Q->moll = pj_moll(Q->moll); if (Q->sinu == nullptr || Q->moll == nullptr) return goode_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->fwd = goode_s_forward; P->inv = goode_s_inverse; return P; } #undef Y_COR #undef PHI_LIM proj-9.8.1/src/projections/robin.cpp000664 001750 001750 00000012600 15166171715 017415 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include PROJ_HEAD(robin, "Robinson") "\n\tPCyl, Sph"; #define V(C, z) (C.c0 + z * (C.c1 + z * (C.c2 + z * C.c3))) #define DV(C, z) (C.c1 + 2 * z * C.c2 + z * z * 3. * C.c3) /* note: following terms based upon 5 deg. intervals in degrees. Some background on these coefficients is available at: http://article.gmane.org/gmane.comp.gis.proj-4.devel/6039 http://trac.osgeo.org/proj/ticket/113 */ namespace { // anonymous namespace struct COEFS { float c0, c1, c2, c3; }; } // anonymous namespace static const struct COEFS X[] = { {1.0f, 2.2199e-17f, -7.15515e-05f, 3.1103e-06f}, {0.9986f, -0.000482243f, -2.4897e-05f, -1.3309e-06f}, {0.9954f, -0.00083103f, -4.48605e-05f, -9.86701e-07f}, {0.99f, -0.00135364f, -5.9661e-05f, 3.6777e-06f}, {0.9822f, -0.00167442f, -4.49547e-06f, -5.72411e-06f}, {0.973f, -0.00214868f, -9.03571e-05f, 1.8736e-08f}, {0.96f, -0.00305085f, -9.00761e-05f, 1.64917e-06f}, {0.9427f, -0.00382792f, -6.53386e-05f, -2.6154e-06f}, {0.9216f, -0.00467746f, -0.00010457f, 4.81243e-06f}, {0.8962f, -0.00536223f, -3.23831e-05f, -5.43432e-06f}, {0.8679f, -0.00609363f, -0.000113898f, 3.32484e-06f}, {0.835f, -0.00698325f, -6.40253e-05f, 9.34959e-07f}, {0.7986f, -0.00755338f, -5.00009e-05f, 9.35324e-07f}, {0.7597f, -0.00798324f, -3.5971e-05f, -2.27626e-06f}, {0.7186f, -0.00851367f, -7.01149e-05f, -8.6303e-06f}, {0.6732f, -0.00986209f, -0.000199569f, 1.91974e-05f}, {0.6213f, -0.010418f, 8.83923e-05f, 6.24051e-06f}, {0.5722f, -0.00906601f, 0.000182f, 6.24051e-06f}, {0.5322f, -0.00677797f, 0.000275608f, 6.24051e-06f}}; static const struct COEFS Y[] = { {-5.20417e-18f, 0.0124f, 1.21431e-18f, -8.45284e-11f}, {0.062f, 0.0124f, -1.26793e-09f, 4.22642e-10f}, {0.124f, 0.0124f, 5.07171e-09f, -1.60604e-09f}, {0.186f, 0.0123999f, -1.90189e-08f, 6.00152e-09f}, {0.248f, 0.0124002f, 7.10039e-08f, -2.24e-08f}, {0.31f, 0.0123992f, -2.64997e-07f, 8.35986e-08f}, {0.372f, 0.0124029f, 9.88983e-07f, -3.11994e-07f}, {0.434f, 0.0123893f, -3.69093e-06f, -4.35621e-07f}, {0.4958f, 0.0123198f, -1.02252e-05f, -3.45523e-07f}, {0.5571f, 0.0121916f, -1.54081e-05f, -5.82288e-07f}, {0.6176f, 0.0119938f, -2.41424e-05f, -5.25327e-07f}, {0.6769f, 0.011713f, -3.20223e-05f, -5.16405e-07f}, {0.7346f, 0.0113541f, -3.97684e-05f, -6.09052e-07f}, {0.7903f, 0.0109107f, -4.89042e-05f, -1.04739e-06f}, {0.8435f, 0.0103431f, -6.4615e-05f, -1.40374e-09f}, {0.8936f, 0.00969686f, -6.4636e-05f, -8.547e-06f}, {0.9394f, 0.00840947f, -0.000192841f, -4.2106e-06f}, {0.9761f, 0.00616527f, -0.000256f, -4.2106e-06f}, {1.0f, 0.00328947f, -0.000319159f, -4.2106e-06f}}; #define FXC 0.8487 #define FYC 1.3523 #define C1 11.45915590261646417544 #define RC1 0.08726646259971647884 #define NODES 18 #define ONEEPS 1.000001 #define EPS 1e-10 /* Not sure at all of the appropriate number for MAX_ITER... */ #define MAX_ITER 100 static PJ_XY robin_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; long i; double dphi; (void)P; dphi = fabs(lp.phi); i = std::isnan(lp.phi) ? -1 : lround(floor(dphi * C1 + 1e-15)); if (i < 0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } if (i >= NODES) i = NODES; dphi = RAD_TO_DEG * (dphi - RC1 * i); xy.x = V(X[i], dphi) * FXC * lp.lam; xy.y = V(Y[i], dphi) * FYC; if (lp.phi < 0.) xy.y = -xy.y; return xy; } static PJ_LP robin_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double t; struct COEFS T; int iters; lp.lam = xy.x / FXC; lp.phi = fabs(xy.y / FYC); if (lp.phi >= 1.) { /* simple pathologic cases */ if (lp.phi > ONEEPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; lp.lam /= X[NODES].c0; } } else { /* general problem */ /* in Y space, reduce to table interval */ long i = std::isnan(lp.phi) ? -1 : lround(floor(lp.phi * NODES)); if (i < 0 || i >= NODES) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } for (;;) { if (Y[i].c0 > lp.phi) --i; else if (Y[i + 1].c0 <= lp.phi) ++i; else break; } T = Y[i]; /* first guess, linear interp */ t = 5. * (lp.phi - T.c0) / (Y[i + 1].c0 - T.c0); for (iters = MAX_ITER; iters; --iters) { /* Newton-Raphson */ const double t1 = (V(T, t) - lp.phi) / DV(T, t); t -= t1; if (fabs(t1) < EPS) break; } if (iters == 0) proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = (5 * i + t) * DEG_TO_RAD; if (xy.y < 0.) lp.phi = -lp.phi; lp.lam /= V(X[i], t); if (fabs(lp.lam) > M_PI) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp = proj_coord_error().lp; } } return lp; } PJ *PJ_PROJECTION(robin) { P->es = 0.; P->inv = robin_s_inverse; P->fwd = robin_s_forward; return P; } proj-9.8.1/src/projections/hatano.cpp000664 001750 001750 00000004072 15166171715 017562 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(hatano, "Hatano Asymmetrical Equal Area") "\n\tPCyl, Sph"; #define NITER 20 #define EPS 1e-7 #define ONETOL 1.000001 #define CN 2.67595 #define CSz 2.43763 #define RCN 0.37369906014686373063 #define RCS 0.41023453108141924738 #define FYCN 1.75859 #define FYCS 1.93052 #define RYCN 0.56863737426006061674 #define RYCS 0.51799515156538134803 #define FXC 0.85 #define RXC 1.17647058823529411764 static PJ_XY hatano_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; int i; (void)P; const double c = sin(lp.phi) * (lp.phi < 0. ? CSz : CN); for (i = NITER; i; --i) { const double th1 = (lp.phi + sin(lp.phi) - c) / (1. + cos(lp.phi)); lp.phi -= th1; if (fabs(th1) < EPS) break; } xy.x = FXC * lp.lam * cos(lp.phi *= .5); xy.y = sin(lp.phi) * (lp.phi < 0. ? FYCS : FYCN); return xy; } static PJ_LP hatano_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double th; th = xy.y * (xy.y < 0. ? RYCS : RYCN); if (fabs(th) > 1.) { if (fabs(th) > ONETOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { th = th > 0. ? M_HALFPI : -M_HALFPI; } } else { th = asin(th); } lp.lam = RXC * xy.x / cos(th); th += th; lp.phi = (th + sin(th)) * (xy.y < 0. ? RCS : RCN); if (fabs(lp.phi) > 1.) { if (fabs(lp.phi) > ONETOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = lp.phi > 0. ? M_HALFPI : -M_HALFPI; } } else { lp.phi = asin(lp.phi); } return (lp); } PJ *PJ_PROJECTION(hatano) { P->es = 0.; P->inv = hatano_s_inverse; P->fwd = hatano_s_forward; return P; } #undef NITER #undef EPS #undef ONETOL #undef CN #undef CSz #undef RCN #undef RCS #undef FYCN #undef FYCS #undef RYCN #undef RYCS #undef FXC #undef RXC proj-9.8.1/src/projections/sterea.cpp000664 001750 001750 00000007174 15166171715 017601 0ustar00eveneven000000 000000 /* ** libproj -- library of cartographic projections ** ** Copyright (c) 2003 Gerald I. Evenden */ /* ** 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 "proj.h" #include "proj_internal.h" #include #include namespace { // anonymous namespace struct pj_opaque { double phic0; double cosc0, sinc0; double R2; void *en; }; } // anonymous namespace PROJ_HEAD(sterea, "Oblique Stereographic Alternative") "\n\tAzimuthal, Sph&Ell"; static PJ_XY sterea_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_opaque *Q = static_cast(P->opaque); double cosc, sinc, cosl, k; lp = pj_gauss(P->ctx, lp, Q->en); sinc = sin(lp.phi); cosc = cos(lp.phi); cosl = cos(lp.lam); const double denom = 1. + Q->sinc0 * sinc + Q->cosc0 * cosc * cosl; if (denom == 0.0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } k = P->k0 * Q->R2 / denom; xy.x = k * cosc * sin(lp.lam); xy.y = k * (Q->cosc0 * sinc - Q->sinc0 * cosc * cosl); return xy; } static PJ_LP sterea_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_opaque *Q = static_cast(P->opaque); double rho, c, sinc, cosc; xy.x /= P->k0; xy.y /= P->k0; if ((rho = hypot(xy.x, xy.y)) != 0.0) { c = 2. * atan2(rho, Q->R2); sinc = sin(c); cosc = cos(c); lp.phi = asin(cosc * Q->sinc0 + xy.y * sinc * Q->cosc0 / rho); lp.lam = atan2(xy.x * sinc, rho * Q->cosc0 * cosc - xy.y * Q->sinc0 * sinc); } else { lp.phi = Q->phic0; lp.lam = 0.; } return pj_inv_gauss(P->ctx, lp, Q->en); } static PJ *destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(sterea) { double R; struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->en = pj_gauss_ini(P->e, P->phi0, &(Q->phic0), &R); if (nullptr == Q->en) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->sinc0 = sin(Q->phic0); Q->cosc0 = cos(Q->phic0); Q->R2 = 2. * R; P->inv = sterea_e_inverse; P->fwd = sterea_e_forward; P->destructor = destructor; return P; } proj-9.8.1/src/projections/tcea.cpp000664 001750 001750 00000001407 15166171715 017223 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(tcea, "Transverse Cylindrical Equal Area") "\n\tCyl, Sph"; static PJ_XY tcea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = cos(lp.phi) * sin(lp.lam) / P->k0; xy.y = P->k0 * (atan2(tan(lp.phi), cos(lp.lam)) - P->phi0); return xy; } static PJ_LP tcea_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double t; xy.y = xy.y / P->k0 + P->phi0; xy.x *= P->k0; t = sqrt(1. - xy.x * xy.x); lp.phi = asin(t * sin(xy.y)); lp.lam = atan2(xy.x, t * cos(xy.y)); return lp; } PJ *PJ_PROJECTION(tcea) { P->inv = tcea_s_inverse; P->fwd = tcea_s_forward; P->es = 0.; return P; } proj-9.8.1/src/projections/nicol.cpp000664 001750 001750 00000002524 15166171715 017414 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(nicol, "Nicolosi Globular") "\n\tMisc Sph, no inv"; #define EPS 1e-10 static PJ_XY nicol_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; if (fabs(lp.lam) < EPS) { xy.x = 0; xy.y = lp.phi; } else if (fabs(lp.phi) < EPS) { xy.x = lp.lam; xy.y = 0.; } else if (fabs(fabs(lp.lam) - M_HALFPI) < EPS) { xy.x = lp.lam * cos(lp.phi); xy.y = M_HALFPI * sin(lp.phi); } else if (fabs(fabs(lp.phi) - M_HALFPI) < EPS) { xy.x = 0; xy.y = lp.phi; } else { double tb, c, d, m, n, r2, sp; tb = M_HALFPI / lp.lam - lp.lam / M_HALFPI; c = lp.phi / M_HALFPI; d = (1 - c * c) / ((sp = sin(lp.phi)) - c); r2 = tb / d; r2 *= r2; m = (tb * sp / d - 0.5 * tb) / (1. + r2); n = (sp / r2 + 0.5 * d) / (1. + 1. / r2); xy.x = cos(lp.phi); xy.x = sqrt(m * m + xy.x * xy.x / (1. + r2)); xy.x = M_HALFPI * (m + (lp.lam < 0. ? -xy.x : xy.x)); xy.y = sqrt(n * n - (sp * sp / r2 + d * sp - 1.) / (1. + 1. / r2)); xy.y = M_HALFPI * (n + (lp.phi < 0. ? xy.y : -xy.y)); } return (xy); } PJ *PJ_PROJECTION(nicol) { P->es = 0.; P->fwd = nicol_s_forward; return P; } proj-9.8.1/src/projections/geos.cpp000664 001750 001750 00000017361 15166171715 017252 0ustar00eveneven000000 000000 /* ** libproj -- library of cartographic projections ** ** Copyright (c) 2004 Gerald I. Evenden ** Copyright (c) 2012 Martin Raspaud ** ** See also (section 4.4.3.2): ** https://www.cgms-info.org/documents/pdf_cgms_03.pdf ** ** 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 #include #include #include "proj.h" #include "proj_internal.h" #include namespace { // anonymous namespace struct pj_geos_data { double h; double radius_p; double radius_p2; double radius_p_inv2; double radius_g; double radius_g_1; double C; int flip_axis; }; } // anonymous namespace PROJ_HEAD(geos, "Geostationary Satellite View") "\n\tAzi, Sph&Ell\n\th="; static PJ_XY geos_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_geos_data *Q = static_cast(P->opaque); double Vx, Vy, Vz, tmp; /* Calculation of the three components of the vector from satellite to ** position on earth surface (long,lat).*/ tmp = cos(lp.phi); Vx = cos(lp.lam) * tmp; Vy = sin(lp.lam) * tmp; Vz = sin(lp.phi); /* Check visibility*/ /* Calculation based on view angles from satellite.*/ tmp = Q->radius_g - Vx; if (Q->flip_axis) { xy.x = Q->radius_g_1 * atan(Vy / hypot(Vz, tmp)); xy.y = Q->radius_g_1 * atan(Vz / tmp); } else { xy.x = Q->radius_g_1 * atan(Vy / tmp); xy.y = Q->radius_g_1 * atan(Vz / hypot(Vy, tmp)); } return xy; } static PJ_XY geos_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_geos_data *Q = static_cast(P->opaque); double r, Vx, Vy, Vz, tmp; /* Calculation of geocentric latitude. */ lp.phi = atan(Q->radius_p2 * tan(lp.phi)); /* Calculation of the three components of the vector from satellite to ** position on earth surface (long,lat).*/ r = (Q->radius_p) / hypot(Q->radius_p * cos(lp.phi), sin(lp.phi)); Vx = r * cos(lp.lam) * cos(lp.phi); Vy = r * sin(lp.lam) * cos(lp.phi); Vz = r * sin(lp.phi); /* Check visibility. */ if (((Q->radius_g - Vx) * Vx - Vy * Vy - Vz * Vz * Q->radius_p_inv2) < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } /* Calculation based on view angles from satellite. */ tmp = Q->radius_g - Vx; if (Q->flip_axis) { xy.x = Q->radius_g_1 * atan(Vy / hypot(Vz, tmp)); xy.y = Q->radius_g_1 * atan(Vz / tmp); } else { xy.x = Q->radius_g_1 * atan(Vy / tmp); xy.y = Q->radius_g_1 * atan(Vz / hypot(Vy, tmp)); } return xy; } static PJ_LP geos_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_geos_data *Q = static_cast(P->opaque); double Vx, Vy, Vz, a, b, k; /* Setting three components of vector from satellite to position.*/ Vx = -1.0; if (Q->flip_axis) { Vz = tan(xy.y / Q->radius_g_1); Vy = tan(xy.x / Q->radius_g_1) * sqrt(1.0 + Vz * Vz); } else { Vy = tan(xy.x / Q->radius_g_1); Vz = tan(xy.y / Q->radius_g_1) * sqrt(1.0 + Vy * Vy); } /* Calculation of terms in cubic equation and determinant.*/ a = Vy * Vy + Vz * Vz + Vx * Vx; b = 2 * Q->radius_g * Vx; const double det = (b * b) - 4 * a * Q->C; if (det < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } /* Calculation of three components of vector from satellite to position.*/ k = (-b - sqrt(det)) / (2 * a); Vx = Q->radius_g + k * Vx; Vy *= k; Vz *= k; /* Calculation of longitude and latitude.*/ lp.lam = atan2(Vy, Vx); // Initial formula: // lp.phi = atan(Vz * cos(lp.lam) / Vx); // Given that cos(atan2(y,x) = x / hypot(x,y) lp.phi = atan(Vz / hypot(Vx, Vy)); return lp; } static PJ_LP geos_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_geos_data *Q = static_cast(P->opaque); double Vx, Vy, Vz, a, b, k; /* Setting three components of vector from satellite to position.*/ Vx = -1.0; if (Q->flip_axis) { Vz = tan(xy.y / Q->radius_g_1); Vy = tan(xy.x / Q->radius_g_1) * hypot(1.0, Vz); } else { Vy = tan(xy.x / Q->radius_g_1); Vz = tan(xy.y / Q->radius_g_1) * hypot(1.0, Vy); } /* Calculation of terms in cubic equation and determinant.*/ a = Vz / Q->radius_p; a = Vy * Vy + a * a + Vx * Vx; b = 2 * Q->radius_g * Vx; const double det = (b * b) - 4 * a * Q->C; if (det < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } /* Calculation of three components of vector from satellite to position.*/ k = (-b - sqrt(det)) / (2. * a); Vx = Q->radius_g + k * Vx; Vy *= k; Vz *= k; /* Calculation of longitude and latitude.*/ lp.lam = atan2(Vy, Vx); // Initial formula: // lp.phi = atan(Q->radius_p_inv2 * Vz * cos(lp.lam) / Vx); // Given that cos(atan2(y,x) = x / hypot(x,y) lp.phi = atan(Q->radius_p_inv2 * Vz / hypot(Vx, Vy)); return lp; } PJ *PJ_PROJECTION(geos) { char *sweep_axis; struct pj_geos_data *Q = static_cast( calloc(1, sizeof(struct pj_geos_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->h = pj_param(P->ctx, P->params, "dh").f; sweep_axis = pj_param(P->ctx, P->params, "ssweep").s; if (sweep_axis == nullptr) Q->flip_axis = 0; else { if ((sweep_axis[0] != 'x' && sweep_axis[0] != 'y') || sweep_axis[1] != '\0') { proj_log_error( P, _("Invalid value for sweep: it should be equal to x or y.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (sweep_axis[0] == 'x') Q->flip_axis = 1; else Q->flip_axis = 0; } Q->radius_g_1 = Q->h / P->a; if (Q->radius_g_1 <= 0 || Q->radius_g_1 > 1e10) { proj_log_error(P, _("Invalid value for h.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->radius_g = 1. + Q->radius_g_1; Q->C = Q->radius_g * Q->radius_g - 1.0; if (P->es != 0.0) { Q->radius_p = sqrt(P->one_es); Q->radius_p2 = P->one_es; Q->radius_p_inv2 = P->rone_es; P->inv = geos_e_inverse; P->fwd = geos_e_forward; } else { Q->radius_p = Q->radius_p2 = Q->radius_p_inv2 = 1.0; P->inv = geos_s_inverse; P->fwd = geos_s_forward; } return P; } proj-9.8.1/src/projections/airocean.cpp000664 001750 001750 00000111150 15166171715 020065 0ustar00eveneven000000 000000 /* enable predefined math constants M_* for MS Visual Studio */ #if defined(_MSC_VER) || defined(_WIN32) #ifndef _USE_MATH_DEFINES #define _USE_MATH_DEFINES #endif #endif #include #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(airocean, "Airocean") "\n\tMisc, Sph&Ell"; namespace { // anonymous namespace struct pj_face { PJ_XYZ p1; PJ_XYZ p2; PJ_XYZ p3; }; } // namespace /* The vertices of the faces of the icosahedron are inspired by those used by Robert W. Gray. Original Reference: Robert W. Gray (1995) Exact Transformation Equations for Fuller's World Map, Vol. 32. Autumn, 1995, pp. 17-25. To accommodate for land parts that would be interrupted by using a mere icosahedron, some faces are split in two (Australia) and 3 (Japan) subfaces. The parameters below were computed using the script located at: scripts/build_airocean_parameters.py (relative to the root of the project) */ // Define the 23 faces and subfaces constexpr pj_face base_ico_faces[23] = { {{0.42015242670871, 0.07814524940278296, 0.9040825506150193}, {0.5188367303273644, 0.8354203803782358, 0.18133183755726245}, {0.9950094394362416, -0.09134779527642793, 0.040147175877166645}}, {{0.42015242670871, 0.07814524940278296, 0.9040825506150193}, {-0.4146822253203352, 0.6559624054348008, 0.6306758078914754}, {0.5188367303273644, 0.8354203803782358, 0.18133183755726245}}, {{0.42015242670871, 0.07814524940278296, 0.9040825506150193}, {-0.5154559599440418, -0.381716898287133, 0.7672009925177475}, {-0.4146822253203352, 0.6559624054348008, 0.6306758078914754}}, {{0.42015242670871, 0.07814524940278296, 0.9040825506150193}, {0.3557814025329447, -0.8435800024661781, 0.40223422660292557}, {-0.5154559599440418, -0.381716898287133, 0.7672009925177475}}, {{0.42015242670871, 0.07814524940278296, 0.9040825506150193}, {0.9950094394362416, -0.09134779527642793, 0.040147175877166645}, {0.3557814025329447, -0.8435800024661781, 0.40223422660292557}}, {{0.9950094394362416, -0.09134779527642793, 0.040147175877166645}, {0.5188367303273644, 0.8354203803782358, 0.18133183755726245}, {0.5154559599440418, 0.381716898287133, -0.7672009925177475}}, {{0.5154559599440418, 0.381716898287133, -0.7672009925177475}, {0.5188367303273644, 0.8354203803782358, 0.18133183755726245}, {-0.3557814025329447, 0.8435800024661781, -0.40223422660292557}}, {{-0.3557814025329447, 0.8435800024661781, -0.40223422660292557}, {0.5188367303273644, 0.8354203803782358, 0.18133183755726245}, {-0.4146822253203352, 0.6559624054348008, 0.6306758078914754}}, {{-0.5154559599440418, -0.381716898287133, 0.7672009925177475}, {-0.9950094394362416, 0.09134779527642793, -0.040147175877166645}, {-0.4146822253203352, 0.6559624054348008, 0.6306758078914754}}, {{-0.5154559599440418, -0.381716898287133, 0.7672009925177475}, {-0.5188367303273644, -0.8354203803782358, -0.18133183755726245}, {-0.9950094394362416, 0.09134779527642793, -0.040147175877166645}}, {{-0.5154559599440418, -0.381716898287133, 0.7672009925177475}, {0.3557814025329447, -0.8435800024661781, 0.40223422660292557}, {-0.5188367303273644, -0.8354203803782358, -0.18133183755726245}}, {{-0.5188367303273644, -0.8354203803782358, -0.18133183755726245}, {0.3557814025329447, -0.8435800024661781, 0.40223422660292557}, {0.4146822253203352, -0.6559624054348008, -0.6306758078914754}}, {{0.4146822253203352, -0.6559624054348008, -0.6306758078914754}, {0.3557814025329447, -0.8435800024661781, 0.40223422660292557}, {0.9950094394362416, -0.09134779527642793, 0.040147175877166645}}, {{0.5154559599440418, 0.381716898287133, -0.7672009925177475}, {0.4146822253203352, -0.6559624054348008, -0.6306758078914754}, {0.9950094394362416, -0.09134779527642793, 0.040147175877166645}}, {{-0.42015242670871, -0.07814524940278296, -0.9040825506150193}, {-0.3557814025329447, 0.8435800024661781, -0.40223422660292557}, {-0.9950094394362416, 0.09134779527642793, -0.040147175877166645}}, {{-0.42015242670871, -0.07814524940278296, -0.9040825506150193}, {-0.9950094394362416, 0.09134779527642793, -0.040147175877166645}, {-0.5188367303273644, -0.8354203803782358, -0.18133183755726245}}, {{-0.42015242670871, -0.07814524940278296, -0.9040825506150193}, {-0.5188367303273644, -0.8354203803782358, -0.18133183755726245}, {0.4146822253203352, -0.6559624054348008, -0.6306758078914754}}, {{-0.42015242670871, -0.07814524940278296, -0.9040825506150193}, {0.4146822253203352, -0.6559624054348008, -0.6306758078914754}, {0.5154559599440418, 0.381716898287133, -0.7672009925177475}}, {{-0.3557814025329447, 0.8435800024661781, -0.40223422660292557}, {-0.38796691462082733, 0.3827173765316976, -0.6531583886089725}, {0.5154559599440418, 0.381716898287133, -0.7672009925177475}}, {{-0.42015242670871, -0.07814524940278296, -0.9040825506150193}, {0.5154559599440418, 0.381716898287133, -0.7672009925177475}, {-0.38796691462082733, 0.3827173765316976, -0.6531583886089725}}, {{-0.9950094394362416, 0.09134779527642793, -0.040147175877166645}, {-0.3557814025329447, 0.8435800024661781, -0.40223422660292557}, {-0.5884910224298405, 0.5302967343924689, 0.06276480180379439}}, {{-0.3557814025329447, 0.8435800024661781, -0.40223422660292557}, {-0.4146822253203352, 0.6559624054348008, 0.6306758078914754}, {-0.5884910224298405, 0.5302967343924689, 0.06276480180379439}}, {{-0.9950094394362416, 0.09134779527642793, -0.040147175877166645}, {-0.5884910224298405, 0.5302967343924689, 0.06276480180379439}, {-0.4146822253203352, 0.6559624054348008, 0.6306758078914754}}}; // // Define the centers for each face or subface constexpr PJ_XYZ base_ico_centers[23] = { {0.6446661988241054, 0.27407261150153034, 0.37518718801648276}, {0.17476897723857973, 0.5231760117386065, 0.5720300653545857}, {-0.16999525285188902, 0.1174635855168169, 0.7673197836747474}, {0.08682595643253761, -0.3823838837835094, 0.6911725899118975}, {0.5903144228926321, -0.28559418277994103, 0.44882131769837047}, {0.6764340432358825, 0.375263161129647, -0.18190732636110615}, {0.22617042924615385, 0.6869057603771823, -0.3293677938544702}, {-0.0838756325086385, 0.778320929426405, 0.13659113961527075}, {-0.6417158749002062, 0.12186443414136523, 0.45257654151068544}, {-0.6764340432358825, -0.375263161129647, 0.18190732636110615}, {-0.22617042924615385, -0.6869057603771823, 0.32936779385447024}, {0.0838756325086385, -0.778320929426405, -0.13659113961527075}, {0.5884910224298405, -0.5302967343924689, -0.06276480180379439}, {0.6417158749002062, -0.12186443414136523, -0.4525765415106855}, {-0.5903144228926321, 0.28559418277994103, -0.44882131769837047}, {-0.6446661988241054, -0.2740726115015303, -0.37518718801648276}, {-0.17476897723857973, -0.5231760117386065, -0.5720300653545857}, {0.16999525285188902, -0.11746358551681692, -0.7673197836747474}, {-0.07609745240324339, 0.5360047590950029, -0.6075312025765486}, {-0.09755446046183185, 0.2287630084720159, -0.7748139772472463}, {-0.646427288133009, 0.48840817737835834, -0.12653886689209928}, {-0.4529848834277068, 0.6766130474311494, 0.09706879436411474}, {-0.6660608957288058, 0.4258689783678992, 0.2177644779393677}}; // // Define the normals for each face and subface constexpr PJ_XYZ base_ico_normals[23] = { {0.8112534709140969, 0.34489532376393844, 0.47213877364139306}, {0.21993077914046083, 0.6583691780274996, 0.7198475378926182}, {-0.21392348345014195, 0.1478171829550702, 0.9656017935214206}, {0.10926252787847963, -0.4811951572873208, 0.8697775121287253}, {0.7428567301586793, -0.35939416782780276, 0.5648005936517034}, {0.8512303986474292, 0.4722343788582682, -0.2289137388687808}, {0.2846148069787909, 0.8644080972654203, -0.4144792552473538}, {-0.10554981496139187, 0.9794457296411412, 0.17188746100093646}, {-0.8075407579970092, 0.15335524858988167, 0.5695261994882688}, {-0.8512303986474292, -0.4722343788582682, 0.22891373886878083}, {-0.2846148069787909, -0.8644080972654203, 0.4144792552473538}, {0.10554981496139185, -0.9794457296411412, -0.17188746100093638}, {0.7405621473854482, -0.6673299564565524, -0.07898376463267347}, {0.8075407579970092, -0.15335524858988167, -0.5695261994882688}, {-0.7428567301586793, 0.35939416782780276, -0.5648005936517034}, {-0.8112534709140969, -0.34489532376393844, -0.47213877364139306}, {-0.21993077914046083, -0.6583691780274996, -0.7198475378926182}, {0.21392348345014195, -0.1478171829550702, -0.9656017935214206}, {-0.10926252787847963, 0.4811951572873209, -0.8697775121287253}, {-0.10926252787847968, 0.4811951572873209, -0.8697775121287253}, {-0.740562147385448, 0.6673299564565524, 0.07898376463267354}, {-0.7405621473854481, 0.6673299564565524, 0.07898376463267347}, {-0.7405621473854481, 0.6673299564565525, 0.07898376463267329}}; // /* // The points of the Airocean projection map are deduced from the unfolded // net of the altered icosahedron defined above. The distances in the // projected 2d space are expressed in meter. // */ // // Define the 23 unfolded surfaces used (from icosahedron + split faces) constexpr pj_face base_airocean_faces[23] = { {{1.8211859946200586, 3.1543866727148018, 1.0}, {1.8211859946200586, 4.205848896953069, 1.0}, {2.7317789919300877, 3.6801177848339353, 1.0}}, {{1.8211859946200586, 3.1543866727148018, 1.0}, {0.9105929973100293, 3.6801177848339353, 1.0}, {1.8211859946200586, 4.205848896953069, 1.0}}, {{1.8211859946200586, 3.1543866727148018, 1.0}, {0.9105929973100293, 2.628655560595668, 1.0}, {0.9105929973100293, 3.6801177848339353, 1.0}}, {{1.8211859946200586, 3.1543866727148018, 1.0}, {1.8211859946200586, 2.1029244484765344, 1.0}, {0.9105929973100293, 2.628655560595668, 1.0}}, {{1.8211859946200586, 3.1543866727148018, 1.0}, {2.7317789919300877, 3.6801177848339353, 1.0}, {2.7317789919300877, 2.628655560595668, 1.0}}, {{2.7317789919300877, 3.6801177848339353, 1.0}, {1.8211859946200586, 4.205848896953069, 1.0}, {2.7317789919300877, 4.731580009072203, 1.0}}, {{1.8211859946200586, 5.257311121191336, 1.0}, {1.8211859946200586, 4.205848896953069, 1.0}, {0.9105929973100293, 4.731580009072203, 1.0}}, {{0.9105929973100293, 4.731580009072203, 1.0}, {1.8211859946200586, 4.205848896953069, 1.0}, {0.9105929973100293, 3.6801177848339353, 1.0}}, {{0.9105929973100293, 2.628655560595668, 1.0}, {0.0, 3.1543866727148018, 1.0}, {0.9105929973100293, 3.6801177848339353, 1.0}}, {{0.9105929973100293, 2.628655560595668, 1.0}, {0.9105929973100293, 1.5771933363574009, 1.0}, {0.0, 2.1029244484765344, 1.0}}, {{0.9105929973100293, 2.628655560595668, 1.0}, {1.8211859946200586, 2.1029244484765344, 1.0}, {0.9105929973100293, 1.5771933363574009, 1.0}}, {{0.9105929973100293, 1.5771933363574009, 1.0}, {1.8211859946200586, 2.1029244484765344, 1.0}, {1.8211859946200586, 1.0514622242382672, 1.0}}, {{1.8211859946200586, 1.0514622242382672, 1.0}, {1.8211859946200586, 2.1029244484765344, 1.0}, {2.7317789919300877, 1.5771933363574009, 1.0}}, {{1.8211859946200586, 0.0, 1.0}, {1.8211859946200586, 1.0514622242382672, 1.0}, {2.7317789919300877, 0.5257311121191336, 1.0}}, {{0.0, 5.257311121191336, 1.0}, {0.9105929973100293, 4.731580009072203, 1.0}, {0.0, 4.205848896953069, 1.0}}, {{0.0, 1.0514622242382672, 1.0}, {0.0, 2.1029244484765344, 1.0}, {0.9105929973100293, 1.5771933363574009, 1.0}}, {{0.9105929973100293, 0.5257311121191336, 1.0}, {0.9105929973100293, 1.5771933363574009, 1.0}, {1.8211859946200586, 1.0514622242382672, 1.0}}, {{0.9105929973100293, 0.5257311121191336, 1.0}, {1.8211859946200586, 1.0514622242382672, 1.0}, {1.8211859946200586, 0.0, 1.0}}, {{0.9105929973100293, 4.731580009072203, 1.0}, {0.45529649865501465, 4.994445565131769, 1.0}, {0.9105929973100293, 5.78304223331047, 1.0}}, {{0.9105929973100293, 0.5257311121191336, 1.0}, {1.8211859946200586, 0.0, 1.0}, {0.9105929973100293, 0.0, 1.0}}, {{0.0, 4.205848896953069, 1.0}, {0.9105929973100293, 4.731580009072203, 1.0}, {0.6070619982066862, 4.205848896953069, 1.0}}, {{0.9105929973100293, 4.731580009072203, 1.0}, {0.9105929973100293, 3.6801177848339353, 1.0}, {0.6070619982066862, 4.205848896953069, 1.0}}, {{0.0, 3.1543866727148018, 1.0}, {0.3035309991033431, 3.6801177848339353, 1.0}, {0.9105929973100293, 3.6801177848339353, 1.0}}}; // /* // The parameters here are extracted from the transition matrices // that allow converting a icosahedron face or subface to its // corresponding face in the Airocean projected space. // Since only a few parameters of those matrices are relevant, // the irrelevant ones has been discarded. // */ // // Icosahedron to Airocean (forward) constexpr double base_ico_air_trans[23][4][4] = { {{0.5771127852625935, -0.6019490725122667, -0.5519041105011566, 2.1247169937234016}, {0.09385435001257117, 0.7202114479424703, -0.6873767753105484, 3.6801177848339357}, {0.8112534709140967, 0.3448953237639384, 0.4721387736413929, -0.7946544722917659}, {0.0, 0.0, 0.0, 1.0}}, {{0.9709901201198636, -0.2187361325341673, -0.09660585361978567, 1.5176549955167156}, {0.09385435001257089, 0.7202114479424708, -0.687376775310548, 3.6801177848339353}, {0.21993077914046077, 0.6583691780274995, 0.7198475378926181, -0.7946544722917659}, {0.0, 0.0, 0.0, 1.0}}, {{0.9721374115064793, -0.06476823821979226, 0.2252863255224028, 1.2141239964133725}, {0.09584151698527507, 0.9868916636293633, -0.12984316647721492, 3.1543866727148013}, {-0.21392348345014195, 0.1478171829550702, 0.9656017935214207, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.9921258753731454, -0.0010987106726278763, -0.1252399307326839, 1.5176549955167151}, {0.06122048200295415, 0.8766128070237673, 0.47728611874350335, 2.6286555605956674}, {0.10926252787847969, -0.4811951572873209, 0.8697775121287256, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.28030414798915965, -0.5991800396948614, -0.7499419075177327, 2.428247992826745}, {0.6079419898954396, 0.7154153424148981, -0.34436524905882676, 3.1543866727148013}, {0.742856730158679, -0.3593941678278027, 0.5648005936517032, -0.794654472291766}, {0.0, 0.0, 0.0, 1.0}}, {{0.25960661905056537, -0.7580069591045613, -0.5983559586852888, 2.428247992826744}, {-0.4560824615830708, 0.4499112594427941, -0.7678337364709403, 4.205848896953069}, {0.8512303986474292, 0.47223437885826813, -0.22891373886878083, -0.7946544722917661}, {0.0, 0.0, 0.0, 1.0}}, {{0.958636570067365, -0.258086064605963, 0.12003128669511368, 1.5176549955167158}, {-0.003215303703157293, -0.43149765310854393, -0.9021083289627215, 4.731580009072202}, {0.2846148069787908, 0.8644080972654206, -0.41447925524735385, -0.7946544722917662}, {0.0, 0.0, 0.0, 1.0}}, {{0.992834940445074, 0.09405868118990741, 0.07370037668995856, 1.2141239964133723}, {0.056018011327093935, 0.17843493822832973, -0.982355819052552, 4.205848896953068}, {-0.10554981496139189, 0.9794457296411413, 0.17188746100093644, -0.7946544722917662}, {0.0, 0.0, 0.0, 1.0}}, {{0.5819727895662967, 0.05026939415592827, 0.8116530417706931, 0.6070619982066865}, {0.09584151698527442, 0.9868916636293634, -0.1298431664772149, 3.1543866727148013}, {-0.8075407579970093, 0.1533552485898817, 0.5695261994882689, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.5247823074767625, -0.7686380596783918, 0.36578554232391575, 0.6070619982066867}, {0.0032153037031566203, 0.4314976531085445, 0.9021083289627214, 2.102924448476535}, {-0.8512303986474292, -0.47223437885826813, 0.22891373886878078, -0.7946544722917661}, {0.0, 0.0, 0.0, 1.0}}, {{0.9586365700673652, -0.2580860646059632, 0.12003128669511379, 1.2141239964133719}, {0.003215303703156878, 0.43149765310854465, 0.9021083289627218, 2.1029244484765344}, {-0.2846148069787909, -0.8644080972654204, 0.4144792552473538, -0.7946544722917662}, {0.0, 0.0, 0.0, 1.0}}, {{0.9928349404450738, 0.0940586811899076, 0.07370037668995869, 1.5176549955167153}, {-0.05601801132709388, -0.17843493822832968, 0.9823558190525513, 1.5771933363574009}, {0.10554981496139189, -0.9794457296411413, -0.17188746100093644, -0.7946544722917662}, {0.0, 0.0, 0.0, 1.0}}, {{0.6696489291164518, 0.7230710214322986, 0.1695246580826539, 2.1247169937234016}, {-0.05601801132709365, -0.17843493822832943, 0.9823558190525516, 1.5771933363574009}, {0.7405621473854482, -0.6673299564565524, -0.07898376463267347, -0.7946544722917662}, {0.0, 0.0, 0.0, 1.0}}, {{0.5819727895662965, 0.050269394155928314, 0.811653041770693, 2.1247169937234016}, {-0.09584151698527484, -0.9868916636293626, 0.129843166477215, 0.5257311121191333}, {0.8075407579970093, -0.1533552485898817, -0.5695261994882689, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.3863411332821331, 0.9191578806358752, 0.07674189989336716, 0.30353099910334314}, {0.5467215078924839, -0.16119746460886833, -0.8216513678023304, 4.731580009072203}, {-0.742856730158679, 0.35939416782780265, -0.5648005936517032, -0.794654472291766}, {0.0, 0.0, 0.0, 1.0}}, {{0.20727614126473407, -0.9246959462706865, 0.31933369413978446, 0.303530999103343}, {-0.5467215078924849, 0.1611974646088691, 0.8216513678023303, 1.5771933363574007}, {-0.8112534709140967, -0.34489532376393833, -0.47213877364139295, -0.794654472291766}, {0.0, 0.0, 0.0, 1.0}}, {{0.9709901201198639, -0.21873613253416718, -0.09660585361978535, 1.2141239964133725}, {-0.09385435001257073, -0.7202114479424704, 0.6873767753105484, 1.0514622242382674}, {-0.21993077914046086, -0.6583691780274995, -0.719847537892618, -0.794654472291766}, {0.0, 0.0, 0.0, 1.0}}, {{0.9721374115064794, -0.0647682382197923, 0.2252863255224031, 1.5176549955167156}, {-0.09584151698527477, -0.9868916636293626, 0.12984316647721514, 0.5257311121191336}, {0.21392348345014198, -0.1478171829550702, -0.9656017935214205, -0.7946544722917661}, {0.0, 0.0, 0.0, 1.0}}, {{0.5490814303330593, 0.7586196048290541, 0.350721938339208, 0.6070619982066862}, {0.8285959708235409, -0.43925791486578636, -0.3471040209544599, 5.257311121191335}, {-0.10926252787847968, 0.48119515728732093, -0.8697775121287254, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.9921258753731453, -0.0010987106726278503, -0.125239930732684, 1.2141239964133725}, {-0.061220482002954366, -0.8766128070237673, -0.4772861187435034, 0.0}, {-0.10926252787847965, 0.48119515728732093, -0.8697775121287254, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.6696489291164521, 0.7230710214322988, 0.169524658082654, 0.607061998206686}, {0.05601801132709396, 0.17843493822832968, -0.9823558190525518, 4.205848896953069}, {-0.7405621473854482, 0.6673299564565525, 0.07898376463267334, -0.7946544722917662}, {0.0, 0.0, 0.0, 1.0}}, {{0.6696489291164525, 0.7230710214322987, 0.1695246580826538, 0.6070619982066863}, {0.05601801132709517, 0.1784349382283307, -0.9823558190525518, 4.205848896953069}, {-0.7405621473854483, 0.6673299564565526, 0.07898376463267348, -0.7946544722917663}, {0.0, 0.0, 0.0, 1.0}}, {{0.28631144367947836, 0.20700632128770896, 0.9355074238963061, 0.3035309991033428}, {0.6079419898954391, 0.7154153424148978, -0.3443652490588263, 3.6801177848339357}, {-0.7405621473854481, 0.6673299564565525, 0.07898376463267341, -0.7946544722917661}, {0.0, 0.0, 0.0, 1.0}}}; // // Airocean to Icosahedron (inverse) constexpr double base_air_ico_trans[23][4][4] = { {{0.577112785262594, 0.09385435001257074, 0.8112534709140972, -0.9269302059836626}, {-0.6019490725122669, 0.7202114479424705, 0.3448953237639385, -1.0974189231897016}, {-0.5519041105011576, -0.6873767753105482, 0.47213877364139284, 4.0774547262062395}, {0.0, 0.0, 0.0, 1.0}}, {{0.970990120119864, 0.09385435001257075, 0.21993077914046097, -1.6442540918239978}, {-0.21873613253416777, 0.7202114479424705, 0.6583691780274992, -1.7953209624349933}, {-0.09660585361978517, -0.6873767753105485, 0.7198475378926187, 3.248271917398959}, {0.0, 0.0, 0.0, 1.0}}, {{0.9721374115064793, 0.09584151698527468, -0.21392348345014173, -1.6526118158442071}, {-0.06476823821979319, 0.9868916636293628, 0.14781718295507035, -2.916937653420916}, {0.22528632552240307, -0.12984316647721503, 0.9656017935214205, 0.9033698036730199}, {0.0, 0.0, 0.0, 1.0}}, {{0.9921258753731454, 0.061220482002954296, 0.10926252787847969, -1.5798063949483236}, {-0.0010987106726281115, 0.8766128070237668, -0.48119515728732043, -2.68502954971497}, {-0.12523993073268413, 0.4772861187435032, 0.8697775121287253, -0.3733772136037109}, {0.0, 0.0, 0.0, 1.0}}, {{0.2803041479891603, 0.6079419898954391, 0.7428567301586791, -2.0080176725529477}, {-0.5991800396948611, 0.7154153424148979, -0.35939416782780287, -1.0873330756182955}, {-0.7499419075177335, -0.3443652490588265, 0.5648005936517035, 3.3561274015422433}, {0.0, 0.0, 0.0, 1.0}}, {{0.2596066190505654, -0.4560824615830712, 0.8512303986474292, 1.9642587095706099}, {-0.7580069591045617, 0.4499112594427949, 0.47223437885826836, 0.3236332638697585}, {-0.5983559586852887, -0.7678337364709401, -0.22891373886878078, 4.500442002892026}, {0.0, 0.0, 0.0, 1.0}}, {{0.958636570067365, -0.003215303703156967, 0.28461480697879094, -1.2134956834766393}, {-0.2580860646059631, -0.43149765310854504, 0.8644080972654203, 3.1202570350096352}, {0.12003128669511316, -0.9021083289627222, -0.4144792552473535, 3.756863859611938}, {0.0, 0.0, 0.0, 1.0}}, {{0.992834940445074, 0.05601801132709367, -0.10554981496139178, -1.5249036493302057}, {0.09405868118990754, 0.17843493822832954, 0.9794457296411412, -0.08634836060276646}, {0.07370037668995799, -0.9823558190525513, 0.1718874610009362, 4.17874988170889}, {0.0, 0.0, 0.0, 1.0}}, {{0.581972789566297, 0.09584151698527493, -0.8075407579970092, -1.297330643307362}, {0.05026939415592872, 0.986891663629363, 0.15335524858988142, -3.0216901158893736}, {0.8116530417706934, -0.12984316647721506, 0.5695261994882689, 0.3694283780016493}, {0.0, 0.0, 0.0, 1.0}}, {{0.5247823074767624, 0.0032153037031565812, -0.8512303986474291, -1.0017709802028867}, {-0.7686380596783923, 0.431497653108545, -0.47223437885826824, -0.8160591689057779}, {0.36578554232391597, 0.9021083289627221, 0.22891373886878089, -1.937212836027187}, {0.0, 0.0, 0.0, 1.0}}, {{0.9586365700673654, 0.0032153037031565886, -0.2846148069787907, -1.3968356335709964}, {-0.258086064605963, 0.43149765310854504, -0.8644080972654202, -1.2809642403813966}, {0.12003128669511362, 0.9021083289627224, 0.41447925524735396, -1.7134307317924613}, {0.0, 0.0, 0.0, 1.0}}, {{0.9928349404450739, -0.05601801132709361, 0.10554981496139221, -1.3345540404002831}, {0.09405868118990741, -0.17843493822832956, -0.9794457296411411, -0.639643161258916}, {0.07370037668995856, 0.982355819052552, -0.17188746100093616, -1.7978079362118518}, {0.0, 0.0, 0.0, 1.0}}, {{0.6696489291164524, -0.056018011327093886, 0.7405621473854481, -0.7459722029114777}, {0.723071021432299, -0.1784349382283297, -0.6673299564565522, -1.7851916257515466}, {0.16952465808265363, 0.9823558190525522, -0.07898376463267373, -1.9723217754287594}, {0.0, 0.0, 0.0, 1.0}}, {{0.5819727895662968, -0.09584151698527477, 0.8075407579970091, -0.5444247336640644}, {0.05026939415592821, -0.9868916636293631, -0.15335524858988164, 0.29016698169232114}, {0.8116530417706935, 0.1298431664772151, -0.569526199488269, -2.2453721446813035}, {0.0, 0.0, 0.0, 1.0}}, {{0.3863411332821329, 0.5467215078924852, -0.7428567301586795, -3.2944374903463687}, {0.9191578806358753, -0.16119746460886916, 0.3593941678278029, 0.7693199739932717}, {0.07674189989336772, -0.8216513678023304, -0.564800593651703, 3.4155943230742447}, {0.0, 0.0, 0.0, 1.0}}, {{0.20727614126473443, -0.546721507892485, -0.8112534709140969, 0.15470458601882164}, {-0.9246959462706867, 0.16119746460886913, -0.3448953237639384, -0.24763829408199384}, {0.31933369413978435, 0.8216513678023302, -0.4721387736413931, -1.768017925352872}, {0.0, 0.0, 0.0, 1.0}}, {{0.9709901201198642, -0.09385435001257068, -0.21993077914046077, -1.2549870787377553}, {-0.2187361325341676, -0.7202114479424703, -0.6583691780274997, 0.4996719066292349}, {-0.09660585361978546, 0.6873767753105482, -0.7198475378926181, -1.1774892933385632}, {0.0, 0.0, 0.0, 1.0}}, {{0.9721374115064794, -0.09584151698527459, 0.21392348345014212, -1.2549870787377553}, {-0.06476823821979266, -0.9868916636293628, -0.14781718295507024, 0.49967190662923483}, {0.2252863255224028, 0.12984316647721506, -0.9656017935214204, -1.1774892933385632}, {0.0, 0.0, 0.0, 1.0}}, {{0.5490814303330579, 0.8285959708235412, -0.10926252787847955, -4.776339239093644}, {0.7586196048290552, -0.4392579148657884, 0.4811951572873209, 2.231170271492442}, {0.3507219383392087, -0.3471040209544594, -0.8697775121287253, 0.9207512789590909}, {0.0, 0.0, 0.0, 1.0}}, {{0.9921258753731456, -0.061220482002954546, -0.10926252787847962, -1.2913897891856965}, {-0.00109871067262767, -0.8766128070237672, 0.48119515728732093, 0.38371785477626236}, {-0.12523993073268386, -0.47728611874350324, -0.8697775121287252, -0.5391157847001973}, {0.0, 0.0, 0.0, 1.0}}, {{0.6696489291164526, 0.0560180113270932, -0.7405621473854482, -1.2306127305858023}, {0.723071021432299, 0.17843493822832968, 0.6673299564565522, -0.6591225928490807}, {0.16952465808265377, -0.9823558190525503, 0.07898376463267326, 4.09149296210043}, {0.0, 0.0, 0.0, 1.0}}, {{0.669648929116452, 0.056018011327093706, -0.740562147385448, -1.230612730585803}, {0.7230710214322988, 0.1784349382283296, 0.6673299564565524, -0.6591225928490807}, {0.1695246580826554, -0.9823558190525514, 0.0789837646326731, 4.091492962100434}, {0.0, 0.0, 0.0, 1.0}}, {{0.2863114436794785, 0.6079419898954399, -0.7405621473854486, -2.9126935501461353}, {0.2070063212877089, 0.7154153424148983, 0.6673299564565521, -2.165348826292825}, {0.935507423896306, -0.3443652490588271, 0.07898376463267351, 1.046113976300111}, {0.0, 0.0, 0.0, 1.0}}}; // By default the resulting orientation of the projection is vertical // the following transforms are used to alter the projection data // so that the resulting orientation is horizontal instead constexpr double orient_horizontal_trans[4][4] = { {0.0, -1.0, 0.0, 5.78304223331047}, {1.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 1.0, 0.0}, {0.0, 0.0, 0.0, 1.0}}; constexpr double orient_horizontal_inv_trans[4][4] = { {0.0, 1.0, 0.0, 0.0}, {-1.0, -0.0, -0.0, 5.78304223331047}, {0.0, 0.0, 1.0, 0.0}, {0.0, 0.0, 0.0, 1.0}}; namespace { // anonymous namespace struct pj_airocean_data { pj_face ico_faces[23] = {}; PJ_XYZ ico_centers[23] = {}; PJ_XYZ ico_normals[23] = {}; pj_face airocean_faces[23] = {}; double ico_air_trans[23][4][4] = {}; double air_ico_trans[23][4][4] = {}; void initialize() { memcpy(this->ico_faces, base_ico_faces, sizeof(pj_face[23])); memcpy(this->airocean_faces, base_airocean_faces, sizeof(pj_face[23])); memcpy(this->ico_centers, base_ico_centers, sizeof(PJ_XYZ[23])); memcpy(this->ico_normals, base_ico_normals, sizeof(PJ_XYZ[23])); memcpy(this->ico_air_trans, base_ico_air_trans, sizeof(double[23][4][4])); memcpy(this->air_ico_trans, base_air_ico_trans, sizeof(double[23][4][4])); } static void mat_mult(const double m1[4][4], const double m2[4][4], double res[4][4]) { for (unsigned char i = 0; i < 4; ++i) for (unsigned char j = 0; j < 4; ++j) res[i][j] = (m1[i][0] * m2[0][j]) + (m1[i][1] * m2[1][j]) + (m1[i][2] * m2[2][j]) + (m1[i][3] * m2[3][j]); } static PJ_XYZ vec_mult(const double m[4][4], const PJ_XYZ *v) { double x = m[0][0] * v->x + m[0][1] * v->y + m[0][2] * v->z + m[0][3]; double y = m[1][0] * v->x + m[1][1] * v->y + m[1][2] * v->z + m[1][3]; double z = m[2][0] * v->x + m[2][1] * v->y + m[2][2] * v->z + m[2][3]; return {x, y, z}; } void transform(const double m[4][4], const double inv_m[4][4]) { for (unsigned char i = 0; i < 23; i++) { mat_mult(m, base_ico_air_trans[i], this->ico_air_trans[i]); mat_mult(base_air_ico_trans[i], inv_m, this->air_ico_trans[i]); this->airocean_faces[i] = { vec_mult(m, &base_airocean_faces[i].p1), vec_mult(m, &base_airocean_faces[i].p2), vec_mult(m, &base_airocean_faces[i].p3), }; } } }; } // anonymous namespace inline double det(const PJ_XYZ *u, const PJ_XYZ *v, const PJ_XYZ *w) { return (u->x * (v->y * w->z - v->z * w->y) - v->x * (u->y * w->z - u->z * w->y) + w->x * (u->y * v->z - u->z * v->y)); } inline bool is_point_in_face(const PJ_XYZ *p, const pj_face *face) { return (det(p, &face->p2, &face->p3) <= 0 && det(&face->p1, p, &face->p3) <= 0 && det(&face->p1, &face->p2, p) <= 0); } inline unsigned char get_ico_face_index(const pj_airocean_data *pj_data, const PJ_XYZ *p) { for (unsigned char i = 0; i < 23; i++) { if (is_point_in_face(p, &pj_data->ico_faces[i])) { return i; } } return 23; } inline unsigned char get_dym_face_index(const pj_airocean_data *pj_data, const PJ_XY *p) { const PJ_XYZ pp{p->x, p->y, 1.0}; for (unsigned char i = 0; i < 23; i++) { if (is_point_in_face(&pp, &pj_data->airocean_faces[i])) { return i; } } return 23; } inline PJ_XY ico_to_dym(const pj_airocean_data *pj_data, const PJ_XYZ *p, unsigned char face_id) { return PJ_XY{ pj_data->ico_air_trans[face_id][0][0] * p->x + // * -1 pj_data->ico_air_trans[face_id][0][1] * p->y + // pj_data->ico_air_trans[face_id][0][2] * p->z + // pj_data->ico_air_trans[face_id][0][3], // +1000 pj_data->ico_air_trans[face_id][1][0] * p->x + pj_data->ico_air_trans[face_id][1][1] * p->y + pj_data->ico_air_trans[face_id][1][2] * p->z + pj_data->ico_air_trans[face_id][1][3], }; } inline PJ_XYZ dym_to_ico(const pj_airocean_data *pj_data, const PJ_XY *p, unsigned char face_id) { return PJ_XYZ{ pj_data->air_ico_trans[face_id][0][0] * p->x + // * -1 pj_data->air_ico_trans[face_id][0][1] * p->y + // pj_data->air_ico_trans[face_id][0][3], // + [face_id][0][0] * 1000 pj_data->air_ico_trans[face_id][1][0] * p->x + pj_data->air_ico_trans[face_id][1][1] * p->y + pj_data->air_ico_trans[face_id][1][3], pj_data->air_ico_trans[face_id][2][0] * p->x + pj_data->air_ico_trans[face_id][2][1] * p->y + pj_data->air_ico_trans[face_id][2][3], }; } inline PJ_XYZ cartesian_to_ico(const pj_airocean_data *pj_data, const PJ_XYZ *p, unsigned char face_id) { const PJ_XYZ *center = &pj_data->ico_centers[face_id]; const PJ_XYZ *normal = &pj_data->ico_normals[face_id]; // cppcheck-suppress unreadVariable double a = 1.0 - (center->x * normal->x + center->y * normal->y + center->z * normal->z) / (p->x * normal->x + p->y * normal->y + p->z * normal->z); return PJ_XYZ{ p->x - a * p->x, p->y - a * p->y, p->z - a * p->z, }; } // ============================================ // // The Forward and Inverse Functions // // ============================================ static PJ_XY airocean_forward(PJ_LP lp, PJ *P) { const struct pj_airocean_data *Q = static_cast(P->opaque); double lat; /* Convert the geodetic latitude to a geocentric latitude. * This corresponds to the shift from the ellipsoid to the sphere * described in [LK12]. */ if (P->es != 0.0) { double one_minus_f = 1.0 - (P->a - P->b) / P->a; double one_minus_f_squared = one_minus_f * one_minus_f; lat = atan(one_minus_f_squared * tan(lp.phi)); } else { lat = lp.phi; } // Convert the lat/long to x,y,z on the unit sphere double x, y, z; double sinlat, coslat; double sinlon, coslon; sinlat = sin(lat); coslat = cos(lat); sinlon = sin(lp.lam); coslon = cos(lp.lam); x = coslat * coslon; y = coslat * sinlon; z = sinlat; PJ_XYZ cartesianPoint{x, y, z}; unsigned char face_id = get_ico_face_index(Q, &cartesianPoint); if (face_id == 23) { // not sure this can happen proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); PJ_XY xy; xy.x = HUGE_VAL; xy.y = HUGE_VAL; return xy; } PJ_XYZ icoPoint = cartesian_to_ico(Q, &cartesianPoint, face_id); PJ_XY airoceanPoint = ico_to_dym(Q, &icoPoint, face_id); return airoceanPoint; } static PJ_LP airocean_inverse(PJ_XY xy, PJ *P) { const struct pj_airocean_data *Q = static_cast(P->opaque); PJ_LP lp = {0.0, 0.0}; unsigned char face_id = get_dym_face_index(Q, &xy); if (face_id == 23) { // Point lies outside icosahedron net faces proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; return lp; } PJ_XYZ sphereCoords = dym_to_ico(Q, &xy, face_id); double norm = sqrt((sphereCoords.x * sphereCoords.x) + (sphereCoords.y * sphereCoords.y) + (sphereCoords.z * sphereCoords.z)); double q = sphereCoords.x / norm; double r = sphereCoords.y / norm; double s = sphereCoords.z / norm; // Get the spherical angles from the x y z lp.phi = acos(-s) - M_HALFPI; lp.lam = atan2(r, q); /* Apply the shift from the sphere to the ellipsoid as described * in [LK12]. */ if (P->es != 0.0) { int invert_sign; volatile double tanphi, xa; invert_sign = (lp.phi < 0.0 ? 1 : 0); tanphi = tan(lp.phi); double one_minus_f = 1.0 - (P->a - P->b) / P->a; double a_squared = P->a * P->a; xa = P->b / sqrt(tanphi * tanphi + one_minus_f * one_minus_f); lp.phi = atan(sqrt(a_squared - xa * xa) / (one_minus_f * xa)); if (invert_sign) { lp.phi = -lp.phi; } } return lp; } PJ *PJ_PROJECTION(airocean) { char *opt; struct pj_airocean_data *Q = static_cast( calloc(1, sizeof(struct pj_airocean_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->initialize(); P->opaque = Q; opt = pj_param(P->ctx, P->params, "sorient").s; if (opt) { if (!strcmp(opt, "horizontal")) { Q->transform(orient_horizontal_trans, orient_horizontal_inv_trans); } else if (!strcmp(opt, "vertical")) { // the orientation is vertical by default. } else { proj_log_error(P, _("Invalid value for orient: only vertical or " "horizontal are supported")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } P->inv = airocean_inverse; P->fwd = airocean_forward; return P; } proj-9.8.1/src/projections/times.cpp000664 001750 001750 00000004676 15166171715 017443 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the Times projection. * Author: Kristian Evers * ****************************************************************************** * Copyright (c) 2016, Kristian Evers * * 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. ***************************************************************************** * Based on description of the Times Projection in * * Flattening the Earth, Snyder, J.P., 1993, p.213-214. *****************************************************************************/ #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(times, "Times") "\n\tCyl, Sph"; static PJ_XY times_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ double T, S, S2; PJ_XY xy = {0.0, 0.0}; (void)P; T = tan(lp.phi / 2.0); S = sin(M_FORTPI * T); S2 = S * S; xy.x = lp.lam * (0.74482 - 0.34588 * S2); xy.y = 1.70711 * T; return xy; } static PJ_LP times_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ double T, S, S2; PJ_LP lp = {0.0, 0.0}; (void)P; T = xy.y / 1.70711; S = sin(M_FORTPI * T); S2 = S * S; lp.lam = xy.x / (0.74482 - 0.34588 * S2); lp.phi = 2 * atan(T); return lp; } PJ *PJ_PROJECTION(times) { P->es = 0.0; P->inv = times_s_inverse; P->fwd = times_s_forward; return P; } proj-9.8.1/src/projections/calcofi.cpp000664 001750 001750 00000012577 15166171715 017721 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(calcofi, "Cal Coop Ocean Fish Invest Lines/Stations") "\n\tCyl, Sph&Ell"; /* Conversions for the California Cooperative Oceanic Fisheries Investigations Line/Station coordinate system following the algorithm of: Eber, L.E., and R.P. Hewitt. 1979. Conversion algorithms for the CalCOFI station grid. California Cooperative Oceanic Fisheries Investigations Reports 20:135-137. (corrected for typographical errors). http://www.calcofi.org/publications/calcofireports/v20/Vol_20_Eber___Hewitt.pdf They assume 1 unit of CalCOFI Line == 1/5 degree in longitude or meridional units at reference point O, and similarly 1 unit of CalCOFI Station == 1/15 of a degree at O. By convention, CalCOFI Line/Station conversions use Clarke 1866 but we use whatever ellipsoid is provided. */ #define EPS10 1.e-10 #define DEG_TO_LINE 5 #define DEG_TO_STATION 15 #define LINE_TO_RAD 0.0034906585039886592 #define STATION_TO_RAD 0.0011635528346628863 #define PT_O_LINE 80 /* reference point O is at line 80, */ #define PT_O_STATION 60 /* station 60, */ #define PT_O_LAMBDA -2.1144663887911301 /* long -121.15 and */ #define PT_O_PHI 0.59602993955606354 /* lat 34.15 */ #define ROTATION_ANGLE 0.52359877559829882 /*CalCOFI angle of 30 deg in rad */ static PJ_XY calcofi_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; double oy; /* pt O y value in Mercator */ double l1; /* l1 and l2 are distances calculated using trig that sum to the east/west distance between point O and point xy */ double l2; double ry; /* r is the point on the same station as o (60) and the same line as xy xy, r, o form a right triangle */ if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = lp.lam; xy.y = -log(pj_tsfn(lp.phi, sin(lp.phi), P->e)); /* Mercator transform xy*/ oy = -log(pj_tsfn(PT_O_PHI, sin(PT_O_PHI), P->e)); l1 = (xy.y - oy) * tan(ROTATION_ANGLE); l2 = -xy.x - l1 + PT_O_LAMBDA; ry = l2 * cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE) + xy.y; ry = pj_phi2(P->ctx, exp(-ry), P->e); /*inverse Mercator*/ xy.x = PT_O_LINE - RAD_TO_DEG * (ry - PT_O_PHI) * DEG_TO_LINE / cos(ROTATION_ANGLE); xy.y = PT_O_STATION + RAD_TO_DEG * (ry - lp.phi) * DEG_TO_STATION / sin(ROTATION_ANGLE); /* set a = 1, x0 = 0, and y0 = 0 so that no further unit adjustments are done */ return xy; } static PJ_XY calcofi_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double oy; double l1; double l2; double ry; if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = lp.lam; xy.y = log(tan(M_FORTPI + .5 * lp.phi)); oy = log(tan(M_FORTPI + .5 * PT_O_PHI)); l1 = (xy.y - oy) * tan(ROTATION_ANGLE); l2 = -xy.x - l1 + PT_O_LAMBDA; ry = l2 * cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE) + xy.y; ry = M_HALFPI - 2. * atan(exp(-ry)); xy.x = PT_O_LINE - RAD_TO_DEG * (ry - PT_O_PHI) * DEG_TO_LINE / cos(ROTATION_ANGLE); xy.y = PT_O_STATION + RAD_TO_DEG * (ry - lp.phi) * DEG_TO_STATION / sin(ROTATION_ANGLE); return xy; } static PJ_LP calcofi_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; double ry; /* y value of point r */ double oymctr; /* Mercator-transformed y value of point O */ double rymctr; /* Mercator-transformed ry */ double xymctr; /* Mercator-transformed xy.y */ double l1; double l2; ry = PT_O_PHI - LINE_TO_RAD * (xy.x - PT_O_LINE) * cos(ROTATION_ANGLE); lp.phi = ry - STATION_TO_RAD * (xy.y - PT_O_STATION) * sin(ROTATION_ANGLE); oymctr = -log(pj_tsfn(PT_O_PHI, sin(PT_O_PHI), P->e)); rymctr = -log(pj_tsfn(ry, sin(ry), P->e)); xymctr = -log(pj_tsfn(lp.phi, sin(lp.phi), P->e)); l1 = (xymctr - oymctr) * tan(ROTATION_ANGLE); l2 = (rymctr - xymctr) / (cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE)); lp.lam = PT_O_LAMBDA - (l1 + l2); return lp; } static PJ_LP calcofi_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double ry; double oymctr; double rymctr; double xymctr; double l1; double l2; (void)P; ry = PT_O_PHI - LINE_TO_RAD * (xy.x - PT_O_LINE) * cos(ROTATION_ANGLE); lp.phi = ry - STATION_TO_RAD * (xy.y - PT_O_STATION) * sin(ROTATION_ANGLE); oymctr = log(tan(M_FORTPI + .5 * PT_O_PHI)); rymctr = log(tan(M_FORTPI + .5 * ry)); xymctr = log(tan(M_FORTPI + .5 * lp.phi)); l1 = (xymctr - oymctr) * tan(ROTATION_ANGLE); l2 = (rymctr - xymctr) / (cos(ROTATION_ANGLE) * sin(ROTATION_ANGLE)); lp.lam = PT_O_LAMBDA - (l1 + l2); return lp; } PJ *PJ_PROJECTION(calcofi) { P->opaque = nullptr; /* if the user has specified +lon_0 or +k0 for some reason, we're going to ignore it so that xy is consistent with point O */ P->lam0 = 0; P->ra = 1; P->a = 1; P->x0 = 0; P->y0 = 0; P->over = 1; if (P->es != 0.0) { /* ellipsoid */ P->inv = calcofi_e_inverse; P->fwd = calcofi_e_forward; } else { /* sphere */ P->inv = calcofi_s_inverse; P->fwd = calcofi_s_forward; } return P; } proj-9.8.1/src/projections/igh_o.cpp000664 001750 001750 00000023255 15166171715 017401 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(igh_o, "Interrupted Goode Homolosine Oceanic View") "\n\tPCyl, Sph"; /* This projection is a variant of the Interrupted Goode Homolosine projection that emphasizes ocean areas. The projection is a compilation of 12 separate sub-projections. Sinusoidal projections are found near the equator and Mollweide projections are found at higher latitudes. The transition between the two occurs at 40 degrees latitude and is represented by `igh_o_phi_boundary`. Each sub-projection is assigned an integer label numbered 1 through 12. Most of this code contains logic to assign the labels based on latitude (phi) and longitude (lam) regions. Original Reference: J. Paul Goode (1925) THE HOMOLOSINE PROJECTION: A NEW DEVICE FOR PORTRAYING THE EARTH'S SURFACE ENTIRE, Annals of the Association of American Geographers, 15:3, 119-125, DOI: 10.1080/00045602509356949 */ C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *); /* Transition from sinusoidal to Mollweide projection Latitude (phi): 40deg 44' 11.8" */ constexpr double igh_o_phi_boundary = (40 + 44 / 60. + 11.8 / 3600.) * DEG_TO_RAD; namespace pj_igh_o_ns { struct pj_igh_o_data { struct PJconsts *pj[12]; double dy0; }; constexpr double d10 = 10 * DEG_TO_RAD; constexpr double d20 = 20 * DEG_TO_RAD; constexpr double d40 = 40 * DEG_TO_RAD; constexpr double d50 = 50 * DEG_TO_RAD; constexpr double d60 = 60 * DEG_TO_RAD; constexpr double d90 = 90 * DEG_TO_RAD; constexpr double d100 = 100 * DEG_TO_RAD; constexpr double d110 = 110 * DEG_TO_RAD; constexpr double d140 = 140 * DEG_TO_RAD; constexpr double d150 = 150 * DEG_TO_RAD; constexpr double d160 = 160 * DEG_TO_RAD; constexpr double d130 = 130 * DEG_TO_RAD; constexpr double d180 = 180 * DEG_TO_RAD; constexpr double EPSLN = 1.e-10; /* allow a little 'slack' on zone edge positions */ } // namespace pj_igh_o_ns /* Assign an integer index representing each of the 12 sub-projection zones based on latitude (phi) and longitude (lam) ranges. */ static PJ_XY igh_o_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ using namespace pj_igh_o_ns; PJ_XY xy; struct pj_igh_o_data *Q = static_cast(P->opaque); int z; if (lp.phi >= igh_o_phi_boundary) { if (lp.lam <= -d90) z = 1; else if (lp.lam >= d60) z = 3; else z = 2; } else if (lp.phi >= 0) { if (lp.lam <= -d90) z = 4; else if (lp.lam >= d60) z = 6; else z = 5; } else if (lp.phi >= -igh_o_phi_boundary) { if (lp.lam <= -d60) z = 7; else if (lp.lam >= d90) z = 9; else z = 8; } else { if (lp.lam <= -d60) z = 10; else if (lp.lam >= d90) z = 12; else z = 11; } lp.lam -= Q->pj[z - 1]->lam0; xy = Q->pj[z - 1]->fwd(lp, Q->pj[z - 1]); xy.x += Q->pj[z - 1]->x0; xy.y += Q->pj[z - 1]->y0; return xy; } static PJ_LP igh_o_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ using namespace pj_igh_o_ns; PJ_LP lp = {0.0, 0.0}; struct pj_igh_o_data *Q = static_cast(P->opaque); const double y90 = Q->dy0 + sqrt(2.0); /* lt=90 corresponds to y=y0+sqrt(2) */ int z = 0; if (xy.y > y90 + EPSLN || xy.y < -y90 + EPSLN) /* 0 */ z = 0; else if (xy.y >= igh_o_phi_boundary) if (xy.x <= -d90) z = 1; else if (xy.x >= d60) z = 3; else z = 2; else if (xy.y >= 0) if (xy.x <= -d90) z = 4; else if (xy.x >= d60) z = 6; else z = 5; else if (xy.y >= -igh_o_phi_boundary) { if (xy.x <= -d60) z = 7; else if (xy.x >= d90) z = 9; else z = 8; } else { if (xy.x <= -d60) z = 10; else if (xy.x >= d90) z = 12; else z = 11; } if (z) { bool ok = false; xy.x -= Q->pj[z - 1]->x0; xy.y -= Q->pj[z - 1]->y0; lp = Q->pj[z - 1]->inv(xy, Q->pj[z - 1]); lp.lam += Q->pj[z - 1]->lam0; switch (z) { /* Plot projectable ranges with exetension lobes in zones 1 & 3 */ case 1: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d90 + EPSLN) || ((lp.lam >= d160 - EPSLN && lp.lam <= d180 + EPSLN) && (lp.phi >= d50 - EPSLN && lp.phi <= d90 + EPSLN)); break; case 2: ok = (lp.lam >= -d90 - EPSLN && lp.lam <= d60 + EPSLN); break; case 3: ok = (lp.lam >= d60 - EPSLN && lp.lam <= d180 + EPSLN) || ((lp.lam >= -d180 - EPSLN && lp.lam <= -d160 + EPSLN) && (lp.phi >= d50 - EPSLN && lp.phi <= d90 + EPSLN)); break; case 4: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d90 + EPSLN); break; case 5: ok = (lp.lam >= -d90 - EPSLN && lp.lam <= d60 + EPSLN); break; case 6: ok = (lp.lam >= d60 - EPSLN && lp.lam <= d180 + EPSLN); break; case 7: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d60 + EPSLN); break; case 8: ok = (lp.lam >= -d60 - EPSLN && lp.lam <= d90 + EPSLN); break; case 9: ok = (lp.lam >= d90 - EPSLN && lp.lam <= d180 + EPSLN); break; case 10: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d60 + EPSLN); break; case 11: ok = (lp.lam >= -d60 - EPSLN && lp.lam <= d90 + EPSLN) || ((lp.lam >= d90 - EPSLN && lp.lam <= d100 + EPSLN) && (lp.phi >= -d90 - EPSLN && lp.phi <= -d40 + EPSLN)); break; case 12: ok = (lp.lam >= d90 - EPSLN && lp.lam <= d180 + EPSLN); break; } z = (!ok ? 0 : z); /* projectable? */ } if (!z) lp.lam = HUGE_VAL; if (!z) lp.phi = HUGE_VAL; return lp; } static PJ *pj_igh_o_destructor(PJ *P, int errlev) { using namespace pj_igh_o_ns; int i; if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); struct pj_igh_o_data *Q = static_cast(P->opaque); for (i = 0; i < 12; ++i) { if (Q->pj[i]) Q->pj[i]->destructor(Q->pj[i], errlev); } return pj_default_destructor(P, errlev); } /* Zones: -180 -90 60 180 +---------+----------------+-------------+ Zones 1,2,3,10,11 & 12: |1 |2 |3 | Mollweide projection | | | | +---------+----------------+-------------+ Zones 4,5,6,7,8 & 9: |4 |5 |6 | Sinusoidal projection | | | | 0 +---------+--+-------------+--+----------+ |7 |8 |9 | | | | | +------------+----------------+----------+ |10 |11 |12 | | | | | +------------+----------------+----------+ -180 -60 90 180 */ static bool pj_igh_o_setup_zone(PJ *P, struct pj_igh_o_ns::pj_igh_o_data *Q, int n, PJ *(*proj_ptr)(PJ *), double x_0, double y_0, double lon_0) { if (!(Q->pj[n - 1] = proj_ptr(nullptr))) return false; if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1]))) return false; Q->pj[n - 1]->ctx = P->ctx; Q->pj[n - 1]->x0 = x_0; Q->pj[n - 1]->y0 = y_0; Q->pj[n - 1]->lam0 = lon_0; return true; } PJ *PJ_PROJECTION(igh_o) { using namespace pj_igh_o_ns; PJ_XY xy1, xy4; PJ_LP lp = {0, igh_o_phi_boundary}; struct pj_igh_o_data *Q = static_cast( calloc(1, sizeof(struct pj_igh_o_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* sinusoidal zones */ if (!pj_igh_o_setup_zone(P, Q, 4, pj_sinu, -d140, 0, -d140) || !pj_igh_o_setup_zone(P, Q, 5, pj_sinu, -d10, 0, -d10) || !pj_igh_o_setup_zone(P, Q, 6, pj_sinu, d130, 0, d130) || !pj_igh_o_setup_zone(P, Q, 7, pj_sinu, -d110, 0, -d110) || !pj_igh_o_setup_zone(P, Q, 8, pj_sinu, d20, 0, d20) || !pj_igh_o_setup_zone(P, Q, 9, pj_sinu, d150, 0, d150)) { return pj_igh_o_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* mollweide zones */ if (!pj_igh_o_setup_zone(P, Q, 1, pj_moll, -d140, 0, -d140)) { return pj_igh_o_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* y0 ? */ xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */ xy4 = Q->pj[3]->fwd(lp, Q->pj[3]); /* zone 4 */ /* y0 + xy1.y = xy4.y for lt = 40d44'11.8" */ Q->dy0 = xy4.y - xy1.y; Q->pj[0]->y0 = Q->dy0; /* mollweide zones (cont'd) */ if (!pj_igh_o_setup_zone(P, Q, 2, pj_moll, -d10, Q->dy0, -d10) || !pj_igh_o_setup_zone(P, Q, 3, pj_moll, d130, Q->dy0, d130) || !pj_igh_o_setup_zone(P, Q, 10, pj_moll, -d110, -Q->dy0, -d110) || !pj_igh_o_setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) || !pj_igh_o_setup_zone(P, Q, 12, pj_moll, d150, -Q->dy0, d150)) { return pj_igh_o_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } P->inv = igh_o_s_inverse; P->fwd = igh_o_s_forward; P->destructor = pj_igh_o_destructor; P->es = 0.; return P; } proj-9.8.1/src/projections/rpoly.cpp000664 001750 001750 00000002612 15166171715 017453 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_rpoly_data { double phi1; double fxa; double fxb; int mode; }; } // anonymous namespace PROJ_HEAD(rpoly, "Rectangular Polyconic") "\n\tConic, Sph, no inv\n\tlat_ts="; #define EPS 1e-9 static PJ_XY rpoly_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_rpoly_data *Q = static_cast(P->opaque); double fa; if (Q->mode) fa = tan(lp.lam * Q->fxb) * Q->fxa; else fa = 0.5 * lp.lam; if (fabs(lp.phi) < EPS) { xy.x = fa + fa; xy.y = -P->phi0; } else { xy.y = 1. / tan(lp.phi); fa = 2. * atan(fa * sin(lp.phi)); xy.x = sin(fa) * xy.y; xy.y = lp.phi - P->phi0 + (1. - cos(fa)) * xy.y; } return xy; } PJ *PJ_PROJECTION(rpoly) { struct pj_rpoly_data *Q = static_cast( calloc(1, sizeof(struct pj_rpoly_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phi1 = fabs(pj_param(P->ctx, P->params, "rlat_ts").f); Q->mode = Q->phi1 > EPS; if (Q->mode) { Q->fxb = 0.5 * sin(Q->phi1); Q->fxa = 0.5 / Q->fxb; } P->es = 0.; P->fwd = rpoly_s_forward; return P; } #undef EPS proj-9.8.1/src/projections/eqc.cpp000664 001750 001750 00000010332 15166171715 017054 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Implementation of the eqc (Equidistant Cylindrical) projection. * Also known as Plate Carree when lat_ts=0. * * This file implements both the spherical (EPSG:1029) and ellipsoidal * (EPSG:1028) forms of the Equidistant Cylindrical projection. * * Spherical formulas (EPSG:1029): * E = FE + R cos(lat_ts) (lon - lon_0) * N = FN + R lat * * Ellipsoidal formulas (EPSG:1028) from IOGP Guidance Note 7-2: * E = FE + nu1 cos(lat_ts) (lon - lon_0) * N = FN + M * * * Reference: IOGP Publication 373-7-2, Section 3.2.5 * *****************************************************************************/ #include #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_eqc_data { double rc; // Spherical: cos(lat_ts); Ellipsoidal: nu1 * cos(lat_ts) double M0; // Meridional arc at latitude of origin (lat_0) double *en; // Coefficients for meridional arc computation }; } // anonymous namespace PROJ_HEAD(eqc, "Equidistant Cylindrical (Plate Carree)") "\n\tCyl, Sph&Ell\n\tlat_ts=[, lat_0=0]"; static PJ_XY eqc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_eqc_data *Q = static_cast(P->opaque); xy.x = Q->rc * lp.lam; xy.y = lp.phi - P->phi0; return xy; } static PJ_LP eqc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_eqc_data *Q = static_cast(P->opaque); lp.lam = xy.x / Q->rc; lp.phi = xy.y + P->phi0; return lp; } // Ellipsoidal forward (EPSG method 1028) // E = FE + nu1 cos(lat_ts) (lon - lon_0) // N = FN + M - M0 // where M is the meridional arc from equator to latitude static PJ_XY eqc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_eqc_data *Q = static_cast(P->opaque); const double sinphi = sin(lp.phi); const double cosphi = cos(lp.phi); xy.x = Q->rc * lp.lam; xy.y = pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->M0; return xy; } // Ellipsoidal inverse (EPSG method 1028) // lon = lon_0 + (E - FE) / (nu1 cos lat_ts) // lat is found by inverting M = N - FN + M0 static PJ_LP eqc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_eqc_data *Q = static_cast(P->opaque); lp.lam = xy.x / Q->rc; lp.phi = pj_inv_mlfn(xy.y + Q->M0, Q->en); return lp; } static PJ *pj_eqc_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(eqc) { struct pj_eqc_data *Q = static_cast( calloc(1, sizeof(struct pj_eqc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_eqc_destructor; const double phi1 = pj_param(P->ctx, P->params, "rlat_ts").f; const double cos_phi1 = cos(phi1); if (cos_phi1 <= 0.) { proj_log_error( P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (P->es != 0.0) { // Ellipsoidal case (EPSG:1028) const double sin_phi1 = sin(phi1); // nu1 = a / sqrt(1 - e^2 sin^2(lat_ts)), normalized by a const double nu1 = 1.0 / sqrt(1.0 - P->es * sin_phi1 * sin_phi1); Q->rc = nu1 * cos_phi1; Q->en = pj_enfn(P->n); if (nullptr == Q->en) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->M0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); P->inv = eqc_e_inverse; P->fwd = eqc_e_forward; } else { // Spheroidal case (EPSG:1029) Q->rc = cos_phi1; Q->en = nullptr; Q->M0 = 0.0; P->inv = eqc_s_inverse; P->fwd = eqc_s_forward; } return P; } proj-9.8.1/src/projections/sts.cpp000664 001750 001750 00000005331 15166171715 017120 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(kav5, "Kavrayskiy V") "\n\tPCyl, Sph"; PROJ_HEAD(qua_aut, "Quartic Authalic") "\n\tPCyl, Sph"; PROJ_HEAD(fouc, "Foucaut") "\n\tPCyl, Sph"; PROJ_HEAD(mbt_s, "McBryde-Thomas Flat-Polar Sine (No. 1)") "\n\tPCyl, Sph"; namespace { // anonymous namespace struct pj_sts { double C_x, C_y, C_p; int tan_mode; }; } // anonymous namespace static PJ_XY sts_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_sts *Q = static_cast(P->opaque); xy.x = Q->C_x * lp.lam * cos(lp.phi); xy.y = Q->C_y; lp.phi *= Q->C_p; const double c = cos(lp.phi); if (Q->tan_mode) { xy.x *= c * c; xy.y *= tan(lp.phi); } else { xy.x /= c; xy.y *= sin(lp.phi); } return xy; } static PJ_LP sts_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_sts *Q = static_cast(P->opaque); xy.y /= Q->C_y; lp.phi = Q->tan_mode ? atan(xy.y) : aasin(P->ctx, xy.y); const double c = cos(lp.phi); lp.phi /= Q->C_p; lp.lam = xy.x / (Q->C_x * cos(lp.phi)); if (Q->tan_mode) lp.lam /= c * c; else lp.lam *= c; return lp; } static PJ *setup(PJ *P, double p, double q, int mode) { P->es = 0.; P->inv = sts_s_inverse; P->fwd = sts_s_forward; static_cast(P->opaque)->C_x = q / p; static_cast(P->opaque)->C_y = p; static_cast(P->opaque)->C_p = 1 / q; static_cast(P->opaque)->tan_mode = mode; return P; } PJ *PJ_PROJECTION(fouc) { struct pj_sts *Q = static_cast(calloc(1, sizeof(struct pj_sts))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 2., 2., 1); } PJ *PJ_PROJECTION(kav5) { struct pj_sts *Q = static_cast(calloc(1, sizeof(struct pj_sts))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 1.50488, 1.35439, 0); } PJ *PJ_PROJECTION(qua_aut) { struct pj_sts *Q = static_cast(calloc(1, sizeof(struct pj_sts))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 2., 2., 0); } PJ *PJ_PROJECTION(mbt_s) { struct pj_sts *Q = static_cast(calloc(1, sizeof(struct pj_sts))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, 1.48875, 1.36509, 0); } proj-9.8.1/src/projections/tobmerc.cpp000664 001750 001750 00000002476 15166171715 017751 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" #include PROJ_HEAD(tobmerc, "Tobler-Mercator") "\n\tCyl, Sph"; static PJ_XY tobmerc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double cosphi; if (fabs(lp.phi) >= M_HALFPI) { // builtins.gie tests "Test expected failure at the poles:". However // given that M_HALFPI is strictly less than pi/2 in double precision, // it's not clear why shouldn't just return a large result for xy.y (and // it's not even that large, merely 38.025...). Even if the logic was // such that phi was strictly equal to pi/2, allowing xy.y = inf would // be a reasonable result. proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } cosphi = cos(lp.phi); xy.x = P->k0 * lp.lam * cosphi * cosphi; xy.y = P->k0 * asinh(tan(lp.phi)); return xy; } static PJ_LP tobmerc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double cosphi; lp.phi = atan(sinh(xy.y / P->k0)); cosphi = cos(lp.phi); lp.lam = xy.x / P->k0 / (cosphi * cosphi); return lp; } PJ *PJ_PROJECTION(tobmerc) { P->inv = tobmerc_s_inverse; P->fwd = tobmerc_s_forward; return P; } proj-9.8.1/src/projections/lcca.cpp000664 001750 001750 00000011340 15166171715 017206 0ustar00eveneven000000 000000 /***************************************************************************** Lambert Conformal Conic Alternative ----------------------------------- This is Gerald Evenden's 2003 implementation of an alternative "almost" LCC, which has been in use historically, but which should NOT be used for new projects - i.e: use this implementation if you need interoperability with old data represented in this projection, but not in any other case. The code was originally discussed on the PROJ.4 mailing list in a thread archived over at http://lists.maptools.org/pipermail/proj/2003-March/000644.html It was discussed again in the thread starting at http://lists.maptools.org/pipermail/proj/2017-October/007828.html and continuing at http://lists.maptools.org/pipermail/proj/2017-November/007831.html which prompted Clifford J. Mugnier to add these clarifying notes: The French Army Truncated Cubic Lambert (partially conformal) Conic projection is the Legal system for the projection in France between the late 1800s and 1948 when the French Legislature changed the law to recognize the fully conformal version. It was (might still be in one or two North African prior French Colonies) used in North Africa in Algeria, Tunisia, & Morocco, as well as in Syria during the Levant. Last time I have seen it used was about 30+ years ago in Algeria when it was used to define Lease Block boundaries for Petroleum Exploration & Production. (signed) Clifford J. Mugnier, c.p., c.m.s. Chief of Geodesy LSU Center for GeoInformatics Dept. of Civil Engineering LOUISIANA STATE UNIVERSITY *****************************************************************************/ #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(lcca, "Lambert Conformal Conic Alternative") "\n\tConic, Sph&Ell\n\tlat_0="; #define MAX_ITER 10 #define DEL_TOL 1e-12 namespace { // anonymous namespace struct pj_lcca_data { double *en; double r0, l, M0; double C; }; } // anonymous namespace static double fS(double S, double C) { /* func to compute dr */ return S * (1. + S * S * C); } static double fSp(double S, double C) { /* deriv of fs */ return 1. + 3. * S * S * C; } static PJ_XY lcca_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_lcca_data *Q = static_cast(P->opaque); double S, r, dr; S = pj_mlfn(lp.phi, sin(lp.phi), cos(lp.phi), Q->en) - Q->M0; dr = fS(S, Q->C); r = Q->r0 - dr; const double lam_mul_l = lp.lam * Q->l; xy.x = P->k0 * (r * sin(lam_mul_l)); xy.y = P->k0 * (Q->r0 - r * cos(lam_mul_l)); return xy; } static PJ_LP lcca_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_lcca_data *Q = static_cast(P->opaque); double theta, dr, S, dif; int i; xy.x /= P->k0; xy.y /= P->k0; theta = atan2(xy.x, Q->r0 - xy.y); dr = xy.y - xy.x * tan(0.5 * theta); lp.lam = theta / Q->l; S = dr; for (i = MAX_ITER; i; --i) { S -= (dif = (fS(S, Q->C) - dr) / fSp(S, Q->C)); if (fabs(dif) < DEL_TOL) break; } if (!i) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = pj_inv_mlfn(S + Q->M0, Q->en); return lp; } static PJ *pj_lcca_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(lcca) { double s2p0, N0, R0, tan0; struct pj_lcca_data *Q = static_cast( calloc(1, sizeof(struct pj_lcca_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; (Q->en = pj_enfn(P->n)); if (!Q->en) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if (P->phi0 == 0.) { proj_log_error( P, _("Invalid value for lat_0: it should be different from 0.")); return pj_lcca_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->l = sin(P->phi0); Q->M0 = pj_mlfn(P->phi0, Q->l, cos(P->phi0), Q->en); s2p0 = Q->l * Q->l; R0 = 1. / (1. - P->es * s2p0); N0 = sqrt(R0); R0 *= P->one_es * N0; tan0 = tan(P->phi0); Q->r0 = N0 / tan0; Q->C = 1. / (6. * R0 * N0); P->inv = lcca_e_inverse; P->fwd = lcca_e_forward; P->destructor = pj_lcca_destructor; return P; } #undef MAX_ITER #undef DEL_TOL proj-9.8.1/src/projections/patterson.cpp000664 001750 001750 00000006537 15166171715 020337 0ustar00eveneven000000 000000 /* * Copyright (c) 2014 Bojan Savric * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * The Patterson Cylindrical projection was designed by Tom Patterson, US * National Park Service, in 2014, using Flex Projector. The polynomial * equations for the projection were developed by Bojan Savric, Oregon State * University, in collaboration with Tom Patterson and Bernhard Jenny, Oregon * State University. * * Java reference algorithm implemented by Bojan Savric in Java Map Projection * Library (a Java port of PROJ.4) in the file PattersonProjection.java. * * References: * Java Map Projection Library * https://github.com/OSUCartography/JMapProjLib * * Patterson Cylindrical Projection * http://shadedrelief.com/patterson/ * * Patterson, T., Savric, B., and Jenny, B. (2015). Cartographic Perspectives * (No.78). Describes the projection design and characteristics, and * developing the equations. doi:10.14714/CP78.1270 * https://doi.org/10.14714/CP78.1270 * * Port to PROJ.4 by Micah Cochran, 26 March 2016 */ #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(patterson, "Patterson Cylindrical") "\n\tCyl"; #define K1 1.0148 #define K2 0.23185 #define K3 -0.14499 #define K4 0.02406 #define C1 K1 #define C2 (5.0 * K2) #define C3 (7.0 * K3) #define C4 (9.0 * K4) #define EPS11 1.0e-11 #define MAX_Y 1.790857183 /* Not sure at all of the appropriate number for MAX_ITER... */ #define MAX_ITER 100 static PJ_XY patterson_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double phi2; (void)P; phi2 = lp.phi * lp.phi; xy.x = lp.lam; xy.y = lp.phi * (K1 + phi2 * phi2 * (K2 + phi2 * (K3 + K4 * phi2))); return xy; } static PJ_LP patterson_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double yc; int i; (void)P; yc = xy.y; /* make sure y is inside valid range */ if (xy.y > MAX_Y) { xy.y = MAX_Y; } else if (xy.y < -MAX_Y) { xy.y = -MAX_Y; } for (i = MAX_ITER; i; --i) { /* Newton-Raphson */ const double y2 = yc * yc; const double f = (yc * (K1 + y2 * y2 * (K2 + y2 * (K3 + K4 * y2)))) - xy.y; const double fder = C1 + y2 * y2 * (C2 + y2 * (C3 + C4 * y2)); const double tol = f / fder; yc -= tol; if (fabs(tol) < EPS11) { break; } } if (i == 0) proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = yc; /* longitude */ lp.lam = xy.x; return lp; } PJ *PJ_PROJECTION(patterson) { P->es = 0.; P->inv = patterson_s_inverse; P->fwd = patterson_s_forward; return P; } #undef K1 #undef K2 #undef K3 #undef K4 #undef C1 #undef C2 #undef C3 #undef C4 #undef EPS11 #undef MAX_Y #undef MAX_ITER proj-9.8.1/src/projections/ocea.cpp000664 001750 001750 00000007775 15166171715 017234 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(ocea, "Oblique Cylindrical Equal Area") "\n\tCyl, Sph" "lonc= alpha= or\n\tlat_1= lat_2= lon_1= lon_2="; namespace { // anonymous namespace struct pj_ocea { double rok; double rtk; double sinphi; double cosphi; }; } // anonymous namespace static PJ_XY ocea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_ocea *Q = static_cast(P->opaque); double t; xy.y = sin(lp.lam); t = cos(lp.lam); xy.x = atan((tan(lp.phi) * Q->cosphi + Q->sinphi * xy.y) / t); if (t < 0.) xy.x += M_PI; xy.x *= Q->rtk; xy.y = Q->rok * (Q->sinphi * sin(lp.phi) - Q->cosphi * cos(lp.phi) * xy.y); return xy; } static PJ_LP ocea_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_ocea *Q = static_cast(P->opaque); xy.y /= Q->rok; xy.x /= Q->rtk; const double t = sqrt(1. - xy.y * xy.y); const double s = sin(xy.x); lp.phi = asin(xy.y * Q->sinphi + t * Q->cosphi * s); lp.lam = atan2(t * Q->sinphi * s - xy.y * Q->cosphi, t * cos(xy.x)); return lp; } PJ *PJ_PROJECTION(ocea) { double phi_1, phi_2, lam_1, lam_2, lonz, alpha; struct pj_ocea *Q = static_cast(calloc(1, sizeof(struct pj_ocea))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->rok = 1. / P->k0; Q->rtk = P->k0; double lam_p, phi_p; /*If the keyword "alpha" is found in the sentence then use 1point+1azimuth*/ if (pj_param(P->ctx, P->params, "talpha").i) { /*Define Pole of oblique transformation from 1 point & 1 azimuth*/ // ERO: I've added M_PI so that the alpha is the angle from point 1 to // point 2 from the North in a clockwise direction (to be consistent // with omerc behavior) alpha = M_PI + pj_param(P->ctx, P->params, "ralpha").f; lonz = pj_param(P->ctx, P->params, "rlonc").f; /*Equation 9-8 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ // Actually slightliy modified to use atan2(), as it is suggested by // Snyder for equation 9-1, but this is not mentioned here lam_p = atan2(-cos(alpha), -sin(P->phi0) * sin(alpha)) + lonz; /*Equation 9-7 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ phi_p = asin(cos(P->phi0) * sin(alpha)); /*If the keyword "alpha" is NOT found in the sentence then use 2points*/ } else { /*Define Pole of oblique transformation from 2 points*/ phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; lam_1 = pj_param(P->ctx, P->params, "rlon_1").f; lam_2 = pj_param(P->ctx, P->params, "rlon_2").f; /*Equation 9-1 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ lam_p = atan2(cos(phi_1) * sin(phi_2) * cos(lam_1) - sin(phi_1) * cos(phi_2) * cos(lam_2), sin(phi_1) * cos(phi_2) * sin(lam_2) - cos(phi_1) * sin(phi_2) * sin(lam_1)); /* take care of P->lam0 wrap-around when +lam_1=-90*/ if (lam_1 == -M_HALFPI) lam_p = -lam_p; /*Equation 9-2 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/ double cos_lamp_m_minus_lam_1 = cos(lam_p - lam_1); double tan_phi_1 = tan(phi_1); if (tan_phi_1 == 0.0) { // Not sure if we want to support this case, but at least this // avoids a division by zero, and gives the same result as the below // atan() phi_p = (cos_lamp_m_minus_lam_1 >= 0.0) ? -M_HALFPI : M_HALFPI; } else { phi_p = atan(-cos_lamp_m_minus_lam_1 / tan_phi_1); } } P->lam0 = lam_p + M_HALFPI; Q->cosphi = cos(phi_p); Q->sinphi = sin(phi_p); P->inv = ocea_s_inverse; P->fwd = ocea_s_forward; P->es = 0.; return P; } proj-9.8.1/src/projections/imoll.cpp000664 001750 001750 00000021331 15166171715 017421 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(imoll, "Interrupted Mollweide") "\n\tPCyl, Sph"; /* This projection is a compilation of 6 separate sub-projections. It is related to the Interrupted Goode Homolosine projection, but uses Mollweide at all latitudes. Each sub-projection is assigned an integer label numbered 1 through 6. Most of this code contains logic to assign the labels based on latitude (phi) and longitude (lam) regions. The code is adapted from igh.cpp. Original Reference: J. Paul Goode (1919) STUDIES IN PROJECTIONS: ADAPTING THE HOMOLOGRAPHIC PROJECTION TO THE PORTRAYAL OF THE EARTH'S ENTIRE SURFACE, Bul. Geog. SOC.Phila., Vol. XWIJNO.3. July, 1919, pp. 103-113. */ C_NAMESPACE PJ *pj_moll(PJ *); namespace pj_imoll_ns { struct pj_imoll_data { struct PJconsts *pj[6]; // We need to know the inverse boundary locations of the "seams". double boundary12; double boundary34; double boundary45; double boundary56; }; constexpr double d20 = 20 * DEG_TO_RAD; constexpr double d30 = 30 * DEG_TO_RAD; constexpr double d40 = 40 * DEG_TO_RAD; constexpr double d60 = 60 * DEG_TO_RAD; constexpr double d80 = 80 * DEG_TO_RAD; constexpr double d100 = 100 * DEG_TO_RAD; constexpr double d140 = 140 * DEG_TO_RAD; constexpr double d160 = 160 * DEG_TO_RAD; constexpr double d180 = 180 * DEG_TO_RAD; constexpr double EPSLN = 1.e-10; /* allow a little 'slack' on zone edge positions */ } // namespace pj_imoll_ns /* Assign an integer index representing each of the 6 sub-projection zones based on latitude (phi) and longitude (lam) ranges. */ static PJ_XY imoll_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ using namespace pj_imoll_ns; PJ_XY xy; struct pj_imoll_data *Q = static_cast(P->opaque); int z; if (lp.phi >= 0) { /* 1|2 */ z = (lp.lam <= -d40 ? 1 : 2); } else { /* 3|4|5|6 */ if (lp.lam <= -d100) z = 3; /* 3 */ else if (lp.lam <= -d20) z = 4; /* 4 */ else if (lp.lam <= d80) z = 5; /* 5 */ else z = 6; /* 6 */ } lp.lam -= Q->pj[z - 1]->lam0; xy = Q->pj[z - 1]->fwd(lp, Q->pj[z - 1]); xy.x += Q->pj[z - 1]->x0; xy.y += Q->pj[z - 1]->y0; return xy; } static PJ_LP imoll_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ using namespace pj_imoll_ns; PJ_LP lp = {0.0, 0.0}; struct pj_imoll_data *Q = static_cast(P->opaque); const double y90 = sqrt(2.0); /* lt=90 corresponds to y=sqrt(2) */ int z = 0; if (xy.y > y90 + EPSLN || xy.y < -y90 + EPSLN) /* 0 */ z = 0; else if (xy.y >= 0) z = (xy.x <= (Q->boundary12) ? 1 : 2); /* 1|2 */ else { if (xy.x <= Q->boundary34) z = 3; /* 3 */ else if (xy.x <= Q->boundary45) z = 4; /* 4 */ else if (xy.x <= Q->boundary56) z = 5; /* 5 */ else z = 6; /* 6 */ } if (z) { bool ok = false; xy.x -= Q->pj[z - 1]->x0; xy.y -= Q->pj[z - 1]->y0; lp = Q->pj[z - 1]->inv(xy, Q->pj[z - 1]); lp.lam += Q->pj[z - 1]->lam0; switch (z) { case 1: ok = ((lp.lam >= -d180 - EPSLN && lp.lam <= -d40 + EPSLN) && (lp.phi >= 0.0 - EPSLN)); break; case 2: ok = ((lp.lam >= -d40 - EPSLN && lp.lam <= d180 + EPSLN) && (lp.phi >= 0.0 - EPSLN)); break; case 3: ok = ((lp.lam >= -d180 - EPSLN && lp.lam <= -d100 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; case 4: ok = ((lp.lam >= -d100 - EPSLN && lp.lam <= -d20 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; case 5: ok = ((lp.lam >= -d20 - EPSLN && lp.lam <= d80 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; case 6: ok = ((lp.lam >= d80 - EPSLN && lp.lam <= d180 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; } z = (!ok ? 0 : z); /* projectable? */ } if (!z) lp.lam = HUGE_VAL; if (!z) lp.phi = HUGE_VAL; return lp; } static PJ *pj_imoll_destructor(PJ *P, int errlev) { using namespace pj_imoll_ns; int i; if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); struct pj_imoll_data *Q = static_cast(P->opaque); for (i = 0; i < 6; ++i) { if (Q->pj[i]) Q->pj[i]->destructor(Q->pj[i], errlev); } return pj_default_destructor(P, errlev); } /* Zones: -180 -40 180 +--------------+-------------------------+ |1 |2 | | | | | | | | | | | | | 0 +-------+------+-+-----------+-----------+ |3 |4 |5 |6 | | | | | | | | | | | | | | | | | | | | | +-------+--------+-----------+-----------+ -180 -100 -20 80 180 */ static bool setup_zone(PJ *P, struct pj_imoll_ns::pj_imoll_data *Q, int n, PJ *(*proj_ptr)(PJ *), double x_0, double y_0, double lon_0) { if (!(Q->pj[n - 1] = proj_ptr(nullptr))) return false; if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1]))) return false; Q->pj[n - 1]->ctx = P->ctx; Q->pj[n - 1]->x0 = x_0; Q->pj[n - 1]->y0 = y_0; Q->pj[n - 1]->lam0 = lon_0; return true; } static double compute_zone_offset(struct pj_imoll_ns::pj_imoll_data *Q, int zone1, int zone2, double lam, double phi1, double phi2) { PJ_LP lp1, lp2; PJ_XY xy1, xy2; lp1.lam = lam - (Q->pj[zone1 - 1]->lam0); lp1.phi = phi1; lp2.lam = lam - (Q->pj[zone2 - 1]->lam0); lp2.phi = phi2; xy1 = Q->pj[zone1 - 1]->fwd(lp1, Q->pj[zone1 - 1]); xy2 = Q->pj[zone2 - 1]->fwd(lp2, Q->pj[zone2 - 1]); return (xy2.x + Q->pj[zone2 - 1]->x0) - (xy1.x + Q->pj[zone1 - 1]->x0); } static double compute_zone_x_boundary(PJ *P, double lam, double phi) { PJ_LP lp1, lp2; PJ_XY xy1, xy2; lp1.lam = lam - pj_imoll_ns::EPSLN; lp1.phi = phi; lp2.lam = lam + pj_imoll_ns::EPSLN; lp2.phi = phi; xy1 = imoll_s_forward(lp1, P); xy2 = imoll_s_forward(lp2, P); return (xy1.x + xy2.x) / 2.; } PJ *PJ_PROJECTION(imoll) { using namespace pj_imoll_ns; struct pj_imoll_data *Q = static_cast( calloc(1, sizeof(struct pj_imoll_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* Setup zones */ if (!setup_zone(P, Q, 1, pj_moll, -d100, 0, -d100) || !setup_zone(P, Q, 2, pj_moll, d30, 0, d30) || !setup_zone(P, Q, 3, pj_moll, -d160, 0, -d160) || !setup_zone(P, Q, 4, pj_moll, -d60, 0, -d60) || !setup_zone(P, Q, 5, pj_moll, d20, 0, d20) || !setup_zone(P, Q, 6, pj_moll, d140, 0, d140)) { return pj_imoll_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* Adjust zones */ /* Match 3 (south) to 1 (north) */ Q->pj[2]->x0 += compute_zone_offset(Q, 3, 1, -d160, 0.0 - EPSLN, 0.0 + EPSLN); /* Match 2 (north-east) to 1 (north-west) */ Q->pj[1]->x0 += compute_zone_offset(Q, 2, 1, -d40, 0.0 + EPSLN, 0.0 + EPSLN); /* Match 4 (south) to 1 (north) */ Q->pj[3]->x0 += compute_zone_offset(Q, 4, 1, -d100, 0.0 - EPSLN, 0.0 + EPSLN); /* Match 5 (south) to 2 (north) */ Q->pj[4]->x0 += compute_zone_offset(Q, 5, 2, -d20, 0.0 - EPSLN, 0.0 + EPSLN); /* Match 6 (south) to 2 (north) */ Q->pj[5]->x0 += compute_zone_offset(Q, 6, 2, d80, 0.0 - EPSLN, 0.0 + EPSLN); /* The most straightforward way of computing the x locations of the "seams" in the interrupted projection is to compute the forward transform at the seams and record these values. */ Q->boundary12 = compute_zone_x_boundary(P, -d40, 0.0 + EPSLN); Q->boundary34 = compute_zone_x_boundary(P, -d100, 0.0 - EPSLN); Q->boundary45 = compute_zone_x_boundary(P, -d20, 0.0 - EPSLN); Q->boundary56 = compute_zone_x_boundary(P, d80, 0.0 - EPSLN); P->inv = imoll_s_inverse; P->fwd = imoll_s_forward; P->destructor = pj_imoll_destructor; P->es = 0.; return P; } proj-9.8.1/src/projections/natearth2.cpp000664 001750 001750 00000005110 15166171715 020172 0ustar00eveneven000000 000000 /* The Natural Earth II projection was designed by Tom Patterson, US National Park Service, in 2012, using Flex Projector. The polynomial equation was developed by Bojan Savric and Bernhard Jenny, College of Earth, Ocean, and Atmospheric Sciences, Oregon State University. Port to PROJ.4 by Bojan Savric, 4 April 2016 */ #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(natearth2, "Natural Earth 2") "\n\tPCyl, Sph"; #define A0 0.84719 #define A1 -0.13063 #define A2 -0.04515 #define A3 0.05494 #define A4 -0.02326 #define A5 0.00331 #define B0 1.01183 #define B1 -0.02625 #define B2 0.01926 #define B3 -0.00396 #define C0 B0 #define C1 (9 * B1) #define C2 (11 * B2) #define C3 (13 * B3) #define EPS 1e-11 #define MAX_Y (0.84719 * 0.535117535153096 * M_PI) /* Not sure at all of the appropriate number for MAX_ITER... */ #define MAX_ITER 100 static PJ_XY natearth2_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double phi2, phi4, phi6; (void)P; phi2 = lp.phi * lp.phi; phi4 = phi2 * phi2; phi6 = phi2 * phi4; xy.x = lp.lam * (A0 + A1 * phi2 + phi6 * phi6 * (A2 + A3 * phi2 + A4 * phi4 + A5 * phi6)); xy.y = lp.phi * (B0 + phi4 * phi4 * (B1 + B2 * phi2 + B3 * phi4)); return xy; } static PJ_LP natearth2_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double yc, y2, y4, y6, f, fder; int i; (void)P; /* make sure y is inside valid range */ if (xy.y > MAX_Y) { xy.y = MAX_Y; } else if (xy.y < -MAX_Y) { xy.y = -MAX_Y; } /* latitude */ yc = xy.y; for (i = MAX_ITER; i; --i) { /* Newton-Raphson */ y2 = yc * yc; y4 = y2 * y2; f = (yc * (B0 + y4 * y4 * (B1 + B2 * y2 + B3 * y4))) - xy.y; fder = C0 + y4 * y4 * (C1 + C2 * y2 + C3 * y4); const double tol = f / fder; yc -= tol; if (fabs(tol) < EPS) { break; } } if (i == 0) proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = yc; /* longitude */ y2 = yc * yc; y4 = y2 * y2; y6 = y2 * y4; lp.lam = xy.x / (A0 + A1 * y2 + y6 * y6 * (A2 + A3 * y2 + A4 * y4 + A5 * y6)); return lp; } PJ *PJ_PROJECTION(natearth2) { P->es = 0; P->inv = natearth2_s_inverse; P->fwd = natearth2_s_forward; return P; } #undef A0 #undef A1 #undef A2 #undef A3 #undef A4 #undef A5 #undef B0 #undef B1 #undef B2 #undef B3 #undef C0 #undef C1 #undef C2 #undef C3 #undef EPS #undef MAX_Y #undef MAX_ITER proj-9.8.1/src/projections/imoll_o.cpp000664 001750 001750 00000022400 15166171715 017735 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(imoll_o, "Interrupted Mollweide Oceanic View") "\n\tPCyl, Sph"; /* This projection is a variant of the Interrupted Mollweide projection that emphasizes ocean areas. This projection is a compilation of 6 separate sub-projections. It is related to the Interrupted Goode Homolosine projection, but uses Mollweide at all latitudes. Each sub-projection is assigned an integer label numbered 1 through 6. Most of this code contains logic to assign the labels based on latitude (phi) and longitude (lam) regions. The code is adapted from igh.cpp. Original Reference: J. Paul Goode (1919) STUDIES IN PROJECTIONS: ADAPTING THE HOMOLOGRAPHIC PROJECTION TO THE PORTRAYAL OF THE EARTH'S ENTIRE SURFACE, Bul. Geog. SOC.Phila., Vol. XWIJNO.3. July, 1919, pp. 103-113. */ C_NAMESPACE PJ *pj_moll(PJ *); namespace pj_imoll_o_ns { struct pj_imoll_o_data { struct PJconsts *pj[6]; // We need to know the inverse boundary locations of the "seams". double boundary12; double boundary23; double boundary45; double boundary56; }; /* SIMPLIFY THIS */ constexpr double d10 = 10 * DEG_TO_RAD; constexpr double d20 = 20 * DEG_TO_RAD; constexpr double d60 = 60 * DEG_TO_RAD; constexpr double d90 = 90 * DEG_TO_RAD; constexpr double d110 = 110 * DEG_TO_RAD; constexpr double d130 = 130 * DEG_TO_RAD; constexpr double d140 = 140 * DEG_TO_RAD; constexpr double d150 = 150 * DEG_TO_RAD; constexpr double d180 = 180 * DEG_TO_RAD; constexpr double EPSLN = 1.e-10; /* allow a little 'slack' on zone edge positions */ } // namespace pj_imoll_o_ns /* Assign an integer index representing each of the 6 sub-projection zones based on latitude (phi) and longitude (lam) ranges. */ static PJ_XY imoll_o_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ using namespace pj_imoll_o_ns; PJ_XY xy; struct pj_imoll_o_data *Q = static_cast(P->opaque); int z; if (lp.phi >= 0) { /* 1|2|3 */ if (lp.lam <= -d90) z = 1; else if (lp.lam >= d60) z = 3; else z = 2; } else { if (lp.lam <= -d60) z = 4; else if (lp.lam >= d90) z = 6; else z = 5; } lp.lam -= Q->pj[z - 1]->lam0; xy = Q->pj[z - 1]->fwd(lp, Q->pj[z - 1]); xy.x += Q->pj[z - 1]->x0; xy.y += Q->pj[z - 1]->y0; return xy; } static PJ_LP imoll_o_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ using namespace pj_imoll_o_ns; PJ_LP lp = {0.0, 0.0}; struct pj_imoll_o_data *Q = static_cast(P->opaque); const double y90 = sqrt(2.0); /* lt=90 corresponds to y=sqrt(2) */ int z = 0; if (xy.y > y90 + EPSLN || xy.y < -y90 + EPSLN) /* 0 */ z = 0; else if (xy.y >= 0) { if (xy.x <= Q->boundary12) z = 1; else if (xy.x >= Q->boundary23) z = 3; else z = 2; } else { if (xy.x <= Q->boundary45) z = 4; else if (xy.x >= Q->boundary56) z = 6; else z = 5; } if (z) { bool ok = false; xy.x -= Q->pj[z - 1]->x0; xy.y -= Q->pj[z - 1]->y0; lp = Q->pj[z - 1]->inv(xy, Q->pj[z - 1]); lp.lam += Q->pj[z - 1]->lam0; switch (z) { case 1: ok = ((lp.lam >= -d180 - EPSLN && lp.lam <= -d90 + EPSLN) && (lp.phi >= 0.0 - EPSLN)); break; case 2: ok = ((lp.lam >= -d90 - EPSLN && lp.lam <= d60 + EPSLN) && (lp.phi >= 0.0 - EPSLN)); break; case 3: ok = ((lp.lam >= d60 - EPSLN && lp.lam <= d180 + EPSLN) && (lp.phi >= 0.0 - EPSLN)); break; case 4: ok = ((lp.lam >= -d180 - EPSLN && lp.lam <= -d60 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; case 5: ok = ((lp.lam >= -d60 - EPSLN && lp.lam <= d90 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; case 6: ok = ((lp.lam >= d90 - EPSLN && lp.lam <= d180 + EPSLN) && (lp.phi <= 0.0 + EPSLN)); break; } z = (!ok ? 0 : z); /* projectable? */ } if (!z) lp.lam = HUGE_VAL; if (!z) lp.phi = HUGE_VAL; return lp; } static PJ *pj_imoll_o_destructor(PJ *P, int errlev) { using namespace pj_imoll_o_ns; int i; if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); struct pj_imoll_o_data *Q = static_cast(P->opaque); for (i = 0; i < 6; ++i) { if (Q->pj[i]) Q->pj[i]->destructor(Q->pj[i], errlev); } return pj_default_destructor(P, errlev); } /* Zones: -180 -90 60 180 +---------+----------------+-------------+ |1 |2 |3 | | | | | | | | | | | | | | | | | 0 +---------+--+-------------+--+----------+ |4 |5 |6 | | | | | | | | | | | | | | | | | +------------+----------------+----------+ -180 -60 90 180 */ static bool pj_imoll_o_setup_zone(PJ *P, struct pj_imoll_o_ns::pj_imoll_o_data *Q, int n, PJ *(*proj_ptr)(PJ *), double x_0, double y_0, double lon_0) { if (!(Q->pj[n - 1] = proj_ptr(nullptr))) return false; if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1]))) return false; Q->pj[n - 1]->ctx = P->ctx; Q->pj[n - 1]->x0 = x_0; Q->pj[n - 1]->y0 = y_0; Q->pj[n - 1]->lam0 = lon_0; return true; } static double pj_imoll_o_compute_zone_offset(struct pj_imoll_o_ns::pj_imoll_o_data *Q, int zone1, int zone2, double lam, double phi1, double phi2) { PJ_LP lp1, lp2; PJ_XY xy1, xy2; lp1.lam = lam - (Q->pj[zone1 - 1]->lam0); lp1.phi = phi1; lp2.lam = lam - (Q->pj[zone2 - 1]->lam0); lp2.phi = phi2; xy1 = Q->pj[zone1 - 1]->fwd(lp1, Q->pj[zone1 - 1]); xy2 = Q->pj[zone2 - 1]->fwd(lp2, Q->pj[zone2 - 1]); return (xy2.x + Q->pj[zone2 - 1]->x0) - (xy1.x + Q->pj[zone1 - 1]->x0); } static double pj_imoll_o_compute_zone_x_boundary(PJ *P, double lam, double phi) { PJ_LP lp1, lp2; PJ_XY xy1, xy2; lp1.lam = lam - pj_imoll_o_ns::EPSLN; lp1.phi = phi; lp2.lam = lam + pj_imoll_o_ns::EPSLN; lp2.phi = phi; xy1 = imoll_o_s_forward(lp1, P); xy2 = imoll_o_s_forward(lp2, P); return (xy1.x + xy2.x) / 2.; } PJ *PJ_PROJECTION(imoll_o) { using namespace pj_imoll_o_ns; struct pj_imoll_o_data *Q = static_cast( calloc(1, sizeof(struct pj_imoll_o_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* Setup zones */ if (!pj_imoll_o_setup_zone(P, Q, 1, pj_moll, -d140, 0, -d140) || !pj_imoll_o_setup_zone(P, Q, 2, pj_moll, -d10, 0, -d10) || !pj_imoll_o_setup_zone(P, Q, 3, pj_moll, d130, 0, d130) || !pj_imoll_o_setup_zone(P, Q, 4, pj_moll, -d110, 0, -d110) || !pj_imoll_o_setup_zone(P, Q, 5, pj_moll, d20, 0, d20) || !pj_imoll_o_setup_zone(P, Q, 6, pj_moll, d150, 0, d150)) { return pj_imoll_o_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* Adjust zones */ /* Match 2 (center) to 1 (west) */ Q->pj[1]->x0 += pj_imoll_o_compute_zone_offset(Q, 2, 1, -d90, 0.0 + EPSLN, 0.0 + EPSLN); /* Match 3 (east) to 2 (center) */ Q->pj[2]->x0 += pj_imoll_o_compute_zone_offset(Q, 3, 2, d60, 0.0 + EPSLN, 0.0 + EPSLN); /* Match 4 (south) to 1 (north) */ Q->pj[3]->x0 += pj_imoll_o_compute_zone_offset(Q, 4, 1, -d180, 0.0 - EPSLN, 0.0 + EPSLN); /* Match 5 (south) to 2 (north) */ Q->pj[4]->x0 += pj_imoll_o_compute_zone_offset(Q, 5, 2, -d60, 0.0 - EPSLN, 0.0 + EPSLN); /* Match 6 (south) to 3 (north) */ Q->pj[5]->x0 += pj_imoll_o_compute_zone_offset(Q, 6, 3, d90, 0.0 - EPSLN, 0.0 + EPSLN); /* The most straightforward way of computing the x locations of the "seams" in the interrupted projection is to compute the forward transform at the seams and record these values. */ Q->boundary12 = pj_imoll_o_compute_zone_x_boundary(P, -d90, 0.0 + EPSLN); Q->boundary23 = pj_imoll_o_compute_zone_x_boundary(P, d60, 0.0 + EPSLN); Q->boundary45 = pj_imoll_o_compute_zone_x_boundary(P, -d60, 0.0 - EPSLN); Q->boundary56 = pj_imoll_o_compute_zone_x_boundary(P, d90, 0.0 - EPSLN); P->inv = imoll_o_s_inverse; P->fwd = imoll_o_s_forward; P->destructor = pj_imoll_o_destructor; P->es = 0.; return P; } proj-9.8.1/src/projections/lask.cpp000664 001750 001750 00000001411 15166171715 017234 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" PROJ_HEAD(lask, "Laskowski") "\n\tMisc Sph, no inv"; #define a10 0.975534 #define a12 -0.119161 #define a32 -0.0143059 #define a14 -0.0547009 #define b01 1.00384 #define b21 0.0802894 #define b03 0.0998909 #define b41 0.000199025 #define b23 -0.0285500 #define b05 -0.0491032 static PJ_XY lask_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double l2, p2; (void)P; l2 = lp.lam * lp.lam; p2 = lp.phi * lp.phi; xy.x = lp.lam * (a10 + p2 * (a12 + l2 * a32 + p2 * a14)); xy.y = lp.phi * (b01 + l2 * (b21 + p2 * b23 + l2 * b41) + p2 * (b03 + p2 * b05)); return xy; } PJ *PJ_PROJECTION(lask) { P->fwd = lask_s_forward; P->es = 0.; return P; } proj-9.8.1/src/projections/eqearth.cpp000664 001750 001750 00000011010 15166171715 017727 0ustar00eveneven000000 000000 /* Equal Earth is a projection inspired by the Robinson projection, but unlike the Robinson projection retains the relative size of areas. The projection was designed in 2018 by Bojan Savric, Tom Patterson and Bernhard Jenny. Publication: Bojan Savric, Tom Patterson & Bernhard Jenny (2018). The Equal Earth map projection, International Journal of Geographical Information Science, DOI: 10.1080/13658816.2018.1504949 Port to PROJ by Juernjakob Dugge, 16 August 2018 Added ellipsoidal equations by Bojan Savric, 22 August 2018 */ #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(eqearth, "Equal Earth") "\n\tPCyl, Sph&Ell"; /* A1..A4, polynomial coefficients */ #define A1 1.340264 #define A2 -0.081106 #define A3 0.000893 #define A4 0.003796 #define M (sqrt(3.0) / 2.0) #define MAX_Y 1.3173627591574 /* 90° latitude on a sphere with radius 1 */ #define EPS 1e-11 #define MAX_ITER 12 namespace { // anonymous namespace struct pj_eqearth { double qp; double rqda; double *apa; }; } // anonymous namespace static PJ_XY eqearth_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal/spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_eqearth *Q = static_cast(P->opaque); double sbeta; double psi, psi2, psi6; /* Spheroidal case, using sine latitude */ sbeta = sin(lp.phi); /* In the ellipsoidal case, we convert sbeta to sine of authalic latitude */ if (P->es != 0.0) { sbeta = pj_authalic_lat_q(sbeta, P) / Q->qp; /* Rounding error. */ if (fabs(sbeta) > 1) sbeta = sbeta > 0 ? 1 : -1; } /* Equal Earth projection */ psi = asin(M * sbeta); psi2 = psi * psi; psi6 = psi2 * psi2 * psi2; xy.x = lp.lam * cos(psi) / (M * (A1 + 3 * A2 * psi2 + psi6 * (7 * A3 + 9 * A4 * psi2))); xy.y = psi * (A1 + A2 * psi2 + psi6 * (A3 + A4 * psi2)); /* Adjusting x and y for authalic radius */ xy.x *= Q->rqda; xy.y *= Q->rqda; return xy; } static PJ_LP eqearth_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal/spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_eqearth *Q = static_cast(P->opaque); double yc, y2, y6; int i; /* Adjusting x and y for authalic radius */ xy.x /= Q->rqda; xy.y /= Q->rqda; /* Make sure y is inside valid range */ if (xy.y > MAX_Y) xy.y = MAX_Y; else if (xy.y < -MAX_Y) xy.y = -MAX_Y; yc = xy.y; /* Newton-Raphson */ for (i = MAX_ITER; i; --i) { double f, fder, tol; y2 = yc * yc; y6 = y2 * y2 * y2; f = yc * (A1 + A2 * y2 + y6 * (A3 + A4 * y2)) - xy.y; fder = A1 + 3 * A2 * y2 + y6 * (7 * A3 + 9 * A4 * y2); tol = f / fder; yc -= tol; if (fabs(tol) < EPS) break; } if (i == 0) { proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } /* Longitude */ y2 = yc * yc; y6 = y2 * y2 * y2; lp.lam = M * xy.x * (A1 + 3 * A2 * y2 + y6 * (7 * A3 + 9 * A4 * y2)) / cos(yc); /* Latitude (for spheroidal case, this is latitude */ lp.phi = asin(sin(yc) / M); /* Ellipsoidal case, converting auth. latitude */ if (P->es != 0.0) lp.phi = pj_authalic_lat_inverse(lp.phi, Q->apa, P, Q->qp); return lp; } static PJ *destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->apa); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(eqearth) { struct pj_eqearth *Q = static_cast(calloc(1, sizeof(struct pj_eqearth))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; P->fwd = eqearth_e_forward; P->inv = eqearth_e_inverse; Q->rqda = 1.0; /* Ellipsoidal case */ if (P->es != 0.0) { Q->apa = pj_authalic_lat_compute_coeffs(P->n); /* For auth_lat(). */ if (nullptr == Q->apa) return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_authalic_lat_q(1.0, P); /* For auth_lat(). */ Q->rqda = sqrt(0.5 * Q->qp); /* Authalic radius divided by major axis */ } return P; } #undef A1 #undef A2 #undef A3 #undef A4 #undef M #undef MAX_Y #undef EPS #undef MAX_ITER proj-9.8.1/src/projections/vandg4.cpp000664 001750 001750 00000002663 15166171715 017477 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(vandg4, "van der Grinten IV") "\n\tMisc Sph, no inv"; #define TOL 1e-10 static PJ_XY vandg4_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double x1, t, bt, ct, ft, bt2, ct2, dt, dt2; (void)P; if (fabs(lp.phi) < TOL) { xy.x = lp.lam; xy.y = 0.; } else if (fabs(lp.lam) < TOL || fabs(fabs(lp.phi) - M_HALFPI) < TOL) { xy.x = 0.; xy.y = lp.phi; } else { bt = fabs(M_TWO_D_PI * lp.phi); bt2 = bt * bt; ct = 0.5 * (bt * (8. - bt * (2. + bt2)) - 5.) / (bt2 * (bt - 1.)); ct2 = ct * ct; dt = M_TWO_D_PI * lp.lam; dt = dt + 1. / dt; dt = sqrt(dt * dt - 4.); if ((fabs(lp.lam) - M_HALFPI) < 0.) dt = -dt; dt2 = dt * dt; x1 = bt + ct; x1 *= x1; t = bt + 3. * ct; ft = x1 * (bt2 + ct2 * dt2 - 1.) + (1. - bt2) * (bt2 * (t * t + 4. * ct2) + ct2 * (12. * bt * ct + 4. * ct2)); x1 = (dt * (x1 + ct2 - 1.) + 2. * sqrt(ft)) / (4. * x1 + dt2); xy.x = M_HALFPI * x1; xy.y = M_HALFPI * sqrt(1. + dt * fabs(x1) - x1 * x1); if (lp.lam < 0.) xy.x = -xy.x; if (lp.phi < 0.) xy.y = -xy.y; } return xy; } PJ *PJ_PROJECTION(vandg4) { P->es = 0.; P->fwd = vandg4_s_forward; return P; } proj-9.8.1/src/projections/imw_p.cpp000664 001750 001750 00000015525 15166171715 017430 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(imw_p, "International Map of the World Polyconic") "\n\tMod. Polyconic, Ell\n\tlat_1= and lat_2= [lon_1=]"; #define TOL 1e-10 #define EPS 1e-10 namespace { // anonymous namespace enum Mode { NONE_IS_ZERO = 0, /* phi_1 and phi_2 != 0 */ PHI_1_IS_ZERO = 1, /* phi_1 = 0 */ PHI_2_IS_ZERO = -1 /* phi_2 = 0 */ }; } // anonymous namespace namespace { // anonymous namespace struct pj_imw_p_data { double P, Pp, Q, Qp, R_1, R_2, sphi_1, sphi_2, C2; double phi_1, phi_2, lam_1; double *en; enum Mode mode; }; } // anonymous namespace static int phi12(PJ *P, double *del, double *sig) { struct pj_imw_p_data *Q = static_cast(P->opaque); int err = 0; if (!pj_param(P->ctx, P->params, "tlat_1").i) { proj_log_error(P, _("Missing parameter: lat_1 should be specified")); err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } else if (!pj_param(P->ctx, P->params, "tlat_2").i) { proj_log_error(P, _("Missing parameter: lat_2 should be specified")); err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } else { Q->phi_1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->phi_2 = pj_param(P->ctx, P->params, "rlat_2").f; *del = 0.5 * (Q->phi_2 - Q->phi_1); *sig = 0.5 * (Q->phi_2 + Q->phi_1); err = (fabs(*del) < EPS || fabs(*sig) < EPS) ? PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE : 0; if (err) { proj_log_error( P, _("Illegal value for lat_1 and lat_2: |lat_1 - lat_2| " "and |lat_1 + lat_2| should be > 0")); } } return err; } static PJ_XY loc_for(PJ_LP lp, PJ *P, double *yc) { struct pj_imw_p_data *Q = static_cast(P->opaque); PJ_XY xy; if (lp.phi == 0.0) { xy.x = lp.lam; xy.y = 0.; } else { double xa, ya, xb, yb, xc, D, B, m, sp, t, R, C; sp = sin(lp.phi); m = pj_mlfn(lp.phi, sp, cos(lp.phi), Q->en); xa = Q->Pp + Q->Qp * m; ya = Q->P + Q->Q * m; R = 1. / (tan(lp.phi) * sqrt(1. - P->es * sp * sp)); C = sqrt(R * R - xa * xa); if (lp.phi < 0.) C = -C; C += ya - R; if (Q->mode == PHI_2_IS_ZERO) { xb = lp.lam; yb = Q->C2; } else { t = lp.lam * Q->sphi_2; xb = Q->R_2 * sin(t); yb = Q->C2 + Q->R_2 * (1. - cos(t)); } if (Q->mode == PHI_1_IS_ZERO) { xc = lp.lam; *yc = 0.; } else { t = lp.lam * Q->sphi_1; xc = Q->R_1 * sin(t); *yc = Q->R_1 * (1. - cos(t)); } D = (xb - xc) / (yb - *yc); B = xc + D * (C + R - *yc); xy.x = D * sqrt(R * R * (1 + D * D) - B * B); if (lp.phi > 0) xy.x = -xy.x; xy.x = (B + xy.x) / (1. + D * D); xy.y = sqrt(R * R - xy.x * xy.x); if (lp.phi > 0) xy.y = -xy.y; xy.y += C + R; } return xy; } static PJ_XY imw_p_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ double yc; PJ_XY xy = loc_for(lp, P, &yc); return (xy); } static PJ_LP imw_p_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_imw_p_data *Q = static_cast(P->opaque); PJ_XY t; double yc = 0.0; int i = 0; const int N_MAX_ITER = 1000; /* Arbitrarily chosen number... */ lp.phi = Q->phi_2; lp.lam = xy.x / cos(lp.phi); do { t = loc_for(lp, P, &yc); const double denom = t.y - yc; if (denom != 0 || fabs(t.y - xy.y) > TOL) { if (denom == 0) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } lp.phi = ((lp.phi - Q->phi_1) * (xy.y - yc) / denom) + Q->phi_1; } if (t.x != 0 && fabs(t.x - xy.x) > TOL) lp.lam = lp.lam * xy.x / t.x; i++; } while (i < N_MAX_ITER && (fabs(t.x - xy.x) > TOL || fabs(t.y - xy.y) > TOL)); if (i == N_MAX_ITER) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } return lp; } static void xy(PJ *P, double phi, double *x, double *y, double *sp, double *R) { double F; *sp = sin(phi); *R = 1. / (tan(phi) * sqrt(1. - P->es * *sp * *sp)); F = static_cast(P->opaque)->lam_1 * *sp; *y = *R * (1 - cos(F)); *x = *R * sin(F); } static PJ *pj_imw_p_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); if (static_cast(P->opaque)->en) free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(imw_p) { double del, sig, s, t, x1, x2, T2, y1, m1, m2, y2; int err; struct pj_imw_p_data *Q = static_cast( calloc(1, sizeof(struct pj_imw_p_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (!(Q->en = pj_enfn(P->n))) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if ((err = phi12(P, &del, &sig)) != 0) { return pj_imw_p_destructor(P, err); } if (Q->phi_2 < Q->phi_1) { /* make sure P->phi_1 most southerly */ del = Q->phi_1; Q->phi_1 = Q->phi_2; Q->phi_2 = del; } if (pj_param(P->ctx, P->params, "tlon_1").i) Q->lam_1 = pj_param(P->ctx, P->params, "rlon_1").f; else { /* use predefined based upon latitude */ sig = fabs(sig * RAD_TO_DEG); if (sig <= 60) sig = 2.; else if (sig <= 76) sig = 4.; else sig = 8.; Q->lam_1 = sig * DEG_TO_RAD; } Q->mode = NONE_IS_ZERO; if (Q->phi_1 != 0.0) xy(P, Q->phi_1, &x1, &y1, &Q->sphi_1, &Q->R_1); else { Q->mode = PHI_1_IS_ZERO; y1 = 0.; x1 = Q->lam_1; } if (Q->phi_2 != 0.0) xy(P, Q->phi_2, &x2, &T2, &Q->sphi_2, &Q->R_2); else { Q->mode = PHI_2_IS_ZERO; T2 = 0.; x2 = Q->lam_1; } m1 = pj_mlfn(Q->phi_1, Q->sphi_1, cos(Q->phi_1), Q->en); m2 = pj_mlfn(Q->phi_2, Q->sphi_2, cos(Q->phi_2), Q->en); t = m2 - m1; s = x2 - x1; y2 = sqrt(t * t - s * s) + y1; Q->C2 = y2 - T2; t = 1. / t; Q->P = (m2 * y1 - m1 * y2) * t; Q->Q = (y2 - y1) * t; Q->Pp = (m2 * x1 - m1 * x2) * t; Q->Qp = (x2 - x1) * t; P->fwd = imw_p_e_forward; P->inv = imw_p_e_inverse; P->destructor = pj_imw_p_destructor; return P; } #undef TOL #undef EPS proj-9.8.1/src/projections/bertin1953.cpp000664 001750 001750 00000004661 15166171715 020121 0ustar00eveneven000000 000000 /* Created by Jacques Bertin in 1953, this projection was the go-to choice of the French cartographic school when they wished to represent phenomena on a global scale. Formula designed by Philippe Rivière, 2017. https://visionscarto.net/bertin-projection-1953 Port to PROJ by Philippe Rivière, 21 September 2018 */ #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(bertin1953, "Bertin 1953") "\n\tMisc Sph no inv."; namespace { // anonymous namespace struct pj_bertin1953 { double cos_delta_phi, sin_delta_phi, cos_delta_gamma, sin_delta_gamma, deltaLambda; }; } // anonymous namespace static PJ_XY bertin1953_s_forward(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; struct pj_bertin1953 *Q = static_cast(P->opaque); double fu = 1.4, k = 12., w = 1.68, d; /* Rotate */ double cosphi, x, y, z, z0; lp.lam += PJ_TORAD(-16.5); cosphi = cos(lp.phi); x = cos(lp.lam) * cosphi; y = sin(lp.lam) * cosphi; z = sin(lp.phi); z0 = z * Q->cos_delta_phi + x * Q->sin_delta_phi; lp.lam = atan2(y * Q->cos_delta_gamma - z0 * Q->sin_delta_gamma, x * Q->cos_delta_phi - z * Q->sin_delta_phi); z0 = z0 * Q->cos_delta_gamma + y * Q->sin_delta_gamma; lp.phi = asin(z0); lp.lam = adjlon(lp.lam); /* Adjust pre-projection */ if (lp.lam + lp.phi < -fu) { d = (lp.lam - lp.phi + 1.6) * (lp.lam + lp.phi + fu) / 8.; lp.lam += d; lp.phi -= 0.8 * d * sin(lp.phi + M_PI / 2.); } /* Project with Hammer (1.68,2) */ cosphi = cos(lp.phi); d = sqrt(2. / (1. + cosphi * cos(lp.lam / 2.))); xy.x = w * d * cosphi * sin(lp.lam / 2.); xy.y = d * sin(lp.phi); /* Adjust post-projection */ d = (1. - cos(lp.lam * lp.phi)) / k; if (xy.y < 0.) { xy.x *= 1. + d; } if (xy.y > 0.) { xy.y *= 1. + d / 1.5 * xy.x * xy.x; } return xy; } PJ *PJ_PROJECTION(bertin1953) { struct pj_bertin1953 *Q = static_cast( calloc(1, sizeof(struct pj_bertin1953))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->lam0 = 0; P->phi0 = PJ_TORAD(-42.); Q->cos_delta_phi = cos(P->phi0); Q->sin_delta_phi = sin(P->phi0); Q->cos_delta_gamma = 1.; Q->sin_delta_gamma = 0.; P->es = 0.; P->fwd = bertin1953_s_forward; return P; } proj-9.8.1/src/projections/aitoff.cpp000664 001750 001750 00000021052 15166171715 017555 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the aitoff (Aitoff) and wintri (Winkel Tripel) * projections. * Author: Gerald Evenden (1995) * Drazen Tutic, Lovro Gradiser (2015) - add inverse * Thomas Knudsen (2016) - revise/add regression tests * ****************************************************************************** * Copyright (c) 1995, Gerald Evenden * * 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 #include #include "proj.h" #include "proj_internal.h" namespace pj_aitoff_ns { enum Mode { AITOFF = 0, WINKEL_TRIPEL = 1 }; } namespace { // anonymous namespace struct pj_aitoff_data { double cosphi1; enum pj_aitoff_ns::Mode mode; }; } // anonymous namespace PROJ_HEAD(aitoff, "Aitoff") "\n\tMisc Sph"; PROJ_HEAD(wintri, "Winkel Tripel") "\n\tMisc Sph\n\tlat_1"; #if 0 FORWARD(aitoff_s_forward); /* spheroid */ #endif static PJ_XY aitoff_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_aitoff_data *Q = static_cast(P->opaque); double c, d; #if 0 // Likely domain of validity for wintri in +over mode. Should be confirmed // Cf https://lists.osgeo.org/pipermail/gdal-dev/2023-April/057164.html if (Q->mode == WINKEL_TRIPEL && fabs(lp.lam) > 2 * M_PI) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } #endif c = 0.5 * lp.lam; d = acos(cos(lp.phi) * cos(c)); if (d != 0.0) { /* basic Aitoff */ xy.x = 2. * d * cos(lp.phi) * sin(c) * (xy.y = 1. / sin(d)); xy.y *= d * sin(lp.phi); } else xy.x = xy.y = 0.; if (Q->mode == pj_aitoff_ns::WINKEL_TRIPEL) { xy.x = (xy.x + lp.lam * Q->cosphi1) * 0.5; xy.y = (xy.y + lp.phi) * 0.5; } return (xy); } /*********************************************************************************** * * Inverse functions added by Drazen Tutic and Lovro Gradiser based on paper: * * I.Özbug Biklirici and Cengizhan Ipbüker. A General Algorithm for the Inverse * Transformation of Map Projections Using Jacobian Matrices. In Proceedings of *the Third International Symposium Mathematical & Computational Applications, * pages 175{182, Turkey, September 2002. * * Expected accuracy is defined by EPSILON = 1e-12. Should be appropriate for * most applications of Aitoff and Winkel Tripel projections. * * Longitudes of 180W and 180E can be mixed in solution obtained. * * Inverse for Aitoff projection in poles is undefined, longitude value of 0 is *assumed. * * Contact : dtutic at geof.hr * Date: 2015-02-16 * ************************************************************************************/ static PJ_LP aitoff_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_aitoff_data *Q = static_cast(P->opaque); int iter, MAXITER = 10, round = 0, MAXROUND = 20; double EPSILON = 1e-12, D, C, f1, f2, f1p, f1l, f2p, f2l, dp, dl, sl, sp, cp, cl, x, y; if ((fabs(xy.x) < EPSILON) && (fabs(xy.y) < EPSILON)) { lp.phi = 0.; lp.lam = 0.; return lp; } /* initial values for Newton-Raphson method */ lp.phi = xy.y; lp.lam = xy.x; do { iter = 0; do { sl = sin(lp.lam * 0.5); cl = cos(lp.lam * 0.5); sp = sin(lp.phi); cp = cos(lp.phi); D = cp * cl; C = 1. - D * D; const double denom = pow(C, 1.5); if (denom == 0) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } D = acos(D) / denom; f1 = 2. * D * C * cp * sl; f2 = D * C * sp; f1p = 2. * (sl * cl * sp * cp / C - D * sp * sl); f1l = cp * cp * sl * sl / C + D * cp * cl * sp * sp; f2p = sp * sp * cl / C + D * sl * sl * cp; f2l = 0.5 * (sp * cp * sl / C - D * sp * cp * cp * sl * cl); if (Q->mode == pj_aitoff_ns::WINKEL_TRIPEL) { f1 = 0.5 * (f1 + lp.lam * Q->cosphi1); f2 = 0.5 * (f2 + lp.phi); f1p *= 0.5; f1l = 0.5 * (f1l + Q->cosphi1); f2p = 0.5 * (f2p + 1.); f2l *= 0.5; } f1 -= xy.x; f2 -= xy.y; dp = f1p * f2l - f2p * f1l; dl = (f2 * f1p - f1 * f2p) / dp; dp = (f1 * f2l - f2 * f1l) / dp; dl = fmod(dl, M_PI); /* set to interval [-M_PI, M_PI] */ lp.phi -= dp; lp.lam -= dl; } while ((fabs(dp) > EPSILON || fabs(dl) > EPSILON) && (iter++ < MAXITER)); if (lp.phi > M_PI_2) lp.phi -= 2. * (lp.phi - M_PI_2); /* correct if symmetrical solution for Aitoff */ if (lp.phi < -M_PI_2) lp.phi -= 2. * (lp.phi + M_PI_2); /* correct if symmetrical solution for Aitoff */ if ((fabs(fabs(lp.phi) - M_PI_2) < EPSILON) && (Q->mode == pj_aitoff_ns::AITOFF)) lp.lam = 0.; /* if pole in Aitoff, return longitude of 0 */ /* calculate x,y coordinates with solution obtained */ if ((D = acos(cos(lp.phi) * cos(C = 0.5 * lp.lam))) != 0.0) { /* Aitoff */ y = 1. / sin(D); x = 2. * D * cos(lp.phi) * sin(C) * y; y *= D * sin(lp.phi); } else x = y = 0.; if (Q->mode == pj_aitoff_ns::WINKEL_TRIPEL) { x = (x + lp.lam * Q->cosphi1) * 0.5; y = (y + lp.phi) * 0.5; } /* if too far from given values of x,y, repeat with better approximation * of phi,lam */ } while (((fabs(xy.x - x) > EPSILON) || (fabs(xy.y - y) > EPSILON)) && (round++ < MAXROUND)); if (iter == MAXITER && round == MAXROUND) { proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); /* fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last * increments: dlat=%e and dlon=%e\n", dp, dl); */ } return lp; } static PJ *pj_aitoff_setup(PJ *P) { P->inv = aitoff_s_inverse; P->fwd = aitoff_s_forward; P->es = 0.; return P; } PJ *PJ_PROJECTION(aitoff) { struct pj_aitoff_data *Q = static_cast( calloc(1, sizeof(struct pj_aitoff_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->mode = pj_aitoff_ns::AITOFF; return pj_aitoff_setup(P); } PJ *PJ_PROJECTION(wintri) { struct pj_aitoff_data *Q = static_cast( calloc(1, sizeof(struct pj_aitoff_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->mode = pj_aitoff_ns::WINKEL_TRIPEL; if (pj_param(P->ctx, P->params, "tlat_1").i) { if ((Q->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f)) == 0.) { proj_log_error( P, _("Invalid value for lat_1: |lat_1| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else /* 50d28' or acos(2/pi) */ Q->cosphi1 = 0.636619772367581343; return pj_aitoff_setup(P); } proj-9.8.1/src/projections/vandg2.cpp000664 001750 001750 00000003672 15166171715 017476 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_vandg2 { int vdg3; }; } // anonymous namespace PROJ_HEAD(vandg2, "van der Grinten II") "\n\tMisc Sph, no inv"; PROJ_HEAD(vandg3, "van der Grinten III") "\n\tMisc Sph, no inv"; #define TOL 1e-10 static PJ_XY vandg2_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_vandg2 *Q = static_cast(P->opaque); double x1, at, bt, ct; bt = fabs(M_TWO_D_PI * lp.phi); ct = 1. - bt * bt; if (ct < 0.) ct = 0.; else ct = sqrt(ct); if (fabs(lp.lam) < TOL) { xy.x = 0.; xy.y = M_PI * (lp.phi < 0. ? -bt : bt) / (1. + ct); } else { at = 0.5 * fabs(M_PI / lp.lam - lp.lam / M_PI); if (Q->vdg3) { x1 = bt / (1. + ct); xy.x = M_PI * (sqrt(at * at + 1. - x1 * x1) - at); xy.y = M_PI * x1; } else { x1 = (ct * sqrt(1. + at * at) - at * ct * ct) / (1. + at * at * bt * bt); xy.x = M_PI * x1; xy.y = M_PI * sqrt(1. - x1 * (x1 + 2. * at) + TOL); } if (lp.lam < 0.) xy.x = -xy.x; if (lp.phi < 0.) xy.y = -xy.y; } return xy; } PJ *PJ_PROJECTION(vandg2) { struct pj_vandg2 *Q = static_cast(calloc(1, sizeof(struct pj_vandg2))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->vdg3 = 0; P->fwd = vandg2_s_forward; return P; } PJ *PJ_PROJECTION(vandg3) { struct pj_vandg2 *Q = static_cast(calloc(1, sizeof(struct pj_vandg2))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->vdg3 = 1; P->es = 0.; P->fwd = vandg2_s_forward; return P; } #undef TOL proj-9.8.1/src/projections/collg.cpp000664 001750 001750 00000002340 15166171715 017404 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(collg, "Collignon") "\n\tPCyl, Sph"; #define FXC 1.12837916709551257390 #define FYC 1.77245385090551602729 #define ONEEPS 1.0000001 static PJ_XY collg_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.y = 1. - sin(lp.phi); if (xy.y <= 0.) xy.y = 0.; else xy.y = sqrt(xy.y); xy.x = FXC * lp.lam * xy.y; xy.y = FYC * (1. - xy.y); return (xy); } static PJ_LP collg_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = xy.y / FYC - 1.; lp.phi = 1. - lp.phi * lp.phi; if (fabs(lp.phi) < 1.) lp.phi = asin(lp.phi); else if (fabs(lp.phi) > ONEEPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; } lp.lam = 1. - sin(lp.phi); if (lp.lam <= 0.) lp.lam = 0.; else lp.lam = xy.x / (FXC * sqrt(lp.lam)); return (lp); } PJ *PJ_PROJECTION(collg) { P->es = 0.0; P->inv = collg_s_inverse; P->fwd = collg_s_forward; return P; } #undef FXC #undef FYC #undef ONEEPS proj-9.8.1/src/projections/denoy.cpp000664 001750 001750 00000001331 15166171715 017421 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(denoy, "Denoyer Semi-Elliptical") "\n\tPCyl, no inv, Sph"; #define C0 0.95 #define C1 -0.08333333333333333333 #define C3 0.00166666666666666666 #define D1 0.9 #define D5 0.03 static PJ_XY denoy_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.y = lp.phi; xy.x = lp.lam; lp.lam = fabs(lp.lam); xy.x *= cos((C0 + lp.lam * (C1 + lp.lam * lp.lam * C3)) * (lp.phi * (D1 + D5 * lp.phi * lp.phi * lp.phi * lp.phi))); return xy; } PJ *PJ_PROJECTION(denoy) { P->es = 0.0; P->fwd = denoy_s_forward; return P; } #undef C0 #undef C1 #undef C3 #undef D1 #undef D5 proj-9.8.1/src/projections/laea.cpp000664 001750 001750 00000021642 15166171715 017214 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(laea, "Lambert Azimuthal Equal Area") "\n\tAzi, Sph&Ell"; namespace pj_laea_ns { enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; } namespace { // anonymous namespace struct pj_laea_data { double sinb1; double cosb1; double xmf; double ymf; double mmf; double qp; double dd; double rq; double *apa; enum pj_laea_ns::Mode mode; }; } // anonymous namespace #define EPS10 1.e-10 static PJ_XY laea_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_laea_data *Q = static_cast(P->opaque); double coslam, sinlam, sinphi, cosphi, q, sinb = 0.0, cosb = 0.0, b = 0.0; coslam = cos(lp.lam); sinlam = sin(lp.lam); sinphi = sin(lp.phi); cosphi = cos(lp.phi); const double xi = pj_authalic_lat(lp.phi, sinphi, cosphi, Q->apa, P, Q->qp); q = sin(xi) * Q->qp; if (Q->mode == pj_laea_ns::OBLIQ || Q->mode == pj_laea_ns::EQUIT) { sinb = sin(xi); cosb = cos(xi); } switch (Q->mode) { case pj_laea_ns::OBLIQ: b = 1. + Q->sinb1 * sinb + Q->cosb1 * cosb * coslam; break; case pj_laea_ns::EQUIT: b = 1. + cosb * coslam; break; case pj_laea_ns::N_POLE: b = M_HALFPI + lp.phi; q = Q->qp - q; break; case pj_laea_ns::S_POLE: b = lp.phi - M_HALFPI; q = Q->qp + q; break; } if (fabs(b) < EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } switch (Q->mode) { case pj_laea_ns::OBLIQ: b = sqrt(2. / b); xy.y = Q->ymf * b * (Q->cosb1 * sinb - Q->sinb1 * cosb * coslam); goto eqcon; break; case pj_laea_ns::EQUIT: b = sqrt(2. / (1. + cosb * coslam)); xy.y = b * sinb * Q->ymf; eqcon: xy.x = Q->xmf * b * cosb * sinlam; break; case pj_laea_ns::N_POLE: case pj_laea_ns::S_POLE: if (q >= 1e-15) { b = sqrt(q); xy.x = b * sinlam; xy.y = coslam * (Q->mode == pj_laea_ns::S_POLE ? b : -b); } else xy.x = xy.y = 0.; break; } return xy; } static PJ_XY laea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_laea_data *Q = static_cast(P->opaque); double coslam, cosphi, sinphi; sinphi = sin(lp.phi); cosphi = cos(lp.phi); coslam = cos(lp.lam); switch (Q->mode) { case pj_laea_ns::EQUIT: xy.y = 1. + cosphi * coslam; goto oblcon; case pj_laea_ns::OBLIQ: xy.y = 1. + Q->sinb1 * sinphi + Q->cosb1 * cosphi * coslam; oblcon: if (xy.y <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = sqrt(2. / xy.y); xy.x = xy.y * cosphi * sin(lp.lam); xy.y *= Q->mode == pj_laea_ns::EQUIT ? sinphi : Q->cosb1 * sinphi - Q->sinb1 * cosphi * coslam; break; case pj_laea_ns::N_POLE: coslam = -coslam; PROJ_FALLTHROUGH; case pj_laea_ns::S_POLE: if (fabs(lp.phi + P->phi0) < EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = M_FORTPI - lp.phi * .5; xy.y = 2. * (Q->mode == pj_laea_ns::S_POLE ? cos(xy.y) : sin(xy.y)); xy.x = xy.y * sin(lp.lam); xy.y *= coslam; break; } return xy; } static PJ_LP laea_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_laea_data *Q = static_cast(P->opaque); double cCe, sCe, q, rho, ab = 0.0; switch (Q->mode) { case pj_laea_ns::EQUIT: case pj_laea_ns::OBLIQ: { xy.x /= Q->dd; xy.y *= Q->dd; rho = hypot(xy.x, xy.y); if (rho < EPS10) { lp.lam = 0.; lp.phi = P->phi0; return lp; } const double asin_argument = .5 * rho / Q->rq; if (asin_argument > 1) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } sCe = 2. * asin(asin_argument); cCe = cos(sCe); sCe = sin(sCe); xy.x *= sCe; if (Q->mode == pj_laea_ns::OBLIQ) { ab = cCe * Q->sinb1 + xy.y * sCe * Q->cosb1 / rho; xy.y = rho * Q->cosb1 * cCe - xy.y * Q->sinb1 * sCe; } else { ab = xy.y * sCe / rho; xy.y = rho * cCe; } break; } case pj_laea_ns::N_POLE: xy.y = -xy.y; PROJ_FALLTHROUGH; case pj_laea_ns::S_POLE: q = (xy.x * xy.x + xy.y * xy.y); if (q == 0.0) { lp.lam = 0.; lp.phi = P->phi0; return (lp); } ab = 1. - q / Q->qp; if (Q->mode == pj_laea_ns::S_POLE) ab = -ab; break; } lp.lam = atan2(xy.x, xy.y); lp.phi = pj_authalic_lat_inverse(asin(ab), Q->apa, P, Q->qp); return lp; } static PJ_LP laea_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_laea_data *Q = static_cast(P->opaque); double cosz = 0.0, rh, sinz = 0.0; rh = hypot(xy.x, xy.y); if ((lp.phi = rh * .5) > 1.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = 2. * asin(lp.phi); if (Q->mode == pj_laea_ns::OBLIQ || Q->mode == pj_laea_ns::EQUIT) { sinz = sin(lp.phi); cosz = cos(lp.phi); } switch (Q->mode) { case pj_laea_ns::EQUIT: lp.phi = fabs(rh) <= EPS10 ? 0. : asin(xy.y * sinz / rh); xy.x *= sinz; xy.y = cosz * rh; break; case pj_laea_ns::OBLIQ: lp.phi = fabs(rh) <= EPS10 ? P->phi0 : asin(cosz * Q->sinb1 + xy.y * sinz * Q->cosb1 / rh); xy.x *= sinz * Q->cosb1; xy.y = (cosz - sin(lp.phi) * Q->sinb1) * rh; break; case pj_laea_ns::N_POLE: xy.y = -xy.y; lp.phi = M_HALFPI - lp.phi; break; case pj_laea_ns::S_POLE: lp.phi -= M_HALFPI; break; } lp.lam = (xy.y == 0. && (Q->mode == pj_laea_ns::EQUIT || Q->mode == pj_laea_ns::OBLIQ)) ? 0. : atan2(xy.x, xy.y); return (lp); } static PJ *pj_laea_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->apa); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(laea) { double t; struct pj_laea_data *Q = static_cast( calloc(1, sizeof(struct pj_laea_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_laea_destructor; t = fabs(P->phi0); if (t > M_HALFPI + EPS10) { proj_log_error(P, _("Invalid value for lat_0: |lat_0| should be <= 90°")); return pj_laea_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(t - M_HALFPI) < EPS10) Q->mode = P->phi0 < 0. ? pj_laea_ns::S_POLE : pj_laea_ns::N_POLE; else if (fabs(t) < EPS10) Q->mode = pj_laea_ns::EQUIT; else Q->mode = pj_laea_ns::OBLIQ; if (P->es != 0.0) { double sinphi, cosphi; P->e = sqrt(P->es); Q->qp = pj_authalic_lat_q(1.0, P); Q->mmf = .5 / (1. - P->es); Q->apa = pj_authalic_lat_compute_coeffs(P->n); if (nullptr == Q->apa) return pj_laea_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); switch (Q->mode) { case pj_laea_ns::N_POLE: case pj_laea_ns::S_POLE: Q->dd = 1.; break; case pj_laea_ns::EQUIT: Q->dd = 1. / (Q->rq = sqrt(.5 * Q->qp)); Q->xmf = 1.; Q->ymf = .5 * Q->qp; break; case pj_laea_ns::OBLIQ: Q->rq = sqrt(.5 * Q->qp); sinphi = sin(P->phi0); cosphi = cos(P->phi0); const double b1 = pj_authalic_lat(P->phi0, sinphi, cosphi, Q->apa, P, Q->qp); Q->sinb1 = sin(b1); Q->cosb1 = cos(b1); Q->dd = cos(P->phi0) / (sqrt(1. - P->es * sinphi * sinphi) * Q->rq * Q->cosb1); Q->ymf = (Q->xmf = Q->rq) / Q->dd; Q->xmf *= Q->dd; break; } P->inv = laea_e_inverse; P->fwd = laea_e_forward; } else { if (Q->mode == pj_laea_ns::OBLIQ) { Q->sinb1 = sin(P->phi0); Q->cosb1 = cos(P->phi0); } P->inv = laea_s_inverse; P->fwd = laea_s_forward; } return P; } proj-9.8.1/src/projections/labrd.cpp000664 001750 001750 00000010543 15166171715 017374 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(labrd, "Laborde") "\n\tCyl, Sph\n\tSpecial for Madagascar\n\tlat_0="; #define EPS 1.e-10 namespace { // anonymous namespace struct pj_opaque { double kRg, p0s, A, C, Ca, Cb, Cc, Cd; }; } // anonymous namespace static PJ_XY labrd_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_opaque *Q = static_cast(P->opaque); double V1, V2, ps, sinps, cosps, sinps2, cosps2; double I1, I2, I3, I4, I5, I6, x2, y2, t; V1 = Q->A * log(tan(M_FORTPI + .5 * lp.phi)); t = P->e * sin(lp.phi); V2 = .5 * P->e * Q->A * log((1. + t) / (1. - t)); ps = 2. * (atan(exp(V1 - V2 + Q->C)) - M_FORTPI); I1 = ps - Q->p0s; cosps = cos(ps); cosps2 = cosps * cosps; sinps = sin(ps); sinps2 = sinps * sinps; I4 = Q->A * cosps; I2 = .5 * Q->A * I4 * sinps; I3 = I2 * Q->A * Q->A * (5. * cosps2 - sinps2) / 12.; I6 = I4 * Q->A * Q->A; I5 = I6 * (cosps2 - sinps2) / 6.; I6 *= Q->A * Q->A * (5. * cosps2 * cosps2 + sinps2 * (sinps2 - 18. * cosps2)) / 120.; t = lp.lam * lp.lam; xy.x = Q->kRg * lp.lam * (I4 + t * (I5 + t * I6)); xy.y = Q->kRg * (I1 + t * (I2 + t * I3)); x2 = xy.x * xy.x; y2 = xy.y * xy.y; V1 = 3. * xy.x * y2 - xy.x * x2; V2 = xy.y * y2 - 3. * x2 * xy.y; xy.x += Q->Ca * V1 + Q->Cb * V2; xy.y += Q->Ca * V2 - Q->Cb * V1; return xy; } static PJ_LP labrd_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_opaque *Q = static_cast(P->opaque); /* t = 0.0 optimization is to avoid a false positive cppcheck warning */ /* (cppcheck git beaf29c15867984aa3c2a15cf15bd7576ccde2b3). Might no */ /* longer be necessary with later versions. */ double x2, y2, V1, V2, V3, V4, t = 0.0, t2, ps, pe, tpe, s; double I7, I8, I9, I10, I11, d, Re; int i; x2 = xy.x * xy.x; y2 = xy.y * xy.y; V1 = 3. * xy.x * y2 - xy.x * x2; V2 = xy.y * y2 - 3. * x2 * xy.y; V3 = xy.x * (5. * y2 * y2 + x2 * (-10. * y2 + x2)); V4 = xy.y * (5. * x2 * x2 + y2 * (-10. * x2 + y2)); xy.x += -Q->Ca * V1 - Q->Cb * V2 + Q->Cc * V3 + Q->Cd * V4; xy.y += Q->Cb * V1 - Q->Ca * V2 - Q->Cd * V3 + Q->Cc * V4; ps = Q->p0s + xy.y / Q->kRg; pe = ps + P->phi0 - Q->p0s; for (i = 20; i; --i) { V1 = Q->A * log(tan(M_FORTPI + .5 * pe)); tpe = P->e * sin(pe); V2 = .5 * P->e * Q->A * log((1. + tpe) / (1. - tpe)); t = ps - 2. * (atan(exp(V1 - V2 + Q->C)) - M_FORTPI); pe += t; if (fabs(t) < EPS) break; } t = P->e * sin(pe); t = 1. - t * t; Re = P->one_es / (t * sqrt(t)); t = tan(ps); t2 = t * t; s = Q->kRg * Q->kRg; d = Re * P->k0 * Q->kRg; I7 = t / (2. * d); I8 = t * (5. + 3. * t2) / (24. * d * s); d = cos(ps) * Q->kRg * Q->A; I9 = 1. / d; d *= s; I10 = (1. + 2. * t2) / (6. * d); I11 = (5. + t2 * (28. + 24. * t2)) / (120. * d * s); x2 = xy.x * xy.x; lp.phi = pe + x2 * (-I7 + I8 * x2); lp.lam = xy.x * (I9 + x2 * (-I10 + x2 * I11)); return lp; } PJ *PJ_PROJECTION(labrd) { double Az, sinp, R, N, t; struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (P->phi0 == 0.) { proj_log_error( P, _("Invalid value for lat_0: lat_0 should be different from 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Az = pj_param(P->ctx, P->params, "razi").f; sinp = sin(P->phi0); t = 1. - P->es * sinp * sinp; N = 1. / sqrt(t); R = P->one_es * N / t; Q->kRg = P->k0 * sqrt(N * R); Q->p0s = atan(sqrt(R / N) * tan(P->phi0)); Q->A = sinp / sin(Q->p0s); t = P->e * sinp; Q->C = .5 * P->e * Q->A * log((1. + t) / (1. - t)) + -Q->A * log(tan(M_FORTPI + .5 * P->phi0)) + log(tan(M_FORTPI + .5 * Q->p0s)); t = Az + Az; Q->Cb = 1. / (12. * Q->kRg * Q->kRg); Q->Ca = (1. - cos(t)) * Q->Cb; Q->Cb *= sin(t); Q->Cc = 3. * (Q->Ca * Q->Ca - Q->Cb * Q->Cb); Q->Cd = 6. * Q->Ca * Q->Cb; P->inv = labrd_e_inverse; P->fwd = labrd_e_forward; return P; } proj-9.8.1/src/projections/latlong.cpp000664 001750 001750 00000006312 15166171715 017747 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Stub projection implementation for lat/long coordinates. We * don't actually change the coordinates, but we want proj=latlong * to act sort of like a projection. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ /* very loosely based upon DMA code by Bradford W. Drew */ #include "proj_internal.h" PROJ_HEAD(lonlat, "Lat/long (Geodetic)") "\n\t"; PROJ_HEAD(latlon, "Lat/long (Geodetic alias)") "\n\t"; PROJ_HEAD(latlong, "Lat/long (Geodetic alias)") "\n\t"; PROJ_HEAD(longlat, "Lat/long (Geodetic alias)") "\n\t"; static PJ_XY latlong_forward(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = lp.lam; xy.y = lp.phi; return xy; } static PJ_LP latlong_inverse(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = xy.y; lp.lam = xy.x; return lp; } static PJ_XYZ latlong_forward_3d(PJ_LPZ lpz, PJ *P) { PJ_XYZ xyz = {0, 0, 0}; (void)P; xyz.x = lpz.lam; xyz.y = lpz.phi; xyz.z = lpz.z; return xyz; } static PJ_LPZ latlong_inverse_3d(PJ_XYZ xyz, PJ *P) { PJ_LPZ lpz = {0, 0, 0}; (void)P; lpz.lam = xyz.x; lpz.phi = xyz.y; lpz.z = xyz.z; return lpz; } static void latlong_forward_4d(PJ_COORD &, PJ *) {} static void latlong_inverse_4d(PJ_COORD &, PJ *) {} static PJ *latlong_setup(PJ *P) { P->is_latlong = 1; P->x0 = 0; P->y0 = 0; P->inv = latlong_inverse; P->fwd = latlong_forward; P->inv3d = latlong_inverse_3d; P->fwd3d = latlong_forward_3d; P->inv4d = latlong_inverse_4d; P->fwd4d = latlong_forward_4d; P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_RADIANS; return P; } PJ *PJ_PROJECTION(latlong) { return latlong_setup(P); } PJ *PJ_PROJECTION(longlat) { return latlong_setup(P); } PJ *PJ_PROJECTION(latlon) { return latlong_setup(P); } PJ *PJ_PROJECTION(lonlat) { return latlong_setup(P); } proj-9.8.1/src/projections/eck3.cpp000664 001750 001750 00000005430 15166171715 017134 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(eck3, "Eckert III") "\n\tPCyl, Sph"; PROJ_HEAD(putp1, "Putnins P1") "\n\tPCyl, Sph"; PROJ_HEAD(wag6, "Wagner VI") "\n\tPCyl, Sph"; PROJ_HEAD(kav7, "Kavrayskiy VII") "\n\tPCyl, Sph"; namespace { // anonymous namespace struct pj_opaque { double C_x, C_y, A, B; }; } // anonymous namespace static PJ_XY eck3_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_opaque *Q = static_cast(P->opaque); xy.y = Q->C_y * lp.phi; xy.x = Q->C_x * lp.lam * (Q->A + asqrt(1. - Q->B * lp.phi * lp.phi)); return xy; } static PJ_LP eck3_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_opaque *Q = static_cast(P->opaque); double denominator; lp.phi = xy.y / Q->C_y; denominator = (Q->C_x * (Q->A + asqrt(1. - Q->B * lp.phi * lp.phi))); if (denominator == 0.0) lp.lam = HUGE_VAL; else lp.lam = xy.x / denominator; return lp; } static PJ *setup(PJ *P) { P->es = 0.; P->inv = eck3_s_inverse; P->fwd = eck3_s_forward; return P; } PJ *PJ_PROJECTION(eck3) { struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.42223820031577120149; Q->C_y = 0.84447640063154240298; Q->A = 1.0; Q->B = 0.4052847345693510857755; return setup(P); } PJ *PJ_PROJECTION(kav7) { struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* Defined twice in original code - Using 0.866..., * but leaving the other one here as a safety measure. * Q->C_x = 0.2632401569273184856851; */ Q->C_x = 0.8660254037844; Q->C_y = 1.; Q->A = 0.; Q->B = 0.30396355092701331433; return setup(P); } PJ *PJ_PROJECTION(wag6) { struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.0; Q->C_y = 1.0; Q->A = 0.0; Q->B = 0.30396355092701331433; return setup(P); } PJ *PJ_PROJECTION(putp1) { struct pj_opaque *Q = static_cast(calloc(1, sizeof(struct pj_opaque))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.89490; Q->C_y = 0.94745; Q->A = -0.5; Q->B = 0.30396355092701331433; return setup(P); } proj-9.8.1/src/projections/moll.cpp000664 001750 001750 00000005413 15166171715 017253 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(moll, "Mollweide") "\n\tPCyl, Sph"; PROJ_HEAD(wag4, "Wagner IV") "\n\tPCyl, Sph"; PROJ_HEAD(wag5, "Wagner V") "\n\tPCyl, Sph"; #define MAX_ITER 30 #define LOOP_TOL 1e-7 namespace { // anonymous namespace struct pj_moll_data { double C_x, C_y, C_p; }; } // anonymous namespace static PJ_XY moll_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_moll_data *Q = static_cast(P->opaque); int i; const double k = Q->C_p * sin(lp.phi); for (i = MAX_ITER; i; --i) { const double V = (lp.phi + sin(lp.phi) - k) / (1. + cos(lp.phi)); lp.phi -= V; if (fabs(V) < LOOP_TOL) break; } if (!i) lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; else lp.phi *= 0.5; xy.x = Q->C_x * lp.lam * cos(lp.phi); xy.y = Q->C_y * sin(lp.phi); return xy; } static PJ_LP moll_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_moll_data *Q = static_cast(P->opaque); lp.phi = aasin(P->ctx, xy.y / Q->C_y); lp.lam = xy.x / (Q->C_x * cos(lp.phi)); if (fabs(lp.lam) < M_PI) { lp.phi += lp.phi; lp.phi = aasin(P->ctx, (lp.phi + sin(lp.phi)) / Q->C_p); } else { lp.lam = lp.phi = HUGE_VAL; } return lp; } static PJ *setup(PJ *P, double p) { struct pj_moll_data *Q = static_cast(P->opaque); double r, sp, p2 = p + p; P->es = 0; sp = sin(p); r = sqrt(M_TWOPI * sp / (p2 + sin(p2))); Q->C_x = 2. * r / M_PI; Q->C_y = r / sp; Q->C_p = p2 + sin(p2); P->inv = moll_s_inverse; P->fwd = moll_s_forward; return P; } PJ *PJ_PROJECTION(moll) { struct pj_moll_data *Q = static_cast( calloc(1, sizeof(struct pj_moll_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, M_HALFPI); } PJ *PJ_PROJECTION(wag4) { struct pj_moll_data *Q = static_cast( calloc(1, sizeof(struct pj_moll_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; return setup(P, M_PI / 3.); } PJ *PJ_PROJECTION(wag5) { struct pj_moll_data *Q = static_cast( calloc(1, sizeof(struct pj_moll_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->es = 0; Q->C_x = 0.90977; Q->C_y = 1.65014; Q->C_p = 3.00896; P->inv = moll_s_inverse; P->fwd = moll_s_forward; return P; } #undef MAX_ITER #undef LOOP_TOL proj-9.8.1/src/projections/poly.cpp000664 001750 001750 00000011667 15166171715 017303 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(poly, "Polyconic (American)") "\n\tConic, Sph&Ell"; namespace { // anonymous namespace struct pj_poly_data { double ml0; double *en; }; } // anonymous namespace #define TOL 1e-10 #define CONV 1e-10 #define N_ITER 10 #define I_ITER 20 #define ITOL 1.e-12 static PJ_XY poly_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_poly_data *Q = static_cast(P->opaque); double ms, sp, cp; if (fabs(lp.phi) <= TOL) { xy.x = lp.lam; xy.y = -Q->ml0; } else { sp = sin(lp.phi); cp = cos(lp.phi); ms = fabs(cp) > TOL ? pj_msfn(sp, cp, P->es) / sp : 0.; lp.lam *= sp; xy.x = ms * sin(lp.lam); xy.y = (pj_mlfn(lp.phi, sp, cp, Q->en) - Q->ml0) + ms * (1. - cos(lp.lam)); } return xy; } static PJ_XY poly_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_poly_data *Q = static_cast(P->opaque); if (fabs(lp.phi) <= TOL) { xy.x = lp.lam; xy.y = Q->ml0; } else { const double cot = 1. / tan(lp.phi); const double E = lp.lam * sin(lp.phi); xy.x = sin(E) * cot; xy.y = lp.phi - P->phi0 + cot * (1. - cos(E)); } return xy; } static PJ_LP poly_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_poly_data *Q = static_cast(P->opaque); xy.y += Q->ml0; if (fabs(xy.y) <= TOL) { lp.lam = xy.x; lp.phi = 0.; } else { int i; const double r = xy.y * xy.y + xy.x * xy.x; lp.phi = xy.y; for (i = I_ITER; i; --i) { const double sp = sin(lp.phi); const double cp = cos(lp.phi); const double s2ph = sp * cp; if (fabs(cp) < ITOL) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } double mlp = sqrt(1. - P->es * sp * sp); const double c = sp * mlp / cp; const double ml = pj_mlfn(lp.phi, sp, cp, Q->en); const double mlb = ml * ml + r; mlp = P->one_es / (mlp * mlp * mlp); const double dPhi = (ml + ml + c * mlb - 2. * xy.y * (c * ml + 1.)) / (P->es * s2ph * (mlb - 2. * xy.y * ml) / c + 2. * (xy.y - ml) * (c * mlp - 1. / s2ph) - mlp - mlp); lp.phi += dPhi; if (fabs(dPhi) <= ITOL) break; } if (!i) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } const double c = sin(lp.phi); lp.lam = asin(xy.x * tan(lp.phi) * sqrt(1. - P->es * c * c)) / sin(lp.phi); } return lp; } static PJ_LP poly_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; if (fabs(xy.y = P->phi0 + xy.y) <= TOL) { lp.lam = xy.x; lp.phi = 0.; } else { lp.phi = xy.y; const double B = xy.x * xy.x + xy.y * xy.y; int i = N_ITER; while (true) { const double tp = tan(lp.phi); const double dphi = (xy.y * (lp.phi * tp + 1.) - lp.phi - .5 * (lp.phi * lp.phi + B) * tp) / ((lp.phi - xy.y) / tp - 1.); lp.phi -= dphi; if (!(fabs(dphi) > CONV)) break; --i; if (i == 0) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } } lp.lam = asin(xy.x * tan(lp.phi)) / sin(lp.phi); } return lp; } static PJ *pj_poly_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); if (static_cast(P->opaque)->en) free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(poly) { struct pj_poly_data *Q = static_cast( calloc(1, sizeof(struct pj_poly_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_poly_destructor; if (P->es != 0.0) { if (!(Q->en = pj_enfn(P->n))) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); P->inv = poly_e_inverse; P->fwd = poly_e_forward; } else { Q->ml0 = -P->phi0; P->inv = poly_s_inverse; P->fwd = poly_s_forward; } return P; } #undef TOL #undef CONV #undef N_ITER #undef I_ITER #undef ITOL proj-9.8.1/src/projections/nell.cpp000664 001750 001750 00000002057 15166171715 017243 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(nell, "Nell") "\n\tPCyl, Sph"; #define MAX_ITER 10 #define LOOP_TOL 1e-7 static PJ_XY nell_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; int i; (void)P; const double k = 2. * sin(lp.phi); const double phi_pow_2 = lp.phi * lp.phi; lp.phi *= 1.00371 + phi_pow_2 * (-0.0935382 + phi_pow_2 * -0.011412); for (i = MAX_ITER; i; --i) { const double V = (lp.phi + sin(lp.phi) - k) / (1. + cos(lp.phi)); lp.phi -= V; if (fabs(V) < LOOP_TOL) break; } xy.x = 0.5 * lp.lam * (1. + cos(lp.phi)); xy.y = lp.phi; return xy; } static PJ_LP nell_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.lam = 2. * xy.x / (1. + cos(xy.y)); lp.phi = aasin(P->ctx, 0.5 * (xy.y + sin(xy.y))); return lp; } PJ *PJ_PROJECTION(nell) { P->es = 0; P->inv = nell_s_inverse; P->fwd = nell_s_forward; return P; } #undef MAX_ITER #undef LOOP_TOL proj-9.8.1/src/projections/nsper.cpp000664 001750 001750 00000013005 15166171715 017433 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include /* Note: EPSG Guidance 7-2 describes a Vertical Perspective method (EPSG::9838), * that extends 'nsper' with ellipsoidal development, and a ellipsoidal height * of topocentric origin for the projection plan. */ namespace pj_nsper_ns { enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; } namespace { // anonymous namespace struct pj_nsper_data { double height; double sinph0; double cosph0; double p; double rp; double pn1; double pfact; double h; double cg; double sg; double sw; double cw; enum pj_nsper_ns::Mode mode; int tilt; }; } // anonymous namespace PROJ_HEAD(nsper, "Near-sided perspective") "\n\tAzi, Sph\n\th="; PROJ_HEAD(tpers, "Tilted perspective") "\n\tAzi, Sph\n\ttilt= azi= h="; #define EPS10 1.e-10 static PJ_XY nsper_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_nsper_data *Q = static_cast(P->opaque); double coslam, cosphi, sinphi; sinphi = sin(lp.phi); cosphi = cos(lp.phi); coslam = cos(lp.lam); switch (Q->mode) { case pj_nsper_ns::OBLIQ: xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam; break; case pj_nsper_ns::EQUIT: xy.y = cosphi * coslam; break; case pj_nsper_ns::S_POLE: xy.y = -sinphi; break; case pj_nsper_ns::N_POLE: xy.y = sinphi; break; } if (xy.y < Q->rp) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = Q->pn1 / (Q->p - xy.y); xy.x = xy.y * cosphi * sin(lp.lam); switch (Q->mode) { case pj_nsper_ns::OBLIQ: xy.y *= (Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam); break; case pj_nsper_ns::EQUIT: xy.y *= sinphi; break; case pj_nsper_ns::N_POLE: coslam = -coslam; PROJ_FALLTHROUGH; case pj_nsper_ns::S_POLE: xy.y *= cosphi * coslam; break; } if (Q->tilt) { double yt, ba; yt = xy.y * Q->cg + xy.x * Q->sg; ba = 1. / (yt * Q->sw * Q->h + Q->cw); xy.x = (xy.x * Q->cg - xy.y * Q->sg) * Q->cw * ba; xy.y = yt * ba; } return xy; } static PJ_LP nsper_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_nsper_data *Q = static_cast(P->opaque); double rh; if (Q->tilt) { double bm, bq, yt; yt = 1. / (Q->pn1 - xy.y * Q->sw); bm = Q->pn1 * xy.x * yt; bq = Q->pn1 * xy.y * Q->cw * yt; xy.x = bm * Q->cg + bq * Q->sg; xy.y = bq * Q->cg - bm * Q->sg; } rh = hypot(xy.x, xy.y); if (fabs(rh) <= EPS10) { lp.lam = 0.; lp.phi = P->phi0; } else { double cosz, sinz; sinz = 1. - rh * rh * Q->pfact; if (sinz < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } sinz = (Q->p - sqrt(sinz)) / (Q->pn1 / rh + rh / Q->pn1); cosz = sqrt(1. - sinz * sinz); switch (Q->mode) { case pj_nsper_ns::OBLIQ: lp.phi = asin(cosz * Q->sinph0 + xy.y * sinz * Q->cosph0 / rh); xy.y = (cosz - Q->sinph0 * sin(lp.phi)) * rh; xy.x *= sinz * Q->cosph0; break; case pj_nsper_ns::EQUIT: lp.phi = asin(xy.y * sinz / rh); xy.y = cosz * rh; xy.x *= sinz; break; case pj_nsper_ns::N_POLE: lp.phi = asin(cosz); xy.y = -xy.y; break; case pj_nsper_ns::S_POLE: lp.phi = -asin(cosz); break; } lp.lam = atan2(xy.x, xy.y); } return lp; } static PJ *nsper_setup(PJ *P) { struct pj_nsper_data *Q = static_cast(P->opaque); Q->height = pj_param(P->ctx, P->params, "dh").f; if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) Q->mode = P->phi0 < 0. ? pj_nsper_ns::S_POLE : pj_nsper_ns::N_POLE; else if (fabs(P->phi0) < EPS10) Q->mode = pj_nsper_ns::EQUIT; else { Q->mode = pj_nsper_ns::OBLIQ; Q->sinph0 = sin(P->phi0); Q->cosph0 = cos(P->phi0); } Q->pn1 = Q->height / P->a; /* normalize by radius */ if (Q->pn1 <= 0 || Q->pn1 > 1e10) { proj_log_error(P, _("Invalid value for h")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->p = 1. + Q->pn1; Q->rp = 1. / Q->p; Q->h = 1. / Q->pn1; Q->pfact = (Q->p + 1.) * Q->h; P->inv = nsper_s_inverse; P->fwd = nsper_s_forward; P->es = 0.; return P; } PJ *PJ_PROJECTION(nsper) { struct pj_nsper_data *Q = static_cast( calloc(1, sizeof(struct pj_nsper_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->tilt = 0; return nsper_setup(P); } PJ *PJ_PROJECTION(tpers) { double omega, gamma; struct pj_nsper_data *Q = static_cast( calloc(1, sizeof(struct pj_nsper_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; omega = pj_param(P->ctx, P->params, "rtilt").f; gamma = pj_param(P->ctx, P->params, "razi").f; Q->tilt = 1; Q->cg = cos(gamma); Q->sg = sin(gamma); Q->cw = cos(omega); Q->sw = sin(omega); return nsper_setup(P); } #undef EPS10 proj-9.8.1/src/projections/somerc.cpp000664 001750 001750 00000005551 15166171715 017603 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(somerc, "Swiss. Obl. Mercator") "\n\tCyl, Ell\n\tFor CH1903"; namespace { // anonymous namespace struct pj_somerc { double K, c, hlf_e, kR, cosp0, sinp0; }; } // anonymous namespace #define EPS 1.e-10 #define NITER 6 static PJ_XY somerc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; double phip, lamp, phipp, lampp, sp, cp; struct pj_somerc *Q = static_cast(P->opaque); sp = P->e * sin(lp.phi); phip = 2. * atan(exp(Q->c * (log(tan(M_FORTPI + 0.5 * lp.phi)) - Q->hlf_e * log((1. + sp) / (1. - sp))) + Q->K)) - M_HALFPI; lamp = Q->c * lp.lam; cp = cos(phip); phipp = aasin(P->ctx, Q->cosp0 * sin(phip) - Q->sinp0 * cp * cos(lamp)); lampp = aasin(P->ctx, cp * sin(lamp) / cos(phipp)); xy.x = Q->kR * lampp; xy.y = Q->kR * log(tan(M_FORTPI + 0.5 * phipp)); return xy; } static PJ_LP somerc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_somerc *Q = static_cast(P->opaque); double phip, lamp, phipp, lampp, cp, esp, con, delp; int i; phipp = 2. * (atan(exp(xy.y / Q->kR)) - M_FORTPI); lampp = xy.x / Q->kR; cp = cos(phipp); phip = aasin(P->ctx, Q->cosp0 * sin(phipp) + Q->sinp0 * cp * cos(lampp)); lamp = aasin(P->ctx, cp * sin(lampp) / cos(phip)); con = (Q->K - log(tan(M_FORTPI + 0.5 * phip))) / Q->c; for (i = NITER; i; --i) { esp = P->e * sin(phip); delp = (con + log(tan(M_FORTPI + 0.5 * phip)) - Q->hlf_e * log((1. + esp) / (1. - esp))) * (1. - esp * esp) * cos(phip) * P->rone_es; phip -= delp; if (fabs(delp) < EPS) break; } if (i) { lp.phi = phip; lp.lam = lamp / Q->c; } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return (lp); } PJ *PJ_PROJECTION(somerc) { double cp, phip0, sp; struct pj_somerc *Q = static_cast(calloc(1, sizeof(struct pj_somerc))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->hlf_e = 0.5 * P->e; cp = cos(P->phi0); cp *= cp; Q->c = sqrt(1 + P->es * cp * cp * P->rone_es); sp = sin(P->phi0); Q->sinp0 = sp / Q->c; phip0 = aasin(P->ctx, Q->sinp0); Q->cosp0 = cos(phip0); sp *= P->e; Q->K = log(tan(M_FORTPI + 0.5 * phip0)) - Q->c * (log(tan(M_FORTPI + 0.5 * P->phi0)) - Q->hlf_e * log((1. + sp) / (1. - sp))); Q->kR = P->k0 * sqrt(P->one_es) / (1. - sp * sp); P->inv = somerc_e_inverse; P->fwd = somerc_e_forward; return P; } #undef EPS #undef NITER proj-9.8.1/src/projections/eck5.cpp000664 001750 001750 00000001406 15166171715 017135 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(eck5, "Eckert V") "\n\tPCyl, Sph"; #define XF 0.44101277172455148219 #define RXF 2.26750802723822639137 #define YF 0.88202554344910296438 #define RYF 1.13375401361911319568 static PJ_XY eck5_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = XF * (1. + cos(lp.phi)) * lp.lam; xy.y = YF * lp.phi; return xy; } static PJ_LP eck5_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = RYF * xy.y; lp.lam = RXF * xy.x / (1. + cos(lp.phi)); return lp; } PJ *PJ_PROJECTION(eck5) { P->es = 0.0; P->inv = eck5_s_inverse; P->fwd = eck5_s_forward; return P; } proj-9.8.1/src/projections/isea.cpp000664 001750 001750 00000122500 15166171715 017226 0ustar00eveneven000000 000000 /* The public domain code for the forward direction was initially written by Nathan Wagner. The inverse projection was adapted from Java and eC by Jérôme Jacovella-St-Louis, originally from the Franz-Benjamin Mocnik's ISEA implementation found at https://github.com/mocnik-science/geogrid/blob/master/ src/main/java/org/giscience/utils/geogrid/projections/ISEAProjection.java with the following license: -------------------------------------------------------------------------- MIT License Copyright (c) 2017-2019 Heidelberg University 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. */ /* The ISEA projection a projects a sphere on the icosahedron. Thereby the size * of areas mapped to the icosahedron are preserved. Angles and distances are * however slightly distorted. The angular distortion is below 17.27 degree, and * the scale variation is less than 16.3 per cent. * * The projection has been proposed and has been described in detail by: * * John P. Snyder: An equal-area map projection for polyhedral globes. * Cartographica, 29(1), 10–21, 1992. doi:10.3138/27H7-8K88-4882-1752 * * Another description and improvements can be found in: * * Erika Harrison, Ali Mahdavi-Amiri, and Faramarz Samavati: Optimization of * inverse Snyder polyhedral projection. International Conference on Cyberworlds * 2011. doi:10.1109/CW.2011.36 * * Erika Harrison, Ali Mahdavi-Amiri, and Faramarz Samavati: Analysis of inverse * Snyder optimizations. In: Marina L. Gavrilova, and C. J. Kenneth Tan (Eds): * Transactions on Computational Science XVI. Heidelberg, Springer, 2012. pp. * 134–148. doi:10.1007/978-3-642-32663-9_8 */ #include #include #include #include #include #include #include #include #include "proj.h" #include "proj_internal.h" #include #define DEG36 0.62831853071795864768 #define DEG72 1.25663706143591729537 #define DEG90 M_PI_2 #define DEG108 1.88495559215387594306 #define DEG120 2.09439510239319549229 #define DEG144 2.51327412287183459075 #define DEG180 M_PI /* sqrt(5)/M_PI */ #define ISEA_SCALE 0.8301572857837594396028083 /* 26.565051177 degrees */ #define V_LAT 0.46364760899944494524 // Latitude of center of top icosahedron faces // atan((3 + sqrt(5)) / 4) = 52.6226318593487 degrees #define E_RAD 0.91843818701052843323 // Latitude of center of faces mirroring top icosahedron face // atan((3 - sqrt(5)) / 4) = 10.8123169635739 degrees #define F_RAD 0.18871053078356206978 // #define phi ((1 + sqrt(5)) / 2) // #define atanphi 1.01722196789785136772 // g: Spherical distance from center of polygon face to // any of its vertices on the sphere // g = F + 2 * atan(phi) - 90 deg -- sdc2vos #define sdc2vos 0.6523581397843681859886783 #define tang 0.76393202250021030358019673567 // tan(sdc2vos) // theta (30 degrees) is plane angle between radius // vector to center and adjacent edge of plane polygon #define tan30 0.57735026918962576450914878 // tan(DEG_TO_RAD * 30) #define cotTheta (1.0 / tan30) // G: spherical angle between radius vector to center and adjacent edge // of spherical polygon on the globe (36 degrees) // cos(DEG_TO_RAD * 36) #define cosG 0.80901699437494742410229341718281905886 // sin(DEG_TO_RAD * 36) #define sinG 0.587785252292473129168705954639072768597652 // cos(g) #define cosSDC2VoS 0.7946544722917661229596057297879189448539 #define sinGcosSDC2VoS (sinG * cosSDC2VoS) // sin G cos g #define SQRT3 1.73205080756887729352744634150587236694280525381038 #define sin60 (SQRT3 / 2.0) #define cos30 (SQRT3 / 2.0) // tang * sin(60 deg) #define TABLE_G (tang * sin60) // (1 / (2 * sqrt(5)) + 1 / 6.0) * sqrt(M_PI * sqrt(3)) #define RprimeOverR 0.9103832815095032 // R' / R /* H = 0.25 R tan g = */ #define TABLE_H (0.25 * tang) /* in radians */ #define ISEA_STD_LAT 1.01722196792335072101 #define ISEA_STD_LONG .19634954084936207740 namespace { // anonymous namespace struct GeoPoint { double lat, lon; }; // In radians struct hex { int iso; long x, y, z; }; } // anonymous namespace /* y *must* be positive down as the xy /iso conversion assumes this */ static void hex_xy(struct hex *h) { if (!h->iso) return; if (h->x >= 0) { h->y = -h->y - (h->x + 1) / 2; } else { /* need to round toward -inf, not toward zero, so x-1 */ h->y = -h->y - h->x / 2; } h->iso = 0; } static void hex_iso(struct hex *h) { if (h->iso) return; if (h->x >= 0) { h->y = (-h->y - (h->x + 1) / 2); } else { /* need to round toward -inf, not toward zero, so x-1 */ h->y = (-h->y - (h->x) / 2); } h->z = -h->x - h->y; h->iso = 1; } static void hexbin2(double width, double x, double y, long *i, long *j) { double z, rx, ry, rz; double abs_dx, abs_dy, abs_dz; long ix, iy, iz, s; struct hex h; x = x / cos(30 * M_PI / 180.0); /* rotated X coord */ y = y - x / 2.0; /* adjustment for rotated X */ /* adjust for actual hexwidth */ if (width == 0) { throw "Division by zero"; } x /= width; y /= width; z = -x - y; rx = floor(x + 0.5); ix = lround(rx); ry = floor(y + 0.5); iy = lround(ry); rz = floor(z + 0.5); iz = lround(rz); if (fabs((double)ix + iy) > std::numeric_limits::max() || fabs((double)ix + iy + iz) > std::numeric_limits::max()) { throw "Integer overflow"; } s = ix + iy + iz; if (s) { abs_dx = fabs(rx - x); abs_dy = fabs(ry - y); abs_dz = fabs(rz - z); if (abs_dx >= abs_dy && abs_dx >= abs_dz) { ix -= s; } else if (abs_dy >= abs_dx && abs_dy >= abs_dz) { iy -= s; } else { iz -= s; } } h.x = ix; h.y = iy; h.z = iz; h.iso = 1; hex_xy(&h); *i = h.x; *j = h.y; } #define numIcosahedronFaces 20 namespace { // anonymous namespace enum isea_address_form { ISEA_PLANE, ISEA_Q2DI, ISEA_Q2DD, ISEA_HEX }; struct isea_sincos { double s, c; }; struct isea_pt { double x, y; }; } // anonymous namespace // distortion // static double maximumAngularDistortion = 17.27; // static double maximumScaleVariation = 1.163; // static double minimumScaleVariation = .860; // Vertices of dodecahedron centered in icosahedron triangular faces static const GeoPoint facesCenterDodecahedronVertices[numIcosahedronFaces] = { {E_RAD, DEG_TO_RAD * -144}, {E_RAD, DEG_TO_RAD * -72}, {E_RAD, DEG_TO_RAD * 0}, {E_RAD, DEG_TO_RAD * 72}, {E_RAD, DEG_TO_RAD * 144}, {F_RAD, DEG_TO_RAD * -144}, {F_RAD, DEG_TO_RAD * -72}, {F_RAD, DEG_TO_RAD * 0}, {F_RAD, DEG_TO_RAD * 72}, {F_RAD, DEG_TO_RAD * 144}, {-F_RAD, DEG_TO_RAD * -108}, {-F_RAD, DEG_TO_RAD * -36}, {-F_RAD, DEG_TO_RAD * 36}, {-F_RAD, DEG_TO_RAD * 108}, {-F_RAD, DEG_TO_RAD * 180}, {-E_RAD, DEG_TO_RAD * -108}, {-E_RAD, DEG_TO_RAD * -36}, {-E_RAD, DEG_TO_RAD * 36}, {-E_RAD, DEG_TO_RAD * 108}, {-E_RAD, DEG_TO_RAD * 180}}; // NOTE: Very similar to ISEAPlanarProjection::faceOrientation(), // but the forward projection sometimes is returning a negative M_PI static inline double az_adjustment(int triangle) { if ((triangle >= 5 && triangle <= 9) || triangle == 15 || triangle == 16) return M_PI; else if (triangle >= 17) return -M_PI; return 0; } static struct isea_pt isea_triangle_xy(int triangle) { struct isea_pt c; triangle %= numIcosahedronFaces; c.x = TABLE_G * ((triangle % 5) - 2) * 2.0; if (triangle > 9) { c.x += TABLE_G; } // REVIEW: This is likely related to // pj_isea_data::yOffsets switch (triangle / 5) { case 0: c.y = 5.0 * TABLE_H; break; case 1: c.y = TABLE_H; break; case 2: c.y = -TABLE_H; break; case 3: c.y = -5.0 * TABLE_H; break; default: /* should be impossible */ exit(EXIT_FAILURE); } c.x *= RprimeOverR; c.y *= RprimeOverR; return c; } namespace { // anonymous namespace class ISEAPlanarProjection; struct pj_isea_data { double o_lat, o_lon, o_az; /* orientation, radians */ int aperture; /* valid values depend on partitioning method */ int resolution; isea_address_form output; /* an isea_address_form */ int triangle; /* triangle of last transformed point */ int quad; /* quad of last transformed point */ isea_sincos vertexLatSinCos[numIcosahedronFaces]; double R2; double Rprime; double Rprime2X; double RprimeTang; double Rprime2Tan2g; double triTang; double centerToBase; double triWidth; double yOffsets[4]; double xo, yo; double sx, sy; ISEAPlanarProjection *p; void initialize(const PJ *P); }; } // anonymous namespace #ifdef _MSC_VER #pragma warning(push) /* disable unreachable code warning for return 0 */ #pragma warning(disable : 4702) #endif #define SAFE_ARC_EPSILON 1E-15 static inline double safeArcSin(double t) { return fabs(t) < SAFE_ARC_EPSILON ? 0 : fabs(t - 1.0) < SAFE_ARC_EPSILON ? M_PI / 2 : fabs(t + 1.0) < SAFE_ARC_EPSILON ? -M_PI / 2 : asin(t); } static inline double safeArcCos(double t) { return fabs(t) < SAFE_ARC_EPSILON ? M_PI / 2 : fabs(t + 1) < SAFE_ARC_EPSILON ? M_PI : fabs(t - 1) < SAFE_ARC_EPSILON ? 0 : acos(t); } #undef SAFE_ARC_EPSILON /* coord needs to be in radians */ static int isea_snyder_forward(const struct pj_isea_data *data, const struct GeoPoint *ll, struct isea_pt *out) { int i; double sinLat = sin(ll->lat), cosLat = cos(ll->lat); /* * TODO by locality of reference, start by trying the same triangle * as last time */ for (i = 0; i < numIcosahedronFaces; i++) { /* additional variables from snyder */ double q, H, Ag, Azprime, Az, dprime, f, rho, x, y; /* variables used to store intermediate results */ double az_offset; /* how many multiples of 60 degrees we adjust the azimuth */ int Az_adjust_multiples; const struct GeoPoint *center = &facesCenterDodecahedronVertices[i]; const struct isea_sincos *centerLatSinCos = &data->vertexLatSinCos[i]; double dLon = ll->lon - center->lon; double cosLat_cosLon = cosLat * cos(dLon); double cosZ = centerLatSinCos->s * sinLat + centerLatSinCos->c * cosLat_cosLon; double sinAz, cosAz; /* step 1 */ double z = safeArcCos(cosZ); /* not on this triangle */ if (z > sdc2vos /*g*/ + 0.000005) { /* TODO DBL_EPSILON */ continue; } /* snyder eq 14 */ Az = atan2(cosLat * sin(dLon), centerLatSinCos->c * sinLat - centerLatSinCos->s * cosLat_cosLon); /* step 2 */ /* This calculates "some" vertex coordinate */ az_offset = az_adjustment(i); Az -= az_offset; /* TODO I don't know why we do this. It's not in snyder */ /* maybe because we should have picked a better vertex */ if (Az < 0.0) { Az += 2.0 * M_PI; } /* * adjust Az for the point to fall within the range of 0 to * 2(90 - theta) or 60 degrees for the hexagon, by * and therefore 120 degrees for the triangle * of the icosahedron * subtracting or adding multiples of 60 degrees to Az and * recording the amount of adjustment */ Az_adjust_multiples = 0; while (Az < 0.0) { Az += DEG120; Az_adjust_multiples--; } while (Az > DEG120 + DBL_EPSILON) { Az -= DEG120; Az_adjust_multiples++; } /* step 3 */ /* Calculate q from eq 9. */ cosAz = cos(Az); sinAz = sin(Az); q = atan2(tang, cosAz + sinAz * cotTheta); /* not in this triangle */ if (z > q + 0.000005) { continue; } /* step 4 */ /* Apply equations 5-8 and 10-12 in order */ /* eq 5 */ /* R' in the paper is for the truncated (icosahedron?) */ /* eq 6 */ H = acos(sinAz * sinGcosSDC2VoS /* sin(G) * cos(g) */ - cosAz * cosG); /* eq 7 */ /* Ag = (Az + G + H - DEG180) * M_PI * R * R / DEG180; */ Ag = Az + DEG_TO_RAD * 36 /* G */ + H - DEG180; /* eq 8 */ Azprime = atan2(2.0 * Ag, RprimeOverR * RprimeOverR * tang * tang - 2.0 * Ag * cotTheta); /* eq 10 */ /* cot(theta) = 1.73205080756887729355 */ dprime = RprimeOverR * tang / (cos(Azprime) + sin(Azprime) * cotTheta); /* eq 11 */ f = dprime / (2.0 * RprimeOverR * sin(q / 2.0)); /* eq 12 */ rho = 2.0 * RprimeOverR * f * sin(z / 2.0); /* * add back the same 60 degree multiple adjustment from step * 2 to Azprime */ Azprime += DEG120 * Az_adjust_multiples; /* calculate rectangular coordinates */ x = rho * sin(Azprime); y = rho * cos(Azprime); /* * TODO * translate coordinates to the origin for the particular * hexagon on the flattened polyhedral map plot */ out->x = x; out->y = y; return i; } /* * should be impossible, this implies that the coordinate is not on * any triangle */ fprintf(stderr, "impossible transform: %f %f is not on any triangle\n", PJ_TODEG(ll->lon), PJ_TODEG(ll->lat)); exit(EXIT_FAILURE); } #ifdef _MSC_VER #pragma warning(pop) #endif /* * return the new coordinates of any point in original coordinate system. * Define a point (newNPold) in original coordinate system as the North Pole in * new coordinate system, and the great circle connect the original and new * North Pole as the lon0 longitude in new coordinate system, given any point * in original coordinate system, this function return the new coordinates. */ /* formula from Snyder, Map Projections: A working manual, p31 */ /* * old north pole at np in new coordinates * could be simplified a bit with fewer intermediates * * TODO take a result pointer */ static struct GeoPoint snyder_ctran(const struct GeoPoint &np, const struct GeoPoint &pt) { struct GeoPoint result; double phi = pt.lat, lambda = pt.lon; double alpha = np.lat, beta = np.lon; double dlambda = lambda - beta /* lambda0 */; double cos_p = cos(phi), sin_p = sin(phi); double cos_a = cos(alpha), sin_a = sin(alpha); double cos_dlambda = cos(dlambda), sin_dlambda = sin(dlambda); /* mpawm 5-7 */ double sin_phip = sin_a * sin_p - cos_a * cos_p * cos_dlambda; /* mpawm 5-8b */ /* use the two argument form so we end up in the right quadrant */ double lp_b = /* lambda prime minus beta */ atan2(cos_p * sin_dlambda, sin_a * cos_p * cos_dlambda + cos_a * sin_p); double lambdap = lp_b + beta; /* normalize longitude */ /* TODO can we just do a modulus ? */ lambdap = fmod(lambdap, 2 * M_PI); while (lambdap > M_PI) lambdap -= 2 * M_PI; while (lambdap < -M_PI) lambdap += 2 * M_PI; result.lat = safeArcSin(sin_phip); result.lon = lambdap; return result; } static struct GeoPoint isea_ctran(const struct GeoPoint *np, const struct GeoPoint *pt, double lon0) { struct GeoPoint cnp = {np->lat, np->lon + M_PI}; struct GeoPoint npt = snyder_ctran(cnp, *pt); npt.lon -= (/* M_PI */ -lon0 + np->lon); /* * snyder is down tri 3, isea is along side of tri1 from vertex 0 to * vertex 1 these are 180 degrees apart */ // npt.lon += M_PI; /* normalize lon */ npt.lon = fmod(npt.lon, 2 * M_PI); while (npt.lon > M_PI) npt.lon -= 2 * M_PI; while (npt.lon < -M_PI) npt.lon += 2 * M_PI; return npt; } /* fuller's at 5.2454 west, 2.3009 N, adjacent at 7.46658 deg */ static int isea_grid_init(struct pj_isea_data *g) { int i; if (!g) return 0; g->o_lat = ISEA_STD_LAT; g->o_lon = ISEA_STD_LONG; g->o_az = 0.0; g->aperture = 4; g->resolution = 6; for (i = 0; i < numIcosahedronFaces; i++) { const GeoPoint *c = &facesCenterDodecahedronVertices[i]; g->vertexLatSinCos[i].s = sin(c->lat); g->vertexLatSinCos[i].c = cos(c->lat); } return 1; } static void isea_orient_isea(struct pj_isea_data *g) { if (!g) return; g->o_lat = ISEA_STD_LAT; g->o_lon = ISEA_STD_LONG; g->o_az = 0.0; } static void isea_orient_pole(struct pj_isea_data *g) { if (!g) return; g->o_lat = M_PI / 2.0; g->o_lon = 0.0; g->o_az = 0; } static int isea_transform(struct pj_isea_data *g, struct GeoPoint *in, struct isea_pt *out) { struct GeoPoint i, pole; int tri; pole.lat = g->o_lat; pole.lon = g->o_lon; i = isea_ctran(&pole, in, g->o_az); tri = isea_snyder_forward(g, &i, out); g->triangle = tri; return tri; } #define DOWNTRI(tri) ((tri / 5) % 2 == 1) static void isea_rotate(struct isea_pt *pt, double degrees) { double rad; double x, y; rad = -degrees * M_PI / 180.0; while (rad >= 2.0 * M_PI) rad -= 2.0 * M_PI; while (rad <= -2.0 * M_PI) rad += 2.0 * M_PI; x = pt->x * cos(rad) + pt->y * sin(rad); y = -pt->x * sin(rad) + pt->y * cos(rad); pt->x = x; pt->y = y; } static void isea_tri_plane(int tri, struct isea_pt *pt) { struct isea_pt tc; /* center of triangle */ if (DOWNTRI(tri)) { pt->x *= -1; pt->y *= -1; } tc = isea_triangle_xy(tri); pt->x += tc.x; pt->y += tc.y; } /* convert projected triangle coords to quad xy coords, return quad number */ static int isea_ptdd(int tri, struct isea_pt *pt) { int downtri, quadz; downtri = ((tri / 5) % 2 == 1); quadz = (tri % 5) + (tri / 10) * 5 + 1; // NOTE: This would always be a 60 degrees rotation if the flip were // already done as in isea_tri_plane() isea_rotate(pt, downtri ? 240.0 : 60.0); if (downtri) { pt->x += 0.5; /* pt->y += cos(30.0 * M_PI / 180.0); */ pt->y += cos30; } return quadz; } static int isea_dddi_ap3odd(struct pj_isea_data *g, int quadz, struct isea_pt *pt, struct isea_pt *di) { struct isea_pt v; double hexwidth; double sidelength; /* in hexes */ long d, i; long maxcoord; struct hex h; /* This is the number of hexes from apex to base of a triangle */ sidelength = (pow(2.0, g->resolution) + 1.0) / 2.0; /* apex to base is cos(30deg) */ hexwidth = cos(M_PI / 6.0) / sidelength; /* TODO I think sidelength is always x.5, so * (int)sidelength * 2 + 1 might be just as good */ maxcoord = lround((sidelength * 2.0)); v = *pt; hexbin2(hexwidth, v.x, v.y, &h.x, &h.y); h.iso = 0; hex_iso(&h); d = h.x - h.z; i = h.x + h.y + h.y; /* * you want to test for max coords for the next quad in the same * "row" first to get the case where both are max */ if (quadz <= 5) { if (d == 0 && i == maxcoord) { /* north pole */ quadz = 0; d = 0; i = 0; } else if (i == maxcoord) { /* upper right in next quad */ quadz += 1; if (quadz == 6) quadz = 1; i = maxcoord - d; d = 0; } else if (d == maxcoord) { /* lower right in quad to lower right */ quadz += 5; d = 0; } } else /* if (quadz >= 6) */ { if (i == 0 && d == maxcoord) { /* south pole */ quadz = 11; d = 0; i = 0; } else if (d == maxcoord) { /* lower right in next quad */ quadz += 1; if (quadz == 11) quadz = 6; d = maxcoord - i; i = 0; } else if (i == maxcoord) { /* upper right in quad to upper right */ quadz = (quadz - 4) % 5; i = 0; } } di->x = d; di->y = i; g->quad = quadz; return quadz; } static int isea_dddi(struct pj_isea_data *g, int quadz, struct isea_pt *pt, struct isea_pt *di) { struct isea_pt v; double hexwidth; long sidelength; /* in hexes */ struct hex h; if (g->aperture == 3 && g->resolution % 2 != 0) { return isea_dddi_ap3odd(g, quadz, pt, di); } /* todo might want to do this as an iterated loop */ if (g->aperture > 0) { double sidelengthDouble = pow(g->aperture, g->resolution / 2.0); if (fabs(sidelengthDouble) > std::numeric_limits::max()) { throw "Integer overflow"; } sidelength = lround(sidelengthDouble); } else { sidelength = g->resolution; } if (sidelength == 0) { throw "Division by zero"; } hexwidth = 1.0 / sidelength; v = *pt; isea_rotate(&v, -30.0); hexbin2(hexwidth, v.x, v.y, &h.x, &h.y); h.iso = 0; hex_iso(&h); /* we may actually be on another quad */ if (quadz <= 5) { if (h.x == 0 && h.z == -sidelength) { /* north pole */ quadz = 0; h.z = 0; h.y = 0; h.x = 0; } else if (h.z == -sidelength) { quadz = quadz + 1; if (quadz == 6) quadz = 1; h.y = sidelength - h.x; h.z = h.x - sidelength; h.x = 0; } else if (h.x == sidelength) { quadz += 5; h.y = -h.z; h.x = 0; } } else /* if (quadz >= 6) */ { if (h.z == 0 && h.x == sidelength) { /* south pole */ quadz = 11; h.x = 0; h.y = 0; h.z = 0; } else if (h.x == sidelength) { quadz = quadz + 1; if (quadz == 11) quadz = 6; h.x = h.y + sidelength; h.y = 0; h.z = -h.x; } else if (h.y == -sidelength) { quadz -= 4; h.y = 0; h.z = -h.x; } } di->x = h.x; di->y = -h.z; g->quad = quadz; return quadz; } static int isea_ptdi(struct pj_isea_data *g, int tri, struct isea_pt *pt, struct isea_pt *di) { struct isea_pt v; int quadz; v = *pt; quadz = isea_ptdd(tri, &v); quadz = isea_dddi(g, quadz, &v, di); return quadz; } /* TODO just encode the quad in the d or i coordinate * quad is 0-11, which can be four bits. * d' = d << 4 + q, d = d' >> 4, q = d' & 0xf */ /* convert a q2di to global hex coord */ static int isea_hex(struct pj_isea_data *g, int tri, struct isea_pt *pt, struct isea_pt *hex) { struct isea_pt v; #ifdef FIXME long sidelength; long d, i, x, y; #endif int quadz; quadz = isea_ptdi(g, tri, pt, &v); if (v.x < (INT_MIN >> 4) || v.x > (INT_MAX >> 4)) { throw "Invalid shift"; } hex->x = ((int)v.x * 16) + quadz; hex->y = v.y; return 1; #ifdef FIXME d = lround(floor(v.x)); i = lround(floor(v.y)); /* Aperture 3 odd resolutions */ if (g->aperture == 3 && g->resolution % 2 != 0) { long offset = lround((pow(3.0, g->resolution - 1) + 0.5)); d += offset * ((g->quadz - 1) % 5); i += offset * ((g->quadz - 1) % 5); if (quadz == 0) { d = 0; i = offset; } else if (quadz == 11) { d = 2 * offset; i = 0; } else if (quadz > 5) { d += offset; } x = (2 * d - i) / 3; y = (2 * i - d) / 3; hex->x = x + offset / 3; hex->y = y + 2 * offset / 3; return 1; } /* aperture 3 even resolutions and aperture 4 */ sidelength = lround((pow(g->aperture, g->resolution / 2.0))); if (g->quad == 0) { hex->x = 0; hex->y = sidelength; } else if (g->quad == 11) { hex->x = sidelength * 2; hex->y = 0; } else { hex->x = d + sidelength * ((g->quad - 1) % 5); if (g->quad > 5) hex->x += sidelength; hex->y = i + sidelength * ((g->quad - 1) % 5); } return 1; #endif } static struct isea_pt isea_forward(struct pj_isea_data *g, struct GeoPoint *in) { isea_pt out; int tri = isea_transform(g, in, &out); if (g->output == ISEA_PLANE) isea_tri_plane(tri, &out); else { isea_pt coord; /* convert to isea standard triangle size */ out.x *= ISEA_SCALE; // / g->radius; out.y *= ISEA_SCALE; // / g->radius; out.x += 0.5; out.y += 2.0 * .14433756729740644112; switch (g->output) { case ISEA_PLANE: /* already handled above -- GCC should not be complaining */ case ISEA_Q2DD: /* Same as above, we just don't print as much */ g->quad = isea_ptdd(tri, &out); break; case ISEA_Q2DI: g->quad = isea_ptdi(g, tri, &out, &coord); return coord; case ISEA_HEX: isea_hex(g, tri, &out, &coord); return coord; } } return out; } /* * Proj 4 integration code follows */ PROJ_HEAD(isea, "Icosahedral Snyder Equal Area") "\n\tSph"; static PJ_XY isea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_isea_data *Q = static_cast(P->opaque); struct isea_pt out; struct GeoPoint in; // TODO: Convert geodetic latitude to authalic latitude if not // spherical as in eqearth, healpix, laea, etc. in.lat = lp.phi; in.lon = lp.lam; try { out = isea_forward(Q, &in); } catch (const char *) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } xy.x = out.x; xy.y = out.y; return xy; } static PJ_LP isea_s_inverse(PJ_XY xy, PJ *P); PJ *PJ_PROJECTION(isea) { char *opt; struct pj_isea_data *Q = static_cast( calloc(1, sizeof(struct pj_isea_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; // NOTE: if a inverse was needed, there is some material at // https://brsr.github.io/2021/08/31/snyder-equal-area.html P->fwd = isea_s_forward; P->inv = isea_s_inverse; isea_grid_init(Q); Q->output = ISEA_PLANE; /* P->radius = P->a; / * otherwise defaults to 1 */ /* calling library will scale, I think */ opt = pj_param(P->ctx, P->params, "sorient").s; if (opt) { if (!strcmp(opt, "isea")) { isea_orient_isea(Q); } else if (!strcmp(opt, "pole")) { isea_orient_pole(Q); } else { proj_log_error( P, _("Invalid value for orient: only isea or pole are supported")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (pj_param(P->ctx, P->params, "tazi").i) { Q->o_az = pj_param(P->ctx, P->params, "razi").f; } if (pj_param(P->ctx, P->params, "tlon_0").i) { Q->o_lon = pj_param(P->ctx, P->params, "rlon_0").f; } if (pj_param(P->ctx, P->params, "tlat_0").i) { Q->o_lat = pj_param(P->ctx, P->params, "rlat_0").f; } opt = pj_param(P->ctx, P->params, "smode").s; if (opt) { if (!strcmp(opt, "plane")) { Q->output = ISEA_PLANE; } else if (!strcmp(opt, "di")) { Q->output = ISEA_Q2DI; } else if (!strcmp(opt, "dd")) { Q->output = ISEA_Q2DD; } else if (!strcmp(opt, "hex")) { Q->output = ISEA_HEX; } else { proj_log_error(P, _("Invalid value for mode: only plane, di, dd or " "hex are supported")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } /* REVIEW: Was this an undocumented +rescale= parameter? if (pj_param(P->ctx, P->params, "trescale").i) { Q->radius = ISEA_SCALE; } */ if (pj_param(P->ctx, P->params, "tresolution").i) { Q->resolution = pj_param(P->ctx, P->params, "iresolution").i; } else { Q->resolution = 4; } if (pj_param(P->ctx, P->params, "taperture").i) { Q->aperture = pj_param(P->ctx, P->params, "iaperture").i; } else { Q->aperture = 3; } Q->initialize(P); return P; } #define Min std::min #define Max std::max #define inf std::numeric_limits::infinity() // static define precision = DEG_TO_RAD * 1e-9; #define precision (DEG_TO_RAD * 1e-11) #define precisionPerDefinition (DEG_TO_RAD * 1e-5) #define AzMax (DEG_TO_RAD * 120) #define westVertexLon (DEG_TO_RAD * -144) namespace { // anonymous namespace struct ISEAFacePoint { int face; double x, y; }; class ISEAPlanarProjection { public: explicit ISEAPlanarProjection(const GeoPoint &value) : orientation(value), cosOrientationLat(cos(value.lat)), sinOrientationLat(sin(value.lat)) {} bool cartesianToGeo(const PJ_XY &inPosition, const pj_isea_data *params, GeoPoint &result) { bool r = false; static const double epsilon = 1E-11; int face = 0; PJ_XY position = inPosition; #define sr -sin60 // sin(-60) #define cr 0.5 // cos(-60) if (position.x < 0 || (position.x < params->triWidth / 2 && position.y < 0 && position.y * cr < position.x * sr)) position.x += 5 * params->triWidth; // Wrap around // Rotate and shear to determine face if not stored in position.z #define shearX (1.0 / SQRT3) double yp = -(position.x * sr + position.y * cr); double x = (position.x * cr - position.y * sr + yp * shearX) * params->sx; double y = yp * params->sy; #undef shearX #undef sr #undef cr if (x < 0 || (y > x && x < 5 - epsilon)) x += epsilon; else if (x > 5 || (y < x && x > 0 + epsilon)) x -= epsilon; if (y < 0 || (x > y && y < 6 - epsilon)) y += epsilon; else if (y > 6 || (x < y && y > 0 + epsilon)) y -= epsilon; if (x >= 0 && x <= 5 && y >= 0 && y <= 6) { int ix = Max(0, Min(4, (int)x)); int iy = Max(0, Min(5, (int)y)); if (iy == ix || iy == ix + 1) { int rhombus = ix + iy; bool top = x - ix > y - iy; face = -1; switch (rhombus) { case 0: face = top ? 0 : 5; break; case 2: face = top ? 1 : 6; break; case 4: face = top ? 2 : 7; break; case 6: face = top ? 3 : 8; break; case 8: face = top ? 4 : 9; break; case 1: face = top ? 10 : 15; break; case 3: face = top ? 11 : 16; break; case 5: face = top ? 12 : 17; break; case 7: face = top ? 13 : 18; break; case 9: face = top ? 14 : 19; break; } face++; } } if (face) { int fy = (face - 1) / 5, fx = (face - 1) - 5 * fy; double rx = position.x - (2 * fx + fy / 2 + 1) * params->triWidth / 2; double ry = position.y - (params->yOffsets[fy] + 3 * params->centerToBase); GeoPoint dst; r = icosahedronToSphere({face - 1, rx, ry}, params, dst); if (dst.lon < -M_PI - epsilon) dst.lon += 2 * M_PI; else if (dst.lon > M_PI + epsilon) dst.lon -= 2 * M_PI; result = {dst.lat, dst.lon}; } return r; } // Converts coordinates on the icosahedron to geographic coordinates // (inverse projection) bool icosahedronToSphere(const ISEAFacePoint &c, const pj_isea_data *params, GeoPoint &r) { if (c.face >= 0 && c.face < numIcosahedronFaces) { double Az = atan2(c.x, c.y); // Az' double rho = sqrt(c.x * c.x + c.y * c.y); double AzAdjustment = faceOrientation(c.face); Az += AzAdjustment; while (Az < 0) { AzAdjustment += AzMax; Az += AzMax; } while (Az > AzMax) { AzAdjustment -= AzMax; Az -= AzMax; } { double sinAz = sin(Az), cosAz = cos(Az); double cotAz = cosAz / sinAz; double area = params->Rprime2Tan2g / (2 * (cotAz + cotTheta)); // A_G or A_{ABD} double deltaAz = 10 * precision; double degAreaOverR2Plus180Minus36 = area / params->R2 - westVertexLon; double Az_earth = Az; while (fabs(deltaAz) > precision) { double sinAzEarth = sin(Az_earth), cosAzEarth = cos(Az_earth); double H = acos(sinAzEarth * sinGcosSDC2VoS - cosAzEarth * cosG); double FAz_earth = degAreaOverR2Plus180Minus36 - H - Az_earth; // F(Az) or g(Az) double F2Az_earth = (cosAzEarth * sinGcosSDC2VoS + sinAzEarth * cosG) / sin(H) - 1; // F'(Az) or g'(Az) deltaAz = -FAz_earth / F2Az_earth; // Delta Az^0 or Delta Az Az_earth += deltaAz; } { double sinAz_earth = sin(Az_earth), cosAz_earth = cos(Az_earth); double q = atan2(tang, (cosAz_earth + sinAz_earth * cotTheta)); double d = params->RprimeTang / (cosAz + sinAz * cotTheta); // d' double f = d / (params->Rprime2X * sin(q / 2)); // f double z = 2 * asin(rho / (params->Rprime2X * f)); Az_earth -= AzAdjustment; { const isea_sincos *latSinCos = ¶ms->vertexLatSinCos[c.face]; double sinLat0 = latSinCos->s, cosLat0 = latSinCos->c; double sinZ = sin(z), cosZ = cos(z); double cosLat0SinZ = cosLat0 * sinZ; double latSin = sinLat0 * cosZ + cosLat0SinZ * cos(Az_earth); double lat = safeArcSin(latSin); double lon = facesCenterDodecahedronVertices[c.face].lon + atan2(sin(Az_earth) * cosLat0SinZ, cosZ - sinLat0 * sin(lat)); revertOrientation({lat, lon}, r); } } } return true; } r = {inf, inf}; return false; } private: GeoPoint orientation; double cosOrientationLat, sinOrientationLat; inline void revertOrientation(const GeoPoint &c, GeoPoint &r) { double lon = (c.lat < DEG_TO_RAD * -90 + precisionPerDefinition || c.lat > DEG_TO_RAD * 90 - precisionPerDefinition) ? 0 : c.lon; if (orientation.lat != 0.0 || orientation.lon != 0.0) { double sinLat = sin(c.lat), cosLat = cos(c.lat); double sinLon = sin(lon), cosLon = cos(lon); double cosLonCosLat = cosLon * cosLat; r = {asin(sinLat * cosOrientationLat - cosLonCosLat * sinOrientationLat), atan2(sinLon * cosLat, cosLonCosLat * cosOrientationLat + sinLat * sinOrientationLat) - orientation.lon}; } else r = {c.lat, lon}; } static inline double faceOrientation(int face) { return (face <= 4 || (10 <= face && face <= 14)) ? 0 : DEG_TO_RAD * 180; } }; // Orientation symmetric to equator (+proj=isea) /* Sets the orientation of the icosahedron such that the north and the south * poles are mapped to the edge midpoints of the icosahedron. The equator is * thus mapped symmetrically. */ static ISEAPlanarProjection standardISEA( /* DEG_TO_RAD * (90 - 58.282525589) = 31.7174744114613 */ {(E_RAD + F_RAD) / 2, DEG_TO_RAD * -11.25}); // Polar orientation (+proj=isea +orient=pole) /* * One corner of the icosahedron is, by default, facing to the north pole, and * one to the south pole. The provided orientation is relative to the default * orientation. * * The orientation shifts every location by the angle orientation.lon in * direction of positive longitude, and thereafter by the angle orientation.lat * in direction of positive latitude. */ static ISEAPlanarProjection polarISEA({0, 0}); void pj_isea_data::initialize(const PJ *P) { struct pj_isea_data *Q = static_cast(P->opaque); // Only supporting default planar options for now if (Q->output == ISEA_PLANE && Q->o_az == 0.0 && Q->aperture == 3.0 && Q->resolution == 4.) { // Only supporting +orient=isea and +orient=pole for now if (Q->o_lat == ISEA_STD_LAT && Q->o_lon == ISEA_STD_LONG) p = &standardISEA; else if (Q->o_lat == M_PI / 2.0 && Q->o_lon == 0) p = &polarISEA; else p = nullptr; } if (p != nullptr) { if (P->e > 0) { double a2 = P->a * P->a, c2 = P->b * P->b; double log1pe_1me = log((1 + P->e) / (1 - P->e)); double S = M_PI * (2 * a2 + c2 / P->e * log1pe_1me); R2 = S / (4 * M_PI); // [WGS84] R = 6371007.1809184747 m Rprime = RprimeOverR * sqrt(R2); // R' } else { R2 = P->a * P->a; // R^2 Rprime = RprimeOverR * P->a; // R' } Rprime2X = 2 * Rprime; RprimeTang = Rprime * tang; // twice the center-to-base distance centerToBase = RprimeTang / 2; triWidth = RprimeTang * SQRT3; Rprime2Tan2g = RprimeTang * RprimeTang; yOffsets[0] = -2 * centerToBase; yOffsets[1] = -4 * centerToBase; yOffsets[2] = -5 * centerToBase; yOffsets[3] = -7 * centerToBase; xo = 2.5 * triWidth; yo = -1.5 * centerToBase; sx = 1.0 / triWidth; sy = 1.0 / (3 * centerToBase); } } } // anonymous namespace static PJ_LP isea_s_inverse(PJ_XY xy, PJ *P) { const struct pj_isea_data *Q = static_cast(P->opaque); ISEAPlanarProjection *p = Q->p; if (p) { // Default origin of +proj=isea is different (OGC:1534 is // +x_0=19186144.870934911 +y_0=-3323137.7717836285) PJ_XY input{xy.x * P->a + Q->xo, xy.y * P->a + Q->yo}; GeoPoint result; if (p->cartesianToGeo(input, Q, result)) // TODO: Convert authalic latitude to geodetic latitude if not // spherical as in eqearth, healpix, laea, etc. return {result.lon, result.lat}; else return {inf, inf}; } else return {inf, inf}; } #undef ISEA_STD_LAT #undef ISEA_STD_LONG #undef numIcosahedronFaces #undef precision #undef precisionPerDefinition #undef AzMax #undef sdc2vos #undef tang #undef cotTheta #undef cosG #undef sinGcosSDC2VoS #undef westVertexLon #undef RprimeOverR #undef Min #undef Max #undef inf #undef E_RAD #undef F_RAD #undef DEG36 #undef DEG72 #undef DEG90 #undef DEG108 #undef DEG120 #undef DEG144 #undef DEG180 #undef ISEA_SCALE #undef V_LAT #undef TABLE_G #undef TABLE_H proj-9.8.1/src/projections/wag2.cpp000664 001750 001750 00000001510 15166171715 017142 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(wag2, "Wagner II") "\n\tPCyl, Sph"; #define C_x 0.92483 #define C_y 1.38725 #define C_p1 0.88022 #define C_p2 0.88550 static PJ_XY wag2_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; lp.phi = aasin(P->ctx, C_p1 * sin(C_p2 * lp.phi)); xy.x = C_x * lp.lam * cos(lp.phi); xy.y = C_y * lp.phi; return (xy); } static PJ_LP wag2_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = xy.y / C_y; lp.lam = xy.x / (C_x * cos(lp.phi)); lp.phi = aasin(P->ctx, sin(lp.phi) / C_p1) / C_p2; return (lp); } PJ *PJ_PROJECTION(wag2) { P->es = 0.; P->inv = wag2_s_inverse; P->fwd = wag2_s_forward; return P; } #undef C_x #undef C_y #undef C_p1 #undef C_p2 proj-9.8.1/src/projections/merc.cpp000664 001750 001750 00000004267 15166171715 017244 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" #include PROJ_HEAD(merc, "Mercator") "\n\tCyl, Sph&Ell\n\tlat_ts="; PROJ_HEAD(webmerc, "Web Mercator / Pseudo Mercator") "\n\tCyl, Ell\n\t"; static PJ_XY merc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = P->k0 * lp.lam; // Instead of calling tan and sin, call sin and cos which the compiler // optimizes to a single call to sincos. double sphi = sin(lp.phi); double cphi = cos(lp.phi); xy.y = P->k0 * (asinh(sphi / cphi) - P->e * atanh(P->e * sphi)); return xy; } static PJ_XY merc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = P->k0 * lp.lam; xy.y = P->k0 * asinh(tan(lp.phi)); return xy; } static PJ_LP merc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = atan(pj_sinhpsi2tanphi(P->ctx, sinh(xy.y / P->k0), P->e)); lp.lam = xy.x / P->k0; return lp; } static PJ_LP merc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = atan(sinh(xy.y / P->k0)); lp.lam = xy.x / P->k0; return lp; } PJ *PJ_PROJECTION(merc) { double phits = 0.0; int is_phits; if ((is_phits = pj_param(P->ctx, P->params, "tlat_ts").i)) { phits = fabs(pj_param(P->ctx, P->params, "rlat_ts").f); if (phits >= M_HALFPI) { proj_log_error( P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (P->es != 0.0) { /* ellipsoid */ if (is_phits) P->k0 = pj_msfn(sin(phits), cos(phits), P->es); P->inv = merc_e_inverse; P->fwd = merc_e_forward; } else { /* sphere */ if (is_phits) P->k0 = cos(phits); P->inv = merc_s_inverse; P->fwd = merc_s_forward; } return P; } PJ *PJ_PROJECTION(webmerc) { /* Overriding k_0 with fixed parameter */ P->k0 = 1.0; P->inv = merc_s_inverse; P->fwd = merc_s_forward; return P; } proj-9.8.1/src/projections/crast.cpp000664 001750 001750 00000001614 15166171715 017423 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(crast, "Craster Parabolic (Putnins P4)") "\n\tPCyl, Sph"; #define XM 0.97720502380583984317 #define RXM 1.02332670794648848847 #define YM 3.06998012383946546542 #define RYM 0.32573500793527994772 #define THIRD 0.333333333333333333 static PJ_XY crast_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; lp.phi *= THIRD; xy.x = XM * lp.lam * (2. * cos(lp.phi + lp.phi) - 1.); xy.y = YM * sin(lp.phi); return xy; } static PJ_LP crast_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = 3. * asin(xy.y * RYM); lp.lam = xy.x * RXM / (2. * cos((lp.phi + lp.phi) * THIRD) - 1); return lp; } PJ *PJ_PROJECTION(crast) { P->es = 0.0; P->inv = crast_s_inverse; P->fwd = crast_s_forward; return P; } proj-9.8.1/src/projections/sch.cpp000664 001750 001750 00000017645 15166171715 017077 0ustar00eveneven000000 000000 /****************************************************************************** * Project: SCH Coordinate system * Purpose: Implementation of SCH Coordinate system * References : * 1. Hensley. Scott. SCH Coordinates and various transformations. June 15, *2000. * 2. Buckley, Sean Monroe. Radar interferometry measurement of land *subsidence. 2000.. PhD Thesis. UT Austin. (Appendix) * 3. Hensley, Scott, Elaine Chapin, and T. Michel. "Improved processing of *AIRSAR data based on the GeoSAR processor." Airsar earth science and *applications workshop, March. 2002. *(http://airsar.jpl.nasa.gov/documents/workshop2002/papers/T3.pdf) * * Author: Piyush Agram (piyush.agram@jpl.nasa.gov) * Copyright (c) 2015 California Institute of Technology. * Government sponsorship acknowledged. * * NOTE: The SCH coordinate system is a sensor aligned coordinate system * developed at JPL for radar mapping missions. Details pertaining to the * coordinate system have been release in the public domain (see references *above). This code is an independent implementation of the SCH coordinate *system that conforms to the PROJ.4 conventions and uses the details presented *in these publicly released documents. All credit for the development of the *coordinate system and its use should be directed towards the original *developers at JPL. ****************************************************************************** * 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 #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_sch_data { double plat; /*Peg Latitude */ double plon; /*Peg Longitude*/ double phdg; /*Peg heading */ double h0; /*Average altitude */ double transMat[9]; double xyzoff[3]; double rcurv; PJ *cart; PJ *cart_sph; }; } // anonymous namespace PROJ_HEAD(sch, "Spherical Cross-track Height") "\n\tMisc\n\tplat_0= plon_0= phdg_0= [h_0=]"; static PJ_LPZ sch_inverse3d(PJ_XYZ xyz, PJ *P) { struct pj_sch_data *Q = static_cast(P->opaque); PJ_LPZ lpz; lpz.lam = xyz.x * (P->a / Q->rcurv); lpz.phi = xyz.y * (P->a / Q->rcurv); lpz.z = xyz.z; xyz = Q->cart_sph->fwd3d(lpz, Q->cart_sph); /* Apply rotation */ xyz = {Q->transMat[0] * xyz.x + Q->transMat[1] * xyz.y + Q->transMat[2] * xyz.z, Q->transMat[3] * xyz.x + Q->transMat[4] * xyz.y + Q->transMat[5] * xyz.z, Q->transMat[6] * xyz.x + Q->transMat[7] * xyz.y + Q->transMat[8] * xyz.z}; /* Apply offset */ xyz.x += Q->xyzoff[0]; xyz.y += Q->xyzoff[1]; xyz.z += Q->xyzoff[2]; /* Convert geocentric coordinates to lat long */ return Q->cart->inv3d(xyz, Q->cart); } static PJ_XYZ sch_forward3d(PJ_LPZ lpz, PJ *P) { struct pj_sch_data *Q = static_cast(P->opaque); /* Convert lat long to geocentric coordinates */ PJ_XYZ xyz = Q->cart->fwd3d(lpz, Q->cart); /* Adjust for offset */ xyz.x -= Q->xyzoff[0]; xyz.y -= Q->xyzoff[1]; xyz.z -= Q->xyzoff[2]; /* Apply rotation */ xyz = {Q->transMat[0] * xyz.x + Q->transMat[3] * xyz.y + Q->transMat[6] * xyz.z, Q->transMat[1] * xyz.x + Q->transMat[4] * xyz.y + Q->transMat[7] * xyz.z, Q->transMat[2] * xyz.x + Q->transMat[5] * xyz.y + Q->transMat[8] * xyz.z}; /* Convert to local lat,long */ lpz = Q->cart_sph->inv3d(xyz, Q->cart_sph); /* Scale by radius */ xyz.x = lpz.lam * (Q->rcurv / P->a); xyz.y = lpz.phi * (Q->rcurv / P->a); xyz.z = lpz.z; return xyz; } static PJ *pj_sch_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; auto Q = static_cast(P->opaque); if (Q) { if (Q->cart) Q->cart->destructor(Q->cart, errlev); if (Q->cart_sph) Q->cart_sph->destructor(Q->cart_sph, errlev); } return pj_default_destructor(P, errlev); } static PJ *pj_sch_setup(PJ *P) { /* general initialization */ struct pj_sch_data *Q = static_cast(P->opaque); /* Setup original geocentric system */ // Pass a dummy ellipsoid definition that will be overridden just afterwards Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart == nullptr) return pj_sch_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); /* inherit ellipsoid definition from P to Q->cart */ pj_inherit_ellipsoid_def(P, Q->cart); const double clt = cos(Q->plat); const double slt = sin(Q->plat); const double clo = cos(Q->plon); const double slo = sin(Q->plon); /* Estimate the radius of curvature for given peg */ const double temp = sqrt(1.0 - (P->es) * slt * slt); const double reast = (P->a) / temp; const double rnorth = (P->a) * (1.0 - (P->es)) / pow(temp, 3); const double chdg = cos(Q->phdg); const double shdg = sin(Q->phdg); Q->rcurv = Q->h0 + (reast * rnorth) / (reast * chdg * chdg + rnorth * shdg * shdg); /* Set up local sphere at the given peg point */ Q->cart_sph = proj_create(P->ctx, "+proj=cart +a=1"); if (Q->cart_sph == nullptr) return pj_sch_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); pj_calc_ellipsoid_params(Q->cart_sph, Q->rcurv, 0); /* Set up the transformation matrices */ Q->transMat[0] = clt * clo; Q->transMat[1] = -shdg * slo - slt * clo * chdg; Q->transMat[2] = slo * chdg - slt * clo * shdg; Q->transMat[3] = clt * slo; Q->transMat[4] = clo * shdg - slt * slo * chdg; Q->transMat[5] = -clo * chdg - slt * slo * shdg; Q->transMat[6] = slt; Q->transMat[7] = clt * chdg; Q->transMat[8] = clt * shdg; PJ_LPZ lpz; lpz.lam = Q->plon; lpz.phi = Q->plat; lpz.z = Q->h0; PJ_XYZ xyz = Q->cart->fwd3d(lpz, Q->cart); Q->xyzoff[0] = xyz.x - (Q->rcurv) * clt * clo; Q->xyzoff[1] = xyz.y - (Q->rcurv) * clt * slo; Q->xyzoff[2] = xyz.z - (Q->rcurv) * slt; P->fwd3d = sch_forward3d; P->inv3d = sch_inverse3d; return P; } PJ *PJ_PROJECTION(sch) { struct pj_sch_data *Q = static_cast( calloc(1, sizeof(struct pj_sch_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_sch_destructor; Q->h0 = 0.0; /* Check if peg latitude was defined */ if (pj_param(P->ctx, P->params, "tplat_0").i) Q->plat = pj_param(P->ctx, P->params, "rplat_0").f; else { proj_log_error(P, _("Missing parameter plat_0.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* Check if peg longitude was defined */ if (pj_param(P->ctx, P->params, "tplon_0").i) Q->plon = pj_param(P->ctx, P->params, "rplon_0").f; else { proj_log_error(P, _("Missing parameter plon_0.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* Check if peg heading is defined */ if (pj_param(P->ctx, P->params, "tphdg_0").i) Q->phdg = pj_param(P->ctx, P->params, "rphdg_0").f; else { proj_log_error(P, _("Missing parameter phdg_0.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* Check if average height was defined - If so read it in */ if (pj_param(P->ctx, P->params, "th_0").i) Q->h0 = pj_param(P->ctx, P->params, "dh_0").f; return pj_sch_setup(P); } proj-9.8.1/src/projections/fahey.cpp000664 001750 001750 00000001412 15166171715 017377 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(fahey, "Fahey") "\n\tPcyl, Sph"; #define TOL 1e-6 static PJ_XY fahey_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = tan(0.5 * lp.phi); xy.y = 1.819152 * xy.x; xy.x = 0.819152 * lp.lam * asqrt(1 - xy.x * xy.x); return xy; } static PJ_LP fahey_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; xy.y /= 1.819152; lp.phi = 2. * atan(xy.y); xy.y = 1. - xy.y * xy.y; lp.lam = fabs(xy.y) < TOL ? 0. : xy.x / (0.819152 * sqrt(xy.y)); return lp; } PJ *PJ_PROJECTION(fahey) { P->es = 0.; P->inv = fahey_s_inverse; P->fwd = fahey_s_forward; return P; } proj-9.8.1/src/projections/adams.cpp000664 001750 001750 00000040760 15166171715 017401 0ustar00eveneven000000 000000 /* * Implementation of the Guyou, Pierce Quincuncial, Adams Hemisphere in a * Square, Adams World in a Square I & II projections. * * Based on original code from libproj4 written by Gerald Evenden. Adapted to * modern PROJ by Kristian Evers. Original code found in file src/proj_guyou.c, * see * https://github.com/rouault/libproj4/blob/master/libproject-1.01/src/proj_guyou.c * for reference. * Fix for Peirce Quincuncial projection to diamond or square by Toby C. * Wilkinson to correctly flip out southern hemisphere into the four triangles * of Peirce's quincunx. The fix inspired by a similar rotate and translate * solution applied by Jonathan Feinberg for cartopy, see * https://github.com/jonathf/cartopy/blob/8172cac7fc45cafc86573d408ce85b74258a9c28/lib/cartopy/peircequincuncial.py * Added original code for horizontal and vertical arrangement of hemispheres by * Toby C. Wilkinson to allow creation of lateral quincuncial projections, such * as Grieger's Triptychial, see, e.g.: * - Grieger, B. (2020). “Optimized global map projections for specific * applications: the triptychial projection and the Spilhaus projection”. * EGU2020-9885. https://doi.org/10.5194/egusphere-egu2020-9885 * * Copyright (c) 2005, 2006, 2009 Gerald I. Evenden * Copyright (c) 2020 Kristian Evers * Copyright (c) 2021 Toby C Wilkinson * * Related material * ---------------- * * CONFORMAL PROJECTION OF THE SPHERE WITHIN A SQUARE, 1929, OSCAR S. ADAMS, * U.S. COAST AND GEODETIC SURVEY, Special Publication No.153, * ftp://ftp.library.noaa.gov/docs.lib/htdocs/rescue/cgs_specpubs/QB275U35no1531929.pdf * * https://en.wikipedia.org/wiki/Guyou_hemisphere-in-a-square_projection * https://en.wikipedia.org/wiki/Adams_hemisphere-in-a-square_projection * https://en.wikipedia.org/wiki/Peirce_quincuncial_projection */ #include #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(guyou, "Guyou") "\n\tMisc Sph No inv"; PROJ_HEAD(peirce_q, "Peirce Quincuncial") "\n\tMisc Sph No inv"; PROJ_HEAD(adams_hemi, "Adams Hemisphere in a Square") "\n\tMisc Sph No inv"; PROJ_HEAD(adams_ws1, "Adams World in a Square I") "\n\tMisc Sph No inv"; PROJ_HEAD(adams_ws2, "Adams World in a Square II") "\n\tMisc Sph No inv"; namespace { // anonymous namespace enum projection_type { GUYOU, PEIRCE_Q, ADAMS_HEMI, ADAMS_WS1, ADAMS_WS2, }; enum peirce_shape { PEIRCE_Q_SQUARE, PEIRCE_Q_DIAMOND, PEIRCE_Q_NHEMISPHERE, PEIRCE_Q_SHEMISPHERE, PEIRCE_Q_HORIZONTAL, PEIRCE_Q_VERTICAL, }; struct pj_adams_data { projection_type mode; peirce_shape pqshape; double scrollx = 0.0; double scrolly = 0.0; }; } // anonymous namespace #define TOL 1e-9 #define RSQRT2 0.7071067811865475244008443620 static double ell_int_5(double phi) { /* Procedure to compute elliptic integral of the first kind * where k^2=0.5. Precision good to better than 1e-7 * The approximation is performed with an even Chebyshev * series, thus the coefficients below are the even values * and where series evaluation must be multiplied by the argument. */ constexpr double C0 = 2.19174570831038; static const double C[] = { -8.58691003636495e-07, 2.02692115653689e-07, 3.12960480765314e-05, 5.30394739921063e-05, -0.0012804644680613, -0.00575574836830288, 0.0914203033408211, }; double y = phi * M_2_PI; y = 2. * y * y - 1.; double y2 = 2. * y; double d1 = 0.0; double d2 = 0.0; for (double c : C) { double temp = d1; d1 = y2 * d1 - d2 + c; d2 = temp; } return phi * (y * d1 - d2 + 0.5 * C0); } static PJ_XY adams_forward(PJ_LP lp, PJ *P) { double a = 0., b = 0.; bool sm = false, sn = false; PJ_XY xy; const struct pj_adams_data *Q = static_cast(P->opaque); switch (Q->mode) { case GUYOU: if ((fabs(lp.lam) - TOL) > M_PI_2) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } if (fabs(fabs(lp.phi) - M_PI_2) < TOL) { xy.x = 0; xy.y = lp.phi < 0 ? -1.85407 : 1.85407; return xy; } else { const double sl = sin(lp.lam); const double sp = sin(lp.phi); const double cp = cos(lp.phi); a = aacos(P->ctx, (cp * sl - sp) * RSQRT2); b = aacos(P->ctx, (cp * sl + sp) * RSQRT2); sm = lp.lam < 0.; sn = lp.phi < 0.; } break; case PEIRCE_Q: { /* lam0 - note that the original Peirce model used a central meridian of * around -70deg, but the default within proj is +lon0=0 */ if (Q->pqshape == PEIRCE_Q_NHEMISPHERE) { if (lp.phi < -TOL) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } } if (Q->pqshape == PEIRCE_Q_SHEMISPHERE) { if (lp.phi > -TOL) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } } const double sl = sin(lp.lam); const double cl = cos(lp.lam); const double cp = cos(lp.phi); a = aacos(P->ctx, cp * (sl + cl) * RSQRT2); b = aacos(P->ctx, cp * (sl - cl) * RSQRT2); sm = sl < 0.; sn = cl > 0.; } break; case ADAMS_HEMI: { const double sp = sin(lp.phi); if ((fabs(lp.lam) - TOL) > M_PI_2) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } a = cos(lp.phi) * sin(lp.lam); sm = (sp + a) < 0.; sn = (sp - a) < 0.; a = aacos(P->ctx, a); b = M_PI_2 - lp.phi; } break; case ADAMS_WS1: { const double sp = tan(0.5 * lp.phi); b = cos(aasin(P->ctx, sp)) * sin(0.5 * lp.lam); a = aacos(P->ctx, (b - sp) * RSQRT2); b = aacos(P->ctx, (b + sp) * RSQRT2); sm = lp.lam < 0.; sn = lp.phi < 0.; } break; case ADAMS_WS2: { const double spp = tan(0.5 * lp.phi); a = cos(aasin(P->ctx, spp)) * sin(0.5 * lp.lam); sm = (spp + a) < 0.; sn = (spp - a) < 0.; b = aacos(P->ctx, spp); a = aacos(P->ctx, a); } break; } double m = aasin(P->ctx, sqrt((1. + std::min(0.0, cos(a + b))))); if (sm) m = -m; double n = aasin(P->ctx, sqrt(fabs(1. - std::max(0.0, cos(a - b))))); if (sn) n = -n; xy.x = ell_int_5(m); xy.y = ell_int_5(n); if (Q->mode == PEIRCE_Q) { /* Constant complete elliptic integral of the first kind with m=0.5, * calculated using * https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.ellipk.html * . Used as basic as scaled shift distance */ constexpr double shd = 1.8540746773013719 * 2; /* For square and diamond Quincuncial projections, spin out southern * hemisphere to triangular segments of quincunx (before rotation for * square)*/ if (Q->pqshape == PEIRCE_Q_SQUARE || (Q->pqshape == PEIRCE_Q_DIAMOND)) { if (lp.phi < 0.) { /* fold out segments */ if (lp.lam < (-0.75 * M_PI)) xy.y = shd - xy.y; /* top left segment, shift up and reflect y */ if ((lp.lam < (-0.25 * M_PI)) && (lp.lam >= (-0.75 * M_PI))) xy.x = -shd - xy.x; /* left segment, shift left and reflect x */ if ((lp.lam < (0.25 * M_PI)) && (lp.lam >= (-0.25 * M_PI))) xy.y = -shd - xy.y; /* bottom segment, shift down and reflect y */ if ((lp.lam < (0.75 * M_PI)) && (lp.lam >= (0.25 * M_PI))) xy.x = shd - xy.x; /* right segment, shift right and reflect x */ if (lp.lam >= (0.75 * M_PI)) xy.y = shd - xy.y; /* top right segment, shift up and reflect y */ } } /* For square types rotate xy by 45 deg */ if (Q->pqshape == PEIRCE_Q_SQUARE) { const double temp = xy.x; xy.x = RSQRT2 * (xy.x - xy.y); xy.y = RSQRT2 * (temp + xy.y); } /* For rectangle Quincuncial projs, spin out southern hemisphere to east * (horizontal) or north (vertical) after rotation */ if (Q->pqshape == PEIRCE_Q_HORIZONTAL) { if (lp.phi < 0.) { xy.x = shd - xy.x; /* reflect x to east */ } xy.x = xy.x - (shd / 2); /* shift everything so origin is in middle of two hemispheres */ } if (Q->pqshape == PEIRCE_Q_VERTICAL) { if (lp.phi < 0.) { xy.y = shd - xy.y; /* reflect y to north */ } xy.y = xy.y - (shd / 2); /* shift everything so origin is in middle of two hemispheres */ } // if o_scrollx param present, scroll x if (!(Q->scrollx == 0.0) && (Q->pqshape == PEIRCE_Q_HORIZONTAL)) { double xscale = 2.0; double xthresh = shd / 2; xy.x = xy.x + (Q->scrollx * (xthresh * 2 * xscale)); /*shift relative to proj width*/ if (xy.x >= (xthresh * xscale)) { xy.x = xy.x - (shd * xscale); } else if (xy.x < -(xthresh * xscale)) { xy.x = xy.x + (shd * xscale); } } // if o_scrolly param present, scroll y if (!(Q->scrolly == 0.0) && (Q->pqshape == PEIRCE_Q_VERTICAL)) { double yscale = 2.0; double ythresh = shd / 2; xy.y = xy.y + (Q->scrolly * (ythresh * 2 * yscale)); /*shift relative to proj height*/ if (xy.y >= (ythresh * yscale)) { xy.y = xy.y - (shd * yscale); } else if (xy.y < -(ythresh * yscale)) { xy.y = xy.y + (shd * yscale); } } } if (Q->mode == ADAMS_HEMI || Q->mode == ADAMS_WS2) { /* rotate by 45deg. */ const double temp = xy.x; xy.x = RSQRT2 * (xy.x - xy.y); xy.y = RSQRT2 * (temp + xy.y); } return xy; } static PJ_LP adams_inverse(PJ_XY xy, PJ *P) { PJ_LP lp; // Only implemented for ADAMS_WS2 // Uses Newton-Raphson method on the following pair of functions: // f_x(lam,phi) = adams_forward(lam, phi).x - xy.x // f_y(lam,phi) = adams_forward(lam, phi).y - xy.y // Initial guess (very rough, especially at high northings) // The magic values are got with: // echo 0 90 | src/proj -f "%.8f" +proj=adams_ws2 +R=1 // echo 180 0 | src/proj -f "%.8f" +proj=adams_ws2 +R=1 lp.phi = std::max(std::min(xy.y / 2.62181347, 1.0), -1.0) * M_HALFPI; lp.lam = fabs(lp.phi) >= M_HALFPI ? 0 : std::max(std::min(xy.x / 2.62205760 / cos(lp.phi), 1.0), -1.0) * M_PI; constexpr double deltaXYTolerance = 1e-10; return pj_generic_inverse_2d(xy, P, lp, deltaXYTolerance); } static PJ_LP peirce_q_square_inverse(PJ_XY xy, PJ *P) { /* Heuristics based on trial and repeat process */ PJ_LP lp; lp.phi = 0; if (xy.x == 0 && xy.y < 0) { lp.lam = -M_PI / 4; if (fabs(xy.y) < 2.622057580396) lp.phi = M_PI / 4; } else if (xy.x > 0 && fabs(xy.y) < 1e-7) lp.lam = M_PI / 4; else if (xy.x < 0 && fabs(xy.y) < 1e-7) { lp.lam = -3 * M_PI / 4; lp.phi = M_PI / 2 / 2.622057574224 * xy.x + M_PI / 2; } else if (fabs(xy.x) < 1e-7 && xy.y > 0) lp.lam = 3 * M_PI / 4; else if (xy.x >= 0 && xy.y <= 0) { lp.lam = 0; if (xy.x == 0 && xy.y == 0) { lp.phi = M_PI / 2; return lp; } } else if (xy.x >= 0 && xy.y >= 0) lp.lam = M_PI / 2; else if (xy.x <= 0 && xy.y >= 0) { if (fabs(xy.x) < fabs(xy.y)) lp.lam = M_PI * 0.9; else lp.lam = -M_PI * 0.9; } else /* if( xy.x <= 0 && xy.y <= 0 ) */ lp.lam = -M_PI / 2; constexpr double deltaXYTolerance = 1e-10; return pj_generic_inverse_2d(xy, P, lp, deltaXYTolerance); } static PJ_LP peirce_q_diamond_inverse(PJ_XY xy, PJ *P) { /* Heuristics based on a trial and repeat process */ PJ_LP lp; lp.phi = 0; if (xy.x >= 0 && xy.y <= 0) { lp.lam = M_PI / 4; if (xy.x > 0 && xy.y == 0) { lp.lam = M_PI / 2; lp.phi = 0; } else if (xy.x == 0 && xy.y == 0) { lp.lam = 0; lp.phi = M_PI / 2; return lp; } else if (xy.x == 0 && xy.y < 0) { lp.lam = 0; lp.phi = M_PI / 4; } } else if (xy.x >= 0 && xy.y >= 0) lp.lam = 3 * M_PI / 4; else if (xy.x <= 0 && xy.y >= 0) { lp.lam = -3 * M_PI / 4; } else /* if( xy.x <= 0 && xy.y <= 0 ) */ lp.lam = -M_PI / 4; if (fabs(xy.x) > 1.8540746773013719 + 1e-3 || fabs(xy.y) > 1.8540746773013719 + 1e-3) { lp.phi = -M_PI / 4; } constexpr double deltaXYTolerance = 1e-10; return pj_generic_inverse_2d(xy, P, lp, deltaXYTolerance); } static PJ *pj_adams_setup(PJ *P, projection_type mode) { struct pj_adams_data *Q = static_cast( calloc(1, sizeof(struct pj_adams_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->es = 0; P->fwd = adams_forward; Q->mode = mode; if (mode == ADAMS_WS2) P->inv = adams_inverse; if (mode == PEIRCE_Q) { // Quincuncial projections shape options: square, diamond, hemisphere, // horizontal (rectangle) or vertical (rectangle) const char *pqshape = pj_param(P->ctx, P->params, "sshape").s; if (!pqshape) pqshape = "diamond"; /* default if shape value not supplied */ if (strcmp(pqshape, "square") == 0) { Q->pqshape = PEIRCE_Q_SQUARE; P->inv = peirce_q_square_inverse; } else if (strcmp(pqshape, "diamond") == 0) { Q->pqshape = PEIRCE_Q_DIAMOND; P->inv = peirce_q_diamond_inverse; } else if (strcmp(pqshape, "nhemisphere") == 0) { Q->pqshape = PEIRCE_Q_NHEMISPHERE; } else if (strcmp(pqshape, "shemisphere") == 0) { Q->pqshape = PEIRCE_Q_SHEMISPHERE; } else if (strcmp(pqshape, "horizontal") == 0) { Q->pqshape = PEIRCE_Q_HORIZONTAL; if (pj_param(P->ctx, P->params, "tscrollx").i) { double scrollx; scrollx = pj_param(P->ctx, P->params, "dscrollx").f; if (scrollx > 1 || scrollx < -1) { proj_log_error( P, _("Invalid value for scrollx: |scrollx| should " "between -1 and 1")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->scrollx = scrollx; } } else if (strcmp(pqshape, "vertical") == 0) { Q->pqshape = PEIRCE_Q_VERTICAL; if (pj_param(P->ctx, P->params, "tscrolly").i) { double scrolly; scrolly = pj_param(P->ctx, P->params, "dscrolly").f; if (scrolly > 1 || scrolly < -1) { proj_log_error( P, _("Invalid value for scrolly: |scrolly| should " "between -1 and 1")); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->scrolly = scrolly; } } else { proj_log_error(P, _("peirce_q: invalid value for 'shape' parameter")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } return P; } PJ *PJ_PROJECTION(guyou) { return pj_adams_setup(P, GUYOU); } PJ *PJ_PROJECTION(peirce_q) { return pj_adams_setup(P, PEIRCE_Q); } PJ *PJ_PROJECTION(adams_hemi) { return pj_adams_setup(P, ADAMS_HEMI); } PJ *PJ_PROJECTION(adams_ws1) { return pj_adams_setup(P, ADAMS_WS1); } PJ *PJ_PROJECTION(adams_ws2) { return pj_adams_setup(P, ADAMS_WS2); } #undef TOL #undef RSQRT2 proj-9.8.1/src/projections/putp4p.cpp000664 001750 001750 00000003524 15166171715 017545 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_putp4p_data { double C_x, C_y; }; } // anonymous namespace PROJ_HEAD(putp4p, "Putnins P4'") "\n\tPCyl, Sph"; PROJ_HEAD(weren, "Werenskiold I") "\n\tPCyl, Sph"; static PJ_XY putp4p_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_putp4p_data *Q = static_cast(P->opaque); lp.phi = aasin(P->ctx, 0.883883476 * sin(lp.phi)); xy.x = Q->C_x * lp.lam * cos(lp.phi); lp.phi *= 0.333333333333333; xy.x /= cos(lp.phi); xy.y = Q->C_y * sin(lp.phi); return xy; } static PJ_LP putp4p_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_putp4p_data *Q = static_cast(P->opaque); lp.phi = aasin(P->ctx, xy.y / Q->C_y); lp.lam = xy.x * cos(lp.phi) / Q->C_x; lp.phi *= 3.; lp.lam /= cos(lp.phi); lp.phi = aasin(P->ctx, 1.13137085 * sin(lp.phi)); return lp; } PJ *PJ_PROJECTION(putp4p) { struct pj_putp4p_data *Q = static_cast( calloc(1, sizeof(struct pj_putp4p_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 0.874038744; Q->C_y = 3.883251825; P->es = 0.; P->inv = putp4p_s_inverse; P->fwd = putp4p_s_forward; return P; } PJ *PJ_PROJECTION(weren) { struct pj_putp4p_data *Q = static_cast( calloc(1, sizeof(struct pj_putp4p_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->C_x = 1.; Q->C_y = 4.442882938; P->es = 0.; P->inv = putp4p_s_inverse; P->fwd = putp4p_s_forward; return P; } proj-9.8.1/src/projections/bipc.cpp000664 001750 001750 00000012171 15166171715 017224 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" #include PROJ_HEAD(bipc, "Bipolar conic of western hemisphere") "\n\tConic Sph"; #define EPS 1e-10 #define EPS10 1e-10 #define ONEEPS 1.000000001 #define NITER 10 #define lamB -.34894976726250681539 #define n .63055844881274687180 #define F 1.89724742567461030582 #define Azab .81650043674686363166 #define Azba 1.82261843856185925133 #define T 1.27246578267089012270 #define rhoc 1.20709121521568721927 #define cAzc .69691523038678375519 #define sAzc .71715351331143607555 #define C45 .70710678118654752469 #define S45 .70710678118654752410 #define C20 .93969262078590838411 #define S20 -.34202014332566873287 #define R110 1.91986217719376253360 #define R104 1.81514242207410275904 namespace { // anonymous namespace struct pj_bipc_data { int noskew; }; } // anonymous namespace static PJ_XY bipc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_bipc_data *Q = static_cast(P->opaque); double cphi, sphi, tphi, t, al, Az, z, Av, cdlam, sdlam, r; int tag; cphi = cos(lp.phi); sphi = sin(lp.phi); cdlam = cos(sdlam = lamB - lp.lam); sdlam = sin(sdlam); if (fabs(fabs(lp.phi) - M_HALFPI) < EPS10) { Az = lp.phi < 0. ? M_PI : 0.; tphi = HUGE_VAL; } else { tphi = sphi / cphi; Az = atan2(sdlam, C45 * (tphi - cdlam)); } if ((tag = (Az > Azba))) { sdlam = lp.lam + R110; cdlam = cos(sdlam); sdlam = sin(sdlam); z = S20 * sphi + C20 * cphi * cdlam; if (fabs(z) > 1.) { if (fabs(z) > ONEEPS) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else z = z < 0. ? -1. : 1.; } else z = acos(z); if (tphi != HUGE_VAL) Az = atan2(sdlam, (C20 * tphi - S20 * cdlam)); Av = Azab; xy.y = rhoc; } else { z = S45 * (sphi + cphi * cdlam); if (fabs(z) > 1.) { if (fabs(z) > ONEEPS) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else z = z < 0. ? -1. : 1.; } else z = acos(z); Av = Azba; xy.y = -rhoc; } if (z < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } t = pow(tan(.5 * z), n); r = F * t; if ((al = .5 * (R104 - z)) < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } al = (t + pow(al, n)) / T; if (fabs(al) > 1.) { if (fabs(al) > ONEEPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else al = al < 0. ? -1. : 1.; } else al = acos(al); t = n * (Av - Az); if (fabs(t) < al) r /= cos(al + (tag ? t : -t)); xy.x = r * sin(t); xy.y += (tag ? -r : r) * cos(t); if (Q->noskew) { t = xy.x; xy.x = -xy.x * cAzc - xy.y * sAzc; xy.y = -xy.y * cAzc + t * sAzc; } return (xy); } static PJ_LP bipc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_bipc_data *Q = static_cast(P->opaque); double t, r, rp, rl, al, z = 0.0, fAz, Az, s, c, Av; int neg, i; if (Q->noskew) { t = xy.x; xy.x = -xy.x * cAzc + xy.y * sAzc; xy.y = -xy.y * cAzc - t * sAzc; } if ((neg = (xy.x < 0.))) { xy.y = rhoc - xy.y; s = S20; c = C20; Av = Azab; } else { xy.y += rhoc; s = S45; c = C45; Av = Azba; } r = hypot(xy.x, xy.y); rl = rp = r; Az = atan2(xy.x, xy.y); fAz = fabs(Az); for (i = NITER; i; --i) { z = 2. * atan(pow(r / F, 1 / n)); al = acos((pow(tan(.5 * z), n) + pow(tan(.5 * (R104 - z)), n)) / T); if (fAz < al) r = rp * cos(al + (neg ? Az : -Az)); if (fabs(rl - r) < EPS) break; rl = r; } if (!i) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } Az = Av - Az / n; lp.phi = asin(s * cos(z) + c * sin(z) * cos(Az)); lp.lam = atan2(sin(Az), c / tan(z) - s * cos(Az)); if (neg) lp.lam -= R110; else lp.lam = lamB - lp.lam; return (lp); } PJ *PJ_PROJECTION(bipc) { struct pj_bipc_data *Q = static_cast( calloc(1, sizeof(struct pj_bipc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->noskew = pj_param(P->ctx, P->params, "bns").i; P->inv = bipc_s_inverse; P->fwd = bipc_s_forward; P->es = 0.; return P; } #undef EPS #undef EPS10 #undef ONEEPS #undef NITER #undef lamB #undef n #undef F #undef Azab #undef Azba #undef T #undef rhoc #undef cAzc #undef sAzc #undef C45 #undef S45 #undef C20 #undef S20 #undef R110 #undef R104 proj-9.8.1/src/projections/ccon.cpp000664 001750 001750 00000006452 15166171715 017236 0ustar00eveneven000000 000000 /****************************************************************************** * Copyright (c) 2017, Lukasz Komsta * * 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 "proj.h" #include "proj_internal.h" #include #include #define EPS10 1e-10 namespace { // anonymous namespace struct pj_ccon_data { double phi1; double ctgphi1; double sinphi1; double cosphi1; double *en; }; } // anonymous namespace PROJ_HEAD(ccon, "Central Conic") "\n\tCentral Conic, Sph\n\tlat_1="; static PJ_XY ccon_forward(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; struct pj_ccon_data *Q = static_cast(P->opaque); double r; r = Q->ctgphi1 - tan(lp.phi - Q->phi1); xy.x = r * sin(lp.lam * Q->sinphi1); xy.y = Q->ctgphi1 - r * cos(lp.lam * Q->sinphi1); return xy; } static PJ_LP ccon_inverse(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; struct pj_ccon_data *Q = static_cast(P->opaque); xy.y = Q->ctgphi1 - xy.y; lp.phi = Q->phi1 - atan(hypot(xy.x, xy.y) - Q->ctgphi1); lp.lam = atan2(xy.x, xy.y) / Q->sinphi1; return lp; } static PJ *pj_ccon_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(ccon) { struct pj_ccon_data *Q = static_cast( calloc(1, sizeof(struct pj_ccon_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_ccon_destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; if (fabs(Q->phi1) < EPS10) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be > 0")); return pj_ccon_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (!(Q->en = pj_enfn(P->n))) return pj_ccon_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->sinphi1 = sin(Q->phi1); Q->cosphi1 = cos(Q->phi1); Q->ctgphi1 = Q->cosphi1 / Q->sinphi1; P->inv = ccon_inverse; P->fwd = ccon_forward; return P; } #undef EPS10 proj-9.8.1/src/projections/s2.cpp000664 001750 001750 00000032035 15166171715 016634 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ * Purpose: Implementing the S2 family of projections in PROJ * Author: Marcus Elia, * ****************************************************************************** * Copyright (c) 2021, Marcus Elia, * * 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. ***************************************************************************** * * This implements the S2 projection. This code is similar * to the QSC projection. * * * You have to choose one of the following projection centers, * corresponding to the centers of the six cube faces: * phi0 = 0.0, lam0 = 0.0 ("front" face) * phi0 = 0.0, lam0 = 90.0 ("right" face) * phi0 = 0.0, lam0 = 180.0 ("back" face) * phi0 = 0.0, lam0 = -90.0 ("left" face) * phi0 = 90.0 ("top" face) * phi0 = -90.0 ("bottom" face) * Other projection centers will not work! * * You must also choose which conversion from UV to ST coordinates * is used (linear, quadratic, tangent). Read about them in * https://github.com/google/s2geometry/blob/0c4c460bdfe696da303641771f9def900b3e440f/src/s2/s2coords.h * The S2 projection functions are adapted from the above link and the S2 * Math util functions are adapted from * https://github.com/google/s2geometry/blob/0c4c460bdfe696da303641771f9def900b3e440f/src/s2/util/math/vector.h ****************************************************************************/ /* enable predefined math constants M_* for MS Visual Studio */ #if defined(_MSC_VER) || defined(_WIN32) #ifndef _USE_MATH_DEFINES #define _USE_MATH_DEFINES #endif #endif #include #include #include #include "proj.h" #include "proj_internal.h" /* The six cube faces. */ namespace { // anonymous namespace enum Face { FACE_FRONT = 0, FACE_RIGHT = 1, FACE_TOP = 2, FACE_BACK = 3, FACE_LEFT = 4, FACE_BOTTOM = 5 }; } // anonymous namespace enum S2ProjectionType { Linear, Quadratic, Tangent, NoUVtoST }; static std::map stringToS2ProjectionType{ {"linear", Linear}, {"quadratic", Quadratic}, {"tangent", Tangent}, {"none", NoUVtoST}}; namespace { // anonymous namespace struct pj_s2 { enum Face face; double a_squared; double one_minus_f; double one_minus_f_squared; S2ProjectionType UVtoST; }; } // anonymous namespace PROJ_HEAD(s2, "S2") "\n\tMisc, Sph&Ell"; /* The four areas on a cube face. AREA_0 is the area of definition, * the other three areas are counted counterclockwise. */ namespace { // anonymous namespace enum Area { AREA_0 = 0, AREA_1 = 1, AREA_2 = 2, AREA_3 = 3 }; } // anonymous namespace // ================================================= // // S2 Math Util // // ================================================= static PJ_XYZ Abs(const PJ_XYZ &p) { return {std::fabs(p.x), std::fabs(p.y), std::fabs(p.z)}; } // return the index of the largest component (fabs) static int LargestAbsComponent(const PJ_XYZ &p) { PJ_XYZ temp = Abs(p); return temp.x > temp.y ? temp.x > temp.z ? 0 : 2 : temp.y > temp.z ? 1 : 2; } // ================================================= // // S2 Projection Functions // // ================================================= // Unfortunately, tan(M_PI_4) is slightly less than 1.0. This isn't due to // a flaw in the implementation of tan(), it's because the derivative of // tan(x) at x=pi/4 is 2, and it happens that the two adjacent floating // point numbers on either side of the infinite-precision value of pi/4 have // tangents that are slightly below and slightly above 1.0 when rounded to // the nearest double-precision result. static double STtoUV(double s, S2ProjectionType s2_projection) { switch (s2_projection) { case Linear: return 2 * s - 1; break; case Quadratic: if (s >= 0.5) return (1 / 3.) * (4 * s * s - 1); else return (1 / 3.) * (1 - 4 * (1 - s) * (1 - s)); break; case Tangent: s = std::tan(M_PI_2 * s - M_PI_4); return s + (1.0 / static_cast(static_cast(1) << 53)) * s; break; default: return s; } } static double UVtoST(double u, S2ProjectionType s2_projection) { double ret = u; switch (s2_projection) { case Linear: ret = 0.5 * (u + 1); break; case Quadratic: if (u >= 0) ret = 0.5 * std::sqrt(1 + 3 * u); else ret = 1 - 0.5 * std::sqrt(1 - 3 * u); break; case Tangent: { volatile double a = std::atan(u); ret = (2 * M_1_PI) * (a + M_PI_4); break; } case NoUVtoST: break; } return ret; } inline PJ_XYZ FaceUVtoXYZ(int face, double u, double v) { switch (face) { case 0: return {1, u, v}; case 1: return {-u, 1, v}; case 2: return {-u, -v, 1}; case 3: return {-1, -v, -u}; case 4: return {v, -1, -u}; default: return {v, u, -1}; } } inline PJ_XYZ FaceUVtoXYZ(int face, const PJ_XY &uv) { return FaceUVtoXYZ(face, uv.x, uv.y); } inline void ValidFaceXYZtoUV(int face, const PJ_XYZ &p, double *pu, double *pv) { switch (face) { case 0: *pu = p.y / p.x; *pv = p.z / p.x; break; case 1: *pu = -p.x / p.y; *pv = p.z / p.y; break; case 2: *pu = -p.x / p.z; *pv = -p.y / p.z; break; case 3: *pu = p.z / p.x; *pv = p.y / p.x; break; case 4: *pu = p.z / p.y; *pv = -p.x / p.y; break; default: *pu = -p.y / p.z; *pv = -p.x / p.z; break; } } inline void ValidFaceXYZtoUV(int face, const PJ_XYZ &p, PJ_XY *puv) { ValidFaceXYZtoUV(face, p, &(*puv).x, &(*puv).y); } inline int GetFace(const PJ_XYZ &p) { int face = LargestAbsComponent(p); double pFace; switch (face) { case 0: pFace = p.x; break; case 1: pFace = p.y; break; default: pFace = p.z; break; } if (pFace < 0) face += 3; return face; } inline int XYZtoFaceUV(const PJ_XYZ &p, double *pu, double *pv) { int face = GetFace(p); ValidFaceXYZtoUV(face, p, pu, pv); return face; } inline int XYZtoFaceUV(const PJ_XYZ &p, PJ_XY *puv) { return XYZtoFaceUV(p, &(*puv).x, &(*puv).y); } inline bool FaceXYZtoUV(int face, const PJ_XYZ &p, double *pu, double *pv) { double pFace; switch (face) { case 0: pFace = p.x; break; case 1: pFace = p.y; break; case 2: pFace = p.z; break; case 3: pFace = p.x; break; case 4: pFace = p.y; break; default: pFace = p.z; break; } if (face < 3) { if (pFace <= 0) return false; } else { if (pFace >= 0) return false; } ValidFaceXYZtoUV(face, p, pu, pv); return true; } inline bool FaceXYZtoUV(int face, const PJ_XYZ &p, PJ_XY *puv) { return FaceXYZtoUV(face, p, &(*puv).x, &(*puv).y); } // This function inverts ValidFaceXYZtoUV() inline bool UVtoSphereXYZ(int face, double u, double v, PJ_XYZ *xyz) { double major_coord = 1 / sqrt(1 + u * u + v * v); double minor_coord_1 = u * major_coord; double minor_coord_2 = v * major_coord; switch (face) { case 0: xyz->x = major_coord; xyz->y = minor_coord_1; xyz->z = minor_coord_2; break; case 1: xyz->x = -minor_coord_1; xyz->y = major_coord; xyz->z = minor_coord_2; break; case 2: xyz->x = -minor_coord_1; xyz->y = -minor_coord_2; xyz->z = major_coord; break; case 3: xyz->x = -major_coord; xyz->y = -minor_coord_2; xyz->z = -minor_coord_1; break; case 4: xyz->x = minor_coord_2; xyz->y = -major_coord; xyz->z = -minor_coord_1; break; default: xyz->x = minor_coord_2; xyz->y = minor_coord_1; xyz->z = -major_coord; break; } return true; } // ============================================ // // The Forward and Inverse Functions // // ============================================ static PJ_XY s2_forward(PJ_LP lp, PJ *P) { struct pj_s2 *Q = static_cast(P->opaque); double lat; /* Convert the geodetic latitude to a geocentric latitude. * This corresponds to the shift from the ellipsoid to the sphere * described in [LK12]. */ if (P->es != 0.0) { lat = atan(Q->one_minus_f_squared * tan(lp.phi)); } else { lat = lp.phi; } // Convert the lat/long to x,y,z on the unit sphere double x, y, z; double sinlat, coslat; double sinlon, coslon; sinlat = sin(lat); coslat = cos(lat); sinlon = sin(lp.lam); coslon = cos(lp.lam); x = coslat * coslon; y = coslat * sinlon; z = sinlat; PJ_XYZ spherePoint{x, y, z}; PJ_XY uvCoords; ValidFaceXYZtoUV(Q->face, spherePoint, &uvCoords.x, &uvCoords.y); double s = UVtoST(uvCoords.x, Q->UVtoST); double t = UVtoST(uvCoords.y, Q->UVtoST); return {s, t}; } static PJ_LP s2_inverse(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; struct pj_s2 *Q = static_cast(P->opaque); // Do the S2 projections to get from s,t to u,v to x,y,z double u = STtoUV(xy.x, Q->UVtoST); double v = STtoUV(xy.y, Q->UVtoST); PJ_XYZ sphereCoords; UVtoSphereXYZ(Q->face, u, v, &sphereCoords); double q = sphereCoords.x; double r = sphereCoords.y; double s = sphereCoords.z; // Get the spherical angles from the x y z lp.phi = acos(-s) - M_HALFPI; lp.lam = atan2(r, q); /* Apply the shift from the sphere to the ellipsoid as described * in [LK12]. */ if (P->es != 0.0) { int invert_sign; volatile double tanphi, xa; invert_sign = (lp.phi < 0.0 ? 1 : 0); tanphi = tan(lp.phi); xa = P->b / sqrt(tanphi * tanphi + Q->one_minus_f_squared); lp.phi = atan(sqrt(Q->a_squared - xa * xa) / (Q->one_minus_f * xa)); if (invert_sign) { lp.phi = -lp.phi; } } return lp; } PJ *PJ_PROJECTION(s2) { struct pj_s2 *Q = static_cast(calloc(1, sizeof(struct pj_s2))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* Determine which UVtoST function is to be used */ PROJVALUE maybeUVtoST = pj_param(P->ctx, P->params, "sUVtoST"); if (nullptr != maybeUVtoST.s) { try { Q->UVtoST = stringToS2ProjectionType.at(maybeUVtoST.s); } catch (const std::out_of_range &) { proj_log_error( P, _("Invalid value for s2 parameter: should be linear, " "quadratic, tangent, or none.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else { Q->UVtoST = Quadratic; } P->left = PJ_IO_UNITS_RADIANS; P->right = PJ_IO_UNITS_PROJECTED; P->from_greenwich = -P->lam0; P->inv = s2_inverse; P->fwd = s2_forward; /* Determine the cube face from the center of projection. */ if (P->phi0 >= M_HALFPI - M_FORTPI / 2.0) { Q->face = FACE_TOP; } else if (P->phi0 <= -(M_HALFPI - M_FORTPI / 2.0)) { Q->face = FACE_BOTTOM; } else if (fabs(P->lam0) <= M_FORTPI) { Q->face = FACE_FRONT; } else if (fabs(P->lam0) <= M_HALFPI + M_FORTPI) { Q->face = (P->lam0 > 0.0 ? FACE_RIGHT : FACE_LEFT); } else { Q->face = FACE_BACK; } /* Fill in useful values for the ellipsoid <-> sphere shift * described in [LK12]. */ if (P->es != 0.0) { Q->a_squared = P->a * P->a; Q->one_minus_f = 1.0 - (P->a - P->b) / P->a; Q->one_minus_f_squared = Q->one_minus_f * Q->one_minus_f; } return P; } proj-9.8.1/src/projections/comill.cpp000664 001750 001750 00000004011 15166171715 017560 0ustar00eveneven000000 000000 /* The Compact Miller projection was designed by Tom Patterson, US National Park Service, in 2014. The polynomial equation was developed by Bojan Savric and Bernhard Jenny, College of Earth, Ocean, and Atmospheric Sciences, Oregon State University. Port to PROJ.4 by Bojan Savric, 4 April 2016 */ #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(comill, "Compact Miller") "\n\tCyl, Sph"; #define K1 0.9902 #define K2 0.1604 #define K3 -0.03054 #define C1 K1 #define C2 (3 * K2) #define C3 (5 * K3) #define EPS 1e-11 #define MAX_Y (0.6000207669862655 * M_PI) /* Not sure at all of the appropriate number for MAX_ITER... */ #define MAX_ITER 100 static PJ_XY comill_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double lat_sq; (void)P; /* silence unused parameter warnings */ lat_sq = lp.phi * lp.phi; xy.x = lp.lam; xy.y = lp.phi * (K1 + lat_sq * (K2 + K3 * lat_sq)); return xy; } static PJ_LP comill_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double yc, tol, y2, f, fder; int i; (void)P; /* silence unused parameter warnings */ /* make sure y is inside valid range */ if (xy.y > MAX_Y) { xy.y = MAX_Y; } else if (xy.y < -MAX_Y) { xy.y = -MAX_Y; } /* latitude */ yc = xy.y; for (i = MAX_ITER; i; --i) { /* Newton-Raphson */ y2 = yc * yc; f = (yc * (K1 + y2 * (K2 + K3 * y2))) - xy.y; fder = C1 + y2 * (C2 + C3 * y2); tol = f / fder; yc -= tol; if (fabs(tol) < EPS) { break; } } if (i == 0) proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = yc; /* longitude */ lp.lam = xy.x; return lp; } PJ *PJ_PROJECTION(comill) { P->es = 0; P->inv = comill_s_inverse; P->fwd = comill_s_forward; return P; } #undef K1 #undef K2 #undef K3 #undef C1 #undef C2 #undef C3 #undef EPS #undef MAX_Y #undef MAX_ITER proj-9.8.1/src/projections/qsc.cpp000664 001750 001750 00000034141 15166171715 017076 0ustar00eveneven000000 000000 /* * This implements the Quadrilateralized Spherical Cube (QSC) projection. * * Copyright (c) 2011, 2012 Martin Lambers * * The QSC projection was introduced in: * [OL76] * E.M. O'Neill and R.E. Laubscher, "Extended Studies of a Quadrilateralized * Spherical Cube Earth Data Base", Naval Environmental Prediction Research * Facility Tech. Report NEPRF 3-76 (CSC), May 1976. * * The preceding shift from an ellipsoid to a sphere, which allows to apply * this projection to ellipsoids as used in the Ellipsoidal Cube Map model, * is described in * [LK12] * M. Lambers and A. Kolb, "Ellipsoidal Cube Maps for Accurate Rendering of * Planetary-Scale Terrain Data", Proc. Pacific Graphics (Short Papers), Sep. * 2012 * * You have to choose one of the following projection centers, * corresponding to the centers of the six cube faces: * phi0 = 0.0, lam0 = 0.0 ("front" face) * phi0 = 0.0, lam0 = 90.0 ("right" face) * phi0 = 0.0, lam0 = 180.0 ("back" face) * phi0 = 0.0, lam0 = -90.0 ("left" face) * phi0 = 90.0 ("top" face) * phi0 = -90.0 ("bottom" face) * Other projection centers will not work! * * In the projection code below, each cube face is handled differently. * See the computation of the face parameter in the PROJECTION(qsc) function * and the handling of different face values (FACE_*) in the forward and * inverse projections. * * Furthermore, the projection is originally only defined for theta angles * between (-1/4 * PI) and (+1/4 * PI) on the current cube face. This area * of definition is named pj_qsc_ns::AREA_0 in the projection code below. The * other three areas of a cube face are handled by rotation of * pj_qsc_ns::AREA_0. */ #include #include #include "proj.h" #include "proj_internal.h" /* The six cube faces. */ namespace pj_qsc_ns { enum Face { FACE_FRONT = 0, FACE_RIGHT = 1, FACE_BACK = 2, FACE_LEFT = 3, FACE_TOP = 4, FACE_BOTTOM = 5 }; } namespace { // anonymous namespace struct pj_qsc_data { enum pj_qsc_ns::Face face; double a_squared; double b; double one_minus_f; double one_minus_f_squared; }; } // anonymous namespace PROJ_HEAD(qsc, "Quadrilateralized Spherical Cube") "\n\tAzi, Sph"; #define EPS10 1.e-10 /* The four areas on a cube face. AREA_0 is the area of definition, * the other three areas are counted counterclockwise. */ namespace pj_qsc_ns { enum Area { AREA_0 = 0, AREA_1 = 1, AREA_2 = 2, AREA_3 = 3 }; } /* Helper function for forward projection: compute the theta angle * and determine the area number. */ static double qsc_fwd_equat_face_theta(double phi, double y, double x, enum pj_qsc_ns::Area *area) { double theta; if (phi < EPS10) { *area = pj_qsc_ns::AREA_0; theta = 0.0; } else { theta = atan2(y, x); if (fabs(theta) <= M_FORTPI) { *area = pj_qsc_ns::AREA_0; } else if (theta > M_FORTPI && theta <= M_HALFPI + M_FORTPI) { *area = pj_qsc_ns::AREA_1; theta -= M_HALFPI; } else if (theta > M_HALFPI + M_FORTPI || theta <= -(M_HALFPI + M_FORTPI)) { *area = pj_qsc_ns::AREA_2; theta = (theta >= 0.0 ? theta - M_PI : theta + M_PI); } else { *area = pj_qsc_ns::AREA_3; theta += M_HALFPI; } } return theta; } /* Helper function: shift the longitude. */ static double qsc_shift_longitude_origin(double longitude, double offset) { double slon = longitude + offset; if (slon < -M_PI) { slon += M_TWOPI; } else if (slon > +M_PI) { slon -= M_TWOPI; } return slon; } static PJ_XY qsc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_qsc_data *Q = static_cast(P->opaque); double lat, longitude; double theta, phi; double t, mu; /* nu; */ enum pj_qsc_ns::Area area; /* Convert the geodetic latitude to a geocentric latitude. * This corresponds to the shift from the ellipsoid to the sphere * described in [LK12]. */ if (P->es != 0.0) { lat = atan(Q->one_minus_f_squared * tan(lp.phi)); } else { lat = lp.phi; } /* Convert the input lat, longitude into theta, phi as used by QSC. * This depends on the cube face and the area on it. * For the top and bottom face, we can compute theta and phi * directly from phi, lam. For the other faces, we must use * unit sphere cartesian coordinates as an intermediate step. */ longitude = lp.lam; if (Q->face == pj_qsc_ns::FACE_TOP) { phi = M_HALFPI - lat; if (longitude >= M_FORTPI && longitude <= M_HALFPI + M_FORTPI) { area = pj_qsc_ns::AREA_0; theta = longitude - M_HALFPI; } else if (longitude > M_HALFPI + M_FORTPI || longitude <= -(M_HALFPI + M_FORTPI)) { area = pj_qsc_ns::AREA_1; theta = (longitude > 0.0 ? longitude - M_PI : longitude + M_PI); } else if (longitude > -(M_HALFPI + M_FORTPI) && longitude <= -M_FORTPI) { area = pj_qsc_ns::AREA_2; theta = longitude + M_HALFPI; } else { area = pj_qsc_ns::AREA_3; theta = longitude; } } else if (Q->face == pj_qsc_ns::FACE_BOTTOM) { phi = M_HALFPI + lat; if (longitude >= M_FORTPI && longitude <= M_HALFPI + M_FORTPI) { area = pj_qsc_ns::AREA_0; theta = -longitude + M_HALFPI; } else if (longitude < M_FORTPI && longitude >= -M_FORTPI) { area = pj_qsc_ns::AREA_1; theta = -longitude; } else if (longitude < -M_FORTPI && longitude >= -(M_HALFPI + M_FORTPI)) { area = pj_qsc_ns::AREA_2; theta = -longitude - M_HALFPI; } else { area = pj_qsc_ns::AREA_3; theta = (longitude > 0.0 ? -longitude + M_PI : -longitude - M_PI); } } else { double q, r, s; double sinlat, coslat; double sinlon, coslon; if (Q->face == pj_qsc_ns::FACE_RIGHT) { longitude = qsc_shift_longitude_origin(longitude, +M_HALFPI); } else if (Q->face == pj_qsc_ns::FACE_BACK) { longitude = qsc_shift_longitude_origin(longitude, +M_PI); } else if (Q->face == pj_qsc_ns::FACE_LEFT) { longitude = qsc_shift_longitude_origin(longitude, -M_HALFPI); } sinlat = sin(lat); coslat = cos(lat); sinlon = sin(longitude); coslon = cos(longitude); q = coslat * coslon; r = coslat * sinlon; s = sinlat; if (Q->face == pj_qsc_ns::FACE_FRONT) { phi = acos(q); theta = qsc_fwd_equat_face_theta(phi, s, r, &area); } else if (Q->face == pj_qsc_ns::FACE_RIGHT) { phi = acos(r); theta = qsc_fwd_equat_face_theta(phi, s, -q, &area); } else if (Q->face == pj_qsc_ns::FACE_BACK) { phi = acos(-q); theta = qsc_fwd_equat_face_theta(phi, s, -r, &area); } else if (Q->face == pj_qsc_ns::FACE_LEFT) { phi = acos(-r); theta = qsc_fwd_equat_face_theta(phi, s, q, &area); } else { /* Impossible */ phi = theta = 0.0; area = pj_qsc_ns::AREA_0; } } /* Compute mu and nu for the area of definition. * For mu, see Eq. (3-21) in [OL76], but note the typos: * compare with Eq. (3-14). For nu, see Eq. (3-38). */ mu = atan((12.0 / M_PI) * (theta + acos(sin(theta) * cos(M_FORTPI)) - M_HALFPI)); t = sqrt((1.0 - cos(phi)) / (cos(mu) * cos(mu)) / (1.0 - cos(atan(1.0 / cos(theta))))); /* nu = atan(t); We don't really need nu, just t, see below. */ /* Apply the result to the real area. */ if (area == pj_qsc_ns::AREA_1) { mu += M_HALFPI; } else if (area == pj_qsc_ns::AREA_2) { mu += M_PI; } else if (area == pj_qsc_ns::AREA_3) { mu += M_PI_HALFPI; } /* Now compute x, y from mu and nu */ /* t = tan(nu); */ xy.x = t * cos(mu); xy.y = t * sin(mu); return xy; } static PJ_LP qsc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_qsc_data *Q = static_cast(P->opaque); double mu, nu, cosmu, tannu; double tantheta, theta, cosphi, phi; double t; int area; /* Convert the input x, y to the mu and nu angles as used by QSC. * This depends on the area of the cube face. */ nu = atan(sqrt(xy.x * xy.x + xy.y * xy.y)); mu = atan2(xy.y, xy.x); if (xy.x >= 0.0 && xy.x >= fabs(xy.y)) { area = pj_qsc_ns::AREA_0; } else if (xy.y >= 0.0 && xy.y >= fabs(xy.x)) { area = pj_qsc_ns::AREA_1; mu -= M_HALFPI; } else if (xy.x < 0.0 && -xy.x >= fabs(xy.y)) { area = pj_qsc_ns::AREA_2; mu = (mu < 0.0 ? mu + M_PI : mu - M_PI); } else { area = pj_qsc_ns::AREA_3; mu += M_HALFPI; } /* Compute phi and theta for the area of definition. * The inverse projection is not described in the original paper, but some * good hints can be found here (as of 2011-12-14): * http://fits.gsfc.nasa.gov/fitsbits/saf.93/saf.9302 * (search for "Message-Id: <9302181759.AA25477 at fits.cv.nrao.edu>") */ t = (M_PI / 12.0) * tan(mu); tantheta = sin(t) / (cos(t) - (1.0 / sqrt(2.0))); theta = atan(tantheta); cosmu = cos(mu); tannu = tan(nu); cosphi = 1.0 - cosmu * cosmu * tannu * tannu * (1.0 - cos(atan(1.0 / cos(theta)))); if (cosphi < -1.0) { cosphi = -1.0; } else if (cosphi > +1.0) { cosphi = +1.0; } /* Apply the result to the real area on the cube face. * For the top and bottom face, we can compute phi and lam directly. * For the other faces, we must use unit sphere cartesian coordinates * as an intermediate step. */ if (Q->face == pj_qsc_ns::FACE_TOP) { phi = acos(cosphi); lp.phi = M_HALFPI - phi; if (area == pj_qsc_ns::AREA_0) { lp.lam = theta + M_HALFPI; } else if (area == pj_qsc_ns::AREA_1) { lp.lam = (theta < 0.0 ? theta + M_PI : theta - M_PI); } else if (area == pj_qsc_ns::AREA_2) { lp.lam = theta - M_HALFPI; } else /* area == pj_qsc_ns::AREA_3 */ { lp.lam = theta; } } else if (Q->face == pj_qsc_ns::FACE_BOTTOM) { phi = acos(cosphi); lp.phi = phi - M_HALFPI; if (area == pj_qsc_ns::AREA_0) { lp.lam = -theta + M_HALFPI; } else if (area == pj_qsc_ns::AREA_1) { lp.lam = -theta; } else if (area == pj_qsc_ns::AREA_2) { lp.lam = -theta - M_HALFPI; } else /* area == pj_qsc_ns::AREA_3 */ { lp.lam = (theta < 0.0 ? -theta - M_PI : -theta + M_PI); } } else { /* Compute phi and lam via cartesian unit sphere coordinates. */ double q, r, s; q = cosphi; t = q * q; if (t >= 1.0) { s = 0.0; } else { s = sqrt(1.0 - t) * sin(theta); } t += s * s; if (t >= 1.0) { r = 0.0; } else { r = sqrt(1.0 - t); } /* Rotate q,r,s into the correct area. */ if (area == pj_qsc_ns::AREA_1) { t = r; r = -s; s = t; } else if (area == pj_qsc_ns::AREA_2) { r = -r; s = -s; } else if (area == pj_qsc_ns::AREA_3) { t = r; r = s; s = -t; } /* Rotate q,r,s into the correct cube face. */ if (Q->face == pj_qsc_ns::FACE_RIGHT) { t = q; q = -r; r = t; } else if (Q->face == pj_qsc_ns::FACE_BACK) { q = -q; r = -r; } else if (Q->face == pj_qsc_ns::FACE_LEFT) { t = q; q = r; r = -t; } /* Now compute phi and lam from the unit sphere coordinates. */ lp.phi = acos(-s) - M_HALFPI; lp.lam = atan2(r, q); if (Q->face == pj_qsc_ns::FACE_RIGHT) { lp.lam = qsc_shift_longitude_origin(lp.lam, -M_HALFPI); } else if (Q->face == pj_qsc_ns::FACE_BACK) { lp.lam = qsc_shift_longitude_origin(lp.lam, -M_PI); } else if (Q->face == pj_qsc_ns::FACE_LEFT) { lp.lam = qsc_shift_longitude_origin(lp.lam, +M_HALFPI); } } /* Apply the shift from the sphere to the ellipsoid as described * in [LK12]. */ if (P->es != 0.0) { int invert_sign; double tanphi, xa; invert_sign = (lp.phi < 0.0 ? 1 : 0); tanphi = tan(lp.phi); xa = Q->b / sqrt(tanphi * tanphi + Q->one_minus_f_squared); lp.phi = atan(sqrt(P->a * P->a - xa * xa) / (Q->one_minus_f * xa)); if (invert_sign) { lp.phi = -lp.phi; } } return lp; } PJ *PJ_PROJECTION(qsc) { struct pj_qsc_data *Q = static_cast( calloc(1, sizeof(struct pj_qsc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->inv = qsc_e_inverse; P->fwd = qsc_e_forward; /* Determine the cube face from the center of projection. */ if (P->phi0 >= M_HALFPI - M_FORTPI / 2.0) { Q->face = pj_qsc_ns::FACE_TOP; } else if (P->phi0 <= -(M_HALFPI - M_FORTPI / 2.0)) { Q->face = pj_qsc_ns::FACE_BOTTOM; } else if (fabs(P->lam0) <= M_FORTPI) { Q->face = pj_qsc_ns::FACE_FRONT; } else if (fabs(P->lam0) <= M_HALFPI + M_FORTPI) { Q->face = (P->lam0 > 0.0 ? pj_qsc_ns::FACE_RIGHT : pj_qsc_ns::FACE_LEFT); } else { Q->face = pj_qsc_ns::FACE_BACK; } /* Fill in useful values for the ellipsoid <-> sphere shift * described in [LK12]. */ if (P->es != 0.0) { Q->a_squared = P->a * P->a; Q->b = P->a * sqrt(1.0 - P->es); Q->one_minus_f = 1.0 - (P->a - Q->b) / P->a; Q->one_minus_f_squared = Q->one_minus_f * Q->one_minus_f; } return P; } #undef EPS10 proj-9.8.1/src/projections/wink1.cpp000664 001750 001750 00000002307 15166171715 017340 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(wink1, "Winkel I") "\n\tPCyl, Sph\n\tlat_ts="; namespace { // anonymous namespace struct pj_wink1_data { double cosphi1; }; } // anonymous namespace static PJ_XY wink1_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = .5 * lp.lam * (static_cast(P->opaque)->cosphi1 + cos(lp.phi)); xy.y = lp.phi; return (xy); } static PJ_LP wink1_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = xy.y; lp.lam = 2. * xy.x / (static_cast(P->opaque)->cosphi1 + cos(lp.phi)); return (lp); } PJ *PJ_PROJECTION(wink1) { struct pj_wink1_data *Q = static_cast( calloc(1, sizeof(struct pj_wink1_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; static_cast(P->opaque)->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_ts").f); P->es = 0.; P->inv = wink1_s_inverse; P->fwd = wink1_s_forward; return P; } proj-9.8.1/src/projections/mbtfpp.cpp000664 001750 001750 00000003164 15166171715 017601 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(mbtfpp, "McBride-Thomas Flat-Polar Parabolic") "\n\tCyl, Sph"; #define CSy .95257934441568037152 #define FXC .92582009977255146156 #define FYC 3.40168025708304504493 #define C23 .66666666666666666666 #define C13 .33333333333333333333 #define ONEEPS 1.0000001 static PJ_XY mbtfpp_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; lp.phi = asin(CSy * sin(lp.phi)); xy.x = FXC * lp.lam * (2. * cos(C23 * lp.phi) - 1.); xy.y = FYC * sin(C13 * lp.phi); return xy; } static PJ_LP mbtfpp_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = xy.y / FYC; if (fabs(lp.phi) >= 1.) { if (fabs(lp.phi) > ONEEPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; } } else lp.phi = asin(lp.phi); lp.phi *= 3.; lp.lam = xy.x / (FXC * (2. * cos(C23 * lp.phi) - 1.)); lp.phi = sin(lp.phi) / CSy; if (fabs(lp.phi) >= 1.) { if (fabs(lp.phi) > ONEEPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = (lp.phi < 0.) ? -M_HALFPI : M_HALFPI; } } else lp.phi = asin(lp.phi); return lp; } PJ *PJ_PROJECTION(mbtfpp) { P->es = 0.; P->inv = mbtfpp_s_inverse; P->fwd = mbtfpp_s_forward; return P; } #undef CSy #undef FXC #undef FYC #undef C23 #undef C13 #undef ONEEPS proj-9.8.1/src/projections/eck1.cpp000664 001750 001750 00000001316 15166171715 017131 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(eck1, "Eckert I") "\n\tPCyl, Sph"; #define FC 0.92131773192356127802 #define RP 0.31830988618379067154 static PJ_XY eck1_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = FC * lp.lam * (1. - RP * fabs(lp.phi)); xy.y = FC * lp.phi; return xy; } static PJ_LP eck1_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = xy.y / FC; lp.lam = xy.x / (FC * (1. - RP * fabs(lp.phi))); return (lp); } PJ *PJ_PROJECTION(eck1) { P->es = 0.0; P->inv = eck1_s_inverse; P->fwd = eck1_s_forward; return P; } proj-9.8.1/src/projections/natearth.cpp000664 001750 001750 00000005761 15166171715 020124 0ustar00eveneven000000 000000 /* The Natural Earth projection was designed by Tom Patterson, US National Park Service, in 2007, using Flex Projector. The shape of the original projection was defined at every 5 degrees and piece-wise cubic spline interpolation was used to compute the complete graticule. The code here uses polynomial functions instead of cubic splines and is therefore much simpler to program. The polynomial approximation was developed by Bojan Savric, in collaboration with Tom Patterson and Bernhard Jenny, Institute of Cartography, ETH Zurich. It slightly deviates from Patterson's original projection by adding additional curvature to meridians where they meet the horizontal pole line. This improvement is by intention and designed in collaboration with Tom Patterson. Port to PROJ.4 by Bernhard Jenny, 6 June 2011 */ #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(natearth, "Natural Earth") "\n\tPCyl, Sph"; #define A0 0.8707 #define A1 -0.131979 #define A2 -0.013791 #define A3 0.003971 #define A4 -0.001529 #define B0 1.007226 #define B1 0.015085 #define B2 -0.044475 #define B3 0.028874 #define B4 -0.005916 #define C0 B0 #define C1 (3 * B1) #define C2 (7 * B2) #define C3 (9 * B3) #define C4 (11 * B4) #define EPS 1e-11 #define MAX_Y (0.8707 * 0.52 * M_PI) /* Not sure at all of the appropriate number for MAX_ITER... */ #define MAX_ITER 100 static PJ_XY natearth_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double phi2, phi4; (void)P; phi2 = lp.phi * lp.phi; phi4 = phi2 * phi2; xy.x = lp.lam * (A0 + phi2 * (A1 + phi2 * (A2 + phi4 * phi2 * (A3 + phi2 * A4)))); xy.y = lp.phi * (B0 + phi2 * (B1 + phi4 * (B2 + B3 * phi2 + B4 * phi4))); return xy; } static PJ_LP natearth_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double yc, y2, y4, f, fder; int i; (void)P; /* make sure y is inside valid range */ if (xy.y > MAX_Y) { xy.y = MAX_Y; } else if (xy.y < -MAX_Y) { xy.y = -MAX_Y; } /* latitude */ yc = xy.y; for (i = MAX_ITER; i; --i) { /* Newton-Raphson */ y2 = yc * yc; y4 = y2 * y2; f = (yc * (B0 + y2 * (B1 + y4 * (B2 + B3 * y2 + B4 * y4)))) - xy.y; fder = C0 + y2 * (C1 + y4 * (C2 + C3 * y2 + C4 * y4)); const double tol = f / fder; yc -= tol; if (fabs(tol) < EPS) { break; } } if (i == 0) proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = yc; /* longitude */ y2 = yc * yc; lp.lam = xy.x / (A0 + y2 * (A1 + y2 * (A2 + y2 * y2 * y2 * (A3 + y2 * A4)))); return lp; } PJ *PJ_PROJECTION(natearth) { P->es = 0; P->inv = natearth_s_inverse; P->fwd = natearth_s_forward; return P; } #undef A0 #undef A1 #undef A2 #undef A3 #undef A4 #undef A5 #undef B0 #undef B1 #undef B2 #undef B3 #undef C0 #undef C1 #undef C2 #undef C3 #undef EPS #undef MAX_Y #undef MAX_ITER proj-9.8.1/src/projections/gnom.cpp000664 001750 001750 00000013775 15166171715 017262 0ustar00eveneven000000 000000 #include #include #include #include "geodesic.h" #include "proj.h" #include "proj_internal.h" PROJ_HEAD(gnom, "Gnomonic") "\n\tAzi, Sph"; #define EPS10 1.e-10 namespace pj_gnom_ns { enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; } namespace { // anonymous namespace struct pj_gnom_data { double sinph0; double cosph0; enum pj_gnom_ns::Mode mode; struct geod_geodesic g; }; } // anonymous namespace static PJ_XY gnom_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_gnom_data *Q = static_cast(P->opaque); double coslam, cosphi, sinphi; sinphi = sin(lp.phi); cosphi = cos(lp.phi); coslam = cos(lp.lam); switch (Q->mode) { case pj_gnom_ns::EQUIT: xy.y = cosphi * coslam; break; case pj_gnom_ns::OBLIQ: xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam; break; case pj_gnom_ns::S_POLE: xy.y = -sinphi; break; case pj_gnom_ns::N_POLE: xy.y = sinphi; break; } if (xy.y <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = (xy.y = 1. / xy.y) * cosphi * sin(lp.lam); switch (Q->mode) { case pj_gnom_ns::EQUIT: xy.y *= sinphi; break; case pj_gnom_ns::OBLIQ: xy.y *= Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam; break; case pj_gnom_ns::N_POLE: coslam = -coslam; PROJ_FALLTHROUGH; case pj_gnom_ns::S_POLE: xy.y *= cosphi * coslam; break; } return xy; } static PJ_LP gnom_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_gnom_data *Q = static_cast(P->opaque); double rh, cosz, sinz; rh = hypot(xy.x, xy.y); sinz = sin(lp.phi = atan(rh)); cosz = sqrt(1. - sinz * sinz); if (fabs(rh) <= EPS10) { lp.phi = P->phi0; lp.lam = 0.; } else { switch (Q->mode) { case pj_gnom_ns::OBLIQ: lp.phi = cosz * Q->sinph0 + xy.y * sinz * Q->cosph0 / rh; if (fabs(lp.phi) >= 1.) lp.phi = lp.phi > 0. ? M_HALFPI : -M_HALFPI; else lp.phi = asin(lp.phi); xy.y = (cosz - Q->sinph0 * sin(lp.phi)) * rh; xy.x *= sinz * Q->cosph0; break; case pj_gnom_ns::EQUIT: lp.phi = xy.y * sinz / rh; if (fabs(lp.phi) >= 1.) lp.phi = lp.phi > 0. ? M_HALFPI : -M_HALFPI; else lp.phi = asin(lp.phi); xy.y = cosz * rh; xy.x *= sinz; break; case pj_gnom_ns::S_POLE: lp.phi -= M_HALFPI; break; case pj_gnom_ns::N_POLE: lp.phi = M_HALFPI - lp.phi; xy.y = -xy.y; break; } lp.lam = atan2(xy.x, xy.y); } return lp; } static PJ_XY gnom_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_gnom_data *Q = static_cast(P->opaque); double lat0 = P->phi0 / DEG_TO_RAD, lon0 = 0, lat1 = lp.phi / DEG_TO_RAD, lon1 = lp.lam / DEG_TO_RAD, azi0, m, M; geod_geninverse(&Q->g, lat0, lon0, lat1, lon1, nullptr, &azi0, nullptr, &m, &M, nullptr, nullptr); if (M <= 0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); xy.x = xy.y = HUGE_VAL; } else { double rho = m / M; azi0 *= DEG_TO_RAD; xy.x = rho * sin(azi0); xy.y = rho * cos(azi0); } return xy; } static PJ_LP gnom_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ constexpr int numit_ = 10; static const double eps_ = 0.01 * sqrt(DBL_EPSILON); PJ_LP lp = {0.0, 0.0}; struct pj_gnom_data *Q = static_cast(P->opaque); double lat0 = P->phi0 / DEG_TO_RAD, lon0 = 0, azi0 = atan2(xy.x, xy.y) / DEG_TO_RAD, // Clockwise from north rho = hypot(xy.x, xy.y), s = atan(rho); bool little = rho <= 1; if (!little) rho = 1 / rho; struct geod_geodesicline l; geod_lineinit(&l, &Q->g, lat0, lon0, azi0, GEOD_LATITUDE | GEOD_LONGITUDE | GEOD_DISTANCE_IN | GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE); int count = numit_, trip = 0; double lat1 = 0, lon1 = 0; while (count--) { double m, M; geod_genposition(&l, 0, s, &lat1, &lon1, nullptr, &s, &m, &M, nullptr, nullptr); if (trip) break; // If little, solve rho(s) = rho with drho(s)/ds = 1/M^2 // else solve 1/rho(s) = 1/rho with d(1/rho(s))/ds = -1/m^2 double ds = little ? (m - rho * M) * M : (rho * m - M) * m; s -= ds; // Reversed test to allow escape with NaNs if (!(fabs(ds) >= eps_)) ++trip; } if (trip) { lp.phi = lat1 * DEG_TO_RAD; lp.lam = lon1 * DEG_TO_RAD; } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = lp.lam = HUGE_VAL; } return lp; } PJ *PJ_PROJECTION(gnom) { struct pj_gnom_data *Q = static_cast( calloc(1, sizeof(struct pj_gnom_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (P->es == 0) { if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) { Q->mode = P->phi0 < 0. ? pj_gnom_ns::S_POLE : pj_gnom_ns::N_POLE; } else if (fabs(P->phi0) < EPS10) { Q->mode = pj_gnom_ns::EQUIT; } else { Q->mode = pj_gnom_ns::OBLIQ; Q->sinph0 = sin(P->phi0); Q->cosph0 = cos(P->phi0); } P->inv = gnom_s_inverse; P->fwd = gnom_s_forward; } else { geod_init(&Q->g, 1, P->f); P->inv = gnom_e_inverse; P->fwd = gnom_e_forward; } P->es = 0.; return P; } proj-9.8.1/src/projections/mbt_fps.cpp000664 001750 001750 00000002606 15166171715 017743 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(mbt_fps, "McBryde-Thomas Flat-Pole Sine (No. 2)") "\n\tCyl, Sph"; #define MAX_ITER 10 #define LOOP_TOL 1e-7 #define C1 0.45503 #define C2 1.36509 #define C3 1.41546 #define C_x 0.22248 #define C_y 1.44492 #define C1_2 0.33333333333333333333333333 static PJ_XY mbt_fps_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; const double k = C3 * sin(lp.phi); for (int i = MAX_ITER; i; --i) { const double t = lp.phi / C2; const double V = (C1 * sin(t) + sin(lp.phi) - k) / (C1_2 * cos(t) + cos(lp.phi)); lp.phi -= V; if (fabs(V) < LOOP_TOL) break; } const double t = lp.phi / C2; xy.x = C_x * lp.lam * (1. + 3. * cos(lp.phi) / cos(t)); xy.y = C_y * sin(t); return xy; } static PJ_LP mbt_fps_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; const double t = aasin(P->ctx, xy.y / C_y); lp.phi = C2 * t; lp.lam = xy.x / (C_x * (1. + 3. * cos(lp.phi) / cos(t))); lp.phi = aasin(P->ctx, (C1 * sin(t) + sin(lp.phi)) / C3); return (lp); } PJ *PJ_PROJECTION(mbt_fps) { P->es = 0; P->inv = mbt_fps_s_inverse; P->fwd = mbt_fps_s_forward; return P; } #undef MAX_ITER #undef LOOP_TOL #undef C1 #undef C2 #undef C3 #undef C_x #undef C_y #undef C1_2 proj-9.8.1/src/projections/eck4.cpp000664 001750 001750 00000005054 15166171715 017137 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(eck4, "Eckert IV") "\n\tPCyl, Sph"; // C_x = 2 / sqrt(4 * M_PI + M_PI * M_PI) constexpr double C_x = .42223820031577120149; // C_y = 2 * sqrt(M_PI / (4 + M_PI)) constexpr double C_y = 1.32650042817700232218; // RC_y = 1. / C_y constexpr double RC_y = .75386330736002178205; // C_p = 2 + M_PI / 2 constexpr double C_p = 3.57079632679489661922; // RC_p = 1. / C_p constexpr double RC_p = .28004957675577868795; static PJ_XY eck4_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; int i; (void)P; // Newton's iterative method to find theta such as // theta + sin(theta) * cos(theta) + 2 * sin(theta) == C_p * sin(phi) const double p = C_p * sin(lp.phi); double V = lp.phi * lp.phi; double theta = lp.phi * (0.895168 + V * (0.0218849 + V * 0.00826809)); constexpr int NITER = 6; for (i = NITER; i; --i) { const double c = cos(theta); const double s = sin(theta); V = (theta + s * (c + 2.) - p) / (1. + c * (c + 2.) - s * s); theta -= V; constexpr double EPS = 1e-7; if (fabs(V) < EPS) break; } if (!i) { xy.x = C_x * lp.lam; xy.y = theta < 0. ? -C_y : C_y; } else { xy.x = C_x * lp.lam * (1. + cos(theta)); xy.y = C_y * sin(theta); } return xy; } static PJ_LP eck4_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; const double sin_theta = xy.y * RC_y; const double one_minus_abs_sin_theta = 1.0 - fabs(sin_theta); if (one_minus_abs_sin_theta >= 0.0 && one_minus_abs_sin_theta <= 1e-12) { lp.lam = xy.x / C_x; lp.phi = sin_theta > 0 ? M_PI / 2 : -M_PI / 2; } else { const double theta = aasin(P->ctx, sin_theta); const double cos_theta = cos(theta); lp.lam = xy.x / (C_x * (1. + cos_theta)); const double sin_phi = (theta + sin_theta * (cos_theta + 2.)) * RC_p; lp.phi = aasin(P->ctx, sin_phi); } if (!P->over) { const double fabs_lam_minus_pi = fabs(lp.lam) - M_PI; if (fabs_lam_minus_pi > 0.0) { if (fabs_lam_minus_pi > 1e-10) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.lam = lp.lam > 0 ? M_PI : -M_PI; } } } return lp; } PJ *PJ_PROJECTION(eck4) { P->es = 0.0; P->inv = eck4_s_inverse; P->fwd = eck4_s_forward; return P; } proj-9.8.1/src/projections/tcc.cpp000664 001750 001750 00000001230 15166171715 017052 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(tcc, "Transverse Central Cylindrical") "\n\tCyl, Sph, no inv"; #define EPS10 1.e-10 static PJ_XY tcc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; const double b = cos(lp.phi) * sin(lp.lam); const double bt = 1. - b * b; if (bt < EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = b / sqrt(bt); xy.y = atan2(tan(lp.phi), cos(lp.lam)); return xy; } PJ *PJ_PROJECTION(tcc) { P->es = 0.; P->fwd = tcc_s_forward; P->inv = nullptr; return P; } proj-9.8.1/src/projections/som.cpp000664 001750 001750 00000025736 15166171715 017120 0ustar00eveneven000000 000000 /****************************************************************************** * This implements the Space Oblique Mercator (SOM) projection, used by the * Multi-angle Imaging SpectroRadiometer (MISR) products, from the NASA EOS *Terra platform among others (e.g. ASTER). * * This code was originally developed for the Landsat SOM projection with the * following parameters set for Landsat satellites 1, 2, and 3: * * inclination angle = 99.092 degrees * period of revolution = 103.2669323 minutes * ascending longitude = 128.87 degrees - (360 / 251) * path_number * * or for Landsat satellites greater than 3: * * inclination angle = 98.2 degrees * period of revolution = 98.8841202 minutes * ascending longitude = 129.3 degrees - (360 / 233) * path_number * * For the MISR path based SOM projection, the code is identical to that of *Landsat SOM with the following parameter changes: * * inclination angle = 98.30382 degrees * period of revolution = 98.88 minutes * ascending longitude = 129.3056 degrees - (360 / 233) * path_number * * and the following code used for Landsat: * * Q->rlm = PI * (1. / 248. + .5161290322580645); * * changed to: * * Q->rlm = 0 * * For the generic SOM projection, the code is identical to the above for MISR * except that the following parameters are now taken as input rather than *derived from path number: * * inclination angle * period of revolution * ascending longitude * * The change of Q->rlm = 0 is kept. * *****************************************************************************/ /* based upon Snyder and Linck, USGS-NMD */ #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(som, "Space Oblique Mercator") "\n\tCyl, Sph&Ell\n\tinc_angle= ps_rev= asc_lon= "; PROJ_HEAD(misrsom, "Space oblique for MISR") "\n\tCyl, Sph&Ell\n\tpath="; PROJ_HEAD(lsat, "Space oblique for LANDSAT") "\n\tCyl, Sph&Ell\n\tlsat= path="; #define TOL 1e-7 namespace { // anonymous namespace struct pj_som_data { double a2, a4, b, c1, c3; double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; double alf; }; } // anonymous namespace static void seraz0(double lam, double mult, PJ *P) { struct pj_som_data *Q = static_cast(P->opaque); double sdsq, h, s, fc, sd, sq, d_1 = 0; lam *= DEG_TO_RAD; sd = sin(lam); sdsq = sd * sd; s = Q->p22 * Q->sa * cos(lam) * sqrt((1. + Q->t * sdsq) / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); d_1 = 1. + Q->q * sdsq; h = sqrt((1. + Q->q * sdsq) / (1. + Q->w * sdsq)) * ((1. + Q->w * sdsq) / (d_1 * d_1) - Q->p22 * Q->ca); sq = sqrt(Q->xj * Q->xj + s * s); fc = mult * (h * Q->xj - s * s) / sq; Q->b += fc; Q->a2 += fc * cos(lam + lam); Q->a4 += fc * cos(lam * 4.); fc = mult * s * (h + Q->xj) / sq; Q->c1 += fc * cos(lam); Q->c3 += fc * cos(lam * 3.); } static PJ_XY som_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_som_data *Q = static_cast(P->opaque); int l, nn; double lamt = 0.0, xlam, sdsq, c, d, s, lamdp = 0.0, phidp, lampp, tanph; double lamtp, cl, sd, sp, sav, tanphi; if (lp.phi > M_HALFPI) lp.phi = M_HALFPI; else if (lp.phi < -M_HALFPI) lp.phi = -M_HALFPI; if (lp.phi >= 0.) lampp = M_HALFPI; else lampp = M_PI_HALFPI; tanphi = tan(lp.phi); for (nn = 0;;) { double fac; sav = lampp; lamtp = lp.lam + Q->p22 * lampp; cl = cos(lamtp); if (cl < 0) fac = lampp + sin(lampp) * M_HALFPI; else fac = lampp - sin(lampp) * M_HALFPI; for (l = 50; l >= 0; --l) { lamt = lp.lam + Q->p22 * sav; c = cos(lamt); if (fabs(c) < TOL) lamt -= TOL; xlam = (P->one_es * tanphi * Q->sa + sin(lamt) * Q->ca) / c; lamdp = atan(xlam) + fac; if (fabs(fabs(sav) - fabs(lamdp)) < TOL) break; sav = lamdp; } if (!l || ++nn >= 3 || (lamdp > Q->rlm && lamdp < Q->rlm2)) break; if (lamdp <= Q->rlm) lampp = M_TWOPI_HALFPI; else if (lamdp >= Q->rlm2) lampp = M_HALFPI; } if (l) { sp = sin(lp.phi); phidp = aasin( P->ctx, (P->one_es * Q->ca * sp - Q->sa * cos(lp.phi) * sin(lamt)) / sqrt(1. - P->es * sp * sp)); tanph = log(tan(M_FORTPI + .5 * phidp)); sd = sin(lamdp); sdsq = sd * sd; s = Q->p22 * Q->sa * cos(lamdp) * sqrt((1. + Q->t * sdsq) / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); d = sqrt(Q->xj * Q->xj + s * s); xy.x = Q->b * lamdp + Q->a2 * sin(2. * lamdp) + Q->a4 * sin(lamdp * 4.) - tanph * s / d; xy.y = Q->c1 * sd + Q->c3 * sin(lamdp * 3.) + tanph * Q->xj / d; } else xy.x = xy.y = HUGE_VAL; return xy; } static PJ_LP som_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_som_data *Q = static_cast(P->opaque); int nn; double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp; lamdp = xy.x / Q->b; nn = 50; do { sav = lamdp; sd = sin(lamdp); sdsq = sd * sd; s = Q->p22 * Q->sa * cos(lamdp) * sqrt((1. + Q->t * sdsq) / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq))); lamdp = xy.x + xy.y * s / Q->xj - Q->a2 * sin(2. * lamdp) - Q->a4 * sin(lamdp * 4.) - s / Q->xj * (Q->c1 * sin(lamdp) + Q->c3 * sin(lamdp * 3.)); lamdp /= Q->b; } while (fabs(lamdp - sav) >= TOL && --nn); sl = sin(lamdp); fac = exp(sqrt(1. + s * s / Q->xj / Q->xj) * (xy.y - Q->c1 * sl - Q->c3 * sin(lamdp * 3.))); phidp = 2. * (atan(fac) - M_FORTPI); dd = sl * sl; if (fabs(cos(lamdp)) < TOL) lamdp -= TOL; spp = sin(phidp); sppsq = spp * spp; const double denom = 1. - sppsq * (1. + Q->u); if (denom == 0.0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } lamt = atan( ((1. - sppsq * P->rone_es) * tan(lamdp) * Q->ca - spp * Q->sa * sqrt((1. + Q->q * dd) * (1. - sppsq) - sppsq * Q->u) / cos(lamdp)) / denom); sl = lamt >= 0. ? 1. : -1.; scl = cos(lamdp) >= 0. ? 1. : -1; lamt -= M_HALFPI * (1. - scl) * sl; lp.lam = lamt - Q->p22 * lamdp; if (fabs(Q->sa) < TOL) lp.phi = aasin(P->ctx, spp / sqrt(P->one_es * P->one_es + P->es * sppsq)); else lp.phi = atan((tan(lamdp) * cos(lamt) - Q->ca * sin(lamt)) / (P->one_es * Q->sa)); return lp; } static PJ *som_setup(PJ *P) { double esc, ess, lam; struct pj_som_data *Q = static_cast(P->opaque); Q->sa = sin(Q->alf); Q->ca = cos(Q->alf); if (fabs(Q->ca) < 1e-9) Q->ca = 1e-9; esc = P->es * Q->ca * Q->ca; ess = P->es * Q->sa * Q->sa; Q->w = (1. - esc) * P->rone_es; Q->w = Q->w * Q->w - 1.; Q->q = ess * P->rone_es; Q->t = ess * (2. - P->es) * P->rone_es * P->rone_es; Q->u = esc * P->rone_es; Q->xj = P->one_es * P->one_es * P->one_es; Q->rlm2 = Q->rlm + M_TWOPI; Q->a2 = Q->a4 = Q->b = Q->c1 = Q->c3 = 0.; seraz0(0., 1., P); for (lam = 9.; lam <= 81.0001; lam += 18.) seraz0(lam, 4., P); for (lam = 18; lam <= 72.0001; lam += 18.) seraz0(lam, 2., P); seraz0(90., 1., P); Q->a2 /= 30.; Q->a4 /= 60.; Q->b /= 30.; Q->c1 /= 15.; Q->c3 /= 45.; P->inv = som_e_inverse; P->fwd = som_e_forward; return P; } PJ *PJ_PROJECTION(som) { struct pj_som_data *Q = static_cast( calloc(1, sizeof(struct pj_som_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; // ascending longitude (radians) P->lam0 = pj_param(P->ctx, P->params, "rasc_lon").f; if (P->lam0 < -M_TWOPI || P->lam0 > M_TWOPI) { proj_log_error(P, _("Invalid value for ascending longitude: should be in " "[-2pi, 2pi] range")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } // inclination angle (radians) Q->alf = pj_param(P->ctx, P->params, "rinc_angle").f; if (Q->alf < 0 || Q->alf > M_PI) { proj_log_error(P, _("Invalid value for inclination angle: should be in " "[0, pi] range")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } // period of revolution (day / rev) Q->p22 = pj_param(P->ctx, P->params, "dps_rev").f; if (Q->p22 < 0) { proj_log_error(P, _("Number of days per rotation should be positive")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->rlm = 0; return som_setup(P); } PJ *PJ_PROJECTION(misrsom) { int path; struct pj_som_data *Q = static_cast( calloc(1, sizeof(struct pj_som_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; path = pj_param(P->ctx, P->params, "ipath").i; if (path <= 0 || path > 233) { proj_log_error( P, _("Invalid value for path: path should be in [1, 233] range")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->lam0 = DEG_TO_RAD * 129.3056 - M_TWOPI / 233. * path; Q->alf = 98.30382 * DEG_TO_RAD; Q->p22 = 98.88 / 1440.0; Q->rlm = 0; return som_setup(P); } PJ *PJ_PROJECTION(lsat) { int land, path; struct pj_som_data *Q = static_cast( calloc(1, sizeof(struct pj_som_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; land = pj_param(P->ctx, P->params, "ilsat").i; if (land <= 0 || land > 5) { proj_log_error( P, _("Invalid value for lsat: lsat should be in [1, 5] range")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } path = pj_param(P->ctx, P->params, "ipath").i; const int maxPathVal = (land <= 3 ? 251 : 233); if (path <= 0 || path > maxPathVal) { proj_log_error( P, _("Invalid value for path: path should be in [1, %d] range"), maxPathVal); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (land <= 3) { P->lam0 = DEG_TO_RAD * 128.87 - M_TWOPI / 251. * path; Q->p22 = 103.2669323; Q->alf = DEG_TO_RAD * 99.092; } else { P->lam0 = DEG_TO_RAD * 129.3 - M_TWOPI / 233. * path; Q->p22 = 98.8841202; Q->alf = DEG_TO_RAD * 98.2; } Q->p22 /= 1440.; Q->rlm = M_PI * (1. / 248. + .5161290322580645); return som_setup(P); } #undef TOL proj-9.8.1/src/projections/mod_ster.cpp000664 001750 001750 00000017466 15166171715 020137 0ustar00eveneven000000 000000 /* based upon Snyder and Linck, USGS-NMD */ #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(mil_os, "Miller Oblated Stereographic") "\n\tAzi(mod)"; PROJ_HEAD(lee_os, "Lee Oblated Stereographic") "\n\tAzi(mod)"; PROJ_HEAD(gs48, "Modified Stereographic of 48 U.S.") "\n\tAzi(mod)"; PROJ_HEAD(alsk, "Modified Stereographic of Alaska") "\n\tAzi(mod)"; PROJ_HEAD(gs50, "Modified Stereographic of 50 U.S.") "\n\tAzi(mod)"; #define EPSLN 1e-12 namespace { // anonymous namespace struct pj_mod_ster_data { const COMPLEX *zcoeff; double cchio, schio; int n; }; } // anonymous namespace static PJ_XY mod_ster_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_mod_ster_data *Q = static_cast(P->opaque); double sinlon, coslon, esphi, chi, schi, cchi, s; COMPLEX p; sinlon = sin(lp.lam); coslon = cos(lp.lam); esphi = P->e * sin(lp.phi); chi = 2. * atan(tan((M_HALFPI + lp.phi) * .5) * pow((1. - esphi) / (1. + esphi), P->e * .5)) - M_HALFPI; schi = sin(chi); cchi = cos(chi); const double denom = 1. + Q->schio * schi + Q->cchio * cchi * coslon; if (denom == 0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } s = 2. / denom; p.r = s * cchi * sinlon; p.i = s * (Q->cchio * schi - Q->schio * cchi * coslon); p = pj_zpoly1(p, Q->zcoeff, Q->n); xy.x = p.r; xy.y = p.i; return xy; } static PJ_LP mod_ster_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_mod_ster_data *Q = static_cast(P->opaque); int nn; COMPLEX p, fxy, fpxy, dp; double den, rh = 0.0, z, sinz = 0.0, cosz = 0.0, chi, phi = 0.0, esphi; p.r = xy.x; p.i = xy.y; for (nn = 20; nn; --nn) { fxy = pj_zpolyd1(p, Q->zcoeff, Q->n, &fpxy); fxy.r -= xy.x; fxy.i -= xy.y; den = fpxy.r * fpxy.r + fpxy.i * fpxy.i; dp.r = -(fxy.r * fpxy.r + fxy.i * fpxy.i) / den; dp.i = -(fxy.i * fpxy.r - fxy.r * fpxy.i) / den; p.r += dp.r; p.i += dp.i; if ((fabs(dp.r) + fabs(dp.i)) <= EPSLN) break; } if (nn) { rh = hypot(p.r, p.i); z = 2. * atan(.5 * rh); sinz = sin(z); cosz = cos(z); if (fabs(rh) <= EPSLN) { /* if we end up here input coordinates were (0,0). * pj_inv() adds P->lam0 to lp.lam, this way we are * sure to get the correct offset */ lp.lam = 0.0; lp.phi = P->phi0; return lp; } chi = aasin(P->ctx, cosz * Q->schio + p.i * sinz * Q->cchio / rh); phi = chi; for (nn = 20; nn; --nn) { double dphi; esphi = P->e * sin(phi); dphi = 2. * atan(tan((M_HALFPI + chi) * .5) * pow((1. + esphi) / (1. - esphi), P->e * .5)) - M_HALFPI - phi; phi += dphi; if (fabs(dphi) <= EPSLN) break; } } if (nn) { lp.phi = phi; lp.lam = atan2(p.r * sinz, rh * Q->cchio * cosz - p.i * Q->schio * sinz); } else lp.lam = lp.phi = HUGE_VAL; return lp; } static PJ *mod_ster_setup(PJ *P) { /* general initialization */ struct pj_mod_ster_data *Q = static_cast(P->opaque); double esphi, chio; if (P->es != 0.0) { esphi = P->e * sin(P->phi0); chio = 2. * atan(tan((M_HALFPI + P->phi0) * .5) * pow((1. - esphi) / (1. + esphi), P->e * .5)) - M_HALFPI; } else chio = P->phi0; Q->schio = sin(chio); Q->cchio = cos(chio); P->inv = mod_ster_e_inverse; P->fwd = mod_ster_e_forward; return P; } /* Miller Oblated Stereographic */ PJ *PJ_PROJECTION(mil_os) { static const COMPLEX AB[] = {{0.924500, 0.}, {0., 0.}, {0.019430, 0.}}; struct pj_mod_ster_data *Q = static_cast( calloc(1, sizeof(struct pj_mod_ster_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 2; P->lam0 = DEG_TO_RAD * 20.; P->phi0 = DEG_TO_RAD * 18.; Q->zcoeff = AB; P->es = 0.; return mod_ster_setup(P); } /* Lee Oblated Stereographic */ PJ *PJ_PROJECTION(lee_os) { static const COMPLEX AB[] = { {0.721316, 0.}, {0., 0.}, {-0.0088162, -0.00617325}}; struct pj_mod_ster_data *Q = static_cast( calloc(1, sizeof(struct pj_mod_ster_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 2; P->lam0 = DEG_TO_RAD * -165.; P->phi0 = DEG_TO_RAD * -10.; Q->zcoeff = AB; P->es = 0.; return mod_ster_setup(P); } PJ *PJ_PROJECTION(gs48) { static const COMPLEX /* 48 United States */ AB[] = { {0.98879, 0.}, {0., 0.}, {-0.050909, 0.}, {0., 0.}, {0.075528, 0.}}; struct pj_mod_ster_data *Q = static_cast( calloc(1, sizeof(struct pj_mod_ster_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 4; P->lam0 = DEG_TO_RAD * -96.; P->phi0 = DEG_TO_RAD * 39.; Q->zcoeff = AB; P->es = 0.; P->a = 6370997.; return mod_ster_setup(P); } PJ *PJ_PROJECTION(alsk) { static const COMPLEX ABe[] = { /* Alaska ellipsoid */ {.9945303, 0.}, {.0052083, -.0027404}, {.0072721, .0048181}, {-.0151089, -.1932526}, {.0642675, -.1381226}, {.3582802, -.2884586}, }; static const COMPLEX ABs[] = {/* Alaska sphere */ {.9972523, 0.}, {.0052513, -.0041175}, {.0074606, .0048125}, {-.0153783, -.1968253}, {.0636871, -.1408027}, {.3660976, -.2937382}}; struct pj_mod_ster_data *Q = static_cast( calloc(1, sizeof(struct pj_mod_ster_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 5; P->lam0 = DEG_TO_RAD * -152.; P->phi0 = DEG_TO_RAD * 64.; if (P->es != 0.0) { /* fixed ellipsoid/sphere */ Q->zcoeff = ABe; P->a = 6378206.4; P->e = sqrt(P->es = 0.00676866); } else { Q->zcoeff = ABs; P->a = 6370997.; } return mod_ster_setup(P); } PJ *PJ_PROJECTION(gs50) { static const COMPLEX ABe[] = { /* GS50 ellipsoid */ {.9827497, 0.}, {.0210669, .0053804}, {-.1031415, -.0571664}, {-.0323337, -.0322847}, {.0502303, .1211983}, {.0251805, .0895678}, {-.0012315, -.1416121}, {.0072202, -.1317091}, {-.0194029, .0759677}, {-.0210072, .0834037}}; static const COMPLEX ABs[] = { /* GS50 sphere */ {.9842990, 0.}, {.0211642, .0037608}, {-.1036018, -.0575102}, {-.0329095, -.0320119}, {.0499471, .1223335}, {.0260460, .0899805}, {.0007388, -.1435792}, {.0075848, -.1334108}, {-.0216473, .0776645}, {-.0225161, .0853673}}; struct pj_mod_ster_data *Q = static_cast( calloc(1, sizeof(struct pj_mod_ster_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = 9; P->lam0 = DEG_TO_RAD * -120.; P->phi0 = DEG_TO_RAD * 45.; if (P->es != 0.0) { /* fixed ellipsoid/sphere */ Q->zcoeff = ABe; P->a = 6378206.4; P->e = sqrt(P->es = 0.00676866); } else { Q->zcoeff = ABs; P->a = 6370997.; } return mod_ster_setup(P); } #undef EPSLN proj-9.8.1/src/projections/urm5.cpp000664 001750 001750 00000003530 15166171715 017176 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(urm5, "Urmaev V") "\n\tPCyl, Sph, no inv\n\tn= q= alpha="; namespace { // anonymous namespace struct pj_urm5 { double m, rmn, q3, n; }; } // anonymous namespace static PJ_XY urm5_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_urm5 *Q = static_cast(P->opaque); double t; t = lp.phi = aasin(P->ctx, Q->n * sin(lp.phi)); xy.x = Q->m * lp.lam * cos(lp.phi); t *= t; xy.y = lp.phi * (1. + t * Q->q3) * Q->rmn; return xy; } PJ *PJ_PROJECTION(urm5) { double alpha, t; struct pj_urm5 *Q = static_cast(calloc(1, sizeof(struct pj_urm5))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (!pj_param(P->ctx, P->params, "tn").i) { proj_log_error(P, _("Missing parameter n.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->n = pj_param(P->ctx, P->params, "dn").f; if (Q->n <= 0. || Q->n > 1.) { proj_log_error(P, _("Invalid value for n: it should be in ]0,1] range.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->q3 = pj_param(P->ctx, P->params, "dq").f / 3.; alpha = pj_param(P->ctx, P->params, "ralpha").f; t = Q->n * sin(alpha); const double denom = sqrt(1. - t * t); if (denom == 0) { proj_log_error( P, _("Invalid value for n / alpha: n * sin(|alpha|) should be < 1.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->m = cos(alpha) / denom; Q->rmn = 1. / (Q->m * Q->n); P->es = 0.; P->inv = nullptr; P->fwd = urm5_s_forward; return P; } proj-9.8.1/src/projections/nzmg.cpp000664 001750 001750 00000010035 15166171715 017257 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the nzmg (New Zealand Map Grid) projection. * Very loosely based upon DMA code by Bradford W. Drew * Author: Gerald Evenden * ****************************************************************************** * Copyright (c) 1995, Gerald Evenden * * 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 #include "proj.h" #include "proj_internal.h" PROJ_HEAD(nzmg, "New Zealand Map Grid") "\n\tfixed Earth"; #define EPSLN 1e-10 #define SEC5_TO_RAD 0.4848136811095359935899141023 #define RAD_TO_SEC5 2.062648062470963551564733573 static const COMPLEX bf[] = { {.7557853228, 0.0}, {.249204646, 0.003371507}, {-.001541739, 0.041058560}, {-.10162907, 0.01727609}, {-.26623489, -0.36249218}, {-.6870983, -1.1651967}}; #define Nbf 5 #define Ntpsi 9 #define Ntphi 8 static PJ_XY nzmg_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; COMPLEX p; const double *C; int i; static const double tpsi[] = { .6399175073, -.1358797613, .063294409, -.02526853, .0117879, -.0055161, .0026906, -.001333, .00067, -.00034}; lp.phi = (lp.phi - P->phi0) * RAD_TO_SEC5; i = Ntpsi; C = tpsi + i; for (p.r = *C; i; --i) { --C; p.r = *C + lp.phi * p.r; } p.r *= lp.phi; p.i = lp.lam; p = pj_zpoly1(p, bf, Nbf); xy.x = p.i; xy.y = p.r; return xy; } static PJ_LP nzmg_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; int nn, i; COMPLEX p, f, fp, dp; double den; const double *C; static const double tphi[] = {1.5627014243, .5185406398, -.03333098, -.1052906, -.0368594, .007317, .01220, .00394, -.0013}; p.r = xy.y; p.i = xy.x; for (nn = 20; nn; --nn) { f = pj_zpolyd1(p, bf, Nbf, &fp); f.r -= xy.y; f.i -= xy.x; den = fp.r * fp.r + fp.i * fp.i; dp.r = -(f.r * fp.r + f.i * fp.i) / den; dp.i = -(f.i * fp.r - f.r * fp.i) / den; p.r += dp.r; p.i += dp.i; if ((fabs(dp.r) + fabs(dp.i)) <= EPSLN) break; } if (nn) { lp.lam = p.i; i = Ntphi; C = tphi + i; for (lp.phi = *C; i; --i) { --C; lp.phi = *C + p.r * lp.phi; } lp.phi = P->phi0 + p.r * lp.phi * SEC5_TO_RAD; } else lp.lam = lp.phi = HUGE_VAL; return lp; } PJ *PJ_PROJECTION(nzmg) { /* force to International major axis */ P->ra = 1. / (P->a = 6378388.0); P->lam0 = DEG_TO_RAD * 173.; P->phi0 = DEG_TO_RAD * -41.; P->x0 = 2510000.; P->y0 = 6023150.; P->inv = nzmg_e_inverse; P->fwd = nzmg_e_forward; return P; } #undef EPSLN #undef SEC5_TO_RAD #undef RAD_TO_SEC5 #undef Nbf #undef Ntpsi #undef Ntphi proj-9.8.1/src/projections/wag7.cpp000664 001750 001750 00000001262 15166171715 017153 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(wag7, "Wagner VII") "\n\tMisc Sph, no inv"; static PJ_XY wag7_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; /* Shut up compiler warnings about unused P */ xy.y = 0.90630778703664996 * sin(lp.phi); const double theta = asin(xy.y); const double ct = cos(theta); lp.lam /= 3.; xy.x = 2.66723 * ct * sin(lp.lam); const double D = 1 / (sqrt(0.5 * (1 + ct * cos(lp.lam)))); xy.y *= 1.24104 * D; xy.x *= D; return (xy); } PJ *PJ_PROJECTION(wag7) { P->fwd = wag7_s_forward; P->inv = nullptr; P->es = 0.; return P; } proj-9.8.1/src/projections/urmfps.cpp000664 001750 001750 00000004534 15166171715 017627 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(urmfps, "Urmaev Flat-Polar Sinusoidal") "\n\tPCyl, Sph\n\tn="; PROJ_HEAD(wag1, "Wagner I (Kavrayskiy VI)") "\n\tPCyl, Sph"; namespace { // anonymous namespace struct pj_urmfps { double n, C_y; }; } // anonymous namespace #define C_x 0.8773826753 #define Cy 1.139753528477 static PJ_XY urmfps_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; lp.phi = aasin(P->ctx, static_cast(P->opaque)->n * sin(lp.phi)); xy.x = C_x * lp.lam * cos(lp.phi); xy.y = static_cast(P->opaque)->C_y * lp.phi; return xy; } static PJ_LP urmfps_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; xy.y /= static_cast(P->opaque)->C_y; lp.phi = aasin(P->ctx, sin(xy.y) / static_cast(P->opaque)->n); lp.lam = xy.x / (C_x * cos(xy.y)); return lp; } static PJ *urmfps_setup(PJ *P) { static_cast(P->opaque)->C_y = Cy / static_cast(P->opaque)->n; P->es = 0.; P->inv = urmfps_s_inverse; P->fwd = urmfps_s_forward; return P; } PJ *PJ_PROJECTION(urmfps) { struct pj_urmfps *Q = static_cast(calloc(1, sizeof(struct pj_urmfps))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (!pj_param(P->ctx, P->params, "tn").i) { proj_log_error(P, _("Missing parameter n.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->n = pj_param(P->ctx, P->params, "dn").f; if (Q->n <= 0. || Q->n > 1.) { proj_log_error(P, _("Invalid value for n: it should be in ]0,1] range.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return urmfps_setup(P); } PJ *PJ_PROJECTION(wag1) { struct pj_urmfps *Q = static_cast(calloc(1, sizeof(struct pj_urmfps))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; static_cast(P->opaque)->n = 0.8660254037844386467637231707; return urmfps_setup(P); } #undef C_x #undef Cy proj-9.8.1/src/projections/gn_sinu.cpp000664 001750 001750 00000013412 15166171715 017750 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(gn_sinu, "General Sinusoidal Series") "\n\tPCyl, Sph\n\tm= n="; PROJ_HEAD(sinu, "Sinusoidal (Sanson-Flamsteed)") "\n\tPCyl, Sph&Ell"; PROJ_HEAD(eck6, "Eckert VI") "\n\tPCyl, Sph"; PROJ_HEAD(mbtfps, "McBryde-Thomas Flat-Polar Sinusoidal") "\n\tPCyl, Sph"; #define EPS10 1e-10 #define MAX_ITER 8 #define LOOP_TOL 1e-7 namespace { // anonymous namespace struct pj_gn_sinu_data { double *en; double m, n, C_x, C_y; }; } // anonymous namespace static PJ_XY gn_sinu_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; const double s = sin(lp.phi); const double c = cos(lp.phi); xy.y = pj_mlfn(lp.phi, s, c, static_cast(P->opaque)->en); xy.x = lp.lam * c / sqrt(1. - P->es * s * s); return xy; } static PJ_LP gn_sinu_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; double s; lp.phi = pj_inv_mlfn(xy.y, static_cast(P->opaque)->en); s = fabs(lp.phi); if (s < M_HALFPI) { s = sin(lp.phi); lp.lam = xy.x * sqrt(1. - P->es * s * s) / cos(lp.phi); } else if ((s - EPS10) < M_HALFPI) { lp.lam = 0.; } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); } return lp; } static PJ_XY gn_sinu_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_gn_sinu_data *Q = static_cast(P->opaque); if (Q->m == 0.0) lp.phi = Q->n != 1. ? aasin(P->ctx, Q->n * sin(lp.phi)) : lp.phi; else { int i; const double k = Q->n * sin(lp.phi); for (i = MAX_ITER; i; --i) { const double V = (Q->m * lp.phi + sin(lp.phi) - k) / (Q->m + cos(lp.phi)); lp.phi -= V; if (fabs(V) < LOOP_TOL) break; } if (!i) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } } xy.x = Q->C_x * lp.lam * (Q->m + cos(lp.phi)); xy.y = Q->C_y * lp.phi; return xy; } static PJ_LP gn_sinu_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_gn_sinu_data *Q = static_cast(P->opaque); xy.y /= Q->C_y; lp.phi = (Q->m != 0.0) ? aasin(P->ctx, (Q->m * xy.y + sin(xy.y)) / Q->n) : (Q->n != 1. ? aasin(P->ctx, sin(xy.y) / Q->n) : xy.y); lp.lam = xy.x / (Q->C_x * (Q->m + cos(xy.y))); return lp; } static PJ *pj_gn_sinu_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } /* for spheres, only */ static void pj_gn_sinu_setup(PJ *P) { struct pj_gn_sinu_data *Q = static_cast(P->opaque); P->es = 0; P->inv = gn_sinu_s_inverse; P->fwd = gn_sinu_s_forward; Q->C_y = sqrt((Q->m + 1.) / Q->n); Q->C_x = Q->C_y / (Q->m + 1.); } PJ *PJ_PROJECTION(sinu) { struct pj_gn_sinu_data *Q = static_cast( calloc(1, sizeof(struct pj_gn_sinu_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_gn_sinu_destructor; if (!(Q->en = pj_enfn(P->n))) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); if (P->es != 0.0) { P->inv = gn_sinu_e_inverse; P->fwd = gn_sinu_e_forward; } else { Q->n = 1.; Q->m = 0.; pj_gn_sinu_setup(P); } return P; } PJ *PJ_PROJECTION(eck6) { struct pj_gn_sinu_data *Q = static_cast( calloc(1, sizeof(struct pj_gn_sinu_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_gn_sinu_destructor; Q->m = 1.; Q->n = 2.570796326794896619231321691; pj_gn_sinu_setup(P); return P; } PJ *PJ_PROJECTION(mbtfps) { struct pj_gn_sinu_data *Q = static_cast( calloc(1, sizeof(struct pj_gn_sinu_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_gn_sinu_destructor; Q->m = 0.5; Q->n = 1.785398163397448309615660845; pj_gn_sinu_setup(P); return P; } PJ *PJ_PROJECTION(gn_sinu) { struct pj_gn_sinu_data *Q = static_cast( calloc(1, sizeof(struct pj_gn_sinu_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_gn_sinu_destructor; if (!pj_param(P->ctx, P->params, "tn").i) { proj_log_error(P, _("Missing parameter n.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } if (!pj_param(P->ctx, P->params, "tm").i) { proj_log_error(P, _("Missing parameter m.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } Q->n = pj_param(P->ctx, P->params, "dn").f; Q->m = pj_param(P->ctx, P->params, "dm").f; if (Q->n <= 0) { proj_log_error(P, _("Invalid value for n: it should be > 0.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (Q->m < 0) { proj_log_error(P, _("Invalid value for m: it should be >= 0.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } pj_gn_sinu_setup(P); return P; } proj-9.8.1/src/projections/fouc_s.cpp000664 001750 001750 00000003751 15166171715 017571 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(fouc_s, "Foucaut Sinusoidal") "\n\tPCyl, Sph"; #define MAX_ITER 10 #define LOOP_TOL 1e-7 namespace { // anonymous namespace struct pj_fouc_s_data { double n, n1; }; } // anonymous namespace static PJ_XY fouc_s_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_fouc_s_data *Q = static_cast(P->opaque); double t; t = cos(lp.phi); xy.x = lp.lam * t / (Q->n + Q->n1 * t); xy.y = Q->n * lp.phi + Q->n1 * sin(lp.phi); return xy; } static PJ_LP fouc_s_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_fouc_s_data *Q = static_cast(P->opaque); int i; if (Q->n != 0.0) { lp.phi = xy.y; for (i = MAX_ITER; i; --i) { const double V = (Q->n * lp.phi + Q->n1 * sin(lp.phi) - xy.y) / (Q->n + Q->n1 * cos(lp.phi)); lp.phi -= V; if (fabs(V) < LOOP_TOL) break; } if (!i) lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; } else lp.phi = aasin(P->ctx, xy.y); const double V = cos(lp.phi); lp.lam = xy.x * (Q->n + Q->n1 * V) / V; return lp; } PJ *PJ_PROJECTION(fouc_s) { struct pj_fouc_s_data *Q = static_cast( calloc(1, sizeof(struct pj_fouc_s_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->n = pj_param(P->ctx, P->params, "dn").f; if (Q->n < 0. || Q->n > 1.) { proj_log_error(P, _("Invalid value for n: it should be in [0,1] range.")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->n1 = 1. - Q->n; P->es = 0; P->inv = fouc_s_s_inverse; P->fwd = fouc_s_s_forward; return P; } #undef MAX_ITER #undef LOOP_TOL proj-9.8.1/src/projections/omerc.cpp000664 001750 001750 00000025243 15166171715 017420 0ustar00eveneven000000 000000 /* ** Copyright (c) 2003, 2006 Gerald I. Evenden */ /* ** 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 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(omerc, "Oblique Mercator") "\n\tCyl, Sph&Ell no_rot\n\t" "alpha= [gamma=] [no_off] lonc= or\n\t lon_1= lat_1= lon_2= lat_2="; namespace { // anonymous namespace struct pj_omerc_data { double A, B, E, AB, ArB, BrA, rB, singam, cosgam, sinrot, cosrot; double v_pole_n, v_pole_s, u_0; int no_rot; }; } // anonymous namespace #define TOL 1.e-7 #define EPS 1.e-10 static PJ_XY omerc_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_omerc_data *Q = static_cast(P->opaque); double u, v; if (fabs(fabs(lp.phi) - M_HALFPI) > EPS) { const double W = Q->E / pow(pj_tsfn(lp.phi, sin(lp.phi), P->e), Q->B); const double one_div_W = 1. / W; const double S = .5 * (W - one_div_W); const double T = .5 * (W + one_div_W); const double V = sin(Q->B * lp.lam); const double U = (S * Q->singam - V * Q->cosgam) / T; if (fabs(fabs(U) - 1.0) < EPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } v = 0.5 * Q->ArB * log((1. - U) / (1. + U)); const double temp = cos(Q->B * lp.lam); if (fabs(temp) < TOL) { u = Q->A * lp.lam; } else { u = Q->ArB * atan2((S * Q->cosgam + V * Q->singam), temp); } } else { v = lp.phi > 0 ? Q->v_pole_n : Q->v_pole_s; u = Q->ArB * lp.phi; } if (Q->no_rot) { xy.x = u; xy.y = v; } else { u -= Q->u_0; xy.x = v * Q->cosrot + u * Q->sinrot; xy.y = u * Q->cosrot - v * Q->sinrot; } return xy; } static PJ_LP omerc_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_omerc_data *Q = static_cast(P->opaque); double u, v, Qp, Sp, Tp, Vp, Up; if (Q->no_rot) { v = xy.y; u = xy.x; } else { v = xy.x * Q->cosrot - xy.y * Q->sinrot; u = xy.y * Q->cosrot + xy.x * Q->sinrot + Q->u_0; } Qp = exp(-Q->BrA * v); if (Qp == 0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } Sp = .5 * (Qp - 1. / Qp); Tp = .5 * (Qp + 1. / Qp); Vp = sin(Q->BrA * u); Up = (Vp * Q->cosgam + Sp * Q->singam) / Tp; if (fabs(fabs(Up) - 1.) < EPS) { lp.lam = 0.; lp.phi = Up < 0. ? -M_HALFPI : M_HALFPI; } else { lp.phi = Q->E / sqrt((1. + Up) / (1. - Up)); if ((lp.phi = pj_phi2(P->ctx, pow(lp.phi, 1. / Q->B), P->e)) == HUGE_VAL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.lam = -Q->rB * atan2((Sp * Q->cosgam - Vp * Q->singam), cos(Q->BrA * u)); } return lp; } PJ *PJ_PROJECTION(omerc) { double con, com, cosph0, D, F, H, L, sinph0, p, J, gamma = 0, gamma0, lamc = 0, lam1 = 0, lam2 = 0, phi1 = 0, phi2 = 0, alpha_c = 0; int alp, gam, no_off = 0; struct pj_omerc_data *Q = static_cast( calloc(1, sizeof(struct pj_omerc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->no_rot = pj_param(P->ctx, P->params, "bno_rot").i; if ((alp = pj_param(P->ctx, P->params, "talpha").i) != 0) alpha_c = pj_param(P->ctx, P->params, "ralpha").f; if ((gam = pj_param(P->ctx, P->params, "tgamma").i) != 0) gamma = pj_param(P->ctx, P->params, "rgamma").f; if (alp || gam) { lamc = pj_param(P->ctx, P->params, "rlonc").f; no_off = /* For libproj4 compatibility */ pj_param(P->ctx, P->params, "tno_off").i /* for backward compatibility */ || pj_param(P->ctx, P->params, "tno_uoff").i; if (no_off) { /* Mark the parameter as used, so that the pj_get_def() return them */ pj_param(P->ctx, P->params, "sno_uoff"); pj_param(P->ctx, P->params, "sno_off"); } } else { lam1 = pj_param(P->ctx, P->params, "rlon_1").f; phi1 = pj_param(P->ctx, P->params, "rlat_1").f; lam2 = pj_param(P->ctx, P->params, "rlon_2").f; phi2 = pj_param(P->ctx, P->params, "rlat_2").f; con = fabs(phi1); if (fabs(phi1) > M_HALFPI - TOL) { proj_log_error( P, _("Invalid value for lat_1: |lat_1| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(phi2) > M_HALFPI - TOL) { proj_log_error( P, _("Invalid value for lat_2: |lat_2| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(phi1 - phi2) <= TOL) { proj_log_error(P, _("Invalid value for lat_1/lat_2: lat_1 should be " "different from lat_2")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (con <= TOL) { proj_log_error( P, _("Invalid value for lat_1: lat_1 should be different from 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(fabs(P->phi0) - M_HALFPI) <= TOL) { proj_log_error( P, _("Invalid value for lat_0: |lat_0| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (pj_param(P->ctx, P->params, "rlon_0").i) { proj_log_trace(P, _("lon_0 is ignored.")); } com = sqrt(P->one_es); if (fabs(P->phi0) > EPS) { sinph0 = sin(P->phi0); cosph0 = cos(P->phi0); con = 1. - P->es * sinph0 * sinph0; Q->B = cosph0 * cosph0; Q->B = sqrt(1. + P->es * Q->B * Q->B / P->one_es); Q->A = Q->B * P->k0 * com / con; D = Q->B * com / (cosph0 * sqrt(con)); if ((F = D * D - 1.) <= 0.) F = 0.; else { F = sqrt(F); if (P->phi0 < 0.) F = -F; } Q->E = F += D; Q->E *= pow(pj_tsfn(P->phi0, sinph0, P->e), Q->B); } else { Q->B = 1. / com; Q->A = P->k0; Q->E = D = F = 1.; } if (alp || gam) { if (alp) { gamma0 = aasin(P->ctx, sin(alpha_c) / D); if (!gam) gamma = alpha_c; } else { gamma0 = gamma; alpha_c = aasin(P->ctx, D * sin(gamma0)); if (proj_errno(P) != 0) { // For a sphere, |gamma| must be <= 90 - |lat_0| // On an ellipsoid, this is very slightly above proj_log_error(P, ("Invalid value for gamma: given lat_0 value, " "|gamma| should be <= " + std::to_string(asin(1. / D) / M_PI * 180) + "°") .c_str()); return pj_default_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (fabs(fabs(P->phi0) - M_HALFPI) <= TOL) { proj_log_error( P, _("Invalid value for lat_0: |lat_0| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->lam0 = lamc - aasin(P->ctx, .5 * (F - 1. / F) * tan(gamma0)) / Q->B; } else { H = pow(pj_tsfn(phi1, sin(phi1), P->e), Q->B); L = pow(pj_tsfn(phi2, sin(phi2), P->e), Q->B); F = Q->E / H; p = (L - H) / (L + H); if (p == 0) { // Not quite, but es is very close to 1... proj_log_error(P, _("Invalid value for eccentricity")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } J = Q->E * Q->E; J = (J - L * H) / (J + L * H); if ((con = lam1 - lam2) < -M_PI) lam2 -= M_TWOPI; else if (con > M_PI) lam2 += M_TWOPI; P->lam0 = adjlon(.5 * (lam1 + lam2) - atan(J * tan(.5 * Q->B * (lam1 - lam2)) / p) / Q->B); const double denom = F - 1. / F; if (denom == 0) { proj_log_error(P, _("Invalid value for eccentricity")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } gamma0 = atan(2. * sin(Q->B * adjlon(lam1 - P->lam0)) / denom); gamma = alpha_c = aasin(P->ctx, D * sin(gamma0)); } Q->singam = sin(gamma0); Q->cosgam = cos(gamma0); Q->sinrot = sin(gamma); Q->cosrot = cos(gamma); Q->BrA = 1. / (Q->ArB = Q->A * (Q->rB = 1. / Q->B)); Q->AB = Q->A * Q->B; if (no_off) Q->u_0 = 0; else { Q->u_0 = fabs(Q->ArB * atan(sqrt(D * D - 1.) / cos(alpha_c))); if (P->phi0 < 0.) Q->u_0 = -Q->u_0; } F = 0.5 * gamma0; Q->v_pole_n = Q->ArB * log(tan(M_FORTPI - F)); Q->v_pole_s = Q->ArB * log(tan(M_FORTPI + F)); P->inv = omerc_e_inverse; P->fwd = omerc_e_forward; return P; } #undef TOL #undef EPS proj-9.8.1/src/projections/vandg.cpp000664 001750 001750 00000012202 15166171715 017401 0ustar00eveneven000000 000000 // Changes to handle +over are: Copyright 2011-2014 Morelli Informatik #include "proj.h" #include "proj_internal.h" PROJ_HEAD(vandg, "van der Grinten (I)") "\n\tMisc Sph"; #define TOL 1.e-10 #define THIRD .33333333333333333333 #define C2_27 .07407407407407407407 // 2/27 #define PI4_3 4.18879020478639098458 // 4*pi/3 #define PISQ 9.86960440108935861869 // pi^2 #define TPISQ 19.73920880217871723738 // 2*pi^2 #define HPISQ 4.93480220054467930934 // pi^2/2 static PJ_XY vandg_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double al, al2, g, g2, p2; // Comments tie this formulation to Snyder (1987), p. 241. p2 = fabs(lp.phi / M_HALFPI); // sin(theta) from (29-6) if ((p2 - TOL) > 1.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } int sign = 1; if (P->over && fabs(lp.lam) > M_PI) sign = -1; if (p2 > 1.) p2 = 1.; if (fabs(lp.phi) <= TOL) { xy.x = lp.lam; xy.y = 0.; } else if (fabs(lp.lam) <= TOL || fabs(p2 - 1.) < TOL) { xy.x = 0.; xy.y = M_PI * tan(.5 * asin(p2)); if (lp.phi < 0.) xy.y = -xy.y; } else { al = .5 * sign * fabs(M_PI / lp.lam - lp.lam / M_PI); // A from (29-3) al2 = al * al; // A^2 g = sqrt(1. - p2 * p2); // cos(theta) g = g / (p2 + g - 1.); // G from (29-4) g2 = g * g; // G^2 p2 = g * (2. / p2 - 1.); // P from (29-5) p2 = p2 * p2; // P^2 { // Force floating-point computations done on 80 bit on // i386 to go back to 64 bit. This is to avoid the test failures // of // https://github.com/OSGeo/PROJ/issues/1906#issuecomment-583168348 // The choice of p2 is completely arbitrary. volatile double p2_tmp = p2; // cppcheck-suppress redundantAssignment p2 = p2_tmp; } xy.x = g - p2; // G - P^2 g = p2 + al2; // P^2 + A^2 // (29-1) xy.x = M_PI * fabs(al * xy.x + sqrt(al2 * xy.x * xy.x - g * (g2 - p2))) / g; if (lp.lam < 0.) xy.x = -xy.x; xy.y = fabs(xy.x / M_PI); // y from (29-2) has been expressed in terms of x here xy.y = 1. - xy.y * (xy.y + 2. * al); if (xy.y < -TOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } if (xy.y < 0.) xy.y = 0.; else xy.y = sqrt(xy.y) * (lp.phi < 0. ? -M_PI : M_PI); } return xy; } static PJ_LP vandg_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double t, c0, c1, c2, c3, al, r2, r, m, d, ay, x2, y2; // Comments tie this formulation to Snyder (1987), p. 242. x2 = xy.x * xy.x; // pi^2 * X^2 if ((ay = fabs(xy.y)) < TOL) { lp.phi = 0.; t = x2 * x2 + TPISQ * (x2 + HPISQ); lp.lam = fabs(xy.x) <= TOL ? 0. : .5 * (x2 - PISQ + sqrt(t)) / xy.x; return (lp); } y2 = xy.y * xy.y; // pi^2 * Y^2 r = x2 + y2; // pi^2 * (X^2+Y^2) r2 = r * r; // pi^4 * (X^2+Y^2)^2 c1 = -M_PI * ay * (r + PISQ); // pi^4 * c1 (29-11) // pi^4 * c3 (29-13) c3 = r2 + M_TWOPI * (ay * r + M_PI * (y2 + M_PI * (ay + M_HALFPI))); c2 = c1 + PISQ * (r - 3. * y2); // pi^4 * c2 (29-12) c0 = M_PI * ay; // pi^2 * Y c2 /= c3; // c2/c3 al = c1 / c3 - THIRD * c2 * c2; // a1 (29-15) m = 2. * sqrt(-THIRD * al); // m1 (29-16) d = C2_27 * c2 * c2 * c2 + (c0 * c0 - THIRD * c2 * c1) / c3; // d (29-14) const double al_mul_m = al * m; // a1*m1 if (fabs(al_mul_m) < 1e-16) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } d = 3. * d / al_mul_m; // cos(3*theta1) (29-17) t = fabs(d); if ((t - TOL) <= 1.) { d = t > 1. ? (d > 0. ? 0. : M_PI) : acos(d); // 3*theta1 (29-17) if (r > PISQ) { // This code path is triggered for coordinates generated in the // forward path when |long|>180deg and +over d = M_TWOPI - d; } // (29-18) but change pi/3 to 4*pi/3 to flip sign of cos lp.phi = M_PI * (m * cos(d * THIRD + PI4_3) - THIRD * c2); if (xy.y < 0.) lp.phi = -lp.phi; t = r2 + TPISQ * (x2 - y2 + HPISQ); lp.lam = fabs(xy.x) <= TOL ? 0. : .5 * (r - PISQ + (t <= 0. ? 0. : sqrt(t))) / xy.x; } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return lp; } PJ *PJ_PROJECTION(vandg) { P->es = 0.; P->inv = vandg_s_inverse; P->fwd = vandg_s_forward; return P; } #undef TOL #undef THIRD #undef C2_27 #undef PI4_3 #undef PISQ #undef TPISQ #undef HPISQ proj-9.8.1/src/projections/putp5.cpp000664 001750 001750 00000003241 15166171715 017362 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_putp5_data { double A, B; }; } // anonymous namespace PROJ_HEAD(putp5, "Putnins P5") "\n\tPCyl, Sph"; PROJ_HEAD(putp5p, "Putnins P5'") "\n\tPCyl, Sph"; #define C 1.01346 #define D 1.2158542 static PJ_XY putp5_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_putp5_data *Q = static_cast(P->opaque); xy.x = C * lp.lam * (Q->A - Q->B * sqrt(1. + D * lp.phi * lp.phi)); xy.y = C * lp.phi; return xy; } static PJ_LP putp5_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_putp5_data *Q = static_cast(P->opaque); lp.phi = xy.y / C; lp.lam = xy.x / (C * (Q->A - Q->B * sqrt(1. + D * lp.phi * lp.phi))); return lp; } PJ *PJ_PROJECTION(putp5) { struct pj_putp5_data *Q = static_cast( calloc(1, sizeof(struct pj_putp5_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 2.; Q->B = 1.; P->es = 0.; P->inv = putp5_s_inverse; P->fwd = putp5_s_forward; return P; } PJ *PJ_PROJECTION(putp5p) { struct pj_putp5_data *Q = static_cast( calloc(1, sizeof(struct pj_putp5_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 1.5; Q->B = 0.5; P->es = 0.; P->inv = putp5_s_inverse; P->fwd = putp5_s_forward; return P; } #undef C #undef D proj-9.8.1/src/projections/mbtfpq.cpp000664 001750 001750 00000004070 15166171715 017577 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(mbtfpq, "McBryde-Thomas Flat-Polar Quartic") "\n\tCyl, Sph"; #define NITER 20 #define EPS 1e-7 #define ONETOL 1.000001 #define C 1.70710678118654752440 #define RC 0.58578643762690495119 #define FYC 1.87475828462269495505 #define RYC 0.53340209679417701685 #define FXC 0.31245971410378249250 #define RXC 3.20041258076506210122 static PJ_XY mbtfpq_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; const double c = C * sin(lp.phi); for (int i = NITER; i; --i) { const double th1 = (sin(.5 * lp.phi) + sin(lp.phi) - c) / (.5 * cos(.5 * lp.phi) + cos(lp.phi)); lp.phi -= th1; if (fabs(th1) < EPS) break; } xy.x = FXC * lp.lam * (1.0 + 2. * cos(lp.phi) / cos(0.5 * lp.phi)); xy.y = FYC * sin(0.5 * lp.phi); return xy; } static PJ_LP mbtfpq_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double t; lp.phi = RYC * xy.y; if (fabs(lp.phi) > 1.) { if (fabs(lp.phi) > ONETOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else if (lp.phi < 0.) { t = -1.; lp.phi = -M_PI; } else { t = 1.; lp.phi = M_PI; } } else { t = lp.phi; lp.phi = 2. * asin(lp.phi); } lp.lam = RXC * xy.x / (1. + 2. * cos(lp.phi) / cos(0.5 * lp.phi)); lp.phi = RC * (t + sin(lp.phi)); if (fabs(lp.phi) > 1.) if (fabs(lp.phi) > ONETOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; else lp.phi = asin(lp.phi); return lp; } PJ *PJ_PROJECTION(mbtfpq) { P->es = 0.; P->inv = mbtfpq_s_inverse; P->fwd = mbtfpq_s_forward; return P; } #undef NITER #undef EPS #undef ONETOL #undef C #undef RC #undef FYC #undef RYC #undef FXC #undef RXC proj-9.8.1/src/projections/gall.cpp000664 001750 001750 00000001404 15166171715 017223 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(gall, "Gall (Gall Stereographic)") "\n\tCyl, Sph"; #define YF 1.70710678118654752440 #define XF 0.70710678118654752440 #define RYF 0.58578643762690495119 #define RXF 1.41421356237309504880 static PJ_XY gall_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = XF * lp.lam; xy.y = YF * tan(.5 * lp.phi); return xy; } static PJ_LP gall_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.lam = RXF * xy.x; lp.phi = 2. * atan(xy.y * RYF); return lp; } PJ *PJ_PROJECTION(gall) { P->es = 0.0; P->inv = gall_s_inverse; P->fwd = gall_s_forward; return P; } proj-9.8.1/src/projections/bacon.cpp000664 001750 001750 00000004052 15166171715 017370 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" #define HLFPI2 2.46740110027233965467 /* (pi/2)^2 */ #define EPS 1e-10 namespace { // anonymous namespace struct pj_bacon { int bacn; int ortl; }; } // anonymous namespace PROJ_HEAD(apian, "Apian Globular I") "\n\tMisc Sph, no inv"; PROJ_HEAD(ortel, "Ortelius Oval") "\n\tMisc Sph, no inv"; PROJ_HEAD(bacon, "Bacon Globular") "\n\tMisc Sph, no inv"; static PJ_XY bacon_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_bacon *Q = static_cast(P->opaque); double ax, f; xy.y = Q->bacn ? M_HALFPI * sin(lp.phi) : lp.phi; ax = fabs(lp.lam); if (ax >= EPS) { if (Q->ortl && ax >= M_HALFPI) xy.x = sqrt(HLFPI2 - lp.phi * lp.phi + EPS) + ax - M_HALFPI; else { f = 0.5 * (HLFPI2 / ax + ax); xy.x = ax - f + sqrt(f * f - xy.y * xy.y); } if (lp.lam < 0.) xy.x = -xy.x; } else xy.x = 0.; return (xy); } PJ *PJ_PROJECTION(bacon) { struct pj_bacon *Q = static_cast(calloc(1, sizeof(struct pj_bacon))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->bacn = 1; Q->ortl = 0; P->es = 0.; P->fwd = bacon_s_forward; return P; } PJ *PJ_PROJECTION(apian) { struct pj_bacon *Q = static_cast(calloc(1, sizeof(struct pj_bacon))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->bacn = Q->ortl = 0; P->es = 0.; P->fwd = bacon_s_forward; return P; } PJ *PJ_PROJECTION(ortel) { struct pj_bacon *Q = static_cast(calloc(1, sizeof(struct pj_bacon))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->bacn = 0; Q->ortl = 1; P->es = 0.; P->fwd = bacon_s_forward; return P; } #undef HLFPI2 #undef EPS proj-9.8.1/src/projections/lagrng.cpp000664 001750 001750 00000005575 15166171715 017573 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(lagrng, "Lagrange") "\n\tMisc Sph\n\tW="; #define TOL 1e-10 namespace { // anonymous namespace struct pj_lagrng { double a1; double a2; double hrw; double hw; double rw; double w; }; } // anonymous namespace static PJ_XY lagrng_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_lagrng *Q = static_cast(P->opaque); double v, c; const double sin_phi = sin(lp.phi); if (fabs(fabs(sin_phi) - 1) < TOL) { xy.x = 0; xy.y = lp.phi < 0 ? -2. : 2.; } else { v = Q->a1 * pow((1. + sin_phi) / (1. - sin_phi), Q->hrw); lp.lam *= Q->rw; c = 0.5 * (v + 1. / v) + cos(lp.lam); if (c < TOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = 2. * sin(lp.lam) / c; xy.y = (v - 1. / v) / c; } return xy; } static PJ_LP lagrng_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_lagrng *Q = static_cast(P->opaque); double c, x2, y2p, y2m; if (fabs(fabs(xy.y) - 2.) < TOL) { lp.phi = xy.y < 0 ? -M_HALFPI : M_HALFPI; lp.lam = 0; } else { x2 = xy.x * xy.x; y2p = 2. + xy.y; y2m = 2. - xy.y; c = y2p * y2m - x2; if (fabs(c) < TOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = 2. * atan(pow((y2p * y2p + x2) / (Q->a2 * (y2m * y2m + x2)), Q->hw)) - M_HALFPI; lp.lam = Q->w * atan2(4. * xy.x, c); } return lp; } PJ *PJ_PROJECTION(lagrng) { double sin_phi1; struct pj_lagrng *Q = static_cast(calloc(1, sizeof(struct pj_lagrng))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (pj_param(P->ctx, P->params, "tW").i) Q->w = pj_param(P->ctx, P->params, "dW").f; else Q->w = 2; if (Q->w <= 0) { proj_log_error(P, _("Invalid value for W: it should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->hw = 0.5 * Q->w; Q->rw = 1. / Q->w; Q->hrw = 0.5 * Q->rw; sin_phi1 = sin(pj_param(P->ctx, P->params, "rlat_1").f); if (fabs(fabs(sin_phi1) - 1.) < TOL) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->a1 = pow((1. - sin_phi1) / (1. + sin_phi1), Q->hrw); Q->a2 = Q->a1 * Q->a1; P->es = 0.; P->inv = lagrng_s_inverse; P->fwd = lagrng_s_forward; return P; } proj-9.8.1/src/projections/gins8.cpp000664 001750 001750 00000001134 15166171715 017334 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" PROJ_HEAD(gins8, "Ginsburg VIII (TsNIIGAiK)") "\n\tPCyl, Sph, no inv"; #define Cl 0.000952426 #define Cp 0.162388 #define C12 0.08333333333333333 static PJ_XY gins8_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double t = lp.phi * lp.phi; (void)P; xy.y = lp.phi * (1. + t * C12); xy.x = lp.lam * (1. - Cp * t); t = lp.lam * lp.lam; xy.x *= (0.87 - Cl * t * t); return xy; } PJ *PJ_PROJECTION(gins8) { P->es = 0.0; P->inv = nullptr; P->fwd = gins8_s_forward; return P; } proj-9.8.1/src/projections/aea.cpp000664 001750 001750 00000017404 15166171715 017041 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the aea (Albers Equal Area) projection. * and the leac (Lambert Equal Area Conic) projection * Author: Gerald Evenden (1995) * Thomas Knudsen (2016) - revise/add regression tests * ****************************************************************************** * Copyright (c) 1995, Gerald Evenden * * 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 "proj.h" #include "proj_internal.h" #include #include #define EPS10 1.e-10 #define TOL7 1.e-7 PROJ_HEAD(aea, "Albers Equal Area") "\n\tConic Sph&Ell\n\tlat_1= lat_2="; PROJ_HEAD(leac, "Lambert Equal Area Conic") "\n\tConic, Sph&Ell\n\tlat_1= south"; namespace { // anonymous namespace struct pj_aea { double ec; double n; double c; double dd; double n2; double rho0; double rho; double phi1; double phi2; int ellips; double *apa; double qp; }; } // anonymous namespace static PJ *pj_aea_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->apa); return pj_default_destructor(P, errlev); } static PJ_XY aea_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoid/spheroid, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_aea *Q = static_cast(P->opaque); Q->rho = Q->c - (Q->ellips ? Q->n * pj_authalic_lat_q(sin(lp.phi), P) : Q->n2 * sin(lp.phi)); if (Q->rho < 0.) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } Q->rho = Q->dd * sqrt(Q->rho); lp.lam *= Q->n; xy.x = Q->rho * sin(lp.lam); xy.y = Q->rho0 - Q->rho * cos(lp.lam); return xy; } static PJ_LP aea_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoid/spheroid, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_aea *Q = static_cast(P->opaque); xy.y = Q->rho0 - xy.y; Q->rho = hypot(xy.x, xy.y); if (Q->rho != 0.0) { if (Q->n < 0.) { Q->rho = -Q->rho; xy.x = -xy.x; xy.y = -xy.y; } lp.phi = Q->rho / Q->dd; if (Q->ellips) { const double qs = (Q->c - lp.phi * lp.phi) / Q->n; if (fabs(Q->ec - fabs(qs)) > TOL7) { if (fabs(qs) > 2) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp.phi = pj_authalic_lat_inverse(asin(qs / Q->qp), Q->apa, P, Q->qp); if (lp.phi == HUGE_VAL) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } } else lp.phi = qs < 0. ? -M_HALFPI : M_HALFPI; } else { const double qs_div_2 = (Q->c - lp.phi * lp.phi) / Q->n2; if (fabs(qs_div_2) <= 1.) lp.phi = asin(qs_div_2); else lp.phi = qs_div_2 < 0. ? -M_HALFPI : M_HALFPI; } lp.lam = atan2(xy.x, xy.y) / Q->n; } else { lp.lam = 0.; lp.phi = Q->n > 0. ? M_HALFPI : -M_HALFPI; } return lp; } static PJ *setup(PJ *P) { struct pj_aea *Q = static_cast(P->opaque); P->inv = aea_e_inverse; P->fwd = aea_e_forward; if (fabs(Q->phi1) > M_HALFPI) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be <= 90°")); return pj_aea_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(Q->phi2) > M_HALFPI) { proj_log_error(P, _("Invalid value for lat_2: |lat_2| should be <= 90°")); return pj_aea_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(Q->phi1 + Q->phi2) < EPS10) { proj_log_error(P, _("Invalid value for lat_1 and lat_2: |lat_1 + " "lat_2| should be > 0")); return pj_aea_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } double sinphi = sin(Q->phi1); Q->n = sinphi; double cosphi = cos(Q->phi1); const int secant = fabs(Q->phi1 - Q->phi2) >= EPS10; Q->ellips = (P->es > 0.); if (Q->ellips) { double ml1, m1; Q->apa = pj_authalic_lat_compute_coeffs(P->n); if (Q->apa == nullptr) return pj_aea_destructor(P, 0); Q->qp = pj_authalic_lat_q(1.0, P); m1 = pj_msfn(sinphi, cosphi, P->es); ml1 = pj_authalic_lat_q(sinphi, P); if (secant) { /* secant cone */ double ml2, m2; sinphi = sin(Q->phi2); cosphi = cos(Q->phi2); m2 = pj_msfn(sinphi, cosphi, P->es); ml2 = pj_authalic_lat_q(sinphi, P); if (ml2 == ml1) return pj_aea_destructor(P, 0); Q->n = (m1 * m1 - m2 * m2) / (ml2 - ml1); if (Q->n == 0) { // Not quite, but es is very close to 1... proj_log_error(P, _("Invalid value for eccentricity")); return pj_aea_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } Q->ec = 1. - .5 * P->one_es * log((1. - P->e) / (1. + P->e)) / P->e; Q->c = m1 * m1 + Q->n * ml1; Q->dd = 1. / Q->n; Q->rho0 = Q->dd * sqrt(Q->c - Q->n * pj_authalic_lat_q(sin(P->phi0), P)); } else { if (secant) Q->n = .5 * (Q->n + sin(Q->phi2)); Q->n2 = Q->n + Q->n; Q->c = cosphi * cosphi + Q->n2 * sinphi; Q->dd = 1. / Q->n; Q->rho0 = Q->dd * sqrt(Q->c - Q->n2 * sin(P->phi0)); } return P; } PJ *PJ_PROJECTION(aea) { struct pj_aea *Q = static_cast(calloc(1, sizeof(struct pj_aea))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_aea_destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->phi2 = pj_param(P->ctx, P->params, "rlat_2").f; return setup(P); } PJ *PJ_PROJECTION(leac) { struct pj_aea *Q = static_cast(calloc(1, sizeof(struct pj_aea))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_aea_destructor; Q->phi2 = pj_param(P->ctx, P->params, "rlat_1").f; Q->phi1 = pj_param(P->ctx, P->params, "bsouth").i ? -M_HALFPI : M_HALFPI; return setup(P); } #undef EPS10 #undef TOL7 proj-9.8.1/src/projections/gstmerc.cpp000664 001750 001750 00000004331 15166171715 017752 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(gstmerc, "Gauss-Schreiber Transverse Mercator (aka Gauss-Laborde Reunion)") "\n\tCyl, Sph&Ell\n\tlat_0= lon_0= k_0="; namespace { // anonymous namespace struct pj_gstmerc_data { double lamc; double phic; double c; double n1; double n2; double XS; double YS; }; } // anonymous namespace static PJ_XY gstmerc_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_gstmerc_data *Q = static_cast(P->opaque); double L, Ls, sinLs1, Ls1; L = Q->n1 * lp.lam; Ls = Q->c + Q->n1 * log(pj_tsfn(-lp.phi, -sin(lp.phi), P->e)); sinLs1 = sin(L) / cosh(Ls); Ls1 = log(pj_tsfn(-asin(sinLs1), -sinLs1, 0.0)); xy.x = (Q->XS + Q->n2 * Ls1) * P->ra; xy.y = (Q->YS + Q->n2 * atan(sinh(Ls) / cos(L))) * P->ra; return xy; } static PJ_LP gstmerc_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_gstmerc_data *Q = static_cast(P->opaque); double L, LC, sinC; L = atan(sinh((xy.x * P->a - Q->XS) / Q->n2) / cos((xy.y * P->a - Q->YS) / Q->n2)); sinC = sin((xy.y * P->a - Q->YS) / Q->n2) / cosh((xy.x * P->a - Q->XS) / Q->n2); LC = log(pj_tsfn(-asin(sinC), -sinC, 0.0)); lp.lam = L / Q->n1; lp.phi = -pj_phi2(P->ctx, exp((LC - Q->c) / Q->n1), P->e); return lp; } PJ *PJ_PROJECTION(gstmerc) { struct pj_gstmerc_data *Q = static_cast( calloc(1, sizeof(struct pj_gstmerc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->lamc = P->lam0; Q->n1 = sqrt(1 + P->es * pow(cos(P->phi0), 4.0) / (1 - P->es)); Q->phic = asin(sin(P->phi0) / Q->n1); Q->c = log(pj_tsfn(-Q->phic, -sin(P->phi0) / Q->n1, 0.0)) - Q->n1 * log(pj_tsfn(-P->phi0, -sin(P->phi0), P->e)); Q->n2 = P->k0 * P->a * sqrt(1 - P->es) / (1 - P->es * sin(P->phi0) * sin(P->phi0)); Q->XS = 0; Q->YS = -Q->n2 * Q->phic; P->inv = gstmerc_s_inverse; P->fwd = gstmerc_s_forward; return P; } proj-9.8.1/src/projections/chamb.cpp000664 001750 001750 00000011232 15166171715 017356 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" typedef struct { double r, Az; } VECT; namespace { // anonymous namespace struct pj_chamb { struct { /* control point data */ double phi, lam; double cosphi, sinphi; VECT v; PJ_XY p; } c[3]; PJ_XY p; double beta_0, beta_1, beta_2; }; } // anonymous namespace PROJ_HEAD(chamb, "Chamberlin Trimetric") "\n\tMisc Sph, no inv" "\n\tlat_1= lon_1= lat_2= lon_2= lat_3= lon_3="; #include #define THIRD 0.333333333333333333 #define TOL 1e-9 /* distance and azimuth from point 1 to point 2 */ static VECT vect(PJ_CONTEXT *ctx, double dphi, double c1, double s1, double c2, double s2, double dlam) { VECT v; double cdl, dp, dl; cdl = cos(dlam); if (fabs(dphi) > 1. || fabs(dlam) > 1.) v.r = aacos(ctx, s1 * s2 + c1 * c2 * cdl); else { /* more accurate for smaller distances */ dp = sin(.5 * dphi); dl = sin(.5 * dlam); v.r = 2. * aasin(ctx, sqrt(dp * dp + c1 * c2 * dl * dl)); } if (fabs(v.r) > TOL) v.Az = atan2(c2 * sin(dlam), c1 * s2 - s1 * c2 * cdl); else v.r = v.Az = 0.; return v; } /* law of cosines */ static double lc(PJ_CONTEXT *ctx, double b, double c, double a) { return aacos(ctx, .5 * (b * b + c * c - a * a) / (b * c)); } static PJ_XY chamb_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy; struct pj_chamb *Q = static_cast(P->opaque); double sinphi, cosphi, a; VECT v[3]; int i, j; sinphi = sin(lp.phi); cosphi = cos(lp.phi); for (i = 0; i < 3; ++i) { /* dist/azimiths from control */ v[i] = vect(P->ctx, lp.phi - Q->c[i].phi, Q->c[i].cosphi, Q->c[i].sinphi, cosphi, sinphi, lp.lam - Q->c[i].lam); if (v[i].r == 0.0) break; v[i].Az = adjlon(v[i].Az - Q->c[i].v.Az); } if (i < 3) /* current point at control point */ xy = Q->c[i].p; else { /* point mean of intercepts */ xy = Q->p; for (i = 0; i < 3; ++i) { j = i == 2 ? 0 : i + 1; a = lc(P->ctx, Q->c[i].v.r, v[i].r, v[j].r); if (v[i].Az < 0.) a = -a; if (!i) { /* coord comp unique to each arc */ xy.x += v[i].r * cos(a); xy.y -= v[i].r * sin(a); } else if (i == 1) { a = Q->beta_1 - a; xy.x -= v[i].r * cos(a); xy.y -= v[i].r * sin(a); } else { a = Q->beta_2 - a; xy.x += v[i].r * cos(a); xy.y += v[i].r * sin(a); } } xy.x *= THIRD; /* mean of arc intercepts */ xy.y *= THIRD; } return xy; } PJ *PJ_PROJECTION(chamb) { int i, j; char line[10]; struct pj_chamb *Q = static_cast(calloc(1, sizeof(struct pj_chamb))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; for (i = 0; i < 3; ++i) { /* get control point locations */ (void)snprintf(line, sizeof(line), "rlat_%d", i + 1); Q->c[i].phi = pj_param(P->ctx, P->params, line).f; (void)snprintf(line, sizeof(line), "rlon_%d", i + 1); Q->c[i].lam = pj_param(P->ctx, P->params, line).f; Q->c[i].lam = adjlon(Q->c[i].lam - P->lam0); Q->c[i].cosphi = cos(Q->c[i].phi); Q->c[i].sinphi = sin(Q->c[i].phi); } for (i = 0; i < 3; ++i) { /* inter ctl pt. distances and azimuths */ j = i == 2 ? 0 : i + 1; Q->c[i].v = vect(P->ctx, Q->c[j].phi - Q->c[i].phi, Q->c[i].cosphi, Q->c[i].sinphi, Q->c[j].cosphi, Q->c[j].sinphi, Q->c[j].lam - Q->c[i].lam); if (Q->c[i].v.r == 0.0) { proj_log_error( P, _("Invalid value for control points: they should be distinct")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } /* co-linearity problem ignored for now */ } Q->beta_0 = lc(P->ctx, Q->c[0].v.r, Q->c[2].v.r, Q->c[1].v.r); Q->beta_1 = lc(P->ctx, Q->c[0].v.r, Q->c[1].v.r, Q->c[2].v.r); Q->beta_2 = M_PI - Q->beta_0; Q->c[0].p.y = Q->c[2].v.r * sin(Q->beta_0); Q->c[1].p.y = Q->c[0].p.y; Q->p.y = 2. * Q->c[0].p.y; Q->c[2].p.y = 0.; Q->c[1].p.x = 0.5 * Q->c[0].v.r; Q->c[0].p.x = -Q->c[1].p.x; Q->c[2].p.x = Q->c[0].p.x + Q->c[2].v.r * cos(Q->beta_0); Q->p.x = Q->c[2].p.x; P->es = 0.; P->fwd = chamb_s_forward; return P; } #undef THIRD #undef TOL proj-9.8.1/src/projections/putp2.cpp000664 001750 001750 00000002706 15166171715 017364 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(putp2, "Putnins P2") "\n\tPCyl, Sph"; #define C_x 1.89490 #define C_y 1.71848 #define C_p 0.6141848493043784 #define EPS 1e-10 #define NITER 10 #define PI_DIV_3 1.0471975511965977 static PJ_XY putp2_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; int i; (void)P; const double p = C_p * sin(lp.phi); const double phi_pow_2 = lp.phi * lp.phi; lp.phi *= 0.615709 + phi_pow_2 * (0.00909953 + phi_pow_2 * 0.0046292); for (i = NITER; i; --i) { const double c = cos(lp.phi); const double s = sin(lp.phi); const double V = (lp.phi + s * (c - 1.) - p) / (1. + c * (c - 1.) - s * s); lp.phi -= V; if (fabs(V) < EPS) break; } if (!i) lp.phi = lp.phi < 0 ? -PI_DIV_3 : PI_DIV_3; xy.x = C_x * lp.lam * (cos(lp.phi) - 0.5); xy.y = C_y * sin(lp.phi); return xy; } static PJ_LP putp2_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = aasin(P->ctx, xy.y / C_y); const double c = cos(lp.phi); lp.lam = xy.x / (C_x * (c - 0.5)); lp.phi = aasin(P->ctx, (lp.phi + sin(lp.phi) * (c - 1.)) / C_p); return lp; } PJ *PJ_PROJECTION(putp2) { P->es = 0.; P->inv = putp2_s_inverse; P->fwd = putp2_s_forward; return P; } #undef C_x #undef C_y #undef C_p #undef EPS #undef NITER #undef PI_DIV_3 proj-9.8.1/src/projections/mill.cpp000664 001750 001750 00000001215 15166171715 017241 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(mill, "Miller Cylindrical") "\n\tCyl, Sph"; static PJ_XY mill_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = lp.lam; xy.y = log(tan(M_FORTPI + lp.phi * .4)) * 1.25; return (xy); } static PJ_LP mill_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.lam = xy.x; lp.phi = 2.5 * (atan(exp(.8 * xy.y)) - M_FORTPI); return (lp); } PJ *PJ_PROJECTION(mill) { P->es = 0.; P->inv = mill_s_inverse; P->fwd = mill_s_forward; return P; } proj-9.8.1/src/projections/loxim.cpp000664 001750 001750 00000004003 15166171715 017432 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(loxim, "Loximuthal") "\n\tPCyl Sph"; #define EPS 1e-8 namespace { // anonymous namespace struct pj_loxim_data { double phi1; double cosphi1; double tanphi1; }; } // anonymous namespace static PJ_XY loxim_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_loxim_data *Q = static_cast(P->opaque); xy.y = lp.phi - Q->phi1; if (fabs(xy.y) < EPS) xy.x = lp.lam * Q->cosphi1; else { xy.x = M_FORTPI + 0.5 * lp.phi; if (fabs(xy.x) < EPS || fabs(fabs(xy.x) - M_HALFPI) < EPS) xy.x = 0.; else xy.x = lp.lam * xy.y / log(tan(xy.x) / Q->tanphi1); } return xy; } static PJ_LP loxim_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_loxim_data *Q = static_cast(P->opaque); lp.phi = xy.y + Q->phi1; if (fabs(xy.y) < EPS) { lp.lam = xy.x / Q->cosphi1; } else { lp.lam = M_FORTPI + 0.5 * lp.phi; if (fabs(lp.lam) < EPS || fabs(fabs(lp.lam) - M_HALFPI) < EPS) lp.lam = 0.; else lp.lam = xy.x * log(tan(lp.lam) / Q->tanphi1) / xy.y; } return lp; } PJ *PJ_PROJECTION(loxim) { struct pj_loxim_data *Q = static_cast( calloc(1, sizeof(struct pj_loxim_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; Q->cosphi1 = cos(Q->phi1); if (Q->cosphi1 < EPS) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->tanphi1 = tan(M_FORTPI + 0.5 * Q->phi1); P->inv = loxim_s_inverse; P->fwd = loxim_s_forward; P->es = 0.; return P; } #undef EPS proj-9.8.1/src/projections/aeqd.cpp000664 001750 001750 00000024754 15166171715 017233 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the aeqd (Azimuthal Equidistant) projection. * Author: Gerald Evenden * ****************************************************************************** * Copyright (c) 1995, Gerald Evenden * * 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 "geodesic.h" #include "proj.h" #include "proj_internal.h" #include #include namespace pj_aeqd_ns { enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; } namespace { // anonymous namespace struct pj_aeqd_data { double sinph0; double cosph0; double *en; double M1; double N1; double Mp; double He; double G; enum ::pj_aeqd_ns::Mode mode; struct geod_geodesic g; }; } // anonymous namespace PROJ_HEAD(aeqd, "Azimuthal Equidistant") "\n\tAzi, Sph&Ell\n\tlat_0 guam"; #define EPS10 1.e-10 #define TOL 1.e-14 static PJ *destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } static PJ_XY e_guam_fwd(PJ_LP lp, PJ *P) { /* Guam elliptical */ PJ_XY xy = {0.0, 0.0}; struct pj_aeqd_data *Q = static_cast(P->opaque); double cosphi, sinphi, t; cosphi = cos(lp.phi); sinphi = sin(lp.phi); t = 1. / sqrt(1. - P->es * sinphi * sinphi); xy.x = lp.lam * cosphi * t; xy.y = pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->M1 + .5 * lp.lam * lp.lam * cosphi * sinphi * t; return xy; } static PJ_XY aeqd_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_aeqd_data *Q = static_cast(P->opaque); double coslam, cosphi, sinphi, rho; double azi1, azi2, s12; double lat1, lon1, lat2, lon2; coslam = cos(lp.lam); switch (Q->mode) { case pj_aeqd_ns::N_POLE: coslam = -coslam; PROJ_FALLTHROUGH; case pj_aeqd_ns::S_POLE: cosphi = cos(lp.phi); sinphi = sin(lp.phi); rho = fabs(Q->Mp - pj_mlfn(lp.phi, sinphi, cosphi, Q->en)); xy.x = rho * sin(lp.lam); xy.y = rho * coslam; break; case pj_aeqd_ns::EQUIT: case pj_aeqd_ns::OBLIQ: if (fabs(lp.lam) < EPS10 && fabs(lp.phi - P->phi0) < EPS10) { xy.x = xy.y = 0.; break; } lat1 = P->phi0 / DEG_TO_RAD; lon1 = 0; lat2 = lp.phi / DEG_TO_RAD; lon2 = lp.lam / DEG_TO_RAD; geod_inverse(&Q->g, lat1, lon1, lat2, lon2, &s12, &azi1, &azi2); azi1 *= DEG_TO_RAD; xy.x = s12 * sin(azi1); xy.y = s12 * cos(azi1); break; } return xy; } static PJ_XY aeqd_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_aeqd_data *Q = static_cast(P->opaque); if (Q->mode == pj_aeqd_ns::EQUIT) { const double cosphi = cos(lp.phi); const double sinphi = sin(lp.phi); const double coslam = cos(lp.lam); const double sinlam = sin(lp.lam); xy.y = cosphi * coslam; if (fabs(fabs(xy.y) - 1.) < TOL) { if (xy.y < 0.) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else return aeqd_e_forward(lp, P); } else { xy.y = acos(xy.y); xy.y /= sin(xy.y); xy.x = xy.y * cosphi * sinlam; xy.y *= sinphi; } } else if (Q->mode == pj_aeqd_ns::OBLIQ) { const double cosphi = cos(lp.phi); const double sinphi = sin(lp.phi); const double coslam = cos(lp.lam); const double sinlam = sin(lp.lam); const double cosphi_x_coslam = cosphi * coslam; xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi_x_coslam; if (fabs(fabs(xy.y) - 1.) < TOL) { if (xy.y < 0.) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else return aeqd_e_forward(lp, P); } else { xy.y = acos(xy.y); xy.y /= sin(xy.y); xy.x = xy.y * cosphi * sinlam; xy.y *= Q->cosph0 * sinphi - Q->sinph0 * cosphi_x_coslam; } } else { double coslam = cos(lp.lam); double sinlam = sin(lp.lam); if (Q->mode == pj_aeqd_ns::N_POLE) { lp.phi = -lp.phi; coslam = -coslam; } if (fabs(lp.phi - M_HALFPI) < EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = (M_HALFPI + lp.phi); xy.x = xy.y * sinlam; xy.y *= coslam; } return xy; } static PJ_LP e_guam_inv(PJ_XY xy, PJ *P) { /* Guam elliptical */ PJ_LP lp = {0.0, 0.0}; struct pj_aeqd_data *Q = static_cast(P->opaque); double x2, t = 0.0; int i; x2 = 0.5 * xy.x * xy.x; lp.phi = P->phi0; for (i = 0; i < 3; ++i) { t = P->e * sin(lp.phi); t = sqrt(1. - t * t); lp.phi = pj_inv_mlfn(Q->M1 + xy.y - x2 * tan(lp.phi) * t, Q->en); } lp.lam = xy.x * t / cos(lp.phi); return lp; } static PJ_LP aeqd_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_aeqd_data *Q = static_cast(P->opaque); double azi1, azi2, s12, lat1, lon1, lat2, lon2; if ((s12 = hypot(xy.x, xy.y)) < EPS10) { lp.phi = P->phi0; lp.lam = 0.; return (lp); } if (Q->mode == pj_aeqd_ns::OBLIQ || Q->mode == pj_aeqd_ns::EQUIT) { lat1 = P->phi0 / DEG_TO_RAD; lon1 = 0; azi1 = atan2(xy.x, xy.y) / DEG_TO_RAD; // Clockwise from north geod_direct(&Q->g, lat1, lon1, azi1, s12, &lat2, &lon2, &azi2); lp.phi = lat2 * DEG_TO_RAD; lp.lam = lon2 * DEG_TO_RAD; } else { /* Polar */ lp.phi = pj_inv_mlfn( Q->mode == pj_aeqd_ns::N_POLE ? Q->Mp - s12 : Q->Mp + s12, Q->en); lp.lam = atan2(xy.x, Q->mode == pj_aeqd_ns::N_POLE ? -xy.y : xy.y); } return lp; } static PJ_LP aeqd_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_aeqd_data *Q = static_cast(P->opaque); double cosc, c_rh, sinc; c_rh = hypot(xy.x, xy.y); if (c_rh > M_PI) { if (c_rh - EPS10 > M_PI) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } c_rh = M_PI; } else if (c_rh < EPS10) { lp.phi = P->phi0; lp.lam = 0.; return (lp); } if (Q->mode == pj_aeqd_ns::OBLIQ || Q->mode == pj_aeqd_ns::EQUIT) { sinc = sin(c_rh); cosc = cos(c_rh); if (Q->mode == pj_aeqd_ns::EQUIT) { lp.phi = aasin(P->ctx, xy.y * sinc / c_rh); xy.x *= sinc; xy.y = cosc * c_rh; } else { lp.phi = aasin(P->ctx, cosc * Q->sinph0 + xy.y * sinc * Q->cosph0 / c_rh); xy.y = (cosc - Q->sinph0 * sin(lp.phi)) * c_rh; xy.x *= sinc * Q->cosph0; } lp.lam = xy.y == 0. ? 0. : atan2(xy.x, xy.y); } else if (Q->mode == pj_aeqd_ns::N_POLE) { lp.phi = M_HALFPI - c_rh; lp.lam = atan2(xy.x, -xy.y); } else { lp.phi = c_rh - M_HALFPI; lp.lam = atan2(xy.x, xy.y); } return lp; } PJ *PJ_PROJECTION(aeqd) { struct pj_aeqd_data *Q = static_cast( calloc(1, sizeof(struct pj_aeqd_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; geod_init(&Q->g, 1, P->f); if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) { Q->mode = P->phi0 < 0. ? pj_aeqd_ns::S_POLE : pj_aeqd_ns::N_POLE; Q->sinph0 = P->phi0 < 0. ? -1. : 1.; Q->cosph0 = 0.; } else if (fabs(P->phi0) < EPS10) { Q->mode = pj_aeqd_ns::EQUIT; Q->sinph0 = 0.; Q->cosph0 = 1.; } else { Q->mode = pj_aeqd_ns::OBLIQ; Q->sinph0 = sin(P->phi0); Q->cosph0 = cos(P->phi0); } if (P->es == 0.0) { P->inv = aeqd_s_inverse; P->fwd = aeqd_s_forward; } else { if (!(Q->en = pj_enfn(P->n))) return pj_default_destructor(P, 0); if (pj_param(P->ctx, P->params, "bguam").i) { Q->M1 = pj_mlfn(P->phi0, Q->sinph0, Q->cosph0, Q->en); P->inv = e_guam_inv; P->fwd = e_guam_fwd; } else { switch (Q->mode) { case pj_aeqd_ns::N_POLE: Q->Mp = pj_mlfn(M_HALFPI, 1., 0., Q->en); break; case pj_aeqd_ns::S_POLE: Q->Mp = pj_mlfn(-M_HALFPI, -1., 0., Q->en); break; case pj_aeqd_ns::EQUIT: case pj_aeqd_ns::OBLIQ: Q->N1 = 1. / sqrt(1. - P->es * Q->sinph0 * Q->sinph0); Q->He = P->e / sqrt(P->one_es); Q->G = Q->sinph0 * Q->He; Q->He *= Q->cosph0; break; } P->inv = aeqd_e_inverse; P->fwd = aeqd_e_forward; } } return P; } #undef EPS10 #undef TOL proj-9.8.1/src/projections/ob_tran.cpp000664 001750 001750 00000023171 15166171715 017735 0ustar00eveneven000000 000000 #include #include #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_ob_tran_data { struct PJconsts *link; double lamp; double cphip, sphip; }; } // anonymous namespace PROJ_HEAD(ob_tran, "General Oblique Transformation") "\n\tMisc Sph" "\n\to_proj= plus parameters for projection" "\n\to_lat_p= o_lon_p= (new pole) or" "\n\to_alpha= o_lon_c= o_lat_c= or" "\n\to_lon_1= o_lat_1= o_lon_2= o_lat_2="; #define TOL 1e-10 static PJ_XY o_forward(PJ_LP lp, PJ *P) { /* spheroid */ struct pj_ob_tran_data *Q = static_cast(P->opaque); double coslam, sinphi, cosphi; coslam = cos(lp.lam); sinphi = sin(lp.phi); cosphi = cos(lp.phi); /* Formula (5-8b) of Snyder's "Map projections: a working manual" */ lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), Q->sphip * cosphi * coslam + Q->cphip * sinphi) + Q->lamp); /* Formula (5-7) */ lp.phi = aasin(P->ctx, Q->sphip * sinphi - Q->cphip * cosphi * coslam); return Q->link->fwd(lp, Q->link); } static PJ_XY t_forward(PJ_LP lp, PJ *P) { /* spheroid */ struct pj_ob_tran_data *Q = static_cast(P->opaque); double cosphi, coslam; cosphi = cos(lp.phi); coslam = cos(lp.lam); lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), sin(lp.phi)) + Q->lamp); lp.phi = aasin(P->ctx, -cosphi * coslam); return Q->link->fwd(lp, Q->link); } static PJ_LP o_inverse(PJ_XY xy, PJ *P) { /* spheroid */ struct pj_ob_tran_data *Q = static_cast(P->opaque); double coslam, sinphi, cosphi; PJ_LP lp = Q->link->inv(xy, Q->link); if (lp.lam != HUGE_VAL) { lp.lam -= Q->lamp; coslam = cos(lp.lam); sinphi = sin(lp.phi); cosphi = cos(lp.phi); /* Formula (5-9) */ lp.phi = aasin(P->ctx, Q->sphip * sinphi + Q->cphip * cosphi * coslam); /* Formula (5-10b) */ lp.lam = aatan2(cosphi * sin(lp.lam), Q->sphip * cosphi * coslam - Q->cphip * sinphi); } return lp; } static PJ_LP t_inverse(PJ_XY xy, PJ *P) { /* spheroid */ struct pj_ob_tran_data *Q = static_cast(P->opaque); double cosphi, t; PJ_LP lp = Q->link->inv(xy, Q->link); if (lp.lam != HUGE_VAL) { cosphi = cos(lp.phi); t = lp.lam - Q->lamp; lp.lam = aatan2(cosphi * sin(t), -sin(lp.phi)); lp.phi = aasin(P->ctx, cosphi * cos(t)); } return lp; } static PJ *destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); if (static_cast(P->opaque)->link) static_cast(P->opaque)->link->destructor( static_cast(P->opaque)->link, errlev); return pj_default_destructor(P, errlev); } /*********************************************************************** These functions are modified versions of the functions "argc_params" and "argv_params" from PJ_pipeline.c Basically, they do the somewhat backwards stunt of turning the paralist representation of the +args back into the original +argv, +argc representation accepted by pj_init_ctx(). This, however, also begs the question of whether we really need the paralist linked list representation, or if we could do with a simpler null-terminated argv style array? This would simplify some code, and keep memory allocations more localized. ***********************************************************************/ typedef struct { int argc; char **argv; } ARGS; /* count the number of args in the linked list */ static size_t paralist_params_argc(paralist *params) { size_t argc = 0; for (; params != nullptr; params = params->next) argc++; return argc; } /* turn paralist into argc/argv style argument list */ static ARGS ob_tran_target_params(paralist *params) { int i = 0; ARGS args = {0, nullptr}; size_t argc = paralist_params_argc(params); if (argc < 2) return args; /* all args except the proj=ob_tran */ args.argv = static_cast(calloc(argc - 1, sizeof(char *))); if (nullptr == args.argv) return args; /* Copy all args *except* the proj=ob_tran or inv arg to the argv array */ for (i = 0; params != nullptr; params = params->next) { if (0 == strcmp(params->param, "proj=ob_tran") || 0 == strcmp(params->param, "inv")) continue; args.argv[i++] = params->param; } args.argc = i; /* Then convert the o_proj=xxx element to proj=xxx */ for (i = 0; i < args.argc; i++) { if (0 != strncmp(args.argv[i], "o_proj=", 7)) continue; args.argv[i] += 2; if (strcmp(args.argv[i], "proj=ob_tran") == 0) { free(args.argv); args.argc = 0; args.argv = nullptr; } break; } return args; } PJ *PJ_PROJECTION(ob_tran) { double phip; ARGS args; PJ *R; /* projection to rotate */ struct pj_ob_tran_data *Q = static_cast( calloc(1, sizeof(struct pj_ob_tran_data))); if (nullptr == Q) return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = destructor; /* get name of projection to be translated */ if (pj_param(P->ctx, P->params, "so_proj").s == nullptr) { proj_log_error(P, _("Missing parameter: o_proj")); return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } /* Create the target projection object to rotate */ args = ob_tran_target_params(P->params); /* avoid endless recursion */ if (args.argv == nullptr) { proj_log_error(P, _("Failed to find projection to be rotated")); return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); } R = pj_create_argv_internal(P->ctx, args.argc, args.argv); free(args.argv); if (nullptr == R) { proj_log_error(P, _("Projection to be rotated is unknown")); return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } // Transfer the used flag from the R object to the P object for (auto p = P->params; p; p = p->next) { if (!p->used) { for (auto r = R->params; r; r = r->next) { if (r->used && strcmp(r->param, p->param) == 0) { p->used = 1; break; } } } } Q->link = R; if (pj_param(P->ctx, P->params, "to_alpha").i) { double lamc, phic, alpha; lamc = pj_param(P->ctx, P->params, "ro_lon_c").f; phic = pj_param(P->ctx, P->params, "ro_lat_c").f; alpha = pj_param(P->ctx, P->params, "ro_alpha").f; if (fabs(fabs(phic) - M_HALFPI) <= TOL) { proj_log_error( P, _("Invalid value for lat_c: |lat_c| should be < 90°")); return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic)); phip = aasin(P->ctx, cos(phic) * sin(alpha)); } else if (pj_param(P->ctx, P->params, "to_lat_p") .i) { /* specified new pole */ Q->lamp = pj_param(P->ctx, P->params, "ro_lon_p").f; phip = pj_param(P->ctx, P->params, "ro_lat_p").f; } else { /* specified new "equator" points */ double lam1, lam2, phi1, phi2, con; lam1 = pj_param(P->ctx, P->params, "ro_lon_1").f; phi1 = pj_param(P->ctx, P->params, "ro_lat_1").f; lam2 = pj_param(P->ctx, P->params, "ro_lon_2").f; phi2 = pj_param(P->ctx, P->params, "ro_lat_2").f; con = fabs(phi1); if (fabs(phi1) > M_HALFPI - TOL) { proj_log_error( P, _("Invalid value for lat_1: |lat_1| should be < 90°")); return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(phi2) > M_HALFPI - TOL) { proj_log_error( P, _("Invalid value for lat_2: |lat_2| should be < 90°")); return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (fabs(phi1 - phi2) < TOL) { proj_log_error( P, _("Invalid value for lat_1 and lat_2: lat_1 should be " "different from lat_2")); return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (con < TOL) { proj_log_error(P, _("Invalid value for lat_1: lat_1 should be " "different from zero")); return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) - sin(phi1) * cos(phi2) * cos(lam2), sin(phi1) * cos(phi2) * sin(lam2) - cos(phi1) * sin(phi2) * sin(lam1)); phip = atan(-cos(Q->lamp - lam1) / tan(phi1)); } if (fabs(phip) > TOL) { /* oblique */ Q->cphip = cos(phip); Q->sphip = sin(phip); P->fwd = Q->link->fwd ? o_forward : nullptr; P->inv = Q->link->inv ? o_inverse : nullptr; } else { /* transverse */ P->fwd = Q->link->fwd ? t_forward : nullptr; P->inv = Q->link->inv ? t_inverse : nullptr; } /* Support some rather speculative test cases, where the rotated projection */ /* is actually latlong. We do not want scaling in that case... */ if (Q->link->right == PJ_IO_UNITS_RADIANS) P->right = PJ_IO_UNITS_WHATEVER; return P; } #undef TOL proj-9.8.1/src/projections/tmerc.cpp000664 001750 001750 00000053602 15166171715 017425 0ustar00eveneven000000 000000 /* * Transverse Mercator implementations * * In this file two transverse mercator implementations are found. One of Gerald * Evenden/John Snyder origin and one of Knud Poder/Karsten Engsager origin. The * former is regarded as "approximate" in the following and the latter is * "exact". This word choice has been made to distinguish between the two * algorithms, where the Evenden/Snyder implementation is the faster, less * accurate implementation and the Poder/Engsager algorithm is a slightly * slower, but more accurate implementation. */ #include #include #include "proj.h" #include "proj_internal.h" #include PROJ_HEAD(tmerc, "Transverse Mercator") "\n\tCyl, Sph&Ell\n\tapprox"; PROJ_HEAD(etmerc, "Extended Transverse Mercator") "\n\tCyl, Sph"; PROJ_HEAD(utm, "Universal Transverse Mercator (UTM)") "\n\tCyl, Ell\n\tzone= south approx"; namespace { // anonymous namespace // Approximate: Evenden/Snyder struct EvendenSnyder { double esp; double ml0; double *en; }; // More exact: Poder/Engsager struct PoderEngsager { double Qn; /* Merid. quad., scaled to the projection */ double Zb; /* Radius vector in polar coord. systems */ double cgb[6]; /* Constants for Gauss -> Geo lat */ double cbg[6]; /* Constants for Geo lat -> Gauss */ double utg[6]; /* Constants for transv. merc. -> geo */ double gtu[6]; /* Constants for geo -> transv. merc. */ }; struct tmerc_data { EvendenSnyder approx; PoderEngsager exact; }; } // anonymous namespace /* Constants for "approximate" transverse mercator */ #define EPS10 1.e-10 #define FC1 1. #define FC2 .5 #define FC3 .16666666666666666666 #define FC4 .08333333333333333333 #define FC5 .05 #define FC6 .03333333333333333333 #define FC7 .02380952380952380952 #define FC8 .01785714285714285714 /* Constant for "exact" transverse mercator */ #define PROJ_ETMERC_ORDER 6 /*****************************************************************************/ // // Approximate Transverse Mercator functions // /*****************************************************************************/ static PJ_XY approx_e_fwd(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; const auto *Q = &(static_cast(P->opaque)->approx); double al, als, n, cosphi, sinphi, t; /* * Fail if our longitude is more than 90 degrees from the * central meridian since the results are essentially garbage. * Is error -20 really an appropriate return value? * * http://trac.osgeo.org/proj/ticket/5 */ if (lp.lam < -M_HALFPI || lp.lam > M_HALFPI) { xy.x = HUGE_VAL; xy.y = HUGE_VAL; proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } sinphi = sin(lp.phi); cosphi = cos(lp.phi); t = fabs(cosphi) > 1e-10 ? sinphi / cosphi : 0.; t *= t; al = cosphi * lp.lam; als = al * al; al /= sqrt(1. - P->es * sinphi * sinphi); n = Q->esp * cosphi * cosphi; xy.x = P->k0 * al * (FC1 + FC3 * als * (1. - t + n + FC5 * als * (5. + t * (t - 18.) + n * (14. - 58. * t) + FC7 * als * (61. + t * (t * (179. - t) - 479.))))); xy.y = P->k0 * (pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->ml0 + sinphi * al * lp.lam * FC2 * (1. + FC4 * als * (5. - t + n * (9. + 4. * n) + FC6 * als * (61. + t * (t - 58.) + n * (270. - 330 * t) + FC8 * als * (1385. + t * (t * (543. - t) - 3111.)))))); return (xy); } static PJ_XY tmerc_spherical_fwd(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; double b, cosphi; const auto *Q = &(static_cast(P->opaque)->approx); cosphi = cos(lp.phi); b = cosphi * sin(lp.lam); if (fabs(fabs(b) - 1.) <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.x = Q->ml0 * log((1. + b) / (1. - b)); if (cosphi == 1.0) { if ((lp.lam < -M_HALFPI || lp.lam > M_HALFPI)) { /* Helps to be able to roundtrip |longitudes| > 90 at lat=0 */ /* We could also map to -M_PI ... */ xy.y = M_PI; } else { xy.y = 0; } } else { xy.y = cosphi * cos(lp.lam) / sqrt(1. - b * b); b = fabs(xy.y); if (b >= 1.) { if ((b - 1.) > EPS10) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } else xy.y = 0.; } else xy.y = acos(xy.y); } if (lp.phi < 0.) xy.y = -xy.y; xy.y = Q->esp * (xy.y - P->phi0); return xy; } static PJ_LP approx_e_inv(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; const auto *Q = &(static_cast(P->opaque)->approx); lp.phi = pj_inv_mlfn(Q->ml0 + xy.y / P->k0, Q->en); if (fabs(lp.phi) >= M_HALFPI) { lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; lp.lam = 0.; } else { double sinphi = sin(lp.phi), cosphi = cos(lp.phi); double t = fabs(cosphi) > 1e-10 ? sinphi / cosphi : 0.; const double n = Q->esp * cosphi * cosphi; double con = 1. - P->es * sinphi * sinphi; const double d = xy.x * sqrt(con) / P->k0; con *= t; t *= t; const double ds = d * d; lp.phi -= (con * ds / (1. - P->es)) * FC2 * (1. - ds * FC4 * (5. + t * (3. - 9. * n) + n * (1. - 4 * n) - ds * FC6 * (61. + t * (90. - 252. * n + 45. * t) + 46. * n - ds * FC8 * (1385. + t * (3633. + t * (4095. + 1575. * t)))))); lp.lam = d * (FC1 - ds * FC3 * (1. + 2. * t + n - ds * FC5 * (5. + t * (28. + 24. * t + 8. * n) + 6. * n - ds * FC7 * (61. + t * (662. + t * (1320. + 720. * t)))))) / cosphi; } return lp; } static PJ_LP tmerc_spherical_inv(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; double h, g; const auto *Q = &(static_cast(P->opaque)->approx); h = exp(xy.x / Q->esp); if (h == 0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().lp; } g = .5 * (h - 1. / h); /* D, as in equation 8-8 of USGS "Map Projections - A Working Manual" */ const double D = P->phi0 + xy.y / Q->esp; h = cos(D); lp.phi = asin(sqrt((1. - h * h) / (1. + g * g))); /* Make sure that phi is on the correct hemisphere when false northing is * used */ lp.phi = copysign(lp.phi, D); lp.lam = (g != 0.0 || h != 0.0) ? atan2(g, h) : 0.; return lp; } static PJ *destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->approx.en); return pj_default_destructor(P, errlev); } static PJ *setup_approx(PJ *P) { auto *Q = &(static_cast(P->opaque)->approx); if (P->es != 0.0) { if (!(Q->en = pj_enfn(P->n))) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->ml0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); Q->esp = P->es / (1. - P->es); } else { Q->esp = P->k0; Q->ml0 = .5 * Q->esp; } return P; } /*****************************************************************************/ // // Exact Transverse Mercator functions // // // The code in this file is largly based upon procedures: // // Written by: Knud Poder and Karsten Engsager // // Based on math from: R.Koenig and K.H. Weise, "Mathematische // Grundlagen der hoeheren Geodaesie und Kartographie, // Springer-Verlag, Berlin/Goettingen" Heidelberg, 1951. // // Modified and used here by permission of Reference Networks // Division, Kort og Matrikelstyrelsen (KMS), Copenhagen, Denmark // /*****************************************************************************/ /* Complex Clenshaw summation */ inline static double clenS(const double *a, int size, double sin_arg_r, double cos_arg_r, double sinh_arg_i, double cosh_arg_i, double *R, double *I) { double r, i, hr, hr1, hr2, hi, hi1, hi2; /* arguments */ const double *p = a + size; r = 2 * cos_arg_r * cosh_arg_i; i = -2 * sin_arg_r * sinh_arg_i; /* summation loop */ hi1 = hr1 = hi = 0; hr = *--p; for (; a - p;) { hr2 = hr1; hi2 = hi1; hr1 = hr; hi1 = hi; hr = -hr2 + r * hr1 - i * hi1 + *--p; hi = -hi2 + i * hr1 + r * hi1; } r = sin_arg_r * cosh_arg_i; i = cos_arg_r * sinh_arg_i; *R = r * hr - i * hi; *I = r * hi + i * hr; return *R; } /* Ellipsoidal, forward */ static PJ_XY exact_e_fwd(PJ_LP lp, PJ *P) { PJ_XY xy = {0.0, 0.0}; const auto *Q = &(static_cast(P->opaque)->exact); /* ell. LAT, LNG -> Gaussian LAT, LNG */ double Cn = pj_auxlat_convert(lp.phi, Q->cbg, PROJ_ETMERC_ORDER); /* Gaussian LAT, LNG -> compl. sph. LAT */ const double sin_Cn = sin(Cn); const double cos_Cn = cos(Cn); const double sin_Ce = sin(lp.lam); const double cos_Ce = cos(lp.lam); const double cos_Cn_cos_Ce = cos_Cn * cos_Ce; Cn = atan2(sin_Cn, cos_Cn_cos_Ce); const double inv_denom_tan_Ce = 1. / hypot(sin_Cn, cos_Cn_cos_Ce); const double tan_Ce = sin_Ce * cos_Cn * inv_denom_tan_Ce; #if 0 // Variant of the above: found not to be measurably faster const double sin_Ce_cos_Cn = sin_Ce*cos_Cn; const double denom = sqrt(1 - sin_Ce_cos_Cn * sin_Ce_cos_Cn); const double tan_Ce = sin_Ce_cos_Cn / denom; #endif /* compl. sph. N, E -> ell. norm. N, E */ double Ce = asinh(tan_Ce); /* Replaces: Ce = log(tan(FORTPI + Ce*0.5)); */ /* * Non-optimized version: * const double sin_arg_r = sin(2*Cn); * const double cos_arg_r = cos(2*Cn); * * Given: * sin(2 * Cn) = 2 sin(Cn) cos(Cn) * sin(atan(y)) = y / sqrt(1 + y^2) * cos(atan(y)) = 1 / sqrt(1 + y^2) * ==> sin(2 * Cn) = 2 tan_Cn / (1 + tan_Cn^2) * * cos(2 * Cn) = 2cos^2(Cn) - 1 * = 2 / (1 + tan_Cn^2) - 1 */ const double two_inv_denom_tan_Ce = 2 * inv_denom_tan_Ce; const double two_inv_denom_tan_Ce_square = two_inv_denom_tan_Ce * inv_denom_tan_Ce; const double tmp_r = cos_Cn_cos_Ce * two_inv_denom_tan_Ce_square; const double sin_arg_r = sin_Cn * tmp_r; const double cos_arg_r = cos_Cn_cos_Ce * tmp_r - 1; /* * Non-optimized version: * const double sinh_arg_i = sinh(2*Ce); * const double cosh_arg_i = cosh(2*Ce); * * Given * sinh(2 * Ce) = 2 sinh(Ce) cosh(Ce) * sinh(asinh(y)) = y * cosh(asinh(y)) = sqrt(1 + y^2) * ==> sinh(2 * Ce) = 2 tan_Ce sqrt(1 + tan_Ce^2) * * cosh(2 * Ce) = 2cosh^2(Ce) - 1 * = 2 * (1 + tan_Ce^2) - 1 * * and 1+tan_Ce^2 = 1 + sin_Ce^2 * cos_Cn^2 / (sin_Cn^2 + cos_Cn^2 * * cos_Ce^2) = (sin_Cn^2 + cos_Cn^2 * cos_Ce^2 + sin_Ce^2 * cos_Cn^2) / * (sin_Cn^2 + cos_Cn^2 * cos_Ce^2) = 1. / (sin_Cn^2 + cos_Cn^2 * cos_Ce^2) * = inv_denom_tan_Ce^2 * */ const double sinh_arg_i = tan_Ce * two_inv_denom_tan_Ce; const double cosh_arg_i = two_inv_denom_tan_Ce_square - 1; double dCn, dCe; Cn += clenS(Q->gtu, PROJ_ETMERC_ORDER, sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i, &dCn, &dCe); Ce += dCe; if (fabs(Ce) <= 2.623395162778) { xy.y = Q->Qn * Cn + Q->Zb; /* Northing */ xy.x = Q->Qn * Ce; /* Easting */ } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); xy.x = xy.y = HUGE_VAL; } return xy; } /* Ellipsoidal, inverse */ static PJ_LP exact_e_inv(PJ_XY xy, PJ *P) { PJ_LP lp = {0.0, 0.0}; const auto *Q = &(static_cast(P->opaque)->exact); /* normalize N, E */ double Cn = (xy.y - Q->Zb) / Q->Qn; double Ce = xy.x / Q->Qn; if (fabs(Ce) <= 2.623395162778) { /* 150 degrees */ /* norm. N, E -> compl. sph. LAT, LNG */ const double sin_arg_r = sin(2 * Cn); const double cos_arg_r = cos(2 * Cn); // const double sinh_arg_i = sinh(2*Ce); // const double cosh_arg_i = cosh(2*Ce); const double exp_2_Ce = exp(2 * Ce); const double half_inv_exp_2_Ce = 0.5 / exp_2_Ce; const double sinh_arg_i = 0.5 * exp_2_Ce - half_inv_exp_2_Ce; const double cosh_arg_i = 0.5 * exp_2_Ce + half_inv_exp_2_Ce; double dCn_ignored, dCe; Cn += clenS(Q->utg, PROJ_ETMERC_ORDER, sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i, &dCn_ignored, &dCe); Ce += dCe; /* compl. sph. LAT -> Gaussian LAT, LNG */ const double sin_Cn = sin(Cn); const double cos_Cn = cos(Cn); #if 0 // Non-optimized version: double sin_Ce, cos_Ce; Ce = atan (sinh (Ce)); // Replaces: Ce = 2*(atan(exp(Ce)) - FORTPI); sin_Ce = sin (Ce); cos_Ce = cos (Ce); Ce = atan2 (sin_Ce, cos_Ce*cos_Cn); Cn = atan2 (sin_Cn*cos_Ce, hypot (sin_Ce, cos_Ce*cos_Cn)); #else /* * One can divide both member of Ce = atan2(...) by cos_Ce, which * gives: Ce = atan2 (tan_Ce, cos_Cn) = atan2(sinh(Ce), cos_Cn) * * and the same for Cn = atan2(...) * Cn = atan2 (sin_Cn, hypot (sin_Ce, cos_Ce*cos_Cn)/cos_Ce) * = atan2 (sin_Cn, hypot (sin_Ce/cos_Ce, cos_Cn)) * = atan2 (sin_Cn, hypot (tan_Ce, cos_Cn)) * = atan2 (sin_Cn, hypot (sinhCe, cos_Cn)) */ const double sinhCe = sinh(Ce); Ce = atan2(sinhCe, cos_Cn); const double modulus_Ce = hypot(sinhCe, cos_Cn), rr = hypot(sin_Cn, modulus_Ce); Cn = atan2(sin_Cn, modulus_Ce); #endif /* Gaussian LAT, LNG -> ell. LAT, LNG */ lp.phi = pj_auxlat_convert(Cn, sin_Cn / rr, modulus_Ce / rr, Q->cgb, PROJ_ETMERC_ORDER); lp.lam = Ce; } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.phi = lp.lam = HUGE_VAL; } return lp; } static PJ *setup_exact(PJ *P) { auto *Q = &(static_cast(P->opaque)->exact); assert(P->es > 0); static_assert(PROJ_ETMERC_ORDER == int(AuxLat::ORDER), "Inconsistent orders etmerc vs auxorder"); /* third flattening */ const double n = P->n; // N.B., Engsager and Poder terminology (simplifying a little here...) // geodetic coordinates = geographic latitude // Soldner sphere + complex gaussian coordinates = conformal latitude // transverse Mercator coordinates = rectifying latitude /* COEF. OF TRIG SERIES GEO <-> GAUSS */ /* cgb := Gaussian -> Geodetic, KW p190 - 191 (61) - (62) */ /* cbg := Geodetic -> Gaussian, KW p186 - 187 (51) - (52) */ /* PROJ_ETMERC_ORDER = 6th degree : Engsager and Poder: ICC2007 */ pj_auxlat_coeffs(n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, Q->cgb); pj_auxlat_coeffs(n, AuxLat::GEOGRAPHIC, AuxLat::CONFORMAL, Q->cbg); /* Constants of the projections */ /* Transverse Mercator (UTM, ITM, etc) */ /* Norm. mer. quad, K&W p.50 (96), p.19 (38b), p.5 (2) */ Q->Qn = P->k0 * pj_rectifying_radius(n); /* coef of trig series */ /* utg := ell. N, E -> sph. N, E, KW p194 (65) */ /* gtu := sph. N, E -> ell. N, E, KW p196 (69) */ pj_auxlat_coeffs(n, AuxLat::RECTIFYING, AuxLat::CONFORMAL, Q->utg); pj_auxlat_coeffs(n, AuxLat::CONFORMAL, AuxLat::RECTIFYING, Q->gtu); /* Gaussian latitude value of the origin latitude */ const double Z = pj_auxlat_convert(P->phi0, Q->cbg, PROJ_ETMERC_ORDER); /* Origin northing minus true northing at the origin latitude */ /* i.e. true northing = N - P->Zb */ Q->Zb = -Q->Qn * pj_auxlat_convert(Z, Q->gtu, PROJ_ETMERC_ORDER); return P; } static PJ_XY auto_e_fwd(PJ_LP lp, PJ *P) { if (fabs(lp.lam) > 3 * DEG_TO_RAD) return exact_e_fwd(lp, P); else return approx_e_fwd(lp, P); } static PJ_LP auto_e_inv(PJ_XY xy, PJ *P) { // For k = 1 and long = 3 (from central meridian), // At lat = 0, we get x ~= 0.052, y = 0 // At lat = 90, we get x = 0, y ~= 1.57 // And the shape of this x=f(y) frontier curve is very very roughly a // parabola. Hence: if (fabs(xy.x) > 0.053 - 0.022 * xy.y * xy.y) return exact_e_inv(xy, P); else return approx_e_inv(xy, P); } static PJ *setup(PJ *P, TMercAlgo eAlg) { struct tmerc_data *Q = static_cast(calloc(1, sizeof(struct tmerc_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (P->es == 0) eAlg = TMercAlgo::EVENDEN_SNYDER; switch (eAlg) { case TMercAlgo::EVENDEN_SNYDER: { P->destructor = destructor; if (!setup_approx(P)) return nullptr; if (P->es == 0) { P->inv = tmerc_spherical_inv; P->fwd = tmerc_spherical_fwd; } else { P->inv = approx_e_inv; P->fwd = approx_e_fwd; } break; } case TMercAlgo::PODER_ENGSAGER: { setup_exact(P); P->inv = exact_e_inv; P->fwd = exact_e_fwd; break; } case TMercAlgo::AUTO: { P->destructor = destructor; if (!setup_approx(P)) return nullptr; setup_exact(P); P->inv = auto_e_inv; P->fwd = auto_e_fwd; break; } } return P; } static bool getAlgoFromParams(PJ *P, TMercAlgo &algo) { if (pj_param(P->ctx, P->params, "bapprox").i) { algo = TMercAlgo::EVENDEN_SNYDER; return true; } const char *algStr = pj_param(P->ctx, P->params, "salgo").s; if (algStr) { if (strcmp(algStr, "evenden_snyder") == 0) { algo = TMercAlgo::EVENDEN_SNYDER; return true; } if (strcmp(algStr, "poder_engsager") == 0) { algo = TMercAlgo::PODER_ENGSAGER; return true; } if (strcmp(algStr, "auto") == 0) { algo = TMercAlgo::AUTO; // Don't return so that we can run a later validity check } else { proj_log_error(P, "unknown value for +algo"); return false; } } else { pj_load_ini(P->ctx); // if not already done proj_context_errno_set( P->ctx, 0); // reset error in case proj.ini couldn't be found algo = P->ctx->defaultTmercAlgo; } // We haven't worked on the criterion on inverse transformation // when phi0 != 0 or if k0 is not close to 1 or for very oblate // ellipsoid (es > 0.1 is ~ rf < 200) if (algo == TMercAlgo::AUTO && (P->es > 0.1 || P->phi0 != 0 || fabs(P->k0 - 1) > 0.01)) { algo = TMercAlgo::PODER_ENGSAGER; } return true; } /*****************************************************************************/ // // Operation Setups // /*****************************************************************************/ PJ *PJ_PROJECTION(tmerc) { /* exact transverse mercator only exists in ellipsoidal form, */ /* use approximate version if +a sphere is requested */ TMercAlgo algo; if (!getAlgoFromParams(P, algo)) { proj_log_error(P, _("Invalid value for algo")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return setup(P, algo); } PJ *PJ_PROJECTION(etmerc) { if (P->es == 0.0) { proj_log_error( P, _("Invalid value for eccentricity: it should not be zero")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return setup(P, TMercAlgo::PODER_ENGSAGER); } /* UTM uses the Poder/Engsager implementation for the underlying projection */ /* UNLESS +approx is set in which case the Evenden/Snyder implementation is * used. */ PJ *PJ_PROJECTION(utm) { long zone; if (P->es == 0.0) { proj_log_error( P, _("Invalid value for eccentricity: it should not be zero")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (P->lam0 < -1000.0 || P->lam0 > 1000.0) { proj_log_error(P, _("Invalid value for lon_0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->y0 = pj_param(P->ctx, P->params, "bsouth").i ? 10000000. : 0.; P->x0 = 500000.; if (pj_param(P->ctx, P->params, "tzone").i) /* zone input ? */ { zone = pj_param(P->ctx, P->params, "izone").i; if (zone > 0 && zone <= 60) --zone; else { proj_log_error(P, _("Invalid value for zone")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else /* nearest central meridian input */ { zone = lround((floor((adjlon(P->lam0) + M_PI) * 30. / M_PI))); if (zone < 0) zone = 0; else if (zone >= 60) zone = 59; } P->lam0 = (zone + .5) * M_PI / 30. - M_PI; P->k0 = 0.9996; P->phi0 = 0.; TMercAlgo algo; if (!getAlgoFromParams(P, algo)) { proj_log_error(P, _("Invalid value for algo")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } return setup(P, algo); } proj-9.8.1/src/projections/eck2.cpp000664 001750 001750 00000002316 15166171715 017133 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(eck2, "Eckert II") "\n\tPCyl, Sph"; #define FXC 0.46065886596178063902 #define FYC 1.44720250911653531871 #define C13 0.33333333333333333333 #define ONEEPS 1.0000001 static PJ_XY eck2_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = FXC * lp.lam * (xy.y = sqrt(4. - 3. * sin(fabs(lp.phi)))); xy.y = FYC * (2. - xy.y); if (lp.phi < 0.) xy.y = -xy.y; return (xy); } static PJ_LP eck2_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; (void)P; lp.phi = 2. - fabs(xy.y) / FYC; lp.lam = xy.x / (FXC * lp.phi); lp.phi = (4. - lp.phi * lp.phi) * C13; if (fabs(lp.phi) >= 1.) { if (fabs(lp.phi) > ONEEPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } else { lp.phi = lp.phi < 0. ? -M_HALFPI : M_HALFPI; } } else lp.phi = asin(lp.phi); if (xy.y < 0) lp.phi = -lp.phi; return (lp); } PJ *PJ_PROJECTION(eck2) { P->es = 0.; P->inv = eck2_s_inverse; P->fwd = eck2_s_forward; return P; } proj-9.8.1/src/projections/bonne.cpp000664 001750 001750 00000010522 15166171715 017406 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(bonne, "Bonne (Werner lat_1=90)") "\n\tConic Sph&Ell\n\tlat_1="; #define EPS10 1e-10 namespace { // anonymous namespace struct pj_bonne_data { double phi1; double cphi1; double am1; double m1; double *en; }; } // anonymous namespace static PJ_XY bonne_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_bonne_data *Q = static_cast(P->opaque); double rh, E, c; E = sin(lp.phi); c = cos(lp.phi); rh = Q->am1 + Q->m1 - pj_mlfn(lp.phi, E, c, Q->en); if (fabs(rh) > EPS10) { E = c * lp.lam / (rh * sqrt(1. - P->es * E * E)); xy.x = rh * sin(E); xy.y = Q->am1 - rh * cos(E); } else { xy.x = 0.; xy.y = 0.; } return xy; } static PJ_XY bonne_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_bonne_data *Q = static_cast(P->opaque); double E, rh; rh = Q->cphi1 + Q->phi1 - lp.phi; if (fabs(rh) > EPS10) { E = lp.lam * cos(lp.phi) / rh; xy.x = rh * sin(E); xy.y = Q->cphi1 - rh * cos(E); } else xy.x = xy.y = 0.; return xy; } static PJ_LP bonne_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_bonne_data *Q = static_cast(P->opaque); xy.y = Q->cphi1 - xy.y; const double rh = copysign(hypot(xy.x, xy.y), Q->phi1); lp.phi = Q->cphi1 + Q->phi1 - rh; const double abs_phi = fabs(lp.phi); if (abs_phi > M_HALFPI) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } if (M_HALFPI - abs_phi <= EPS10) lp.lam = 0.; else { const double lm = rh / cos(lp.phi); if (Q->phi1 > 0) { lp.lam = lm * atan2(xy.x, xy.y); } else { lp.lam = lm * atan2(-xy.x, -xy.y); } } return lp; } static PJ_LP bonne_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_bonne_data *Q = static_cast(P->opaque); xy.y = Q->am1 - xy.y; const double rh = copysign(hypot(xy.x, xy.y), Q->phi1); lp.phi = pj_inv_mlfn(Q->am1 + Q->m1 - rh, Q->en); const double abs_phi = fabs(lp.phi); if (abs_phi < M_HALFPI) { const double sinphi = sin(lp.phi); const double lm = rh * sqrt(1. - P->es * sinphi * sinphi) / cos(lp.phi); if (Q->phi1 > 0) { lp.lam = lm * atan2(xy.x, xy.y); } else { lp.lam = lm * atan2(-xy.x, -xy.y); } } else if (abs_phi - M_HALFPI <= EPS10) lp.lam = 0.; else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return lp; } static PJ *pj_bonne_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(bonne) { double c; struct pj_bonne_data *Q = static_cast( calloc(1, sizeof(struct pj_bonne_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_bonne_destructor; Q->phi1 = pj_param(P->ctx, P->params, "rlat_1").f; if (fabs(Q->phi1) < EPS10) { proj_log_error(P, _("Invalid value for lat_1: |lat_1| should be > 0")); return pj_bonne_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (P->es != 0.0) { Q->en = pj_enfn(P->n); if (nullptr == Q->en) return pj_bonne_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->am1 = sin(Q->phi1); c = cos(Q->phi1); Q->m1 = pj_mlfn(Q->phi1, Q->am1, c, Q->en); Q->am1 = c / (sqrt(1. - P->es * Q->am1 * Q->am1) * Q->am1); P->inv = bonne_e_inverse; P->fwd = bonne_e_forward; } else { if (fabs(Q->phi1) + EPS10 >= M_HALFPI) Q->cphi1 = 0.; else Q->cphi1 = 1. / tan(Q->phi1); P->inv = bonne_s_inverse; P->fwd = bonne_s_forward; } return P; } #undef EPS10 proj-9.8.1/src/projections/putp3.cpp000664 001750 001750 00000003153 15166171715 017362 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_putp3_data { double A; }; } // anonymous namespace PROJ_HEAD(putp3, "Putnins P3") "\n\tPCyl, Sph"; PROJ_HEAD(putp3p, "Putnins P3'") "\n\tPCyl, Sph"; #define C 0.79788456 #define RPISQ 0.1013211836 static PJ_XY putp3_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = C * lp.lam * (1. - static_cast(P->opaque)->A * lp.phi * lp.phi); xy.y = C * lp.phi; return xy; } static PJ_LP putp3_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = xy.y / C; lp.lam = xy.x / (C * (1. - static_cast(P->opaque)->A * lp.phi * lp.phi)); return lp; } PJ *PJ_PROJECTION(putp3) { struct pj_putp3_data *Q = static_cast( calloc(1, sizeof(struct pj_putp3_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 4. * RPISQ; P->es = 0.; P->inv = putp3_s_inverse; P->fwd = putp3_s_forward; return P; } PJ *PJ_PROJECTION(putp3p) { struct pj_putp3_data *Q = static_cast( calloc(1, sizeof(struct pj_putp3_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->A = 2. * RPISQ; P->es = 0.; P->inv = putp3_s_inverse; P->fwd = putp3_s_forward; return P; } #undef C #undef RPISQ proj-9.8.1/src/projections/stere.cpp000664 001750 001750 00000022557 15166171715 017442 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(stere, "Stereographic") "\n\tAzi, Sph&Ell\n\tlat_ts="; PROJ_HEAD(ups, "Universal Polar Stereographic") "\n\tAzi, Ell\n\tsouth"; namespace { // anonymous namespace enum Mode { S_POLE = 0, N_POLE = 1, OBLIQ = 2, EQUIT = 3 }; } // anonymous namespace namespace { // anonymous namespace struct pj_stere { double phits; double sinX1; double cosX1; double akm1; enum Mode mode; }; } // anonymous namespace #define sinph0 static_cast(P->opaque)->sinX1 #define cosph0 static_cast(P->opaque)->cosX1 #define EPS10 1.e-10 #define TOL 1.e-8 #define NITER 8 #define CONV 1.e-10 static double ssfn_(double phit, double sinphi, double eccen) { sinphi *= eccen; return (tan(.5 * (M_HALFPI + phit)) * pow((1. - sinphi) / (1. + sinphi), .5 * eccen)); } static PJ_XY stere_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_stere *Q = static_cast(P->opaque); double coslam, sinlam, sinX = 0.0, cosX = 0.0, A = 0.0, sinphi; coslam = cos(lp.lam); sinlam = sin(lp.lam); sinphi = sin(lp.phi); if (Q->mode == OBLIQ || Q->mode == EQUIT) { const double X = 2. * atan(ssfn_(lp.phi, sinphi, P->e)) - M_HALFPI; sinX = sin(X); cosX = cos(X); } switch (Q->mode) { case OBLIQ: { const double denom = Q->cosX1 * (1. + Q->sinX1 * sinX + Q->cosX1 * cosX * coslam); if (denom == 0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } A = Q->akm1 / denom; xy.y = A * (Q->cosX1 * sinX - Q->sinX1 * cosX * coslam); xy.x = A * cosX; break; } case EQUIT: /* avoid zero division */ if (1. + cosX * coslam == 0.0) { xy.y = HUGE_VAL; } else { A = Q->akm1 / (1. + cosX * coslam); xy.y = A * sinX; } xy.x = A * cosX; break; case S_POLE: lp.phi = -lp.phi; coslam = -coslam; sinphi = -sinphi; PROJ_FALLTHROUGH; case N_POLE: if (fabs(lp.phi - M_HALFPI) < 1e-15) xy.x = 0; else xy.x = Q->akm1 * pj_tsfn(lp.phi, sinphi, P->e); xy.y = -xy.x * coslam; break; } xy.x = xy.x * sinlam; return xy; } static PJ_XY stere_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_stere *Q = static_cast(P->opaque); double sinphi, cosphi, coslam, sinlam; sinphi = sin(lp.phi); cosphi = cos(lp.phi); coslam = cos(lp.lam); sinlam = sin(lp.lam); switch (Q->mode) { case EQUIT: xy.y = 1. + cosphi * coslam; goto oblcon; case OBLIQ: xy.y = 1. + sinph0 * sinphi + cosph0 * cosphi * coslam; oblcon: if (xy.y <= EPS10) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = Q->akm1 / xy.y; xy.x = xy.y * cosphi * sinlam; xy.y *= (Q->mode == EQUIT) ? sinphi : cosph0 * sinphi - sinph0 * cosphi * coslam; break; case N_POLE: coslam = -coslam; lp.phi = -lp.phi; PROJ_FALLTHROUGH; case S_POLE: if (fabs(lp.phi - M_HALFPI) < TOL) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } xy.y = Q->akm1 * tan(M_FORTPI + .5 * lp.phi); xy.x = sinlam * xy.y; xy.y *= coslam; break; } return xy; } static PJ_LP stere_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_stere *Q = static_cast(P->opaque); double cosphi, sinphi, tp = 0.0, phi_l = 0.0, rho, halfe = 0.0, halfpi = 0.0; rho = hypot(xy.x, xy.y); switch (Q->mode) { case OBLIQ: case EQUIT: tp = 2. * atan2(rho * Q->cosX1, Q->akm1); cosphi = cos(tp); sinphi = sin(tp); if (rho == 0.0) phi_l = asin(cosphi * Q->sinX1); else phi_l = asin(cosphi * Q->sinX1 + (xy.y * sinphi * Q->cosX1 / rho)); tp = tan(.5 * (M_HALFPI + phi_l)); xy.x *= sinphi; xy.y = rho * Q->cosX1 * cosphi - xy.y * Q->sinX1 * sinphi; halfpi = M_HALFPI; halfe = .5 * P->e; break; case N_POLE: xy.y = -xy.y; PROJ_FALLTHROUGH; case S_POLE: tp = -rho / Q->akm1; phi_l = M_HALFPI - 2. * atan(tp); halfpi = -M_HALFPI; halfe = -.5 * P->e; break; } for (int i = NITER; i > 0; --i) { sinphi = P->e * sin(phi_l); lp.phi = 2. * atan(tp * pow((1. + sinphi) / (1. - sinphi), halfe)) - halfpi; if (fabs(phi_l - lp.phi) < CONV) { if (Q->mode == S_POLE) lp.phi = -lp.phi; lp.lam = (xy.x == 0. && xy.y == 0.) ? 0. : atan2(xy.x, xy.y); return lp; } phi_l = lp.phi; } proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } static PJ_LP stere_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_stere *Q = static_cast(P->opaque); double c, sinc, cosc; const double rh = hypot(xy.x, xy.y); c = 2. * atan(rh / Q->akm1); sinc = sin(c); cosc = cos(c); lp.lam = 0.; switch (Q->mode) { case EQUIT: if (fabs(rh) <= EPS10) lp.phi = 0.; else lp.phi = asin(xy.y * sinc / rh); if (cosc != 0. || xy.x != 0.) lp.lam = atan2(xy.x * sinc, cosc * rh); break; case OBLIQ: if (fabs(rh) <= EPS10) lp.phi = P->phi0; else lp.phi = asin(cosc * sinph0 + xy.y * sinc * cosph0 / rh); c = cosc - sinph0 * sin(lp.phi); if (c != 0. || xy.x != 0.) lp.lam = atan2(xy.x * sinc * cosph0, c * rh); break; case N_POLE: xy.y = -xy.y; PROJ_FALLTHROUGH; case S_POLE: if (fabs(rh) <= EPS10) lp.phi = P->phi0; else lp.phi = asin(Q->mode == S_POLE ? -cosc : cosc); lp.lam = (xy.x == 0. && xy.y == 0.) ? 0. : atan2(xy.x, xy.y); break; } return lp; } static PJ *stere_setup(PJ *P) { /* general initialization */ double t; struct pj_stere *Q = static_cast(P->opaque); if (fabs((t = fabs(P->phi0)) - M_HALFPI) < EPS10) Q->mode = P->phi0 < 0. ? S_POLE : N_POLE; else Q->mode = t > EPS10 ? OBLIQ : EQUIT; Q->phits = fabs(Q->phits); if (P->es != 0.0) { double X; switch (Q->mode) { case N_POLE: case S_POLE: if (fabs(Q->phits - M_HALFPI) < EPS10) Q->akm1 = 2. * P->k0 / sqrt(pow(1 + P->e, 1 + P->e) * pow(1 - P->e, 1 - P->e)); else { t = sin(Q->phits); Q->akm1 = cos(Q->phits) / pj_tsfn(Q->phits, t, P->e); t *= P->e; Q->akm1 /= sqrt(1. - t * t); } break; case EQUIT: case OBLIQ: t = sin(P->phi0); X = 2. * atan(ssfn_(P->phi0, t, P->e)) - M_HALFPI; t *= P->e; Q->akm1 = 2. * P->k0 * cos(P->phi0) / sqrt(1. - t * t); Q->sinX1 = sin(X); Q->cosX1 = cos(X); break; } P->inv = stere_e_inverse; P->fwd = stere_e_forward; } else { switch (Q->mode) { case OBLIQ: sinph0 = sin(P->phi0); cosph0 = cos(P->phi0); PROJ_FALLTHROUGH; case EQUIT: Q->akm1 = 2. * P->k0; break; case S_POLE: case N_POLE: Q->akm1 = fabs(Q->phits - M_HALFPI) >= EPS10 ? cos(Q->phits) / tan(M_FORTPI - .5 * Q->phits) : 2. * P->k0; break; } P->inv = stere_s_inverse; P->fwd = stere_s_forward; } return P; } PJ *PJ_PROJECTION(stere) { struct pj_stere *Q = static_cast(calloc(1, sizeof(struct pj_stere))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->phits = pj_param(P->ctx, P->params, "tlat_ts").i ? pj_param(P->ctx, P->params, "rlat_ts").f : M_HALFPI; return stere_setup(P); } PJ *PJ_PROJECTION(ups) { struct pj_stere *Q = static_cast(calloc(1, sizeof(struct pj_stere))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* International Ellipsoid */ P->phi0 = pj_param(P->ctx, P->params, "bsouth").i ? -M_HALFPI : M_HALFPI; if (P->es == 0.0) { proj_log_error( P, _("Invalid value for es: only ellipsoidal formulation supported")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } P->k0 = .994; P->x0 = 2000000.; P->y0 = 2000000.; Q->phits = M_HALFPI; P->lam0 = 0.; return stere_setup(P); } #undef sinph0 #undef cosph0 #undef EPS10 #undef TOL #undef NITER #undef CONV proj-9.8.1/src/projections/igh.cpp000664 001750 001750 00000022530 15166171715 017056 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(igh, "Interrupted Goode Homolosine") "\n\tPCyl, Sph"; /* This projection is a compilation of 12 separate sub-projections. Sinusoidal projections are found near the equator and Mollweide projections are found at higher latitudes. The transition between the two occurs at 40 degrees latitude and is represented by the constant `igh_phi_boundary`. Each sub-projection is assigned an integer label numbered 1 through 12. Most of this code contains logic to assign the labels based on latitude (phi) and longitude (lam) regions. Original Reference: J. Paul Goode (1925) THE HOMOLOSINE PROJECTION: A NEW DEVICE FOR PORTRAYING THE EARTH'S SURFACE ENTIRE, Annals of the Association of American Geographers, 15:3, 119-125, DOI: 10.1080/00045602509356949 */ C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *); /* Transition from sinusoidal to Mollweide projection Latitude (phi): 40deg 44' 11.8" */ constexpr double igh_phi_boundary = (40 + 44 / 60. + 11.8 / 3600.) * DEG_TO_RAD; namespace pj_igh_ns { struct pj_igh_data { struct PJconsts *pj[12]; double dy0; }; constexpr double d10 = 10 * DEG_TO_RAD; constexpr double d20 = 20 * DEG_TO_RAD; constexpr double d30 = 30 * DEG_TO_RAD; constexpr double d40 = 40 * DEG_TO_RAD; constexpr double d50 = 50 * DEG_TO_RAD; constexpr double d60 = 60 * DEG_TO_RAD; constexpr double d80 = 80 * DEG_TO_RAD; constexpr double d90 = 90 * DEG_TO_RAD; constexpr double d100 = 100 * DEG_TO_RAD; constexpr double d140 = 140 * DEG_TO_RAD; constexpr double d160 = 160 * DEG_TO_RAD; constexpr double d180 = 180 * DEG_TO_RAD; constexpr double EPSLN = 1.e-10; /* allow a little 'slack' on zone edge positions */ } // namespace pj_igh_ns static PJ_XY igh_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ using namespace pj_igh_ns; PJ_XY xy; struct pj_igh_data *Q = static_cast(P->opaque); int z; if (lp.phi >= igh_phi_boundary) { /* 1|2 */ z = (lp.lam <= -d40 ? 1 : 2); } else if (lp.phi >= 0) { /* 3|4 */ z = (lp.lam <= -d40 ? 3 : 4); } else if (lp.phi >= -igh_phi_boundary) { /* 5|6|7|8 */ if (lp.lam <= -d100) z = 5; /* 5 */ else if (lp.lam <= -d20) z = 6; /* 6 */ else if (lp.lam <= d80) z = 7; /* 7 */ else z = 8; /* 8 */ } else { /* 9|10|11|12 */ if (lp.lam <= -d100) z = 9; /* 9 */ else if (lp.lam <= -d20) z = 10; /* 10 */ else if (lp.lam <= d80) z = 11; /* 11 */ else z = 12; /* 12 */ } lp.lam -= Q->pj[z - 1]->lam0; xy = Q->pj[z - 1]->fwd(lp, Q->pj[z - 1]); xy.x += Q->pj[z - 1]->x0; xy.y += Q->pj[z - 1]->y0; return xy; } static PJ_LP igh_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ using namespace pj_igh_ns; PJ_LP lp = {0.0, 0.0}; struct pj_igh_data *Q = static_cast(P->opaque); const double y90 = Q->dy0 + sqrt(2.0); /* lt=90 corresponds to y=y0+sqrt(2) */ int z = 0; if (xy.y > y90 + EPSLN || xy.y < -y90 + EPSLN) /* 0 */ z = 0; else if (xy.y >= igh_phi_boundary) /* 1|2 */ z = (xy.x <= -d40 ? 1 : 2); else if (xy.y >= 0) /* 3|4 */ z = (xy.x <= -d40 ? 3 : 4); else if (xy.y >= -igh_phi_boundary) { /* 5|6|7|8 */ if (xy.x <= -d100) z = 5; /* 5 */ else if (xy.x <= -d20) z = 6; /* 6 */ else if (xy.x <= d80) z = 7; /* 7 */ else z = 8; /* 8 */ } else { /* 9|10|11|12 */ if (xy.x <= -d100) z = 9; /* 9 */ else if (xy.x <= -d20) z = 10; /* 10 */ else if (xy.x <= d80) z = 11; /* 11 */ else z = 12; /* 12 */ } if (z) { bool ok = false; xy.x -= Q->pj[z - 1]->x0; xy.y -= Q->pj[z - 1]->y0; lp = Q->pj[z - 1]->inv(xy, Q->pj[z - 1]); lp.lam += Q->pj[z - 1]->lam0; switch (z) { case 1: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d40 + EPSLN) || ((lp.lam >= -d40 - EPSLN && lp.lam <= -d10 + EPSLN) && (lp.phi >= d60 - EPSLN && lp.phi <= d90 + EPSLN)); break; case 2: ok = (lp.lam >= -d40 - EPSLN && lp.lam <= d180 + EPSLN) || ((lp.lam >= -d180 - EPSLN && lp.lam <= -d160 + EPSLN) && (lp.phi >= d50 - EPSLN && lp.phi <= d90 + EPSLN)) || ((lp.lam >= -d50 - EPSLN && lp.lam <= -d40 + EPSLN) && (lp.phi >= d60 - EPSLN && lp.phi <= d90 + EPSLN)); break; case 3: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d40 + EPSLN); break; case 4: ok = (lp.lam >= -d40 - EPSLN && lp.lam <= d180 + EPSLN); break; case 5: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d100 + EPSLN); break; case 6: ok = (lp.lam >= -d100 - EPSLN && lp.lam <= -d20 + EPSLN); break; case 7: ok = (lp.lam >= -d20 - EPSLN && lp.lam <= d80 + EPSLN); break; case 8: ok = (lp.lam >= d80 - EPSLN && lp.lam <= d180 + EPSLN); break; case 9: ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d100 + EPSLN); break; case 10: ok = (lp.lam >= -d100 - EPSLN && lp.lam <= -d20 + EPSLN); break; case 11: ok = (lp.lam >= -d20 - EPSLN && lp.lam <= d80 + EPSLN); break; case 12: ok = (lp.lam >= d80 - EPSLN && lp.lam <= d180 + EPSLN); break; } z = (!ok ? 0 : z); /* projectable? */ } if (!z) lp.lam = HUGE_VAL; if (!z) lp.phi = HUGE_VAL; return lp; } static PJ *pj_igh_data_destructor(PJ *P, int errlev) { using namespace pj_igh_ns; int i; if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); struct pj_igh_data *Q = static_cast(P->opaque); for (i = 0; i < 12; ++i) { if (Q->pj[i]) Q->pj[i]->destructor(Q->pj[i], errlev); } return pj_default_destructor(P, errlev); } /* Zones: -180 -40 180 +--------------+-------------------------+ Zones 1,2,9,10,11 & 12: |1 |2 | Mollweide projection | | | +--------------+-------------------------+ Zones 3,4,5,6,7 & 8: |3 |4 | Sinusoidal projection | | | 0 +-------+------+-+-----------+-----------+ |5 |6 |7 |8 | | | | | | +-------+--------+-----------+-----------+ |9 |10 |11 |12 | | | | | | +-------+--------+-----------+-----------+ -180 -100 -20 80 180 */ static bool pj_igh_setup_zone(PJ *P, struct pj_igh_ns::pj_igh_data *Q, int n, PJ *(*proj_ptr)(PJ *), double x_0, double y_0, double lon_0) { if (!(Q->pj[n - 1] = proj_ptr(nullptr))) return false; if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1]))) return false; Q->pj[n - 1]->ctx = P->ctx; Q->pj[n - 1]->x0 = x_0; Q->pj[n - 1]->y0 = y_0; Q->pj[n - 1]->lam0 = lon_0; return true; } PJ *PJ_PROJECTION(igh) { using namespace pj_igh_ns; PJ_XY xy1, xy3; PJ_LP lp = {0, igh_phi_boundary}; struct pj_igh_data *Q = static_cast( calloc(1, sizeof(struct pj_igh_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* sinusoidal zones */ if (!pj_igh_setup_zone(P, Q, 3, pj_sinu, -d100, 0, -d100) || !pj_igh_setup_zone(P, Q, 4, pj_sinu, d30, 0, d30) || !pj_igh_setup_zone(P, Q, 5, pj_sinu, -d160, 0, -d160) || !pj_igh_setup_zone(P, Q, 6, pj_sinu, -d60, 0, -d60) || !pj_igh_setup_zone(P, Q, 7, pj_sinu, d20, 0, d20) || !pj_igh_setup_zone(P, Q, 8, pj_sinu, d140, 0, d140)) { return pj_igh_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* mollweide zones */ if (!pj_igh_setup_zone(P, Q, 1, pj_moll, -d100, 0, -d100)) { return pj_igh_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } /* y0 ? */ xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */ xy3 = Q->pj[2]->fwd(lp, Q->pj[2]); /* zone 3 */ /* y0 + xy1.y = xy3.y for lt = 40d44'11.8" */ Q->dy0 = xy3.y - xy1.y; Q->pj[0]->y0 = Q->dy0; /* mollweide zones (cont'd) */ if (!pj_igh_setup_zone(P, Q, 2, pj_moll, d30, Q->dy0, d30) || !pj_igh_setup_zone(P, Q, 9, pj_moll, -d160, -Q->dy0, -d160) || !pj_igh_setup_zone(P, Q, 10, pj_moll, -d60, -Q->dy0, -d60) || !pj_igh_setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) || !pj_igh_setup_zone(P, Q, 12, pj_moll, d140, -Q->dy0, d140)) { return pj_igh_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } P->inv = igh_s_inverse; P->fwd = igh_s_forward; P->destructor = pj_igh_data_destructor; P->es = 0.; return P; } proj-9.8.1/src/projections/boggs.cpp000664 001750 001750 00000001757 15166171715 017420 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(boggs, "Boggs Eumorphic") "\n\tPCyl, no inv, Sph"; #define NITER 20 #define EPS 1e-7 #define FXC 2.00276 #define FXC2 1.11072 #define FYC 0.49931 static PJ_XY boggs_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double theta, th1, c; int i; (void)P; theta = lp.phi; if (fabs(fabs(lp.phi) - M_HALFPI) < EPS) xy.x = 0.; else { c = sin(theta) * M_PI; for (i = NITER; i; --i) { th1 = (theta + sin(theta) - c) / (1. + cos(theta)); theta -= th1; if (fabs(th1) < EPS) break; } theta *= 0.5; xy.x = FXC * lp.lam / (1. / cos(lp.phi) + FXC2 / cos(theta)); } xy.y = FYC * (lp.phi + M_SQRT2 * sin(theta)); return (xy); } PJ *PJ_PROJECTION(boggs) { P->es = 0.; P->fwd = boggs_s_forward; return P; } #undef NITER #undef EPS #undef FXC #undef FXC2 #undef FYC proj-9.8.1/src/projections/wag3.cpp000664 001750 001750 00000002363 15166171715 017152 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(wag3, "Wagner III") "\n\tPCyl, Sph\n\tlat_ts="; #define TWOTHIRD 0.6666666666666666666667 namespace { // anonymous namespace struct pj_wag3 { double C_x; }; } // anonymous namespace static PJ_XY wag3_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = static_cast(P->opaque)->C_x * lp.lam * cos(TWOTHIRD * lp.phi); xy.y = lp.phi; return xy; } static PJ_LP wag3_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; lp.phi = xy.y; lp.lam = xy.x / (static_cast(P->opaque)->C_x * cos(TWOTHIRD * lp.phi)); return lp; } PJ *PJ_PROJECTION(wag3) { double ts; struct pj_wag3 *Q = static_cast(calloc(1, sizeof(struct pj_wag3))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; ts = pj_param(P->ctx, P->params, "rlat_ts").f; static_cast(P->opaque)->C_x = cos(ts) / cos(2. * ts / 3.); P->es = 0.; P->inv = wag3_s_inverse; P->fwd = wag3_s_forward; return P; } #undef TWOTHIRD proj-9.8.1/src/projections/hammer.cpp000664 001750 001750 00000005005 15166171715 017556 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(hammer, "Hammer & Eckert-Greifendorff") "\n\tMisc Sph, \n\tW= M="; #define EPS 1.0e-10 namespace { // anonymous namespace struct pq_hammer { double w; double m, rm; }; } // anonymous namespace static PJ_XY hammer_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pq_hammer *Q = static_cast(P->opaque); double cosphi, d; cosphi = cos(lp.phi); lp.lam *= Q->w; double denom = 1. + cosphi * cos(lp.lam); if (denom == 0.0) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; } d = sqrt(2. / denom); xy.x = Q->m * d * cosphi * sin(lp.lam); xy.y = Q->rm * d * sin(lp.phi); return xy; } static PJ_LP hammer_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pq_hammer *Q = static_cast(P->opaque); double z; z = sqrt(1. - 0.25 * Q->w * Q->w * xy.x * xy.x - 0.25 * xy.y * xy.y); if (fabs(2. * z * z - 1.) < EPS) { lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); } else { lp.lam = aatan2(Q->w * xy.x * z, 2. * z * z - 1) / Q->w; lp.phi = aasin(P->ctx, z * xy.y); } return lp; } PJ *PJ_PROJECTION(hammer) { struct pq_hammer *Q = static_cast(calloc(1, sizeof(struct pq_hammer))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (pj_param(P->ctx, P->params, "tW").i) { Q->w = fabs(pj_param(P->ctx, P->params, "dW").f); if (Q->w <= 0.) { proj_log_error(P, _("Invalid value for W: it should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else Q->w = .5; if (pj_param(P->ctx, P->params, "tM").i) { Q->m = fabs(pj_param(P->ctx, P->params, "dM").f); if (Q->m <= 0.) { proj_log_error(P, _("Invalid value for M: it should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } else Q->m = 1.; Q->rm = 1. / Q->m; Q->m /= Q->w; P->es = 0.; P->fwd = hammer_s_forward; P->inv = hammer_s_inverse; return P; } #undef EPS proj-9.8.1/src/projections/larr.cpp000664 001750 001750 00000000751 15166171715 017250 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(larr, "Larrivee") "\n\tMisc Sph, no inv"; #define SIXTH .16666666666666666 static PJ_XY larr_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = 0.5 * lp.lam * (1. + sqrt(cos(lp.phi))); xy.y = lp.phi / (cos(0.5 * lp.phi) * cos(SIXTH * lp.lam)); return xy; } PJ *PJ_PROJECTION(larr) { P->es = 0; P->fwd = larr_s_forward; return P; } proj-9.8.1/src/projections/healpix.cpp000664 001750 001750 00000056577 15166171715 017763 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the HEALPix and rHEALPix projections. * For background see *. * Authors: Alex Raichev (raichev@cs.auckland.ac.nz) * Michael Speth (spethm@landcareresearch.co.nz) * Notes: Raichev implemented these projections in Python and * Speth translated them into C here. ****************************************************************************** * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substcounteral 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 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(healpix, "HEALPix") "\n\tSph&Ell\n\trot_xy="; PROJ_HEAD(rhealpix, "rHEALPix") "\n\tSph&Ell\n\tnorth_square= south_square="; /* Matrix for counterclockwise rotation by pi/2: */ #define R1 \ { \ {0, -1}, { 1, 0 } \ } /* Matrix for counterclockwise rotation by pi: */ #define R2 \ { \ {-1, 0}, { 0, -1 } \ } /* Matrix for counterclockwise rotation by 3*pi/2: */ #define R3 \ { \ {0, 1}, { -1, 0 } \ } /* Identity matrix */ #define IDENT \ { \ {1, 0}, { 0, 1 } \ } /* IDENT, R1, R2, R3, R1 inverse, R2 inverse, R3 inverse:*/ #define ROT \ { IDENT, R1, R2, R3, R3, R2, R1 } /* Fuzz to handle rounding errors: */ #define EPS 1e-15 namespace { // anonymous namespace struct pj_healpix_data { int north_square; int south_square; double rot_xy; double qp; double *apa; }; } // anonymous namespace typedef struct { int cn; /* An integer 0--3 indicating the position of the polar cap. */ double x, y; /* Coordinates of the pole point (point of most extreme latitude on the polar caps). */ enum Region { north, south, equatorial } region; } CapMap; static const double rot[7][2][2] = ROT; /** * Returns the sign of the double. * @param v the parameter whose sign is returned. * @return 1 for positive number, -1 for negative, and 0 for zero. **/ static double sign(double v) { return v > 0 ? 1 : (v < 0 ? -1 : 0); } static PJ_XY rotate(PJ_XY p, double angle) { PJ_XY result; result.x = p.x * cos(angle) - p.y * sin(angle); result.y = p.y * cos(angle) + p.x * sin(angle); return result; } /** * Return the index of the matrix in ROT. * @param index ranges from -3 to 3. */ static int get_rotate_index(int index) { switch (index) { case 0: return 0; case 1: return 1; case 2: return 2; case 3: return 3; case -1: return 4; case -2: return 5; case -3: return 6; } return 0; } /** * Return 1 if point (testx, testy) lies in the interior of the polygon * determined by the vertices in vert, and return 0 otherwise. * See http://paulbourke.net/geometry/polygonmesh/ for more details. * @param nvert the number of vertices in the polygon. * @param vert the (x, y)-coordinates of the polygon's vertices **/ static int pnpoly(int nvert, double vert[][2], double testx, double testy) { int i; int counter = 0; double xinters; PJ_XY p1, p2; /* Check for boundary cases */ for (i = 0; i < nvert; i++) { if (testx == vert[i][0] && testy == vert[i][1]) { return 1; } } p1.x = vert[0][0]; p1.y = vert[0][1]; for (i = 1; i < nvert; i++) { p2.x = vert[i % nvert][0]; p2.y = vert[i % nvert][1]; if (testy > MIN(p1.y, p2.y) && testy <= MAX(p1.y, p2.y) && testx <= MAX(p1.x, p2.x) && p1.y != p2.y) { xinters = (testy - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x; if (p1.x == p2.x || testx <= xinters) counter++; } p1 = p2; } if (counter % 2 == 0) { return 0; } else { return 1; } } /** * Return 1 if (x, y) lies in (the interior or boundary of) the image of the * HEALPix projection (in case proj=0) or in the image the rHEALPix projection * (in case proj=1), and return 0 otherwise. * @param north_square the position of the north polar square (rHEALPix only) * @param south_square the position of the south polar square (rHEALPix only) **/ static int in_image(double x, double y, int proj, int north_square, int south_square) { if (proj == 0) { double healpixVertsJit[][2] = {{-M_PI - EPS, M_FORTPI}, {-3 * M_FORTPI, M_HALFPI + EPS}, {-M_HALFPI, M_FORTPI + EPS}, {-M_FORTPI, M_HALFPI + EPS}, {0.0, M_FORTPI + EPS}, {M_FORTPI, M_HALFPI + EPS}, {M_HALFPI, M_FORTPI + EPS}, {3 * M_FORTPI, M_HALFPI + EPS}, {M_PI + EPS, M_FORTPI}, {M_PI + EPS, -M_FORTPI}, {3 * M_FORTPI, -M_HALFPI - EPS}, {M_HALFPI, -M_FORTPI - EPS}, {M_FORTPI, -M_HALFPI - EPS}, {0.0, -M_FORTPI - EPS}, {-M_FORTPI, -M_HALFPI - EPS}, {-M_HALFPI, -M_FORTPI - EPS}, {-3 * M_FORTPI, -M_HALFPI - EPS}, {-M_PI - EPS, -M_FORTPI}, {-M_PI - EPS, M_FORTPI}}; return pnpoly((int)sizeof(healpixVertsJit) / sizeof(healpixVertsJit[0]), healpixVertsJit, x, y); } else { /** * We need to assign the array this way because the input is * dynamic (north_square and south_square vars are unknown at * compile time). **/ double rhealpixVertsJit[][2] = { {-M_PI - EPS, M_FORTPI + EPS}, {-M_PI + north_square * M_HALFPI - EPS, M_FORTPI + EPS}, {-M_PI + north_square * M_HALFPI - EPS, 3 * M_FORTPI + EPS}, {-M_PI + (north_square + 1.0) * M_HALFPI + EPS, 3 * M_FORTPI + EPS}, {-M_PI + (north_square + 1.0) * M_HALFPI + EPS, M_FORTPI + EPS}, {M_PI + EPS, M_FORTPI + EPS}, {M_PI + EPS, -M_FORTPI - EPS}, {-M_PI + (south_square + 1.0) * M_HALFPI + EPS, -M_FORTPI - EPS}, {-M_PI + (south_square + 1.0) * M_HALFPI + EPS, -3 * M_FORTPI - EPS}, {-M_PI + south_square * M_HALFPI - EPS, -3 * M_FORTPI - EPS}, {-M_PI + south_square * M_HALFPI - EPS, -M_FORTPI - EPS}, {-M_PI - EPS, -M_FORTPI - EPS}}; return pnpoly((int)sizeof(rhealpixVertsJit) / sizeof(rhealpixVertsJit[0]), rhealpixVertsJit, x, y); } } /** * Return the authalic latitude of latitude alpha (if inverse=0) or * return the latitude of authalic latitude alpha (if inverse=1). * P contains the relevant ellipsoid parameters. **/ static double auth_lat(PJ *P, double alpha, int inverse) { const struct pj_healpix_data *Q = static_cast(P->opaque); if (inverse == 0) { /* Authalic latitude from geographic latitude. */ return pj_authalic_lat(alpha, sin(alpha), cos(alpha), Q->apa, P, Q->qp); } else { /* Geographic latitude from authalic latitude. */ return pj_authalic_lat_inverse(alpha, Q->apa, P, Q->qp); } } /** * Return the HEALPix projection of the longitude-latitude point lp on * the unit sphere. **/ static PJ_XY healpix_sphere(PJ_LP lp) { double lam = lp.lam; double phi = lp.phi; double phi0 = asin(2.0 / 3.0); PJ_XY xy; /* equatorial region */ if (fabs(phi) <= phi0) { xy.x = lam; xy.y = 3 * M_PI / 8 * sin(phi); } else { double lamc; double sigma = sqrt(3 * (1 - fabs(sin(phi)))); double cn = floor(2 * lam / M_PI + 2); if (cn >= 4) { cn = 3; } lamc = -3 * M_FORTPI + M_HALFPI * cn; xy.x = lamc + (lam - lamc) * sigma; xy.y = sign(phi) * M_FORTPI * (2 - sigma); } return xy; } /** * Return the inverse of healpix_sphere(). **/ static PJ_LP healpix_spherhealpix_e_inverse(PJ_XY xy) { PJ_LP lp; double x = xy.x; double y = xy.y; double y0 = M_FORTPI; /* Equatorial region. */ if (fabs(y) <= y0) { lp.lam = x; lp.phi = asin(8 * y / (3 * M_PI)); } else if (fabs(y) < M_HALFPI) { double cn = floor(2 * x / M_PI + 2); double xc, tau; if (cn >= 4) { cn = 3; } xc = -3 * M_FORTPI + M_HALFPI * cn; tau = 2.0 - 4 * fabs(y) / M_PI; lp.lam = xc + (x - xc) / tau; lp.phi = sign(y) * asin(1.0 - pow(tau, 2) / 3.0); } else { lp.lam = -M_PI; lp.phi = sign(y) * M_HALFPI; } return (lp); } /** * Return the vector sum a + b, where a and b are 2-dimensional vectors. * @param ret holds a + b. **/ static void vector_add(const double a[2], const double b[2], double *ret) { int i; for (i = 0; i < 2; i++) { ret[i] = a[i] + b[i]; } } /** * Return the vector difference a - b, where a and b are 2-dimensional vectors. * @param ret holds a - b. **/ static void vector_sub(const double a[2], const double b[2], double *ret) { int i; for (i = 0; i < 2; i++) { ret[i] = a[i] - b[i]; } } /** * Return the 2 x 1 matrix product a*b, where a is a 2 x 2 matrix and * b is a 2 x 1 matrix. * @param ret holds a*b. **/ static void dot_product(const double a[2][2], const double b[2], double *ret) { int i, j; int length = 2; for (i = 0; i < length; i++) { ret[i] = 0; for (j = 0; j < length; j++) { ret[i] += a[i][j] * b[j]; } } } /** * Return the number of the polar cap, the pole point coordinates, and * the region that (x, y) lies in. * If inverse=0, then assume (x,y) lies in the image of the HEALPix * projection of the unit sphere. * If inverse=1, then assume (x,y) lies in the image of the * (north_square, south_square)-rHEALPix projection of the unit sphere. **/ static CapMap get_cap(double x, double y, int north_square, int south_square, int inverse) { CapMap capmap; double c; capmap.x = x; capmap.y = y; if (inverse == 0) { if (y > M_FORTPI) { capmap.region = CapMap::north; c = M_HALFPI; } else if (y < -M_FORTPI) { capmap.region = CapMap::south; c = -M_HALFPI; } else { capmap.region = CapMap::equatorial; capmap.cn = 0; return capmap; } /* polar region */ if (x < -M_HALFPI) { capmap.cn = 0; capmap.x = (-3 * M_FORTPI); capmap.y = c; } else if (x >= -M_HALFPI && x < 0) { capmap.cn = 1; capmap.x = -M_FORTPI; capmap.y = c; } else if (x >= 0 && x < M_HALFPI) { capmap.cn = 2; capmap.x = M_FORTPI; capmap.y = c; } else { capmap.cn = 3; capmap.x = 3 * M_FORTPI; capmap.y = c; } } else { if (y > M_FORTPI) { capmap.region = CapMap::north; capmap.x = -3 * M_FORTPI + north_square * M_HALFPI; capmap.y = M_HALFPI; x = x - north_square * M_HALFPI; } else if (y < -M_FORTPI) { capmap.region = CapMap::south; capmap.x = -3 * M_FORTPI + south_square * M_HALFPI; capmap.y = -M_HALFPI; x = x - south_square * M_HALFPI; } else { capmap.region = CapMap::equatorial; capmap.cn = 0; return capmap; } /* Polar Region, find the HEALPix polar cap number that x, y moves to when rHEALPix polar square is disassembled. */ if (capmap.region == CapMap::north) { if (y >= -x - M_FORTPI - EPS && y < x + 5 * M_FORTPI - EPS) { capmap.cn = (north_square + 1) % 4; } else if (y > -x - M_FORTPI + EPS && y >= x + 5 * M_FORTPI - EPS) { capmap.cn = (north_square + 2) % 4; } else if (y <= -x - M_FORTPI + EPS && y > x + 5 * M_FORTPI + EPS) { capmap.cn = (north_square + 3) % 4; } else { capmap.cn = north_square; } } else /* if (capmap.region == CapMap::south) */ { if (y <= x + M_FORTPI + EPS && y > -x - 5 * M_FORTPI + EPS) { capmap.cn = (south_square + 1) % 4; } else if (y < x + M_FORTPI - EPS && y <= -x - 5 * M_FORTPI + EPS) { capmap.cn = (south_square + 2) % 4; } else if (y >= x + M_FORTPI - EPS && y < -x - 5 * M_FORTPI - EPS) { capmap.cn = (south_square + 3) % 4; } else { capmap.cn = south_square; } } } return capmap; } /** * Rearrange point (x, y) in the HEALPix projection by * combining the polar caps into two polar squares. * Put the north polar square in position north_square and * the south polar square in position south_square. * If inverse=1, then uncombine the polar caps. * @param north_square integer between 0 and 3. * @param south_square integer between 0 and 3. **/ static PJ_XY combine_caps(double x, double y, int north_square, int south_square, int inverse) { PJ_XY xy; double vector[2]; double v_min_c[2]; double ret_dot[2]; const double(*tmpRot)[2]; int pole = 0; CapMap capmap = get_cap(x, y, north_square, south_square, inverse); if (capmap.region == CapMap::equatorial) { xy.x = capmap.x; xy.y = capmap.y; return xy; } double v[] = {x, y}; double c[] = {capmap.x, capmap.y}; if (inverse == 0) { /* Rotate (x, y) about its polar cap tip and then translate it to north_square or south_square. */ if (capmap.region == CapMap::north) { pole = north_square; tmpRot = rot[get_rotate_index(capmap.cn - pole)]; } else { pole = south_square; tmpRot = rot[get_rotate_index(-1 * (capmap.cn - pole))]; } } else { /* Inverse function. Unrotate (x, y) and then translate it back. */ /* disassemble */ if (capmap.region == CapMap::north) { pole = north_square; tmpRot = rot[get_rotate_index(-1 * (capmap.cn - pole))]; } else { pole = south_square; tmpRot = rot[get_rotate_index(capmap.cn - pole)]; } } vector_sub(v, c, v_min_c); dot_product(tmpRot, v_min_c, ret_dot); { double a[] = {-3 * M_FORTPI + ((inverse == 0) ? pole : capmap.cn) * M_HALFPI, ((capmap.region == CapMap::north) ? 1 : -1) * M_HALFPI}; vector_add(ret_dot, a, vector); } xy.x = vector[0]; xy.y = vector[1]; return xy; } static PJ_XY s_healpix_forward(PJ_LP lp, PJ *P) { /* sphere */ (void)P; struct pj_healpix_data *Q = static_cast(P->opaque); return rotate(healpix_sphere(lp), -Q->rot_xy); } static PJ_XY e_healpix_forward(PJ_LP lp, PJ *P) { /* ellipsoid */ lp.phi = auth_lat(P, lp.phi, 0); struct pj_healpix_data *Q = static_cast(P->opaque); return rotate(healpix_sphere(lp), -Q->rot_xy); } static PJ_LP s_healpix_inverse(PJ_XY xy, PJ *P) { /* sphere */ struct pj_healpix_data *Q = static_cast(P->opaque); xy = rotate(xy, Q->rot_xy); /* Check whether (x, y) lies in the HEALPix image */ if (in_image(xy.x, xy.y, 0, 0, 0) == 0) { PJ_LP lp; lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return healpix_spherhealpix_e_inverse(xy); } static PJ_LP e_healpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */ PJ_LP lp = {0.0, 0.0}; struct pj_healpix_data *Q = static_cast(P->opaque); xy = rotate(xy, Q->rot_xy); /* Check whether (x, y) lies in the HEALPix image. */ if (in_image(xy.x, xy.y, 0, 0, 0) == 0) { lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } lp = healpix_spherhealpix_e_inverse(xy); lp.phi = auth_lat(P, lp.phi, 1); return lp; } static PJ_XY s_rhealpix_forward(PJ_LP lp, PJ *P) { /* sphere */ struct pj_healpix_data *Q = static_cast(P->opaque); PJ_XY xy = healpix_sphere(lp); return combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 0); } static PJ_XY e_rhealpix_forward(PJ_LP lp, PJ *P) { /* ellipsoid */ struct pj_healpix_data *Q = static_cast(P->opaque); PJ_XY xy; lp.phi = auth_lat(P, lp.phi, 0); xy = healpix_sphere(lp); return combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 0); } static PJ_LP s_rhealpix_inverse(PJ_XY xy, PJ *P) { /* sphere */ struct pj_healpix_data *Q = static_cast(P->opaque); /* Check whether (x, y) lies in the rHEALPix image. */ if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) { PJ_LP lp; lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1); return healpix_spherhealpix_e_inverse(xy); } static PJ_LP e_rhealpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */ struct pj_healpix_data *Q = static_cast(P->opaque); PJ_LP lp = {0.0, 0.0}; /* Check whether (x, y) lies in the rHEALPix image. */ if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) { lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1); lp = healpix_spherhealpix_e_inverse(xy); lp.phi = auth_lat(P, lp.phi, 1); return lp; } static PJ *pj_healpix_data_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->apa); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(healpix) { struct pj_healpix_data *Q = static_cast( calloc(1, sizeof(struct pj_healpix_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_healpix_data_destructor; double angle = pj_param(P->ctx, P->params, "drot_xy").f; Q->rot_xy = PJ_TORAD(angle); if (P->es != 0.0) { Q->apa = pj_authalic_lat_compute_coeffs(P->n); /* For auth_lat(). */ if (nullptr == Q->apa) return pj_healpix_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_authalic_lat_q(1.0, P); /* For auth_lat(). */ P->a = P->a * sqrt(0.5 * Q->qp); /* Set P->a to authalic radius. */ pj_calc_ellipsoid_params( P, P->a, P->es); /* Ensure we have a consistent parameter set */ P->fwd = e_healpix_forward; P->inv = e_healpix_inverse; } else { P->fwd = s_healpix_forward; P->inv = s_healpix_inverse; } return P; } PJ *PJ_PROJECTION(rhealpix) { struct pj_healpix_data *Q = static_cast( calloc(1, sizeof(struct pj_healpix_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_healpix_data_destructor; Q->north_square = pj_param(P->ctx, P->params, "inorth_square").i; Q->south_square = pj_param(P->ctx, P->params, "isouth_square").i; /* Check for valid north_square and south_square inputs. */ if (Q->north_square < 0 || Q->north_square > 3) { proj_log_error( P, _("Invalid value for north_square: it should be in [0,3] range.")); return pj_healpix_data_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (Q->south_square < 0 || Q->south_square > 3) { proj_log_error( P, _("Invalid value for south_square: it should be in [0,3] range.")); return pj_healpix_data_destructor( P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (P->es != 0.0) { Q->apa = pj_authalic_lat_compute_coeffs(P->n); /* For auth_lat(). */ if (nullptr == Q->apa) return pj_healpix_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_authalic_lat_q(1.0, P); /* For auth_lat(). */ P->a = P->a * sqrt(0.5 * Q->qp); /* Set P->a to authalic radius. */ P->ra = 1.0 / P->a; P->fwd = e_rhealpix_forward; P->inv = e_rhealpix_inverse; } else { P->fwd = s_rhealpix_forward; P->inv = s_rhealpix_inverse; } return P; } #undef R1 #undef R2 #undef R3 #undef IDENT #undef ROT #undef EPS proj-9.8.1/src/projections/sconics.cpp000664 001750 001750 00000014000 15166171715 017741 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include namespace pj_sconics_ns { enum Type { EULER = 0, MURD1 = 1, MURD2 = 2, MURD3 = 3, PCONIC = 4, TISSOT = 5, VITK1 = 6 }; } namespace { // anonymous namespace struct pj_sconics_data { double n; double rho_c; double rho_0; double sig; double c1, c2; enum pj_sconics_ns::Type type; }; } // anonymous namespace #define EPS10 1.e-10 #define EPS 1e-10 #define LINE2 "\n\tConic, Sph\n\tlat_1= and lat_2=" PROJ_HEAD(euler, "Euler") LINE2; PROJ_HEAD(murd1, "Murdoch I") LINE2; PROJ_HEAD(murd2, "Murdoch II") LINE2; PROJ_HEAD(murd3, "Murdoch III") LINE2; PROJ_HEAD(pconic, "Perspective Conic") LINE2; PROJ_HEAD(tissot, "Tissot") LINE2; PROJ_HEAD(vitk1, "Vitkovsky I") LINE2; /* get common factors for simple conics */ static int phi12(PJ *P, double *del) { double p1, p2; int err = 0; if (!pj_param(P->ctx, P->params, "tlat_1").i) { proj_log_error(P, _("Missing parameter: lat_1 should be specified")); err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } else if (!pj_param(P->ctx, P->params, "tlat_2").i) { proj_log_error(P, _("Missing parameter: lat_2 should be specified")); err = PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE; } else { p1 = pj_param(P->ctx, P->params, "rlat_1").f; p2 = pj_param(P->ctx, P->params, "rlat_2").f; *del = 0.5 * (p2 - p1); const double sig = 0.5 * (p2 + p1); static_cast(P->opaque)->sig = sig; err = (fabs(*del) < EPS || fabs(sig) < EPS) ? PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE : 0; if (err) { proj_log_error( P, _("Illegal value for lat_1 and lat_2: |lat_1 - lat_2| " "and |lat_1 + lat_2| should be > 0")); } } return err; } static PJ_XY sconics_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_sconics_data *Q = static_cast(P->opaque); double rho; switch (Q->type) { case pj_sconics_ns::MURD2: rho = Q->rho_c + tan(Q->sig - lp.phi); break; case pj_sconics_ns::PCONIC: rho = Q->c2 * (Q->c1 - tan(lp.phi - Q->sig)); break; default: rho = Q->rho_c - lp.phi; break; } xy.x = rho * sin(lp.lam *= Q->n); xy.y = Q->rho_0 - rho * cos(lp.lam); return xy; } static PJ_LP sconics_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, (and ellipsoidal?) inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_sconics_data *Q = static_cast(P->opaque); double rho; xy.y = Q->rho_0 - xy.y; rho = hypot(xy.x, xy.y); if (Q->n < 0.) { rho = -rho; xy.x = -xy.x; xy.y = -xy.y; } lp.lam = atan2(xy.x, xy.y) / Q->n; switch (Q->type) { case pj_sconics_ns::PCONIC: lp.phi = atan(Q->c1 - rho / Q->c2) + Q->sig; break; case pj_sconics_ns::MURD2: lp.phi = Q->sig - atan(rho - Q->rho_c); break; default: lp.phi = Q->rho_c - rho; } return lp; } static PJ *pj_sconics_setup(PJ *P, enum pj_sconics_ns::Type type) { double del, cs; int err; struct pj_sconics_data *Q = static_cast( calloc(1, sizeof(struct pj_sconics_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->type = type; err = phi12(P, &del); if (err) return pj_default_destructor(P, err); switch (Q->type) { case pj_sconics_ns::TISSOT: Q->n = sin(Q->sig); cs = cos(del); Q->rho_c = Q->n / cs + cs / Q->n; Q->rho_0 = sqrt((Q->rho_c - 2 * sin(P->phi0)) / Q->n); break; case pj_sconics_ns::MURD1: Q->rho_c = sin(del) / (del * tan(Q->sig)) + Q->sig; Q->rho_0 = Q->rho_c - P->phi0; Q->n = sin(Q->sig); break; case pj_sconics_ns::MURD2: Q->rho_c = (cs = sqrt(cos(del))) / tan(Q->sig); Q->rho_0 = Q->rho_c + tan(Q->sig - P->phi0); Q->n = sin(Q->sig) * cs; break; case pj_sconics_ns::MURD3: Q->rho_c = del / (tan(Q->sig) * tan(del)) + Q->sig; Q->rho_0 = Q->rho_c - P->phi0; Q->n = sin(Q->sig) * sin(del) * tan(del) / (del * del); break; case pj_sconics_ns::EULER: Q->n = sin(Q->sig) * sin(del) / del; del *= 0.5; Q->rho_c = del / (tan(del) * tan(Q->sig)) + Q->sig; Q->rho_0 = Q->rho_c - P->phi0; break; case pj_sconics_ns::PCONIC: Q->n = sin(Q->sig); Q->c2 = cos(del); Q->c1 = 1. / tan(Q->sig); del = P->phi0 - Q->sig; if (fabs(del) - EPS10 >= M_HALFPI) { proj_log_error( P, _("Invalid value for lat_0/lat_1/lat_2: |lat_0 - 0.5 * " "(lat_1 + lat_2)| should be < 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->rho_0 = Q->c2 * (Q->c1 - tan(del)); break; case pj_sconics_ns::VITK1: cs = tan(del); Q->n = cs * sin(Q->sig) / del; Q->rho_c = del / (cs * tan(Q->sig)) + Q->sig; Q->rho_0 = Q->rho_c - P->phi0; break; } P->inv = sconics_s_inverse; P->fwd = sconics_s_forward; P->es = 0; return (P); } PJ *PJ_PROJECTION(euler) { return pj_sconics_setup(P, pj_sconics_ns::EULER); } PJ *PJ_PROJECTION(tissot) { return pj_sconics_setup(P, pj_sconics_ns::TISSOT); } PJ *PJ_PROJECTION(murd1) { return pj_sconics_setup(P, pj_sconics_ns::MURD1); } PJ *PJ_PROJECTION(murd2) { return pj_sconics_setup(P, pj_sconics_ns::MURD2); } PJ *PJ_PROJECTION(murd3) { return pj_sconics_setup(P, pj_sconics_ns::MURD3); } PJ *PJ_PROJECTION(pconic) { return pj_sconics_setup(P, pj_sconics_ns::PCONIC); } PJ *PJ_PROJECTION(vitk1) { return pj_sconics_setup(P, pj_sconics_ns::VITK1); } #undef EPS10 #undef EPS #undef LINE2 proj-9.8.1/src/projections/col_urban.cpp000664 001750 001750 00000004663 15166171715 020262 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(col_urban, "Colombia Urban") "\n\tMisc\n\th_0="; // Notations and formulas taken from IOGP Publication 373-7-2 - // Geomatics Guidance Note number 7, part 2 - March 2020 namespace { // anonymous namespace struct pj_col_urban { double h0; // height of projection origin, divided by semi-major axis (a) double rho0; // adimensional value, contrary to Guidance note 7.2 double A; double B; // adimensional value, contrary to Guidance note 7.2 double C; double D; // adimensional value, contrary to Guidance note 7.2 }; } // anonymous namespace static PJ_XY col_urban_forward(PJ_LP lp, PJ *P) { PJ_XY xy; struct pj_col_urban *Q = static_cast(P->opaque); const double cosphi = cos(lp.phi); const double sinphi = sin(lp.phi); const double nu = 1. / sqrt(1 - P->es * sinphi * sinphi); const double lam_nu_cosphi = lp.lam * nu * cosphi; xy.x = Q->A * lam_nu_cosphi; const double sinphi_m = sin(0.5 * (lp.phi + P->phi0)); const double rho_m = (1 - P->es) / pow(1 - P->es * sinphi_m * sinphi_m, 1.5); const double G = 1 + Q->h0 / rho_m; xy.y = G * Q->rho0 * ((lp.phi - P->phi0) + Q->B * lam_nu_cosphi * lam_nu_cosphi); return xy; } static PJ_LP col_urban_inverse(PJ_XY xy, PJ *P) { PJ_LP lp; struct pj_col_urban *Q = static_cast(P->opaque); lp.phi = P->phi0 + xy.y / Q->D - Q->B * (xy.x / Q->C) * (xy.x / Q->C); const double sinphi = sin(lp.phi); const double nu = 1. / sqrt(1 - P->es * sinphi * sinphi); lp.lam = xy.x / (Q->C * nu * cos(lp.phi)); return lp; } PJ *PJ_PROJECTION(col_urban) { struct pj_col_urban *Q = static_cast( calloc(1, sizeof(struct pj_col_urban))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; const double h0_unscaled = pj_param(P->ctx, P->params, "dh_0").f; Q->h0 = h0_unscaled / P->a; const double sinphi0 = sin(P->phi0); const double nu0 = 1. / sqrt(1 - P->es * sinphi0 * sinphi0); Q->A = 1 + Q->h0 / nu0; Q->rho0 = (1 - P->es) / pow(1 - P->es * sinphi0 * sinphi0, 1.5); Q->B = tan(P->phi0) / (2 * Q->rho0 * nu0); Q->C = 1 + Q->h0; Q->D = Q->rho0 * (1 + Q->h0 / (1 - P->es)); P->fwd = col_urban_forward; P->inv = col_urban_inverse; return P; } proj-9.8.1/src/projections/airy.cpp000664 001750 001750 00000011436 15166171715 017256 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Implementation of the airy (Airy) projection. * Author: Gerald Evenden (1995) * Thomas Knudsen (2016) - revise/add regression tests * ****************************************************************************** * Copyright (c) 1995, Gerald Evenden * * 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 "proj.h" #include "proj_internal.h" #include PROJ_HEAD(airy, "Airy") "\n\tMisc Sph, no inv\n\tno_cut lat_b="; namespace { // anonymous namespace enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; } // anonymous namespace namespace { // anonymous namespace struct pj_airy { double p_halfpi; double sinph0; double cosph0; double Cb; enum Mode mode; int no_cut; /* do not cut at hemisphere limit */ }; } // anonymous namespace #define EPS 1.e-10 static PJ_XY airy_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_airy *Q = static_cast(P->opaque); double sinlam, coslam, cosphi, sinphi, t, s, Krho, cosz; sinlam = sin(lp.lam); coslam = cos(lp.lam); switch (Q->mode) { case EQUIT: case OBLIQ: sinphi = sin(lp.phi); cosphi = cos(lp.phi); cosz = cosphi * coslam; if (Q->mode == OBLIQ) cosz = Q->sinph0 * sinphi + Q->cosph0 * cosz; if (!Q->no_cut && cosz < -EPS) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } s = 1. - cosz; if (fabs(s) > EPS) { t = 0.5 * (1. + cosz); if (t == 0) { proj_errno_set( P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } Krho = -log(t) / s - Q->Cb / t; } else Krho = 0.5 - Q->Cb; xy.x = Krho * cosphi * sinlam; if (Q->mode == OBLIQ) xy.y = Krho * (Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam); else xy.y = Krho * sinphi; break; case S_POLE: case N_POLE: lp.phi = fabs(Q->p_halfpi - lp.phi); if (!Q->no_cut && (lp.phi - EPS) > M_HALFPI) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return xy; } lp.phi *= 0.5; if (lp.phi > EPS) { t = tan(lp.phi); Krho = -2. * (log(cos(lp.phi)) / t + t * Q->Cb); xy.x = Krho * sinlam; xy.y = Krho * coslam; if (Q->mode == N_POLE) xy.y = -xy.y; } else xy.x = xy.y = 0.; } return xy; } PJ *PJ_PROJECTION(airy) { double beta; struct pj_airy *Q = static_cast(calloc(1, sizeof(struct pj_airy))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; Q->no_cut = pj_param(P->ctx, P->params, "bno_cut").i; beta = 0.5 * (M_HALFPI - pj_param(P->ctx, P->params, "rlat_b").f); if (fabs(beta) < EPS) Q->Cb = -0.5; else { Q->Cb = 1. / tan(beta); Q->Cb *= Q->Cb * log(cos(beta)); } if (fabs(fabs(P->phi0) - M_HALFPI) < EPS) if (P->phi0 < 0.) { Q->p_halfpi = -M_HALFPI; Q->mode = S_POLE; } else { Q->p_halfpi = M_HALFPI; Q->mode = N_POLE; } else { if (fabs(P->phi0) < EPS) Q->mode = EQUIT; else { Q->mode = OBLIQ; Q->sinph0 = sin(P->phi0); Q->cosph0 = cos(P->phi0); } } P->fwd = airy_s_forward; P->es = 0.; return P; } #undef EPS proj-9.8.1/src/projections/cea.cpp000664 001750 001750 00000005670 15166171715 017045 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_cea_data { double qp; double *apa; }; } // anonymous namespace PROJ_HEAD(cea, "Equal Area Cylindrical") "\n\tCyl, Sph&Ell\n\tlat_ts="; #define EPS 1e-10 static PJ_XY cea_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = P->k0 * lp.lam; xy.y = 0.5 * pj_authalic_lat_q(sin(lp.phi), P) / P->k0; return xy; } static PJ_XY cea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = P->k0 * lp.lam; xy.y = sin(lp.phi) / P->k0; return xy; } static PJ_LP cea_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; const struct pj_cea_data *Q = static_cast(P->opaque); lp.phi = pj_authalic_lat_inverse(asin(2. * xy.y * P->k0 / Q->qp), Q->apa, P, Q->qp); lp.lam = xy.x / P->k0; return lp; } static PJ_LP cea_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; xy.y *= P->k0; const double t = fabs(xy.y); if (t - EPS <= 1.) { if (t >= 1.) lp.phi = xy.y < 0. ? -M_HALFPI : M_HALFPI; else lp.phi = asin(xy.y); lp.lam = xy.x / P->k0; } else { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return lp; } return (lp); } static PJ *pj_cea_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->apa); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(cea) { double t = 0.0; struct pj_cea_data *Q = static_cast( calloc(1, sizeof(struct pj_cea_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; P->destructor = pj_cea_destructor; if (pj_param(P->ctx, P->params, "tlat_ts").i) { t = pj_param(P->ctx, P->params, "rlat_ts").f; P->k0 = cos(t); if (P->k0 < 0.) { proj_log_error( P, _("Invalid value for lat_ts: |lat_ts| should be <= 90°")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } } if (P->es != 0.0) { t = sin(t); P->k0 /= sqrt(1. - P->es * t * t); P->e = sqrt(P->es); Q->apa = pj_authalic_lat_compute_coeffs(P->n); if (!(Q->apa)) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->qp = pj_authalic_lat_q(1.0, P); P->inv = cea_e_inverse; P->fwd = cea_e_forward; } else { P->inv = cea_s_inverse; P->fwd = cea_s_forward; } return P; } #undef EPS proj-9.8.1/src/projections/krovak.cpp000664 001750 001750 00000027276 15166171715 017620 0ustar00eveneven000000 000000 /* * Project: PROJ * Purpose: Implementation of the krovak (Krovak) projection. * Definition: http://www.ihsenergy.com/epsg/guid7.html#1.4.3 * Author: Thomas Flemming, tf@ttqv.com * ****************************************************************************** * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ****************************************************************************** * A description of the (forward) projection is found in: * * Bohuslav Veverka, * * KROVAK’S PROJECTION AND ITS USE FOR THE * CZECH REPUBLIC AND THE SLOVAK REPUBLIC, * * 50 years of the Research Institute of * and the Slovak Republic Geodesy, Topography and Cartography * * which can be found via the Wayback Machine: * * https://web.archive.org/web/20150216143806/https://www.vugtk.cz/odis/sborniky/sb2005/Sbornik_50_let_VUGTK/Part_1-Scientific_Contribution/16-Veverka.pdf * * Further info, including the inverse projection, is given by EPSG: * * Guidance Note 7 part 2 * Coordinate Conversions and Transformations including Formulas * * http://www.iogp.org/pubs/373-07-2.pdf * * Variable names in this file mostly follows what is used in the * paper by Veverka. * * According to EPSG the full Krovak projection method should have * the following parameters. Within PROJ the azimuth, and pseudo * standard parallel are hardcoded in the algorithm and can't be * altered from outside. The others all have defaults to match the * common usage with Krovak projection. * * lat_0 = latitude of centre of the projection * * lon_0 = longitude of centre of the projection * * ** = azimuth (true) of the centre line passing through the * centre of the projection * * ** = latitude of pseudo standard parallel * * k = scale factor on the pseudo standard parallel * * x_0 = False Easting of the centre of the projection at the * apex of the cone * * y_0 = False Northing of the centre of the projection at * the apex of the cone * *****************************************************************************/ #include #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(krovak, "Krovak") "\n\tPCyl, Ell"; PROJ_HEAD(mod_krovak, "Modified Krovak") "\n\tPCyl, Ell"; #define EPS 1e-15 #define UQ 1.04216856380474 /* DU(2, 59, 42, 42.69689) */ #define S0 \ 1.37008346281555 /* Latitude of pseudo standard parallel 78deg 30'00" N */ /* Not sure at all of the appropriate number for MAX_ITER... */ #define MAX_ITER 100 namespace { // anonymous namespace struct pj_krovak_data { double alpha; double k; double n; double rho0; double ad; bool easting_northing; // true, in default mode. false when using +czech bool modified; }; } // anonymous namespace namespace pj_modified_krovak { constexpr double X0 = 1089000.0; constexpr double Y0 = 654000.0; constexpr double C1 = 2.946529277E-02; constexpr double C2 = 2.515965696E-02; constexpr double C3 = 1.193845912E-07; constexpr double C4 = -4.668270147E-07; constexpr double C5 = 9.233980362E-12; constexpr double C6 = 1.523735715E-12; constexpr double C7 = 1.696780024E-18; constexpr double C8 = 4.408314235E-18; constexpr double C9 = -8.331083518E-24; constexpr double C10 = -3.689471323E-24; // Correction terms to be applied to regular Krovak to obtain Modified Krovak. // Note that Xr is a Southing in metres and Yr a Westing in metres, // and output (dX, dY) is a corrective term in (Southing, Westing) in metres // Reference: // https://www.cuzk.cz/Zememerictvi/Geodeticke-zaklady-na-uzemi-CR/GNSS/Nova-realizace-systemu-ETRS89-v-CR/Metodika-prevodu-ETRF2000-vs-S-JTSK-var2(101208).aspx static void mod_krovak_compute_dx_dy(const double Xr, const double Yr, double &dX, double &dY) { const double Xr2 = Xr * Xr; const double Yr2 = Yr * Yr; const double Xr4 = Xr2 * Xr2; const double Yr4 = Yr2 * Yr2; dX = C1 + C3 * Xr - C4 * Yr - 2 * C6 * Xr * Yr + C5 * (Xr2 - Yr2) + C7 * Xr * (Xr2 - 3 * Yr2) - C8 * Yr * (3 * Xr2 - Yr2) + 4 * C9 * Xr * Yr * (Xr2 - Yr2) + C10 * (Xr4 + Yr4 - 6 * Xr2 * Yr2); dY = C2 + C3 * Yr + C4 * Xr + 2 * C5 * Xr * Yr + C6 * (Xr2 - Yr2) + C8 * Xr * (Xr2 - 3 * Yr2) + C7 * Yr * (3 * Xr2 - Yr2) - 4 * C10 * Xr * Yr * (Xr2 - Yr2) + C9 * (Xr4 + Yr4 - 6 * Xr2 * Yr2); } } // namespace pj_modified_krovak static PJ_XY krovak_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ struct pj_krovak_data *Q = static_cast(P->opaque); PJ_XY xy = {0.0, 0.0}; const double gfi = pow((1. + P->e * sin(lp.phi)) / (1. - P->e * sin(lp.phi)), Q->alpha * P->e / 2.); const double u = 2. * (atan(Q->k * pow(tan(lp.phi / 2. + M_PI_4), Q->alpha) / gfi) - M_PI_4); const double deltav = -lp.lam * Q->alpha; const double s = asin(cos(Q->ad) * sin(u) + sin(Q->ad) * cos(u) * cos(deltav)); const double cos_s = cos(s); if (cos_s < 1e-12) { xy.x = 0; xy.y = 0; return xy; } const double d = asin(cos(u) * sin(deltav) / cos_s); const double eps = Q->n * d; const double rho = Q->rho0 * pow(tan(S0 / 2. + M_PI_4), Q->n) / pow(tan(s / 2. + M_PI_4), Q->n); xy.x = rho * cos(eps); xy.y = rho * sin(eps); // At this point, xy.x is a southing and xy.y is a westing if (Q->modified) { using namespace pj_modified_krovak; const double Xp = xy.x; const double Yp = xy.y; // Reduced X and Y const double Xr = Xp * P->a - X0; const double Yr = Yp * P->a - Y0; double dX, dY; mod_krovak_compute_dx_dy(Xr, Yr, dX, dY); xy.x = Xp - dX / P->a; xy.y = Yp - dY / P->a; } // PROJ always return values in (easting, northing) (default mode) // or (westing, southing) (+czech mode), so swap X/Y std::swap(xy.x, xy.y); if (Q->easting_northing) { // The default non-Czech convention uses easting, northing, so we have // to reverse the sign of the coordinates. But to do so, we have to take // into account the false easting/northing. xy.x = -xy.x - 2 * P->x0 / P->a; xy.y = -xy.y - 2 * P->y0 / P->a; } return xy; } static PJ_LP krovak_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ struct pj_krovak_data *Q = static_cast(P->opaque); PJ_LP lp = {0.0, 0.0}; if (Q->easting_northing) { // The default non-Czech convention uses easting, northing, so we have // to reverse the sign of the coordinates. But to do so, we have to take // into account the false easting/northing. xy.y = -xy.y - 2 * P->x0 / P->a; xy.x = -xy.x - 2 * P->y0 / P->a; } std::swap(xy.x, xy.y); if (Q->modified) { using namespace pj_modified_krovak; // Note: in EPSG guidance node 7-2, below Xr/Yr/dX/dY are actually // Xr'/Yr'/dX'/dY' const double Xr = xy.x * P->a - X0; const double Yr = xy.y * P->a - Y0; double dX, dY; mod_krovak_compute_dx_dy(Xr, Yr, dX, dY); xy.x = xy.x + dX / P->a; xy.y = xy.y + dY / P->a; } const double rho = sqrt(xy.x * xy.x + xy.y * xy.y); const double eps = atan2(xy.y, xy.x); const double d = eps / sin(S0); double s; if (rho == 0.0) { s = M_PI_2; } else { s = 2. * (atan(pow(Q->rho0 / rho, 1. / Q->n) * tan(S0 / 2. + M_PI_4)) - M_PI_4); } const double u = asin(cos(Q->ad) * sin(s) - sin(Q->ad) * cos(s) * cos(d)); const double deltav = asin(cos(s) * sin(d) / cos(u)); lp.lam = P->lam0 - deltav / Q->alpha; /* ITERATION FOR lp.phi */ double fi1 = u; int i; for (i = MAX_ITER; i; --i) { lp.phi = 2. * (atan(pow(Q->k, -1. / Q->alpha) * pow(tan(u / 2. + M_PI_4), 1. / Q->alpha) * pow((1. + P->e * sin(fi1)) / (1. - P->e * sin(fi1)), P->e / 2.)) - M_PI_4); if (fabs(fi1 - lp.phi) < EPS) break; fi1 = lp.phi; } if (i == 0) proj_context_errno_set( P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); lp.lam -= P->lam0; return lp; } static PJ *krovak_setup(PJ *P, bool modified) { double u0, n0, g; struct pj_krovak_data *Q = static_cast( calloc(1, sizeof(struct pj_krovak_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; /* we want Bessel as fixed ellipsoid */ P->a = 6377397.155; P->es = 0.006674372230614; P->e = sqrt(P->es); /* if latitude of projection center is not set, use 49d30'N */ if (!pj_param(P->ctx, P->params, "tlat_0").i) P->phi0 = 0.863937979737193; /* if center long is not set use 42d30'E of Ferro - 17d40' for Ferro */ /* that will correspond to using longitudes relative to greenwich */ /* as input and output, instead of lat/long relative to Ferro */ if (!pj_param(P->ctx, P->params, "tlon_0").i) P->lam0 = 0.7417649320975901 - 0.308341501185665; /* if scale not set default to 0.9999 */ if (!pj_param(P->ctx, P->params, "tk").i && !pj_param(P->ctx, P->params, "tk_0").i) P->k0 = 0.9999; Q->modified = modified; Q->easting_northing = true; if (pj_param(P->ctx, P->params, "tczech").i) Q->easting_northing = false; /* Set up shared parameters between forward and inverse */ Q->alpha = sqrt(1. + (P->es * pow(cos(P->phi0), 4)) / (1. - P->es)); u0 = asin(sin(P->phi0) / Q->alpha); g = pow((1. + P->e * sin(P->phi0)) / (1. - P->e * sin(P->phi0)), Q->alpha * P->e / 2.); double tan_half_phi0_plus_pi_4 = tan(P->phi0 / 2. + M_PI_4); if (tan_half_phi0_plus_pi_4 == 0.0) { proj_log_error(P, _("Invalid value for lat_0: lat_0 + PI/4 should be " "different from 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->k = tan(u0 / 2. + M_PI_4) / pow(tan_half_phi0_plus_pi_4, Q->alpha) * g; n0 = sqrt(1. - P->es) / (1. - P->es * pow(sin(P->phi0), 2)); Q->n = sin(S0); Q->rho0 = P->k0 * n0 / tan(S0); Q->ad = M_PI_2 - UQ; P->inv = krovak_e_inverse; P->fwd = krovak_e_forward; return P; } PJ *PJ_PROJECTION(krovak) { return krovak_setup(P, false); } PJ *PJ_PROJECTION(mod_krovak) { return krovak_setup(P, true); } #undef EPS #undef UQ #undef S0 #undef MAX_ITER proj-9.8.1/src/projections/nell_h.cpp000664 001750 001750 00000002074 15166171715 017551 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(nell_h, "Nell-Hammer") "\n\tPCyl, Sph"; #define NITER 9 #define EPS 1e-7 static PJ_XY nell_h_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; (void)P; xy.x = 0.5 * lp.lam * (1. + cos(lp.phi)); xy.y = 2.0 * (lp.phi - tan(0.5 * lp.phi)); return xy; } static PJ_LP nell_h_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; int i; (void)P; const double p = 0.5 * xy.y; for (i = NITER; i; --i) { const double c = cos(0.5 * lp.phi); const double V = (lp.phi - tan(lp.phi / 2) - p) / (1. - 0.5 / (c * c)); lp.phi -= V; if (fabs(V) < EPS) break; } if (!i) { lp.phi = p < 0. ? -M_HALFPI : M_HALFPI; lp.lam = 2. * xy.x; } else lp.lam = 2. * xy.x / (1. + cos(lp.phi)); return lp; } PJ *PJ_PROJECTION(nell_h) { P->es = 0.; P->inv = nell_h_s_inverse; P->fwd = nell_h_s_forward; return P; } #undef NITER #undef EPS proj-9.8.1/src/projections/rouss.cpp000664 001750 001750 00000013237 15166171715 017466 0ustar00eveneven000000 000000 /* ** libproj -- library of cartographic projections ** ** Copyright (c) 2003, 2006 Gerald I. Evenden */ /* ** 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 #include #include "proj.h" #include "proj_internal.h" namespace { // anonymous namespace struct pj_rouss_data { double s0; double A1, A2, A3, A4, A5, A6; double B1, B2, B3, B4, B5, B6, B7, B8; double C1, C2, C3, C4, C5, C6, C7, C8; double D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11; void *en; }; } // anonymous namespace PROJ_HEAD(rouss, "Roussilhe Stereographic") "\n\tAzi, Ell"; static PJ_XY rouss_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_rouss_data *Q = static_cast(P->opaque); double s, al, cp, sp, al2, s2; cp = cos(lp.phi); sp = sin(lp.phi); s = proj_mdist(lp.phi, sp, cp, Q->en) - Q->s0; s2 = s * s; al = lp.lam * cp / sqrt(1. - P->es * sp * sp); al2 = al * al; xy.x = P->k0 * al * (1. + s2 * (Q->A1 + s2 * Q->A4) - al2 * (Q->A2 + s * Q->A3 + s2 * Q->A5 + al2 * Q->A6)); xy.y = P->k0 * (al2 * (Q->B1 + al2 * Q->B4) + s * (1. + al2 * (Q->B3 - al2 * Q->B6) + s2 * (Q->B2 + s2 * Q->B8) + s * al2 * (Q->B5 + s * Q->B7))); return xy; } static PJ_LP rouss_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_rouss_data *Q = static_cast(P->opaque); double s, al, x = xy.x / P->k0, y = xy.y / P->k0, x2, y2; x2 = x * x; y2 = y * y; al = x * (1. - Q->C1 * y2 + x2 * (Q->C2 + Q->C3 * y - Q->C4 * x2 + Q->C5 * y2 - Q->C7 * x2 * y) + y2 * (Q->C6 * y2 - Q->C8 * x2 * y)); s = Q->s0 + y * (1. + y2 * (-Q->D2 + Q->D8 * y2)) + x2 * (-Q->D1 + y * (-Q->D3 + y * (-Q->D5 + y * (-Q->D7 + y * Q->D11))) + x2 * (Q->D4 + y * (Q->D6 + y * Q->D10) - x2 * Q->D9)); lp.phi = proj_inv_mdist(P->ctx, s, Q->en); s = sin(lp.phi); lp.lam = al * sqrt(1. - P->es * s * s) / cos(lp.phi); return lp; } static PJ *pj_rouss_destructor(PJ *P, int errlev) { if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); if (static_cast(P->opaque)->en) free(static_cast(P->opaque)->en); return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); } PJ *PJ_PROJECTION(rouss) { double N0, es2, t, t2, R_R0_2, R_R0_4; struct pj_rouss_data *Q = static_cast( calloc(1, sizeof(struct pj_rouss_data))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (!((Q->en = proj_mdist_ini(P->es)))) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); es2 = sin(P->phi0); Q->s0 = proj_mdist(P->phi0, es2, cos(P->phi0), Q->en); t = 1. - (es2 = P->es * es2 * es2); N0 = 1. / sqrt(t); R_R0_2 = t * t / P->one_es; R_R0_4 = R_R0_2 * R_R0_2; t = tan(P->phi0); t2 = t * t; Q->C1 = Q->A1 = R_R0_2 / 4.; Q->C2 = Q->A2 = R_R0_2 * (2 * t2 - 1. - 2. * es2) / 12.; Q->A3 = R_R0_2 * t * (1. + 4. * t2) / (12. * N0); Q->A4 = R_R0_4 / 24.; Q->A5 = R_R0_4 * (-1. + t2 * (11. + 12. * t2)) / 24.; Q->A6 = R_R0_4 * (-2. + t2 * (11. - 2. * t2)) / 240.; Q->B1 = t / (2. * N0); Q->B2 = R_R0_2 / 12.; Q->B3 = R_R0_2 * (1. + 2. * t2 - 2. * es2) / 4.; Q->B4 = R_R0_2 * t * (2. - t2) / (24. * N0); Q->B5 = R_R0_2 * t * (5. + 4. * t2) / (8. * N0); Q->B6 = R_R0_4 * (-2. + t2 * (-5. + 6. * t2)) / 48.; Q->B7 = R_R0_4 * (5. + t2 * (19. + 12. * t2)) / 24.; Q->B8 = R_R0_4 / 120.; Q->C3 = R_R0_2 * t * (1. + t2) / (3. * N0); Q->C4 = R_R0_4 * (-3. + t2 * (34. + 22. * t2)) / 240.; Q->C5 = R_R0_4 * (4. + t2 * (13. + 12. * t2)) / 24.; Q->C6 = R_R0_4 / 16.; Q->C7 = R_R0_4 * t * (11. + t2 * (33. + t2 * 16.)) / (48. * N0); Q->C8 = R_R0_4 * t * (1. + t2 * 4.) / (36. * N0); Q->D1 = t / (2. * N0); Q->D2 = R_R0_2 / 12.; Q->D3 = R_R0_2 * (2 * t2 + 1. - 2. * es2) / 4.; Q->D4 = R_R0_2 * t * (1. + t2) / (8. * N0); Q->D5 = R_R0_2 * t * (1. + t2 * 2.) / (4. * N0); Q->D6 = R_R0_4 * (1. + t2 * (6. + t2 * 6.)) / 16.; Q->D7 = R_R0_4 * t2 * (3. + t2 * 4.) / 8.; Q->D8 = R_R0_4 / 80.; Q->D9 = R_R0_4 * t * (-21. + t2 * (178. - t2 * 26.)) / 720.; Q->D10 = R_R0_4 * t * (29. + t2 * (86. + t2 * 48.)) / (96. * N0); Q->D11 = R_R0_4 * t * (37. + t2 * 44.) / (96. * N0); P->fwd = rouss_e_forward; P->inv = rouss_e_inverse; P->destructor = pj_rouss_destructor; return P; } proj-9.8.1/src/projections/cass.cpp000664 001750 001750 00000010070 15166171715 017234 0ustar00eveneven000000 000000 #include #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(cass, "Cassini") "\n\tCyl, Sph&Ell"; #define C1 .16666666666666666666 #define C2 .00833333333333333333 #define C3 .04166666666666666666 #define C4 .33333333333333333333 #define C5 .06666666666666666666 namespace { // anonymous namespace struct cass_data { double *en; double m0; bool hyperbolic; }; } // anonymous namespace static PJ_XY cass_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ PJ_XY xy = {0.0, 0.0}; struct cass_data *Q = static_cast(P->opaque); const double sinphi = sin(lp.phi); const double cosphi = cos(lp.phi); const double M = pj_mlfn(lp.phi, sinphi, cosphi, Q->en); const double nu_square = 1. / (1. - P->es * sinphi * sinphi); const double nu = sqrt(nu_square); const double tanphi = tan(lp.phi); const double T = tanphi * tanphi; const double A = lp.lam * cosphi; const double C = P->es * (cosphi * cosphi) / (1 - P->es); const double A2 = A * A; xy.x = nu * A * (1. - A2 * T * (C1 + (8. - T + 8. * C) * A2 * C2)); xy.y = M - Q->m0 + nu * tanphi * A2 * (.5 + (5. - T + 6. * C) * A2 * C3); if (Q->hyperbolic) { const double rho = nu_square * (1. - P->es) * nu; xy.y -= xy.y * xy.y * xy.y / (6 * rho * nu); } return xy; } static PJ_XY cass_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; xy.x = asin(cos(lp.phi) * sin(lp.lam)); xy.y = atan2(tan(lp.phi), cos(lp.lam)) - P->phi0; return xy; } static PJ_LP cass_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct cass_data *Q = static_cast(P->opaque); const double phi1 = pj_inv_mlfn(Q->m0 + xy.y, Q->en); const double tanphi1 = tan(phi1); const double T1 = tanphi1 * tanphi1; const double sinphi1 = sin(phi1); const double nu1_square = 1. / (1. - P->es * sinphi1 * sinphi1); const double nu1 = sqrt(nu1_square); const double rho1 = nu1_square * (1. - P->es) * nu1; const double D = xy.x / nu1; const double D2 = D * D; lp.phi = phi1 - (nu1 * tanphi1 / rho1) * D2 * (.5 - (1. + 3. * T1) * D2 * C3); lp.lam = D * (1. + T1 * D2 * (-C4 + (1. + 3. * T1) * D2 * C5)) / cos(phi1); // EPSG guidance note 7-2 suggests a custom approximation for the // 'Vanua Levu 1915 / Vanua Levu Grid' case, but better use the // generic inversion method // Actually use it in the non-hyperbolic case. It enables to make the // 5108.gie roundtripping tests to success, with at most 2 iterations. constexpr double deltaXYTolerance = 1e-12; lp = pj_generic_inverse_2d(xy, P, lp, deltaXYTolerance); return lp; } static PJ_LP cass_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; double dd; lp.phi = asin(sin(dd = xy.y + P->phi0) * cos(xy.x)); lp.lam = atan2(tan(xy.x), cos(dd)); return lp; } static PJ *pj_cass_destructor(PJ *P, int errlev) { /* Destructor */ if (nullptr == P) return nullptr; if (nullptr == P->opaque) return pj_default_destructor(P, errlev); free(static_cast(P->opaque)->en); return pj_default_destructor(P, errlev); } PJ *PJ_PROJECTION(cass) { /* Spheroidal? */ if (0 == P->es) { P->inv = cass_s_inverse; P->fwd = cass_s_forward; return P; } /* otherwise it's ellipsoidal */ auto Q = static_cast(calloc(1, sizeof(struct cass_data))); P->opaque = Q; if (nullptr == P->opaque) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->destructor = pj_cass_destructor; Q->en = pj_enfn(P->n); if (nullptr == Q->en) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); Q->m0 = pj_mlfn(P->phi0, sin(P->phi0), cos(P->phi0), Q->en); if (pj_param_exists(P->params, "hyperbolic")) Q->hyperbolic = true; P->inv = cass_e_inverse; P->fwd = cass_e_forward; return P; } #undef C1 #undef C2 #undef C3 #undef C4 #undef C5 proj-9.8.1/src/projections/oea.cpp000664 001750 001750 00000005441 15166171715 017055 0ustar00eveneven000000 000000 #include "proj.h" #include "proj_internal.h" #include #include PROJ_HEAD(oea, "Oblated Equal Area") "\n\tMisc Sph\n\tn= m= theta="; namespace { // anonymous namespace struct pj_oea { double theta; double m, n; double two_r_m, two_r_n, rm, rn, hm, hn; double cp0, sp0; }; } // anonymous namespace static PJ_XY oea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; struct pj_oea *Q = static_cast(P->opaque); const double cp = cos(lp.phi); const double sp = sin(lp.phi); const double cl = cos(lp.lam); const double Az = aatan2(cp * sin(lp.lam), Q->cp0 * sp - Q->sp0 * cp * cl) + Q->theta; const double shz = sin(0.5 * aacos(P->ctx, Q->sp0 * sp + Q->cp0 * cp * cl)); const double M = aasin(P->ctx, shz * sin(Az)); const double N = aasin(P->ctx, shz * cos(Az) * cos(M) / cos(M * Q->two_r_m)); xy.y = Q->n * sin(N * Q->two_r_n); xy.x = Q->m * sin(M * Q->two_r_m) * cos(N) / cos(N * Q->two_r_n); return xy; } static PJ_LP oea_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ PJ_LP lp = {0.0, 0.0}; struct pj_oea *Q = static_cast(P->opaque); const double N = Q->hn * aasin(P->ctx, xy.y * Q->rn); const double M = Q->hm * aasin(P->ctx, xy.x * Q->rm * cos(N * Q->two_r_n) / cos(N)); const double xp = 2. * sin(M); const double yp = 2. * sin(N) * cos(M * Q->two_r_m) / cos(M); const double Az = aatan2(xp, yp) - Q->theta; const double cAz = cos(Az); const double z = 2. * aasin(P->ctx, 0.5 * hypot(xp, yp)); const double sz = sin(z); const double cz = cos(z); lp.phi = aasin(P->ctx, Q->sp0 * cz + Q->cp0 * sz * cAz); lp.lam = aatan2(sz * sin(Az), Q->cp0 * cz - Q->sp0 * sz * cAz); return lp; } PJ *PJ_PROJECTION(oea) { struct pj_oea *Q = static_cast(calloc(1, sizeof(struct pj_oea))); if (nullptr == Q) return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); P->opaque = Q; if (((Q->n = pj_param(P->ctx, P->params, "dn").f) <= 0.)) { proj_log_error(P, _("Invalid value for n: it should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } if (((Q->m = pj_param(P->ctx, P->params, "dm").f) <= 0.)) { proj_log_error(P, _("Invalid value for m: it should be > 0")); return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); } Q->theta = pj_param(P->ctx, P->params, "rtheta").f; Q->sp0 = sin(P->phi0); Q->cp0 = cos(P->phi0); Q->rn = 1. / Q->n; Q->rm = 1. / Q->m; Q->two_r_n = 2. * Q->rn; Q->two_r_m = 2. * Q->rm; Q->hm = 0.5 * Q->m; Q->hn = 0.5 * Q->n; P->fwd = oea_s_forward; P->inv = oea_s_inverse; P->es = 0.; return P; } proj-9.8.1/src/projections/august.cpp000664 001750 001750 00000001322 15166171715 017613 0ustar00eveneven000000 000000 #include #include "proj.h" #include "proj_internal.h" PROJ_HEAD(august, "August Epicycloidal") "\n\tMisc Sph, no inv"; #define M 1.333333333333333 static PJ_XY august_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ PJ_XY xy = {0.0, 0.0}; double t, c1, c, x1, x12, y1, y12; (void)P; t = tan(.5 * lp.phi); c1 = sqrt(1. - t * t); lp.lam *= .5; c = 1. + c1 * cos(lp.lam); x1 = sin(lp.lam) * c1 / c; y1 = t / c; x12 = x1 * x1; y12 = y1 * y1; xy.x = M * x1 * (3. + x12 - 3. * y12); xy.y = M * y1 * (3. + 3. * x12 - y12); return (xy); } PJ *PJ_PROJECTION(august) { P->inv = nullptr; P->fwd = august_s_forward; P->es = 0.; return P; } proj-9.8.1/src/proj_mdist.cpp000664 001750 001750 00000007361 15166171715 016127 0ustar00eveneven000000 000000 /* ** libproj -- library of cartographic projections ** ** Copyright (c) 2003, 2006 Gerald I. Evenden */ /* ** 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. */ /* Computes distance from equator along the meridian to latitude phi ** and inverse on unit ellipsoid. ** Precision commensurate with double precision. */ #include #include #include "proj.h" #include "proj_internal.h" #define MAX_ITER 20 #define TOL 1e-14 namespace { // anonymous namespace struct MDIST { int nb; double es; double E; double b[1]; }; } // anonymous namespace void *proj_mdist_ini(double es) { double numf, numfi, twon1, denf, denfi, ens, T, twon; double den, El = 1., Es = 1.; double E[MAX_ITER] = {1.}; struct MDIST *b; int i, j; /* generate E(e^2) and its terms E[] */ ens = es; numf = twon1 = denfi = 1.; denf = 1.; twon = 4.; for (i = 1; i < MAX_ITER; ++i) { numf *= (twon1 * twon1); den = twon * denf * denf * twon1; T = numf / den; Es -= (E[i] = T * ens); ens *= es; twon *= 4.; denf *= ++denfi; twon1 += 2.; if (Es == El) /* jump out if no change */ break; El = Es; } if ((b = (struct MDIST *)malloc(sizeof(struct MDIST) + (i * sizeof(double)))) == nullptr) return (nullptr); b->nb = i - 1; b->es = es; b->E = Es; /* generate b_n coefficients--note: collapse with prefix ratios */ b->b[0] = Es = 1. - Es; numf = denf = 1.; numfi = 2.; denfi = 3.; for (j = 1; j < i; ++j) { Es -= E[j]; numf *= numfi; denf *= denfi; b->b[j] = Es * numf / denf; numfi += 2.; denfi += 2.; } return (b); } double proj_mdist(double phi, double sphi, double cphi, const void *data) { const struct MDIST *b = (const struct MDIST *)data; double sc, sum, sphi2, D; int i; sc = sphi * cphi; sphi2 = sphi * sphi; D = phi * b->E - b->es * sc / sqrt(1. - b->es * sphi2); sum = b->b[i = b->nb]; while (i) sum = b->b[--i] + sphi2 * sum; return (D + sc * sum); } double proj_inv_mdist(PJ_CONTEXT *ctx, double dist, const void *data) { const struct MDIST *b = (const struct MDIST *)data; double s, t, phi, k; int i; k = 1. / (1. - b->es); i = MAX_ITER; phi = dist; while (i--) { s = sin(phi); t = 1. - b->es * s * s; phi -= t = (proj_mdist(phi, s, cos(phi), b) - dist) * (t * sqrt(t)) * k; if (fabs(t) < TOL) /* that is no change */ return phi; } /* convergence failed */ proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return phi; } proj-9.8.1/src/proj_json_streaming_writer.cpp000664 001750 001750 00000017372 15166171715 021430 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: CPL - Common Portability Library * Purpose: JSon streaming writer * Author: Even Rouault, even.rouault at spatialys.com * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * 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. ****************************************************************************/ /*! @cond Doxygen_Suppress */ #include #include #include #include "proj_json_streaming_writer.hpp" #include #include #include #include #define CPLAssert(x) \ do { \ } while (0) #define CPLIsNan std::isnan #define CPLIsInf std::isinf #define CPL_FRMT_GIB "%lld" #define CPL_FRMT_GUIB "%llu" typedef std::uint64_t GUIntBig; static std::string CPLSPrintf(const char *fmt, ...) { std::string res; res.resize(256); va_list list; va_start(list, fmt); sqlite3_vsnprintf(256, &res[0], fmt, list); va_end(list); res.resize(strlen(&res[0])); return res; } NS_PROJ_START CPLJSonStreamingWriter::CPLJSonStreamingWriter( SerializationFuncType pfnSerializationFunc, void *pUserData) : m_pfnSerializationFunc(pfnSerializationFunc), m_pUserData(pUserData) {} CPLJSonStreamingWriter::~CPLJSonStreamingWriter() { CPLAssert(m_nLevel == 0); CPLAssert(m_states.empty()); } void CPLJSonStreamingWriter::Print(const std::string &text) { if (m_pfnSerializationFunc) { m_pfnSerializationFunc(text.c_str(), m_pUserData); } else { m_osStr += text; } } void CPLJSonStreamingWriter::SetIndentationSize(int nSpaces) { CPLAssert(m_nLevel == 0); m_osIndent.clear(); m_osIndent.resize(nSpaces, ' '); } void CPLJSonStreamingWriter::IncIndent() { m_nLevel++; if (m_bPretty) m_osIndentAcc += m_osIndent; } void CPLJSonStreamingWriter::DecIndent() { CPLAssert(m_nLevel > 0); m_nLevel--; if (m_bPretty) m_osIndentAcc.resize(m_osIndentAcc.size() - m_osIndent.size()); } std::string CPLJSonStreamingWriter::FormatString(const std::string &str) { std::string ret; ret += '"'; for (char ch : str) { switch (ch) { case '"': ret += "\\\""; break; case '\\': ret += "\\\\"; break; case '\b': ret += "\\b"; break; case '\f': ret += "\\f"; break; case '\n': ret += "\\n"; break; case '\r': ret += "\\r"; break; case '\t': ret += "\\t"; break; default: if (static_cast(ch) < ' ') ret += CPLSPrintf("\\u%04X", ch); else ret += ch; break; } } ret += '"'; return ret; } void CPLJSonStreamingWriter::EmitCommaIfNeeded() { if (m_bWaitForValue) { m_bWaitForValue = false; } else if (!m_states.empty()) { if (!m_states.back().bFirstChild) { Print(","); if (m_bPretty && !m_bNewLineEnabled) Print(" "); } if (m_bPretty && m_bNewLineEnabled) { Print("\n"); Print(m_osIndentAcc); } m_states.back().bFirstChild = false; } } void CPLJSonStreamingWriter::StartObj() { EmitCommaIfNeeded(); Print("{"); IncIndent(); m_states.emplace_back(State(true)); } void CPLJSonStreamingWriter::EndObj() { CPLAssert(!m_bWaitForValue); CPLAssert(!m_states.empty()); CPLAssert(m_states.back().bIsObj); DecIndent(); if (!m_states.back().bFirstChild) { if (m_bPretty && m_bNewLineEnabled) { Print("\n"); Print(m_osIndentAcc); } } m_states.pop_back(); Print("}"); } void CPLJSonStreamingWriter::StartArray() { EmitCommaIfNeeded(); Print("["); IncIndent(); m_states.emplace_back(State(false)); } void CPLJSonStreamingWriter::EndArray() { CPLAssert(!m_states.empty()); CPLAssert(!m_states.back().bIsObj); DecIndent(); if (!m_states.back().bFirstChild) { if (m_bPretty && m_bNewLineEnabled) { Print("\n"); Print(m_osIndentAcc); } } m_states.pop_back(); Print("]"); } void CPLJSonStreamingWriter::AddObjKey(const std::string &key) { CPLAssert(!m_states.empty()); CPLAssert(m_states.back().bIsObj); CPLAssert(!m_bWaitForValue); EmitCommaIfNeeded(); Print(FormatString(key)); Print(m_bPretty ? ": " : ":"); m_bWaitForValue = true; } void CPLJSonStreamingWriter::Add(bool bVal) { EmitCommaIfNeeded(); Print(bVal ? "true" : "false"); } void CPLJSonStreamingWriter::Add(const std::string &str) { EmitCommaIfNeeded(); Print(FormatString(str)); } void CPLJSonStreamingWriter::Add(const char *pszStr) { EmitCommaIfNeeded(); Print(FormatString(pszStr)); } void CPLJSonStreamingWriter::AddUnquoted(const char *pszStr) { EmitCommaIfNeeded(); Print(pszStr); } void CPLJSonStreamingWriter::Add(GIntBig nVal) { EmitCommaIfNeeded(); Print(CPLSPrintf(CPL_FRMT_GIB, nVal)); } void CPLJSonStreamingWriter::Add(GUInt64 nVal) { EmitCommaIfNeeded(); Print(CPLSPrintf(CPL_FRMT_GUIB, static_cast(nVal))); } void CPLJSonStreamingWriter::Add(float fVal, int nPrecision) { EmitCommaIfNeeded(); if (CPLIsNan(fVal)) { Print("\"NaN\""); } else if (CPLIsInf(fVal)) { Print(fVal > 0 ? "\"Infinity\"" : "\"-Infinity\""); } else { char szFormatting[10]; snprintf(szFormatting, sizeof(szFormatting), "%%.%dg", nPrecision); Print(CPLSPrintf(szFormatting, fVal)); } } void CPLJSonStreamingWriter::Add(double dfVal, int nPrecision) { EmitCommaIfNeeded(); if (CPLIsNan(dfVal)) { Print("\"NaN\""); } else if (CPLIsInf(dfVal)) { Print(dfVal > 0 ? "\"Infinity\"" : "\"-Infinity\""); } else if (dfVal >= std::numeric_limits::min() && dfVal <= std::numeric_limits::max() && static_cast(dfVal) == dfVal) { // Avoid rounding issues on some platforms like armel, with numbers // like 2005. See https://github.com/OSGeo/PROJ/issues/3297 Print(CPLSPrintf("%d", static_cast(dfVal))); } else { char szFormatting[10]; snprintf(szFormatting, sizeof(szFormatting), "%%.%dg", nPrecision); Print(CPLSPrintf(szFormatting, dfVal)); } } void CPLJSonStreamingWriter::AddNull() { EmitCommaIfNeeded(); Print("null"); } NS_PROJ_END /*! @endcond */ proj-9.8.1/src/apps/000775 001750 001750 00000000000 15166171735 014207 5ustar00eveneven000000 000000 proj-9.8.1/src/apps/utils.h000664 001750 001750 00000003241 15166171715 015516 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: Utilities for command line arguments * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2019, Even Rouault * * 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 bool validate_form_string_for_numbers(const char *formatString); void limited_fprintf_for_number(FILE *f, const char *formatString, double val); proj-9.8.1/src/apps/geod_interface.h000664 001750 001750 00000001613 15166171715 017315 0ustar00eveneven000000 000000 #if !defined(GEOD_INTERFACE_H) #define GEOD_INTERFACE_H #include "geodesic.h" #ifdef __cplusplus extern "C" { #endif #ifndef GEOD_IN_GEOD_SET #define GEOD_EXTERN extern #else #define GEOD_EXTERN #endif GEOD_EXTERN struct geodesic { double A, FLAT, LAM1, PHI1, ALPHA12, LAM2, PHI2, ALPHA21, DIST; } GEODESIC; #define geod_a GEODESIC.A #define geod_f GEODESIC.FLAT #define lam1 GEODESIC.LAM1 #define phi1 GEODESIC.PHI1 #define al12 GEODESIC.ALPHA12 #define lam2 GEODESIC.LAM2 #define phi2 GEODESIC.PHI2 #define al21 GEODESIC.ALPHA21 #define geod_S GEODESIC.DIST GEOD_EXTERN struct geod_geodesic GlobalGeodesic; GEOD_EXTERN struct geod_geodesicline GlobalGeodesicLine; GEOD_EXTERN int n_alpha, n_S; GEOD_EXTERN double to_meter, fr_meter, del_alpha; void geod_set(int, char **); void geod_ini(void); void geod_pre(void); void geod_for(void); void geod_inv(void); #ifdef __cplusplus } #endif #endif proj-9.8.1/src/apps/geod_set.cpp000664 001750 001750 00000005515 15166171715 016510 0ustar00eveneven000000 000000 #define GEOD_IN_GEOD_SET #include #include #include #include "emess.h" #include "geod_interface.h" #include "proj.h" #include "proj_internal.h" void geod_set(int argc, char **argv) { paralist *start = nullptr, *curr; double es; char *name; /* put arguments into internal linked list */ if (argc <= 0) emess(1, "no arguments in initialization list"); start = curr = pj_mkparam(argv[0]); if (!curr) emess(1, "memory allocation failed"); for (int i = 1; curr != nullptr && i < argc; ++i) { curr->next = pj_mkparam(argv[i]); if (!curr->next) emess(1, "memory allocation failed"); curr = curr->next; } /* set elliptical parameters */ if (pj_ell_set(pj_get_default_ctx(), start, &geod_a, &es)) emess(1, "ellipsoid setup failure"); /* set units */ if ((name = pj_param(nullptr, start, "sunits").s) != nullptr) { bool unit_found = false; auto units = proj_get_units_from_database(nullptr, nullptr, "linear", false, nullptr); for (int i = 0; units && units[i]; i++) { if (units[i]->proj_short_name && strcmp(units[i]->proj_short_name, name) == 0) { unit_found = true; to_meter = units[i]->conv_factor; fr_meter = 1 / to_meter; } } proj_unit_list_destroy(units); if (!unit_found) emess(1, "%s unknown unit conversion id", name); } else to_meter = fr_meter = 1; geod_f = es / (1 + sqrt(1 - es)); geod_ini(); /* check if line or arc mode */ if (pj_param(nullptr, start, "tlat_1").i) { double del_S; phi1 = pj_param(nullptr, start, "rlat_1").f; lam1 = pj_param(nullptr, start, "rlon_1").f; if (pj_param(nullptr, start, "tlat_2").i) { phi2 = pj_param(nullptr, start, "rlat_2").f; lam2 = pj_param(nullptr, start, "rlon_2").f; geod_inv(); geod_pre(); } else if ((geod_S = pj_param(nullptr, start, "dS").f) != 0.) { al12 = pj_param(nullptr, start, "rA").f; geod_pre(); geod_for(); } else emess(1, "incomplete geodesic/arc info"); if ((n_alpha = pj_param(nullptr, start, "in_A").i) > 0) { if ((del_alpha = pj_param(nullptr, start, "rdel_A").f) == 0.0) emess(1, "del azimuth == 0"); } else if ((del_S = fabs(pj_param(nullptr, start, "ddel_S").f)) != 0.) { n_S = (int)(geod_S / del_S + .5); } else if ((n_S = pj_param(nullptr, start, "in_S").i) <= 0) emess(1, "no interval divisor selected"); } /* free up linked list */ for (; start; start = curr) { curr = start->next; free(start); } } proj-9.8.1/src/apps/CMakeLists.txt000664 001750 001750 00000004665 15166171715 016760 0ustar00eveneven000000 000000 set(CMAKE_UNITY_BUILD OFF) # Configure executable builds option(BUILD_APPS "Build PROJ applications (default value for BUILD_CCT, BUILD_CS2CS, etc.)" ON) option(BUILD_CCT "Build cct (coordinate conversion and transformation tool)" "${BUILD_APPS}") option(BUILD_CS2CS "Build cs2cs (coordinate systems to coordinate systems translation tool)" "${BUILD_APPS}") option(BUILD_GEOD "Build geod (computation of geodesic lines)" "${BUILD_APPS}") option(BUILD_GIE "Build gie (geospatial integrity investigation environment)" "${BUILD_APPS}") option(BUILD_PROJ "Build proj (cartographic projection tool)" "${BUILD_APPS}") option(BUILD_PROJINFO "Build projinfo (SRS and coordinate operation metadata/query tool)" "${BUILD_APPS}") option(BUILD_PROJSYNC "Build projsync (synchronize transformation support data)" "${BUILD_APPS}") if(NOT MSVC) # Use relative path so that package is relocatable if(APPLE) set(CMAKE_INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") else() set(CMAKE_INSTALL_RPATH "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") endif() # Other apps can link to libproj using e.g. LDFLAGS -Wl,-rpath,${prefix}/lib else() if(NOT WINDOWS_STORE) # Linking to setargv.obj enables wildcard globbing for the # command line utilities, when compiling with MSVC # https://docs.microsoft.com/cpp/c-language/expanding-wildcard-arguments set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} setargv.obj") endif() endif() if(BUILD_CCT) include(bin_cct.cmake) list(APPEND BIN_TARGETS cct) endif() if(BUILD_CS2CS) include(bin_cs2cs.cmake) list(APPEND BIN_TARGETS cs2cs) endif() if(BUILD_GEOD) include(bin_geod.cmake) list(APPEND BIN_TARGETS geod) endif() if(BUILD_PROJ) include(bin_proj.cmake) list(APPEND BIN_TARGETS binproj) endif() if(BUILD_PROJINFO) include(bin_projinfo.cmake) list(APPEND BIN_TARGETS projinfo) endif() # Always build gie if testing is requested if(BUILD_GIE OR BUILD_TESTING) include(bin_gie.cmake) list(APPEND BIN_TARGETS gie) endif() if(BUILD_PROJSYNC) if(NOT ENABLE_CURL) message(SEND_ERROR "projsync requires Curl") endif() include(bin_projsync.cmake) list(APPEND BIN_TARGETS projsync) endif() # global configurations for all apps if(MSVC OR CMAKE_CONFIGURATION_TYPES) if(BIN_TARGETS) # Add _d suffix for debug versions of the apps set_target_properties(${BIN_TARGETS} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) endif() endif() proj-9.8.1/src/apps/bin_proj.cmake000664 001750 001750 00000002155 15166171715 017014 0ustar00eveneven000000 000000 set(PROJ_SRC proj.cpp emess.cpp utils.cpp ) source_group("Source Files\\Bin" FILES ${PROJ_SRC}) add_executable(binproj ${PROJ_SRC}) set_target_properties(binproj PROPERTIES RUNTIME_OUTPUT_NAME proj) target_link_libraries(binproj PRIVATE ${PROJ_LIBRARIES}) install(TARGETS binproj DESTINATION ${CMAKE_INSTALL_BINDIR}) # invproj target: symlink or copy of proj executable if(UNIX) set(link_target "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/invproj${CMAKE_EXECUTABLE_SUFFIX}") set(link_source "proj${CMAKE_EXECUTABLE_SUFFIX}") add_custom_command( OUTPUT ${link_target} COMMAND ${CMAKE_COMMAND} -E create_symlink ${link_source} ${link_target} WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" DEPENDS binproj COMMENT "Generating invproj" VERBATIM ) add_custom_target(invproj ALL DEPENDS ${link_target}) install(FILES ${link_target} DESTINATION ${CMAKE_INSTALL_BINDIR}) else() add_executable(invproj ${PROJ_SRC}) target_link_libraries(invproj PRIVATE ${PROJ_LIBRARIES}) install(TARGETS invproj DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() proj-9.8.1/src/apps/emess.h000664 001750 001750 00000001465 15166171715 015500 0ustar00eveneven000000 000000 /* Error message processing header file */ #ifndef EMESS_H #define EMESS_H struct EMESS { char *File_name, /* input file name */ *Prog_name; /* name of program */ int File_line; /* approximate line read where error occurred */ }; #ifdef EMESS_ROUTINE /* use type */ /* for emess procedure */ struct EMESS emess_dat = {nullptr, nullptr, 0}; #else /* for for calling procedures */ extern struct EMESS emess_dat; #endif /* use type */ #if defined(__GNUC__) #define EMESS_PRINT_FUNC_FORMAT(format_idx, arg_idx) \ __attribute__((__format__(__printf__, format_idx, arg_idx))) #else #define EMESS_PRINT_FUNC_FORMAT(format_idx, arg_idx) #endif void emess(int, const char *, ...) EMESS_PRINT_FUNC_FORMAT(2, 3); #endif /* end EMESS_H */ proj-9.8.1/src/apps/bin_geod.cmake000664 001750 001750 00000002221 15166171715 016752 0ustar00eveneven000000 000000 set(GEOD_SRC geod.cpp geod_set.cpp geod_interface.cpp emess.cpp utils.cpp ) set(GEOD_INCLUDE geod_interface.h) source_group("Source Files\\Bin" FILES ${GEOD_SRC} ${GEOD_INCLUDE}) add_executable(geod ${GEOD_SRC} ${GEOD_INCLUDE}) target_link_libraries(geod PRIVATE ${PROJ_LIBRARIES}) install(TARGETS geod DESTINATION ${CMAKE_INSTALL_BINDIR}) # invgeod target: symlink or copy of geod executable if(UNIX) set(link_target "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/invgeod${CMAKE_EXECUTABLE_SUFFIX}") set(link_source "geod${CMAKE_EXECUTABLE_SUFFIX}") add_custom_command( OUTPUT ${link_target} COMMAND ${CMAKE_COMMAND} -E create_symlink ${link_source} ${link_target} WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" DEPENDS geod COMMENT "Generating invgeod" VERBATIM ) add_custom_target(invgeod ALL DEPENDS ${link_target}) install(FILES ${link_target} DESTINATION ${CMAKE_INSTALL_BINDIR}) else() add_executable(invgeod ${GEOD_SRC} ${GEOD_INCLUDE}) target_link_libraries(invgeod PRIVATE ${PROJ_LIBRARIES}) install(TARGETS invgeod DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() proj-9.8.1/src/apps/geod.cpp000664 001750 001750 00000024164 15166171715 015636 0ustar00eveneven000000 000000 /* <<<< Geodesic filter program >>>> */ #include "emess.h" #include "geod_interface.h" #include "proj.h" #include "proj_internal.h" #include "utils.h" #include #include #include #define MAXLINE 200 #define MAX_PARGS 50 #define TAB putchar('\t') static int fullout = 0, /* output full set of geodesic values */ tag = '#', /* beginning of line tag character */ pos_azi = 0, /* output azimuths as positive values */ inverse = 0; /* != 0 then inverse geodesic */ static const char *oform = nullptr; /* output format for decimal degrees */ static const char *osform = "%.3f"; /* output format for S */ static char pline[50]; /* work string */ static const char *usage = "%s\nusage: %s [-afFIlptwW [args]] [+opt[=arg] ...] [file ...]\n"; static void printLL(double p, double l) { if (oform) { (void)limited_fprintf_for_number(stdout, oform, p * RAD_TO_DEG); TAB; (void)limited_fprintf_for_number(stdout, oform, l * RAD_TO_DEG); } else { (void)fputs(rtodms(pline, sizeof(pline), p, 'N', 'S'), stdout); TAB; (void)fputs(rtodms(pline, sizeof(pline), l, 'E', 'W'), stdout); } } static void do_arc(void) { double az; printLL(phi2, lam2); putchar('\n'); for (az = al12; n_alpha--;) { al12 = az = adjlon(az + del_alpha); geod_pre(); geod_for(); printLL(phi2, lam2); putchar('\n'); } } static void /* generate intermediate geodesic coordinates */ do_geod(void) { double phil, laml, del_S; phil = phi2; laml = lam2; printLL(phi1, lam1); putchar('\n'); for (geod_S = del_S = geod_S / n_S; --n_S; geod_S += del_S) { geod_for(); printLL(phi2, lam2); putchar('\n'); } printLL(phil, laml); putchar('\n'); } static void /* file processing function */ process(FILE *fid) { char line[MAXLINE + 3], *s; for (;;) { ++emess_dat.File_line; if (!(s = fgets(line, MAXLINE, fid))) break; if (!strchr(s, '\n')) { /* overlong line */ int c; strcat(s, "\n"); /* gobble up to newline */ while ((c = fgetc(fid)) != EOF && c != '\n') ; } if (*s == tag) { fputs(line, stdout); continue; } phi1 = dmstor(s, &s); lam1 = dmstor(s, &s); if (inverse) { phi2 = dmstor(s, &s); lam2 = dmstor(s, &s); geod_inv(); } else { al12 = dmstor(s, &s); geod_S = strtod(s, &s) * to_meter; geod_pre(); geod_for(); } if (!*s && (s > line)) --s; /* assumed we gobbled \n */ if (pos_azi) { if (al12 < 0.) al12 += M_TWOPI; if (al21 < 0.) al21 += M_TWOPI; } if (fullout) { printLL(phi1, lam1); TAB; printLL(phi2, lam2); TAB; if (oform) { (void)limited_fprintf_for_number(stdout, oform, al12 * RAD_TO_DEG); TAB; (void)limited_fprintf_for_number(stdout, oform, al21 * RAD_TO_DEG); TAB; (void)limited_fprintf_for_number(stdout, osform, geod_S * fr_meter); } else { (void)fputs(rtodms(pline, sizeof(pline), al12, 0, 0), stdout); TAB; (void)fputs(rtodms(pline, sizeof(pline), al21, 0, 0), stdout); TAB; (void)limited_fprintf_for_number(stdout, osform, geod_S * fr_meter); } } else if (inverse) if (oform) { (void)limited_fprintf_for_number(stdout, oform, al12 * RAD_TO_DEG); TAB; (void)limited_fprintf_for_number(stdout, oform, al21 * RAD_TO_DEG); TAB; (void)limited_fprintf_for_number(stdout, osform, geod_S * fr_meter); } else { (void)fputs(rtodms(pline, sizeof(pline), al12, 0, 0), stdout); TAB; (void)fputs(rtodms(pline, sizeof(pline), al21, 0, 0), stdout); TAB; (void)limited_fprintf_for_number(stdout, osform, geod_S * fr_meter); } else { printLL(phi2, lam2); TAB; if (oform) (void)limited_fprintf_for_number(stdout, oform, al21 * RAD_TO_DEG); else (void)fputs(rtodms(pline, sizeof(pline), al21, 0, 0), stdout); } (void)fputs(s, stdout); fflush(stdout); } } static char *pargv[MAX_PARGS]; static int pargc = 0; int main(int argc, char **argv) { char *arg, **eargv = argv; FILE *fid; static int eargc = 0, c; if (argc == 0) { exit(1); } if ((emess_dat.Prog_name = strrchr(*argv, '/')) != nullptr) ++emess_dat.Prog_name; else emess_dat.Prog_name = *argv; inverse = strncmp(emess_dat.Prog_name, "inv", 3) == 0 || strncmp(emess_dat.Prog_name, "lt-inv", 6) == 0; // older libtool have a lt- prefix if (argc <= 1) { (void)fprintf(stderr, usage, pj_get_release(), emess_dat.Prog_name); exit(0); } /* process run line arguments */ while (--argc > 0) { /* collect run line arguments */ if (**++argv == '-') for (arg = *argv;;) { switch (*++arg) { case '\0': /* position of "stdin" */ if (arg[-1] == '-') eargv[eargc++] = const_cast("-"); break; case 'a': /* output full set of values */ fullout = 1; continue; case 'I': /* alt. inverse spec. */ inverse = 1; continue; case 't': /* set col. one char */ if (arg[1]) tag = *++arg; else emess(1, "missing -t col. 1 tag"); continue; case 'W': /* specify seconds precision */ case 'w': /* -W for constant field width */ if ((c = arg[1]) && isdigit(c)) { set_rtodms(c - '0', *arg == 'W'); ++arg; } else emess(1, "-W argument missing or non-digit"); continue; case 'f': /* alternate output format degrees or xy */ if (--argc <= 0) noargument: emess(1, "missing argument for -%c", *arg); oform = *++argv; continue; case 'F': /* alternate output format degrees or xy */ if (--argc <= 0) goto noargument; osform = *++argv; continue; case 'l': if (!arg[1] || arg[1] == 'e') { /* list of ellipsoids */ const struct PJ_ELLPS *le; for (le = proj_list_ellps(); le->id; ++le) (void)printf("%9s %-16s %-16s %s\n", le->id, le->major, le->ell, le->name); } else if (arg[1] == 'u') { /* list of units */ auto units = proj_get_units_from_database( nullptr, nullptr, "linear", false, nullptr); for (int i = 0; units && units[i]; i++) { if (units[i]->proj_short_name) { (void)printf("%12s %-20.15g %s\n", units[i]->proj_short_name, units[i]->conv_factor, units[i]->name); } } proj_unit_list_destroy(units); } else emess(1, "invalid list option: l%c", arg[1]); exit(0); case 'p': /* output azimuths as positive */ pos_azi = 1; continue; default: emess(1, "invalid option: -%c", *arg); break; } break; } else if (**argv == '+') /* + argument */ if (pargc < MAX_PARGS) pargv[pargc++] = *argv + 1; else emess(1, "overflowed + argument table"); else /* assumed to be input file name(s) */ eargv[eargc++] = *argv; } /* done with parameter and control input */ geod_set(pargc, pargv); /* setup projection */ if ((n_alpha || n_S) && eargc) emess(1, "files specified for arc/geodesic mode"); if (n_alpha) do_arc(); else if (n_S) do_geod(); else { /* process input file list */ if (eargc == 0) /* if no specific files force sysin */ eargv[eargc++] = const_cast("-"); for (; eargc--; ++eargv) { if (**eargv == '-') { fid = stdin; emess_dat.File_name = const_cast(""); } else { if ((fid = fopen(*eargv, "r")) == nullptr) { emess(-2, "input file: %s", *eargv); continue; } emess_dat.File_name = *eargv; } emess_dat.File_line = 0; process(fid); (void)fclose(fid); emess_dat.File_name = (char *)nullptr; } } exit(0); /* normal completion */ } proj-9.8.1/src/apps/cct.cpp000664 001750 001750 00000052233 15166171715 015467 0ustar00eveneven000000 000000 /*********************************************************************** The cct 4D Transformation program ************************************************************************ cct is a 4D equivalent to the "proj" projection program. cct is an acronym meaning "Coordinate Conversion and Transformation". The acronym refers to definitions given in the OGC 08-015r2/ISO-19111 standard "Geographical Information -- Spatial Referencing by Coordinates", which defines two different classes of coordinate operations: *Coordinate Conversions*, which are coordinate operations where input and output datum are identical (e.g. conversion from geographical to cartesian coordinates) and *Coordinate Transformations*, which are coordinate operations where input and output datums differ (e.g. change of reference frame). cct, however, also refers to Carl Christian Tscherning (1942--2014), professor of Geodesy at the University of Copenhagen, mentor and advisor for a generation of Danish geodesists, colleague and collaborator for two generations of global geodesists, Secretary General for the International Association of Geodesy, IAG (1995--2007), fellow of the American Geophysical Union (1991), recipient of the IAG Levallois Medal (2007), the European Geosciences Union Vening Meinesz Medal (2008), and of numerous other honours. cct, or Christian, as he was known to most of us, was recognized for his good mood, his sharp wit, his tireless work, and his great commitment to the development of geodesy - both through his scientific contributions, comprising more than 250 publications, and by his mentoring and teaching of the next generations of geodesists. As Christian was an avid Fortran programmer, and a keen Unix connoisseur, he would have enjoyed to know that his initials would be used to name a modest Unix style transformation filter, hinting at the tireless aspect of his personality, which was certainly one of the reasons he accomplished so much, and meant so much to so many people. Hence, in honour of cct (the geodesist) this is cct (the program). ************************************************************************ Thomas Knudsen, thokn@sdfe.dk, 2016-05-25/2017-10-26 ************************************************************************ * Copyright (c) 2016, 2017 Thomas Knudsen * Copyright (c) 2017, SDFE * * 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 #include #include #include #include #include #include #include #include // std::ifstream #include #include "optargpm.h" #include "proj.h" #include "proj_internal.h" #include "proj_strtod.h" static void logger(void *data, int level, const char *msg); static void print(PJ_LOG_LEVEL log_level, const char *fmt, ...); /* Prototypes from functions in this file */ static const char *column(const char *buf, int n); static char *column(char *buf, int n); PJ_COORD parse_input_line(const char *buf, int *columns, double fixed_height, double fixed_time); static const char usage[] = { "--------------------------------------------------------------------------" "------\n" "Usage: %s [-options]... [+operator_specs]... infile...\n" "--------------------------------------------------------------------------" "------\n" "Options:\n" "--------------------------------------------------------------------------" "------\n" " -c x,y,z,t Specify input columns for (up to) 4 input " "parameters.\n" " Defaults to 1,2,3,4\n" " -d n Specify number of decimals in output.\n" " -I Do the inverse transformation\n" " -o /path/to/file Specify output file name\n" " -t value Provide a fixed t value for all input data (e.g. -t " "0)\n" " -z value Provide a fixed z value for all input data (e.g. -z " "0)\n" " -s n Skip n first lines of a infile\n" " -v Verbose: Provide non-essential informational " "output.\n" " Repeat -v for more verbosity (e.g. -vv)\n" "--------------------------------------------------------------------------" "------\n" "Long Options:\n" "--------------------------------------------------------------------------" "------\n" " --output Alias for -o\n" " --columns Alias for -c\n" " --decimals Alias for -d\n" " --height Alias for -z\n" " --time Alias for -t\n" " --verbose Alias for -v\n" " --inverse Alias for -I\n" " --skip-lines Alias for -s\n" " --help Alias for -h\n" " --version Print version number\n" "--------------------------------------------------------------------------" "------\n" "Operator Specs:\n" "--------------------------------------------------------------------------" "------\n" "The operator specs describe the action to be performed by cct, e.g:\n" "\n" " +proj=utm +ellps=GRS80 +zone=32\n" "\n" "instructs cct to convert input data to Universal Transverse Mercator, " "zone 32\n" "coordinates, based on the GRS80 ellipsoid.\n" "\n" "Hence, the command\n" "\n" " echo 12 55 | cct -z0 -t0 +proj=utm +zone=32 +ellps=GRS80\n" "\n" "Should give results comparable to the classic proj command\n" "\n" " echo 12 55 | proj +proj=utm +zone=32 +ellps=GRS80\n" "--------------------------------------------------------------------------" "------\n" "Examples:\n" "--------------------------------------------------------------------------" "------\n" "1. convert geographical input to UTM zone 32 on the GRS80 ellipsoid:\n" " cct +proj=utm +ellps=GRS80 +zone=32\n" "2. roundtrip accuracy check for the case above:\n" " cct +proj=pipeline +ellps=GRS80 +zone=32 +step +proj=utm \\\n" " +step +proj=utm +inv\n" "3. as (1) but specify input columns for longitude, latitude, height and " "time:\n" " cct -c 5,2,1,4 +proj=utm +ellps=GRS80 +zone=32\n" "4. as (1) but specify fixed height and time, hence needing only 2 cols in " "input:\n" " cct -t 0 -z 0 +proj=utm +ellps=GRS80 +zone=32\n" "--------------------------------------------------------------------------" "------\n"}; static void logger(void *data, int level, const char *msg) { FILE *stream; int log_tell = proj_log_level(PJ_DEFAULT_CTX, PJ_LOG_TELL); stream = (FILE *)data; /* if we use PJ_LOG_NONE we always want to print stuff to stream */ if (level == PJ_LOG_NONE) { fprintf(stream, "%s\n", msg); return; } /* otherwise only print if log level set by user is high enough or error */ if (level <= log_tell || level == PJ_LOG_ERROR) fprintf(stderr, "%s\n", msg); } FILE *fout; static void print(PJ_LOG_LEVEL log_level, const char *fmt, ...) { va_list args; char *msg_buf; va_start(args, fmt); const size_t msg_buf_size = 100000; msg_buf = (char *)malloc(msg_buf_size); if (msg_buf == nullptr) { va_end(args); return; } vsnprintf(msg_buf, msg_buf_size, fmt, args); logger((void *)fout, log_level, msg_buf); va_end(args); free(msg_buf); } int main(int argc, char **argv) { PJ *P = nullptr; PJ_COORD point; PJ_PROJ_INFO info; OPTARGS *o; char blank_comment[] = ""; char whitespace[] = " "; int i, nfields = 4, skip_lines = 0, verbose; double fixed_z = HUGE_VAL, fixed_time = HUGE_VAL; int decimals_angles = 10; int decimals_distances = 4; int columns_xyzt[] = {1, 2, 3, 4}; const char *longflags[] = {"v=verbose", "h=help", "I=inverse", "version", nullptr}; const char *longkeys[] = {"o=output", "c=columns", "d=decimals", "z=height", "t=time", "s=skip-lines", nullptr}; fout = stdout; pj_stderr_proj_lib_deprecation_warning(); /* coverity[tainted_data] */ o = opt_parse(argc, argv, "hvI", "cdozts", longflags, longkeys); if (nullptr == o) return 1; if (opt_given(o, "h") || argc == 1) { printf(usage, o->progname); free(o); return 0; } PJ_DIRECTION direction = opt_given(o, "I") ? PJ_INV : PJ_FWD; verbose = std::min(opt_given(o, "v"), 3); /* log level can't be larger than 3 */ if (verbose > 0) { proj_log_level(PJ_DEFAULT_CTX, static_cast(verbose)); } proj_log_func(PJ_DEFAULT_CTX, (void *)fout, logger); if (opt_given(o, "version")) { print(PJ_LOG_NONE, "%s: %s", o->progname, pj_get_release()); free(o); return 0; } if (opt_given(o, "o")) fout = fopen(opt_arg(o, "output"), "wt"); if (nullptr == fout) { print(PJ_LOG_ERROR, "%s: Cannot open '%s' for output", o->progname, opt_arg(o, "output")); free(o); return 1; } print(PJ_LOG_TRACE, "%s: Running in very verbose mode", o->progname); if (opt_given(o, "z")) { fixed_z = proj_atof(opt_arg(o, "z")); nfields--; } if (opt_given(o, "t")) { fixed_time = proj_atof(opt_arg(o, "t")); nfields--; } if (opt_given(o, "d")) { int dec = atoi(opt_arg(o, "d")); decimals_angles = dec; decimals_distances = dec; } if (opt_given(o, "s")) { skip_lines = atoi(opt_arg(o, "s")); } if (opt_given(o, "c")) { int ncols; /* reset column numbers to ease comment output later on */ for (i = 0; i < 4; i++) columns_xyzt[i] = 0; /* cppcheck-suppress invalidscanf */ ncols = sscanf(opt_arg(o, "c"), "%d,%d,%d,%d", columns_xyzt, columns_xyzt + 1, columns_xyzt + 2, columns_xyzt + 3); if (ncols != nfields) { print(PJ_LOG_ERROR, "%s: Too few input columns given: '%s'", o->progname, opt_arg(o, "c")); free(o); if (stdout != fout) fclose(fout); return 1; } } /* Setup transformation */ if (o->pargc == 0 && o->fargc > 0) { std::string input(o->fargv[0]); if (!input.empty() && input[0] == '@') { std::ifstream fs; auto filename = input.substr(1); fs.open(filename, std::fstream::in | std::fstream::binary); if (!fs.is_open()) { std::cerr << "cannot open " << filename << std::endl; std::exit(1); } input.clear(); while (!fs.eof()) { char buffer[256]; fs.read(buffer, sizeof(buffer)); input.append(buffer, static_cast(fs.gcount())); if (input.size() > 100 * 1000) { fs.close(); std::cerr << "too big file " << filename << std::endl; std::exit(1); } } fs.close(); } /* Assume we got a auth:code combination */ auto n = input.find(":"); if (n > 0) { std::string auth = input.substr(0, n); std::string code = input.substr(n + 1, input.length()); // Check that the authority matches one of the known ones auto authorityList = proj_get_authorities_from_database(nullptr); if (authorityList) { for (auto iter = authorityList; *iter; iter++) { if (*iter == auth) { P = proj_create_from_database( nullptr, auth.c_str(), code.c_str(), PJ_CATEGORY_COORDINATE_OPERATION, 0, nullptr); break; } } proj_string_list_destroy(authorityList); } } if (P == nullptr) { /* if we didn't get a auth:code combo we try to see if the input * matches */ /* anything else */ P = proj_create(nullptr, input.c_str()); if (P) { const auto type = proj_get_type(P); switch (type) { case PJ_TYPE_CONVERSION: case PJ_TYPE_TRANSFORMATION: case PJ_TYPE_CONCATENATED_OPERATION: case PJ_TYPE_OTHER_COORDINATE_OPERATION: // ok; break; default: print(PJ_LOG_ERROR, "%s: Input object is not a coordinate operation%s.", o->progname, proj_is_crs(P) ? ", but a CRS" : ""); free(o); proj_destroy(P); if (stdout != fout) fclose(fout); return 1; } } } /* If instantiating operation without +-options optargpm thinks the * input is */ /* a file, hence we move all o->fargv entries one place closer to the * start */ /* of the array. This effectively overwrites the input and only leaves a * list */ /* of files in o->fargv. */ o->fargc = o->fargc - 1; for (int j = 0; j < o->fargc; j++) { o->fargv[j] = o->fargv[j + 1]; } } else { P = proj_create_argv(nullptr, o->pargc, o->pargv); } if (nullptr == P) { print(PJ_LOG_ERROR, "%s: Bad transformation arguments - (%s)\n '%s -h' for help", o->progname, proj_errno_string(proj_errno(P)), o->progname); free(o); if (stdout != fout) fclose(fout); return 1; } info = proj_pj_info(P); print(PJ_LOG_TRACE, "Final: %s argc=%d pargc=%d", info.definition, argc, o->pargc); if (direction == PJ_INV) { /* fail if an inverse operation is not available */ if (!info.has_inverse) { print(PJ_LOG_ERROR, "Inverse operation not available"); free(o); if (stdout != fout) fclose(fout); return 1; } /* We have no API call for inverting an operation, so we brute force it. */ P->inverted = !(P->inverted); } direction = PJ_FWD; /* Allocate input buffer */ constexpr int BUFFER_SIZE = 10000; char *buf = static_cast(calloc(1, BUFFER_SIZE)); if (nullptr == buf) { print(PJ_LOG_ERROR, "%s: Out of memory", o->progname); proj_destroy(P); free(o); if (stdout != fout) fclose(fout); return 1; } /* Loop over all records of all input files */ int previous_index = -1; bool gotError = false; while (opt_input_loop(o, optargs_file_format_text, &gotError)) { int err; char *bufptr = fgets(buf, BUFFER_SIZE - 1, o->input); if (opt_eof(o)) { continue; } if (nullptr == bufptr) { print(PJ_LOG_ERROR, "Read error in record %d", (int)o->record_index); continue; } const bool bFirstLine = o->input_index != previous_index; previous_index = o->input_index; if (bFirstLine && static_cast(bufptr[0]) == 0xEF && static_cast(bufptr[1]) == 0xBB && static_cast(bufptr[2]) == 0xBF) { // Skip UTF-8 Byte Order Marker (BOM) bufptr += 3; } point = parse_input_line(bufptr, columns_xyzt, fixed_z, fixed_time); if (skip_lines > 0) { skip_lines--; continue; } /* if it's a comment or blank line, we reflect it */ const char *c = column(bufptr, 1); if (c && ((*c == '\0') || (*c == '#'))) { fprintf(fout, "%s", bufptr); continue; } if (HUGE_VAL == point.xyzt.x) { /* otherwise, it must be a syntax error */ print(PJ_LOG_NONE, "# Record %d UNREADABLE: %s", (int)o->record_index, bufptr); print(PJ_LOG_ERROR, "%s: Could not parse file '%s' line %d", o->progname, opt_filename(o), opt_record(o)); continue; } if (proj_angular_input(P, direction)) { point.lpzt.lam = proj_torad(point.lpzt.lam); point.lpzt.phi = proj_torad(point.lpzt.phi); } err = proj_errno_reset(P); /* coverity[returned_value] */ point = proj_trans(P, direction, point); if (HUGE_VAL == point.xyzt.x) { /* transformation error */ print(PJ_LOG_NONE, "# Record %d TRANSFORMATION ERROR: %s (%s)", (int)o->record_index, bufptr, proj_errno_string(proj_errno(P))); proj_errno_restore(P, err); continue; } proj_errno_restore(P, err); /* handle comment string */ char *comment = column(bufptr, nfields + 1); if (opt_given(o, "c")) { /* what number is the last coordinate column in the input data? */ int colmax = 0; for (i = 0; i < 4; i++) colmax = MAX(colmax, columns_xyzt[i]); comment = column(bufptr, colmax + 1); } /* remove the line feed from comment, as logger() above, invoked by print() below (output), will add one */ size_t len = strlen(comment); if (len >= 1) comment[len - 1] = '\0'; const char *comment_delimiter = *comment ? whitespace : blank_comment; /* Time to print the result */ /* use same arguments to printf format string for both radians and degrees; convert radians to degrees before printing */ if (proj_angular_output(P, direction) || proj_degree_output(P, direction)) { if (proj_angular_output(P, direction)) { point.lpzt.lam = proj_todeg(point.lpzt.lam); point.lpzt.phi = proj_todeg(point.lpzt.phi); } print(PJ_LOG_NONE, "%14.*f %14.*f %12.*f %12.4f%s%s", decimals_angles, point.xyzt.x, decimals_angles, point.xyzt.y, decimals_distances, point.xyzt.z, point.xyzt.t, comment_delimiter, comment); } else print(PJ_LOG_NONE, "%13.*f %13.*f %12.*f %12.4f%s%s", decimals_distances, point.xyzt.x, decimals_distances, point.xyzt.y, decimals_distances, point.xyzt.z, point.xyzt.t, comment_delimiter, comment); if (fout == stdout) fflush(stdout); } proj_destroy(P); if (stdout != fout) fclose(fout); free(o); free(buf); return gotError ? 1 : 0; } /* return a pointer to the n'th column of buf */ static const char *column(const char *buf, int n) { int i; if (n <= 0) return buf; for (i = 0; i < n; i++) { while (isspace(*buf)) buf++; if (i == n - 1) break; while ((0 != *buf) && !isspace(*buf)) buf++; } return buf; } static char *column(char *buf, int n) { return const_cast(column(const_cast(buf), n)); } /* column to double */ static double cold(const char *args, int col) { char *endp; double d; const char *target = column(args, col); d = proj_strtod(target, &endp); if (endp == target) return HUGE_VAL; return d; } PJ_COORD parse_input_line(const char *buf, int *columns, double fixed_height, double fixed_time) { PJ_COORD err = proj_coord(HUGE_VAL, HUGE_VAL, HUGE_VAL, HUGE_VAL); PJ_COORD result = err; int prev_errno = errno; errno = 0; result.xyzt.z = fixed_height; result.xyzt.t = fixed_time; result.xyzt.x = cold(buf, columns[0]); result.xyzt.y = cold(buf, columns[1]); if (result.xyzt.z == HUGE_VAL) result.xyzt.z = cold(buf, columns[2]); if (result.xyzt.t == HUGE_VAL) result.xyzt.t = cold(buf, columns[3]); if (0 != errno) return err; errno = prev_errno; return result; } proj-9.8.1/src/apps/proj_strtod.cpp000664 001750 001750 00000031015 15166171715 017262 0ustar00eveneven000000 000000 /*********************************************************************** proj_strtod: Convert string to double, accepting underscore separators Thomas Knudsen, 2017-01-17/09-19 ************************************************************************ Conventionally, PROJ.4 does not honor locale settings, consistently behaving as if LC_ALL=C. For this to work, we have, for many years, been using other solutions than the C standard library strtod/atof functions for converting strings to doubles. In the early versions of proj, iirc, a gnu version of strtod was used, mostly to work around cases where the same system library was used for C and Fortran linking, hence making strtod accept "D" and "d" as exponentiation indicators, following Fortran Double Precision constant syntax. This broke the proj angular syntax, accepting a "d" to mean "degree": 12d34'56", meaning 12 degrees 34 minutes and 56 seconds. With an explicit MIT licence, PROJ.4 could not include GPL code any longer, and apparently at some time, the GPL code was replaced by the current C port of a GDAL function (in pj_strtod.c), which reads the LC_NUMERIC setting and, behind the back of the user, momentarily changes the conventional '.' delimiter to whatever the locale requires, then calls the system supplied strtod. While this requires a minimum amount of coding, it only solves one problem, and not in a very generic way. Another problem, I would like to see solved, is the handling of underscores as generic delimiters. This is getting popular in a number of programming languages (Ada, C++, C#, D, Java, Julia, Perl 5, Python, Rust, etc. cf. e.g. https://www.python.org/dev/peps/pep-0515/), and in our case of handling numbers being in the order of magnitude of the Earth's dimensions, and a resolution of submillimetre, i.e. having 10 or more significant digits, splitting the "wall of digits" into smaller chunks is of immense value. Hence this reimplementation of strtod, which hardcodes '.' as indicator of numeric fractions, and accepts '_' anywhere in a numerical string sequence: So a typical northing value can be written 6_098_907.8250 m rather than 6098907.8250 m which, in my humble opinion, is well worth the effort. While writing this code, I took ample inspiration from Michael Ringgaard's strtod version over at http://www.jbox.dk/sanos/source/lib/strtod.c.html, and Yasuhiro Matsumoto's public domain version over at https://gist.github.com/mattn/1890186. The code below is, however, not copied from any of the two mentioned - it is a reimplementation, and probably suffers from its own set of bugs. So for now, it is intended not as a replacement of pj_strtod, but only as an experimental piece of code for use in an experimental new transformation program, cct. ************************************************************************ Thomas Knudsen, thokn@sdfe.dk, 2017-01-17/2017-09-18 ************************************************************************ * Copyright (c) 2017 Thomas Knudsen & SDFE * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ***********************************************************************/ #define FROM_PROJ_CPP #include "proj_strtod.h" #include #include #include /* for HUGE_VAL */ #include /* for pow() */ #include /* for abs */ #include /* for strchr, strncmp */ #include #include "proj/internal/internal.hpp" using namespace NS_PROJ::internal; double proj_strtod(const char *str, char **endptr) { double number = 0, integral_part = 0; int exponent = 0; int fraction_is_nonzero = 0; int sign = 0; const char *p = str; int n = 0; int num_digits_total = 0; int num_digits_after_comma = 0; int num_prefixed_zeros = 0; if (nullptr == str) { errno = EFAULT; if (endptr) *endptr = nullptr; return HUGE_VAL; } /* First skip leading whitespace */ while (isspace(*p)) p++; /* Empty string? */ if (0 == *p) { if (endptr) *endptr = const_cast(str); return 0; } /* NaN */ if (ci_starts_with(p, "NaN")) { if (endptr) *endptr = const_cast(p + 3); return std::numeric_limits::quiet_NaN(); } /* non-numeric? */ if (nullptr == strchr("0123456789+-._", *p)) { if (endptr) *endptr = const_cast(str); return 0; } /* Then handle optional prefixed sign and skip prefix zeros */ switch (*p) { case '-': sign = -1; p++; break; case '+': sign = 1; p++; break; default: if (isdigit(*p) || '_' == *p || '.' == *p) break; if (endptr) *endptr = const_cast(str); return 0; } /* stray sign, as in "+/-"? */ if (0 != sign && (nullptr == strchr("0123456789._", *p) || 0 == *p)) { if (endptr) *endptr = const_cast(str); return 0; } /* skip prefixed zeros before '.' */ while ('0' == *p || '_' == *p) p++; /* zero? */ if ((0 == *p) || nullptr == strchr("0123456789eE.", *p) || isspace(*p)) { if (endptr) *endptr = const_cast(p); if (sign == -1) return -number; return number; } /* Now expect a (potentially zero-length) string of digits */ while (isdigit(*p) || ('_' == *p)) { if ('_' == *p) { p++; continue; } number = number * 10. + (*p - '0'); p++; num_digits_total++; } integral_part = number; /* Done? */ if (0 == *p) { if (endptr) *endptr = const_cast(p); if (sign == -1) return -number; return number; } /* Do we have a fractional part? */ if ('.' == *p) { p++; /* keep on skipping prefixed zeros (i.e. allow writing 1e-20 */ /* as 0.00000000000000000001 without losing precision) */ if (0 == integral_part) while ('0' == *p || '_' == *p) { if ('0' == *p) num_prefixed_zeros++; p++; } /* if the next character is nonnumeric, we have reached the end */ if (0 == *p || nullptr == strchr("_0123456789eE+-", *p)) { if (endptr) *endptr = const_cast(p); if (sign == -1) return -number; return number; } while (isdigit(*p) || '_' == *p) { /* Don't let pathologically long fractions destroy precision */ if ('_' == *p || num_digits_total > 17) { p++; continue; } number = number * 10. + (*p - '0'); if (*p != '0') fraction_is_nonzero = 1; p++; num_digits_total++; num_digits_after_comma++; } /* Avoid having long zero-tails (4321.000...000) destroy precision */ if (fraction_is_nonzero) exponent = -(num_digits_after_comma + num_prefixed_zeros); else number = integral_part; } /* end of fractional part */ /* non-digit */ if (0 == num_digits_total) { errno = EINVAL; if (endptr) *endptr = const_cast(p); return HUGE_VAL; } if (sign == -1) number = -number; /* Do we have an exponent part? */ while (*p == 'e' || *p == 'E') { p++; /* Just a stray "e", as in 100elephants? */ if (0 == *p || nullptr == strchr("0123456789+-_", *p)) { p--; break; } while ('_' == *p) p++; /* Does it have a sign? */ sign = 0; if ('-' == *p) { sign = -1; p++; } else if ('+' == *p) { sign = +1; p++; } else if (!(isdigit(*p))) { if (endptr) *endptr = const_cast(p); return HUGE_VAL; } /* Go on and read the exponent */ n = 0; while (isdigit(*p) || '_' == *p) { if ('_' == *p) { p++; continue; } n = n * 10 + (*p - '0'); p++; } if (-1 == sign) n = -n; exponent += n; break; } if (endptr) *endptr = const_cast(p); if ((exponent < DBL_MIN_EXP) || (exponent > DBL_MAX_EXP)) { errno = ERANGE; return HUGE_VAL; } /* on some platforms pow() is very slow - so don't call it if exponent is * close to 0 */ if (0 == exponent) return number; if (abs(exponent) < 20) { double ex = 1; int absexp = exponent < 0 ? -exponent : exponent; while (absexp--) ex *= 10; number = exponent < 0 ? number / ex : number * ex; } else number *= pow(10.0, static_cast(exponent)); return number; } double proj_atof(const char *str) { return proj_strtod(str, nullptr); } #ifdef TEST /* compile/run: gcc -DTEST -o proj_strtod_test proj_strtod.c && * proj_strtod_test */ #include char *un_underscore(char *s) { static char u[1024]; int i, m, n; for (i = m = 0, n = strlen(s); i < n; i++) { if (s[i] == '_') { m++; continue; } u[i - m] = s[i]; } u[n - m] = 0; return u; } int thetest(char *s, int line) { char *endp, *endq, *u; double p, q; int errnop, errnoq, prev_errno; prev_errno = errno; u = un_underscore(s); errno = 0; p = proj_strtod(s, &endp); errnop = errno; errno = 0; q = strtod(u, &endq); errnoq = errno; errno = prev_errno; if (q == p && 0 == strcmp(endp, endq) && errnop == errnoq) return 0; errno = line; printf("Line: %3.3d - [%s] [%s]\n", line, s, u); printf("proj_strtod: %2d %.17g [%s]\n", errnop, p, endp); printf("libc_strtod: %2d %.17g [%s]\n", errnoq, q, endq); return 1; } #define test(s) thetest(s, __LINE__) int main(int argc, char **argv) { double res; char *endptr; errno = 0; test(""); test(" "); test(" abcde"); test(" edcba"); test("abcde"); test("edcba"); test("+"); test("-"); test("+ "); test("- "); test(" + "); test(" - "); test("e 1"); test("e1"); test("0 66"); test("1."); test("0."); test("1.0"); test("0.0"); test("1 "); test("0 "); test("-0 "); test("0_ "); test("0_"); test("1e"); test("_1.0"); test("_0.0"); test("1_.0"); test("0_.0"); test("1__.0"); test("0__.0"); test("1.__0"); test("0.__0"); test("1.0___"); test("0.0___"); test("1e2"); test("__123_456_789_._10_11_12"); test("1______"); test("1___e__2__"); test("-1"); test("-1.0"); test("-0"); test("-1e__-_2__rest"); test("0.00002"); test("0.00001"); test("-0.00002"); test("-0.00001"); test("-0.00001e-2"); test("-0.00001e2"); test("1e9999"); /* We expect this one to differ */ test("0." "000000000000000000000000000000000000000000000000000000000000000000000" "00" "0002"); if (errno) printf("First discrepancy in line %d\n", errno); if (argc < 2) return 0; res = proj_strtod(argv[1], &endptr); printf("res = %20.15g. Rest = [%s], errno = %d\n", res, endptr, (int)errno); return 0; } #endif proj-9.8.1/src/apps/projapps_lib.h000664 001750 001750 00000005021 15166171715 017040 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: projinfo C API * Author: Javier Jimenez Shaw * ****************************************************************************** * Copyright (c) 2025 Javier Jimenez Shaw * * 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. ****************************************************************************/ #if !defined(PROJAPPS_LIB_H) #define PROJAPPS_LIB_H #include "proj.h" #ifdef __cplusplus extern "C" { #endif /* * Level for the output given by projinfo in its callback. */ typedef enum { PJ_PROJINFO_LOG_LEVEL_INFO = 1, PJ_PROJINFO_LOG_LEVEL_WARN = 2, PJ_PROJINFO_LOG_LEVEL_ERR = 3, } PJ_PROJINFO_LOG_LEVEL; typedef void (*projinfo_cb_t)(PJ_PROJINFO_LOG_LEVEL level, const char *msg, void *user_data); /* * Internal C implementation of projinfo CLI application. * See https://proj.org/apps/projinfo.html for more documentation. * @param ctx context. It can be nullptr. * @param argc number for parameters in argv. * @param argv list of char* with the command parameters of projinfo. * It does not contain the program name as first parameter. * @param cb callback that to get the output of projinfo. * It can be very fragmented, no necessarily by lines. * @param user_data pointer for data passed to the callback. */ int PROJ_DLL projinfo(PJ_CONTEXT *ctx, int argc, char **argv, projinfo_cb_t cb, void *user_data); #ifdef __cplusplus } #endif #endif proj-9.8.1/src/apps/bin_cct.cmake000664 001750 001750 00000000452 15166171715 016611 0ustar00eveneven000000 000000 set(CCT_SRC cct.cpp proj_strtod.cpp proj_strtod.h ) set(CCT_INCLUDE optargpm.h) source_group("Source Files\\Bin" FILES ${CCT_SRC}) add_executable(cct ${CCT_SRC} ${CCT_INCLUDE}) target_link_libraries(cct PRIVATE ${PROJ_LIBRARIES}) install(TARGETS cct DESTINATION ${CMAKE_INSTALL_BINDIR}) proj-9.8.1/src/apps/cs2cs.cpp000664 001750 001750 00000112304 15166171715 015727 0ustar00eveneven000000 000000 /****************************************************************************** * Project: PROJ.4 * Purpose: Mainline program sort of like ``proj'' for converting between * two coordinate systems. * Author: Frank Warmerdam, warmerda@home.com * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #define FROM_PROJ_CPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_experimental.h" #include "proj_internal.h" #include "emess.h" #include "utils.h" // clang-format on #define MAX_LINE 1000 static PJ *transformation = nullptr; static bool srcIsLongLat = false; static double srcToRadians = 0.0; static bool destIsLongLat = false; static double destToRadians = 0.0; static bool destIsLatLong = false; static int reversein = 0, /* != 0 reverse input arguments */ reverseout = 0, /* != 0 reverse output arguments */ echoin = 0, /* echo input data to output line */ tag = '#'; /* beginning of line tag character */ static const char *oform = nullptr; /* output format for x-y or decimal degrees */ static char oform_buffer[16]; /* buffer for oform when using -d */ static const char *oterr = "*\t*"; /* output line for unprojectable input */ static const char *usage = "%s\nusage: %s [-dDeEfIlrstvwW [args]]\n" " [[--area name_or_code] | [--bbox " "west_long,south_lat,east_long,north_lat]]\n" " [--authority {name}] [--3d]\n" " [--accuracy {accuracy}] [--only-best[=yes|=no]] " "[--no-ballpark]\n" " [--s_epoch {epoch}] [--t_epoch {epoch}]\n" " [+opt[=arg] ...] [+to +opt[=arg] ...] [file ...]\n"; static double (*informat)(const char *, char **); /* input data deformatter function */ using namespace NS_PROJ::io; using namespace NS_PROJ::metadata; using namespace NS_PROJ::util; using namespace NS_PROJ::internal; /************************************************************************/ /* process() */ /* */ /* File processing function. */ /************************************************************************/ static void process(FILE *fid) { char line[MAX_LINE + 3], *s, pline[40]; PJ_UV data; int nLineNumber = 0; while (true) { double z; ++nLineNumber; ++emess_dat.File_line; if (!(s = fgets(line, MAX_LINE, fid))) break; if (nLineNumber == 1 && static_cast(s[0]) == 0xEF && static_cast(s[1]) == 0xBB && static_cast(s[2]) == 0xBF) { // Skip UTF-8 Byte Order Marker (BOM) s += 3; } const char *pszLineAfterBOM = s; if (!strchr(s, '\n')) { /* overlong line */ int c; (void)strcat(s, "\n"); /* gobble up to newline */ while ((c = fgetc(fid)) != EOF && c != '\n') ; } if (*s == tag) { fputs(line, stdout); continue; } if (reversein) { data.v = (*informat)(s, &s); data.u = (*informat)(s, &s); } else { data.u = (*informat)(s, &s); data.v = (*informat)(s, &s); } z = strtod(s, &s); /* To avoid breaking existing tests, we read what is a possible t */ /* component of the input and rewind the s-pointer so that the final */ /* output has consistent behavior, with or without t values. */ /* This is a bit of a hack, in most cases 4D coordinates will be */ /* written to STDOUT (except when using -E) but the output format */ /* specified with -f is not respected for the t component, rather it */ /* is forward verbatim from the input. */ char *before_time = s; double t = strtod(s, &s); if (s == before_time) t = HUGE_VAL; s = before_time; if (data.v == HUGE_VAL) data.u = HUGE_VAL; if (!*s && (s > line)) --s; /* assumed we gobbled \n */ if (echoin) { char temp; temp = *s; *s = '\0'; (void)fputs(pszLineAfterBOM, stdout); *s = temp; putchar('\t'); } if (data.u != HUGE_VAL) { if (srcIsLongLat && fabs(srcToRadians - M_PI / 180) < 1e-10) { /* dmstor gives values to radians. Convert now to the SRS unit */ data.u /= srcToRadians; data.v /= srcToRadians; } PJ_COORD coord; coord.xyzt.x = data.u; coord.xyzt.y = data.v; coord.xyzt.z = z; coord.xyzt.t = t; coord = proj_trans(transformation, PJ_FWD, coord); data.u = coord.xyz.x; data.v = coord.xyz.y; z = coord.xyz.z; } if (data.u == HUGE_VAL) /* error output */ fputs(oterr, stdout); else if (destIsLongLat && !oform) { /*ascii DMS output */ // rtodms() expect radians: convert from the output SRS unit data.u *= destToRadians; data.v *= destToRadians; if (destIsLatLong) { if (reverseout) { fputs(rtodms(pline, sizeof(pline), data.v, 'E', 'W'), stdout); putchar('\t'); fputs(rtodms(pline, sizeof(pline), data.u, 'N', 'S'), stdout); } else { fputs(rtodms(pline, sizeof(pline), data.u, 'N', 'S'), stdout); putchar('\t'); fputs(rtodms(pline, sizeof(pline), data.v, 'E', 'W'), stdout); } } else if (reverseout) { fputs(rtodms(pline, sizeof(pline), data.v, 'N', 'S'), stdout); putchar('\t'); fputs(rtodms(pline, sizeof(pline), data.u, 'E', 'W'), stdout); } else { fputs(rtodms(pline, sizeof(pline), data.u, 'E', 'W'), stdout); putchar('\t'); fputs(rtodms(pline, sizeof(pline), data.v, 'N', 'S'), stdout); } } else { /* x-y or decimal degree ascii output */ if (destIsLongLat) { data.v *= destToRadians * RAD_TO_DEG; data.u *= destToRadians * RAD_TO_DEG; } if (reverseout) { limited_fprintf_for_number(stdout, oform, data.v); putchar('\t'); limited_fprintf_for_number(stdout, oform, data.u); } else { limited_fprintf_for_number(stdout, oform, data.u); putchar('\t'); limited_fprintf_for_number(stdout, oform, data.v); } } putchar(' '); if (oform != nullptr) limited_fprintf_for_number(stdout, oform, z); else printf("%.3f", z); if (s) printf("%s", s); else printf("\n"); fflush(stdout); } } /************************************************************************/ /* instantiate_crs() */ /************************************************************************/ static PJ *instantiate_crs(const PJ *crs_in, bool &isLongLatCS, double &toRadians, bool &isLatFirst) { PJ *crs = nullptr; isLongLatCS = false; toRadians = 0.0; isLatFirst = false; auto type = proj_get_type(crs_in); if (type == PJ_TYPE_BOUND_CRS) { crs = proj_get_source_crs(nullptr, crs_in); type = proj_get_type(crs); } else { crs = proj_clone(nullptr, crs_in); } if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || type == PJ_TYPE_GEOGRAPHIC_3D_CRS || type == PJ_TYPE_GEODETIC_CRS) { auto cs = proj_crs_get_coordinate_system(nullptr, crs); assert(cs); const char *axisName = ""; proj_cs_get_axis_info(nullptr, cs, 0, &axisName, // name, nullptr, // abbrev nullptr, // direction &toRadians, nullptr, // unit name nullptr, // unit authority nullptr // unit code ); isLatFirst = NS_PROJ::internal::ci_find(std::string(axisName), "latitude") != std::string::npos; isLongLatCS = isLatFirst || NS_PROJ::internal::ci_find( std::string(axisName), "longitude") != std::string::npos; proj_destroy(cs); } return crs; } /************************************************************************/ /* get_geog_crs_proj_string_from_proj_crs() */ /************************************************************************/ static PJ *get_geog_crs_proj_string_from_proj_crs(const PJ *src, double &toRadians, bool &isLatFirst) { auto srcType = proj_get_type(src); if (srcType != PJ_TYPE_PROJECTED_CRS) { return nullptr; } auto base = proj_get_source_crs(nullptr, src); assert(base); auto baseType = proj_get_type(base); if (baseType != PJ_TYPE_GEOGRAPHIC_2D_CRS && baseType != PJ_TYPE_GEOGRAPHIC_3D_CRS) { proj_destroy(base); return nullptr; } auto cs = proj_crs_get_coordinate_system(nullptr, base); assert(cs); const char *axisName = ""; proj_cs_get_axis_info(nullptr, cs, 0, &axisName, // name, nullptr, // abbrev nullptr, // direction &toRadians, nullptr, // unit name nullptr, // unit authority nullptr // unit code ); isLatFirst = NS_PROJ::internal::ci_find(std::string(axisName), "latitude") != std::string::npos; proj_destroy(cs); return base; } // --------------------------------------------------------------------------- static bool is3DCRS(const PJ *crs) { auto type = proj_get_type(crs); if (type == PJ_TYPE_COMPOUND_CRS) return true; if (type == PJ_TYPE_GEOGRAPHIC_3D_CRS) return true; if (type == PJ_TYPE_GEODETIC_CRS || type == PJ_TYPE_PROJECTED_CRS || type == PJ_TYPE_DERIVED_PROJECTED_CRS) { auto cs = proj_crs_get_coordinate_system(nullptr, crs); assert(cs); const bool ret = proj_cs_get_axis_count(nullptr, cs) == 3; proj_destroy(cs); return ret; } return false; } /************************************************************************/ /* main() */ /************************************************************************/ int main(int argc, char **argv) { char *arg; char **eargv = argv; std::string fromStr; std::string toStr; FILE *fid; int eargc = 0, mon = 0; int have_to_flag = 0, inverse = 0; int use_env_locale = 0; pj_stderr_proj_lib_deprecation_warning(); if (argc == 0) { exit(1); } /* This is just to check that pj_init() is locale-safe */ /* Used by test/cli/test_cs2cs_locale.sh */ if (getenv("PROJ_USE_ENV_LOCALE") != nullptr) use_env_locale = 1; /* Enable compatibility mode for init=epsg:XXXX by default */ if (getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr) { proj_context_use_proj4_init_rules(nullptr, true); } if ((emess_dat.Prog_name = strrchr(*argv, DIR_CHAR)) != nullptr) ++emess_dat.Prog_name; else emess_dat.Prog_name = *argv; inverse = !strncmp(emess_dat.Prog_name, "inv", 3); if (argc <= 1) { (void)fprintf(stderr, usage, pj_get_release(), emess_dat.Prog_name); exit(0); } // First pass to check if we have "cs2cs [-bla]* []" // syntax bool isProj4StyleSyntax = false; for (int i = 1; i < argc; i++) { if (argv[i][0] == '+') { isProj4StyleSyntax = true; break; } } ExtentPtr bboxFilter; std::string area; const char *authority = nullptr; double accuracy = -1; bool allowBallpark = true; bool onlyBestSet = false; bool errorIfBestTransformationNotAvailable = false; bool promoteTo3D = false; std::string sourceEpoch; std::string targetEpoch; /* process run line arguments */ while (--argc > 0) { /* collect run line arguments */ ++argv; if (strcmp(*argv, "--area") == 0) { ++argv; --argc; if (argc == 0) { emess(1, "missing argument for --area"); std::exit(1); } area = *argv; } else if (strcmp(*argv, "--bbox") == 0) { ++argv; --argc; if (argc == 0) { emess(1, "missing argument for --bbox"); std::exit(1); } auto bboxStr(*argv); auto bbox(split(bboxStr, ',')); if (bbox.size() != 4) { std::cerr << "Incorrect number of values for option --bbox: " << bboxStr << std::endl; std::exit(1); } try { std::vector bboxValues = { c_locale_stod(bbox[0]), c_locale_stod(bbox[1]), c_locale_stod(bbox[2]), c_locale_stod(bbox[3])}; const double west = bboxValues[0]; const double south = bboxValues[1]; const double east = bboxValues[2]; const double north = bboxValues[3]; constexpr double SOME_MARGIN = 10; if (south < -90 - SOME_MARGIN && std::fabs(west) <= 90 && std::fabs(east) <= 90) std::cerr << "Warning: suspicious south latitude: " << south << std::endl; if (north > 90 + SOME_MARGIN && std::fabs(west) <= 90 && std::fabs(east) <= 90) std::cerr << "Warning: suspicious north latitude: " << north << std::endl; bboxFilter = Extent::createFromBBOX(west, south, east, north) .as_nullable(); } catch (const std::exception &e) { std::cerr << "Invalid value for option --bbox: " << bboxStr << ", " << e.what() << std::endl; std::exit(1); } } else if (strcmp(*argv, "--accuracy") == 0) { ++argv; --argc; if (argc == 0) { emess(1, "missing argument for --accuracy"); std::exit(1); } try { accuracy = c_locale_stod(*argv); } catch (const std::exception &e) { std::cerr << "Invalid value for option --accuracy: " << e.what() << std::endl; std::exit(1); } } else if (strcmp(*argv, "--authority") == 0) { ++argv; --argc; if (argc == 0) { emess(1, "missing argument for --authority"); std::exit(1); } authority = *argv; } else if (strcmp(*argv, "--no-ballpark") == 0) { allowBallpark = false; } else if (strcmp(*argv, "--only-best") == 0 || strcmp(*argv, "--only-best=yes") == 0) { onlyBestSet = true; errorIfBestTransformationNotAvailable = true; } else if (strcmp(*argv, "--only-best=no") == 0) { onlyBestSet = true; errorIfBestTransformationNotAvailable = false; } else if (strcmp(*argv, "--3d") == 0) { promoteTo3D = true; } else if (strcmp(*argv, "--s_epoch") == 0) { ++argv; --argc; if (argc == 0) { emess(1, "missing argument for --s_epoch"); std::exit(1); } sourceEpoch = *argv; } else if (strcmp(*argv, "--t_epoch") == 0) { ++argv; --argc; if (argc == 0) { emess(1, "missing argument for --t_epoch"); std::exit(1); } targetEpoch = *argv; } else if (**argv == '-') { for (arg = *argv;;) { switch (*++arg) { case '\0': /* position of "stdin" */ if (arg[-1] == '-') eargv[eargc++] = const_cast("-"); break; case 'v': /* monitor dump of initialization */ mon = 1; continue; case 'I': /* alt. method to spec inverse */ inverse = 1; continue; case 'E': /* echo ascii input to ascii output */ echoin = 1; continue; case 't': /* set col. one char */ if (arg[1]) tag = *++arg; else emess(1, "missing -t col. 1 tag"); continue; case 'l': /* list projections, ellipses or units */ if (!arg[1] || arg[1] == 'p' || arg[1] == 'P') { /* list projections */ const struct PJ_LIST *lp; int do_long = arg[1] == 'P', c; const char *str; for (lp = proj_list_operations(); lp->id; ++lp) { (void)printf("%s : ", lp->id); if (do_long) /* possibly multiline description */ (void)puts(*lp->descr); else { /* first line, only */ str = *lp->descr; while ((c = *str++) && c != '\n') putchar(c); putchar('\n'); } } } else if (arg[1] == '=') { /* list projection 'descr' */ const struct PJ_LIST *lp; arg += 2; for (lp = proj_list_operations(); lp->id; ++lp) if (!strcmp(lp->id, arg)) { (void)printf("%9s : %s\n", lp->id, *lp->descr); break; } } else if (arg[1] == 'e') { /* list ellipses */ const struct PJ_ELLPS *le; for (le = proj_list_ellps(); le->id; ++le) (void)printf("%9s %-16s %-16s %s\n", le->id, le->major, le->ell, le->name); } else if (arg[1] == 'u') { /* list units */ auto units = proj_get_units_from_database( nullptr, nullptr, "linear", false, nullptr); for (int i = 0; units && units[i]; i++) { if (units[i]->proj_short_name) { (void)printf("%12s %-20.15g %s\n", units[i]->proj_short_name, units[i]->conv_factor, units[i]->name); } } proj_unit_list_destroy(units); } else if (arg[1] == 'm') { /* list prime meridians */ (void)fprintf(stderr, "This list is no longer updated, " "and some values may " "conflict with other sources.\n"); const struct PJ_PRIME_MERIDIANS *lpm; for (lpm = proj_list_prime_meridians(); lpm->id; ++lpm) (void)printf("%12s %-30s\n", lpm->id, lpm->defn); } else emess(1, "invalid list option: l%c", arg[1]); exit(0); /* cppcheck-suppress duplicateBreak */ continue; /* artificial */ case 'e': /* error line alternative */ if (--argc <= 0) noargument: emess(1, "missing argument for -%c", *arg); oterr = *++argv; continue; case 'W': /* specify seconds precision */ case 'w': /* -W for constant field width */ { char c = arg[1]; // Check that the value is in the [0, 8] range if (c >= '0' && c <= '8' && ((arg[2] == 0 || !(arg[2] >= '0' && arg[2] <= '9')))) { set_rtodms(c - '0', *arg == 'W'); ++arg; } else emess(1, "-W argument missing or not in range [0,8]"); continue; } case 'f': /* alternate output format degrees or xy */ if (--argc <= 0) goto noargument; oform = *++argv; continue; case 'r': /* reverse input */ reversein = 1; continue; case 's': /* reverse output */ reverseout = 1; continue; case 'D': /* set debug level */ { if (--argc <= 0) goto noargument; int log_level = atoi(*++argv); if (log_level <= 0) { proj_log_level(pj_get_default_ctx(), PJ_LOG_NONE); } else if (log_level == 1) { proj_log_level(pj_get_default_ctx(), PJ_LOG_ERROR); } else if (log_level == 2) { proj_log_level(pj_get_default_ctx(), PJ_LOG_DEBUG); } else if (log_level == 3) { proj_log_level(pj_get_default_ctx(), PJ_LOG_TRACE); } else { proj_log_level(pj_get_default_ctx(), PJ_LOG_TELL); } continue; } case 'd': if (--argc <= 0) goto noargument; snprintf(oform_buffer, sizeof(oform_buffer), "%%.%df", atoi(*++argv)); oform = oform_buffer; break; default: emess(1, "invalid option: -%c", *arg); break; } break; } } else if (!isProj4StyleSyntax) { if (fromStr.empty()) fromStr = *argv; else if (toStr.empty()) toStr = *argv; else { /* assumed to be input file name(s) */ eargv[eargc++] = *argv; } } else if (strcmp(*argv, "+to") == 0) { have_to_flag = 1; } else if (**argv == '+') { /* + argument */ if (have_to_flag) { if (!toStr.empty()) toStr += ' '; toStr += *argv; } else { if (!fromStr.empty()) fromStr += ' '; fromStr += *argv; } } else if (!have_to_flag) { fromStr = *argv; } else if (toStr.empty()) { toStr = *argv; } else /* assumed to be input file name(s) */ eargv[eargc++] = *argv; } if (eargc == 0) /* if no specific files force sysin */ eargv[eargc++] = const_cast("-"); if (oform) { if (!validate_form_string_for_numbers(oform)) { emess(3, "invalid format string"); exit(0); } } if (bboxFilter && !area.empty()) { std::cerr << "ERROR: --bbox and --area are exclusive" << std::endl; std::exit(1); } PJ_AREA *pj_area = nullptr; if (!area.empty()) { DatabaseContextPtr dbContext; try { dbContext = DatabaseContext::create().as_nullable(); } catch (const std::exception &e) { std::cerr << "ERROR: Cannot create database connection: " << e.what() << std::endl; std::exit(1); } // Process area of use try { if (area.find(' ') == std::string::npos && area.find(':') != std::string::npos) { auto tokens = split(area, ':'); if (tokens.size() == 2) { const std::string &areaAuth = tokens[0]; const std::string &areaCode = tokens[1]; bboxFilter = AuthorityFactory::create( NN_NO_CHECK(dbContext), areaAuth) ->createExtent(areaCode) .as_nullable(); } } if (!bboxFilter) { auto authFactory = AuthorityFactory::create( NN_NO_CHECK(dbContext), std::string()); auto res = authFactory->listAreaOfUseFromName(area, false); if (res.size() == 1) { bboxFilter = AuthorityFactory::create( NN_NO_CHECK(dbContext), res.front().first) ->createExtent(res.front().second) .as_nullable(); } else { res = authFactory->listAreaOfUseFromName(area, true); if (res.size() == 1) { bboxFilter = AuthorityFactory::create(NN_NO_CHECK(dbContext), res.front().first) ->createExtent(res.front().second) .as_nullable(); } else if (res.empty()) { std::cerr << "No area of use matching provided name" << std::endl; std::exit(1); } else { std::cerr << "Several candidates area of use " "matching provided name :" << std::endl; for (const auto &candidate : res) { auto obj = AuthorityFactory::create(NN_NO_CHECK(dbContext), candidate.first) ->createExtent(candidate.second); std::cerr << " " << candidate.first << ":" << candidate.second << " : " << *obj->description() << std::endl; } std::exit(1); } } } } catch (const std::exception &e) { std::cerr << "Area of use retrieval failed: " << e.what() << std::endl; std::exit(1); } } if (bboxFilter) { auto geogElts = bboxFilter->geographicElements(); if (geogElts.size() == 1) { auto bbox = std::dynamic_pointer_cast( geogElts[0].as_nullable()); if (bbox) { pj_area = proj_area_create(); proj_area_set_bbox(pj_area, bbox->westBoundLongitude(), bbox->southBoundLatitude(), bbox->eastBoundLongitude(), bbox->northBoundLatitude()); if (bboxFilter->description().has_value()) { proj_area_set_name(pj_area, bboxFilter->description()->c_str()); } } } } /* * If the user has requested inverse, then just reverse the * coordinate systems. */ if (inverse) { std::swap(fromStr, toStr); } if (use_env_locale) { /* Set locale from environment */ setlocale(LC_ALL, ""); } if (fromStr.empty() && toStr.empty()) { emess(3, "missing source and target coordinate systems"); } proj_context_use_proj4_init_rules( nullptr, proj_context_get_use_proj4_init_rules(nullptr, TRUE)); PJ *src = !fromStr.empty() ? proj_create(nullptr, pj_add_type_crs_if_needed(fromStr).c_str()) : nullptr; PJ *dst = !toStr.empty() ? proj_create(nullptr, pj_add_type_crs_if_needed(toStr).c_str()) : nullptr; PJ *src_unbound = nullptr; if (src) { bool ignored; src_unbound = instantiate_crs(src, srcIsLongLat, srcToRadians, ignored); if (!src_unbound) { emess(3, "cannot instantiate source coordinate system"); } } PJ *dst_unbound = nullptr; if (dst) { dst_unbound = instantiate_crs(dst, destIsLongLat, destToRadians, destIsLatLong); if (!dst_unbound) { emess(3, "cannot instantiate target coordinate system"); } } if (!dst) { assert(src_unbound); dst = get_geog_crs_proj_string_from_proj_crs(src_unbound, destToRadians, destIsLatLong); if (!dst) { emess(3, "missing target CRS and source CRS is not a projected CRS"); } destIsLongLat = true; } else if (!src) { assert(dst_unbound); bool ignored; src = get_geog_crs_proj_string_from_proj_crs(dst_unbound, srcToRadians, ignored); if (!src) { emess(3, "missing source CRS and target CRS is not a projected CRS"); } srcIsLongLat = true; } proj_destroy(src_unbound); proj_destroy(dst_unbound); if (promoteTo3D) { auto src3D = proj_crs_promote_to_3D(nullptr, nullptr, src); if (src3D) { proj_destroy(src); src = src3D; } auto dst3D = proj_crs_promote_to_3D(nullptr, nullptr, dst); if (dst3D) { proj_destroy(dst); dst = dst3D; } } else { // Auto-promote source/target CRS if it is specified by its name, // if it has a known 3D version of it and that the other CRS is 3D. // e.g cs2cs "WGS 84 + EGM96 height" "WGS 84" if (is3DCRS(dst) && !is3DCRS(src) && proj_get_id_code(src, 0) != nullptr && Identifier::isEquivalentName(fromStr.c_str(), proj_get_name(src))) { auto promoted = proj_crs_promote_to_3D(nullptr, nullptr, src); if (promoted) { if (proj_get_id_code(promoted, 0) != nullptr) { proj_destroy(src); src = promoted; } else { proj_destroy(promoted); } } } else if (is3DCRS(src) && !is3DCRS(dst) && proj_get_id_code(dst, 0) != nullptr && Identifier::isEquivalentName(toStr.c_str(), proj_get_name(dst))) { auto promoted = proj_crs_promote_to_3D(nullptr, nullptr, dst); if (promoted) { if (proj_get_id_code(promoted, 0) != nullptr) { proj_destroy(dst); dst = promoted; } else { proj_destroy(promoted); } } } } if (!sourceEpoch.empty()) { PJ *srcMetadata = nullptr; double sourceEpochDbl; try { sourceEpochDbl = c_locale_stod(sourceEpoch); } catch (const std::exception &e) { sourceEpochDbl = 0; emess(3, "%s", e.what()); } srcMetadata = proj_coordinate_metadata_create(nullptr, src, sourceEpochDbl); if (!srcMetadata) { emess(3, "cannot instantiate source coordinate system"); } proj_destroy(src); src = srcMetadata; } if (!targetEpoch.empty()) { PJ *dstMetadata = nullptr; double targetEpochDbl; try { targetEpochDbl = c_locale_stod(targetEpoch); } catch (const std::exception &e) { targetEpochDbl = 0; emess(3, "%s", e.what()); } dstMetadata = proj_coordinate_metadata_create(nullptr, dst, targetEpochDbl); if (!dstMetadata) { emess(3, "cannot instantiate target coordinate system"); } proj_destroy(dst); dst = dstMetadata; } std::string authorityOption; /* keep this variable in this outer scope ! */ std::string accuracyOption; /* keep this variable in this outer scope ! */ std::vector options; if (authority) { authorityOption = "AUTHORITY="; authorityOption += authority; options.push_back(authorityOption.data()); } if (accuracy >= 0) { accuracyOption = "ACCURACY="; accuracyOption += toString(accuracy); options.push_back(accuracyOption.data()); } if (!allowBallpark) { options.push_back("ALLOW_BALLPARK=NO"); } if (onlyBestSet) { if (errorIfBestTransformationNotAvailable) { options.push_back("ONLY_BEST=YES"); } else { options.push_back("ONLY_BEST=NO"); } } options.push_back(nullptr); transformation = proj_create_crs_to_crs_from_pj(nullptr, src, dst, pj_area, options.data()); proj_destroy(src); proj_destroy(dst); proj_area_destroy(pj_area); if (!transformation) { emess(3, "cannot initialize transformation\ncause: %s", proj_errno_string(proj_context_errno(nullptr))); } if (use_env_locale) { /* Restore C locale to avoid issues in parsing/outputting numbers*/ setlocale(LC_ALL, "C"); } if (mon) { printf("%c ---- From Coordinate System ----\n", tag); printf("%s\n", fromStr.c_str()); printf("%c ---- To Coordinate System ----\n", tag); printf("%s\n", toStr.c_str()); } /* set input formatting control */ if (srcIsLongLat && fabs(srcToRadians - M_PI / 180) < 1e-10) informat = dmstor; else { informat = strtod; } if (!destIsLongLat && !oform) oform = "%.2f"; /* process input file list */ for (; eargc--; ++eargv) { if (**eargv == '-') { fid = stdin; emess_dat.File_name = const_cast(""); } else { if ((fid = fopen(*eargv, "rt")) == nullptr) { emess(-2, "input file: %s", *eargv); continue; } emess_dat.File_name = *eargv; } emess_dat.File_line = 0; process(fid); fclose(fid); emess_dat.File_name = nullptr; } proj_destroy(transformation); proj_cleanup(); exit(0); /* normal completion */ } proj-9.8.1/src/apps/bin_projinfo.cmake000664 001750 001750 00000000525 15166171715 017667 0ustar00eveneven000000 000000 set(PROJINFO_SRC projinfo.cpp) source_group("Source Files\\Bin" FILES ${PROJINFO_SRC}) add_executable(projinfo ${PROJINFO_SRC}) target_link_libraries(projinfo PRIVATE ${PROJ_LIBRARIES}) install(TARGETS projinfo DESTINATION ${CMAKE_INSTALL_BINDIR}) if(CURL_ENABLED) target_compile_definitions(projinfo PRIVATE -DCURL_ENABLED) endif() proj-9.8.1/src/apps/emess.cpp000664 001750 001750 00000003700 15166171715 016025 0ustar00eveneven000000 000000 /* Error message processing */ #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_NO_DEPRECATE #endif #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #endif #endif #include #include #include #include #include #include "proj_config.h" #include "proj_internal.h" #define EMESS_ROUTINE #include "emess.h" void emess(int code, const char *fmt, ...) { va_list args; va_start(args, fmt); /* prefix program name, if given */ if (emess_dat.Prog_name != nullptr) { // For unit test purposes, allow PROJ_DISPLAY_PROGRAM_NAME=NO const char *pszDisplayProgramName = getenv("PROJ_DISPLAY_PROGRAM_NAME"); if (!(pszDisplayProgramName && strcmp(pszDisplayProgramName, "NO") == 0)) { (void)fprintf(stderr, "%s\n<%s>: ", pj_get_release(), emess_dat.Prog_name); } } /* print file name and line, if given */ if (emess_dat.File_name != nullptr && *emess_dat.File_name) { (void)fprintf(stderr, "while processing file: %s", emess_dat.File_name); if (emess_dat.File_line > 0) (void)fprintf(stderr, ", line %d\n", emess_dat.File_line); else (void)fputc('\n', stderr); } else putc('\n', stderr); /* if |code|==2, print errno code data */ if (code == 2 || code == -2) { int my_errno = errno; #ifdef HAVE_STRERROR const char *my_strerror = strerror(my_errno); #endif #ifndef HAVE_STRERROR const char *my_strerror = ""; #endif (void)fprintf(stderr, "Sys errno: %d: %s\n", my_errno, my_strerror); } /* post remainder of call data */ (void)vfprintf(stderr, fmt, args); va_end(args); /* die if code positive */ if (code > 0) { (void)fputs("\nprogram abnormally terminated\n", stderr); exit(code); } else putc('\n', stderr); } proj-9.8.1/src/apps/bin_projsync.cmake000664 001750 001750 00000000375 15166171715 017713 0ustar00eveneven000000 000000 set(PROJSYNC_SRC projsync.cpp) source_group("Source Files\\Bin" FILES ${PROJSYNC_SRC}) add_executable(projsync ${PROJSYNC_SRC}) target_link_libraries(projsync PRIVATE ${PROJ_LIBRARIES}) install(TARGETS projsync DESTINATION ${CMAKE_INSTALL_BINDIR}) proj-9.8.1/src/apps/proj_strtod.h000664 001750 001750 00000000200 15166171715 016717 0ustar00eveneven000000 000000 /* Internal header for proj_strtod.c */ double proj_strtod(const char *str, char **endptr); double proj_atof(const char *str); proj-9.8.1/src/apps/projinfo.cpp000664 001750 001750 00000004330 15166171715 016537 0ustar00eveneven000000 000000 /****************************************************************************** * * Project: PROJ * Purpose: projinfo utility * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * 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. ****************************************************************************/ //! @cond Doxygen_Suppress #define FROM_PROJ_CPP #include #include "proj.h" #include "proj_internal.h" #include "projapps_lib.h" // --------------------------------------------------------------------------- int main(int argc, char **argv) { pj_stderr_proj_lib_deprecation_warning(); auto dump = [](PJ_PROJINFO_LOG_LEVEL level, const char *s, void *) { switch (level) { case PJ_PROJINFO_LOG_LEVEL_WARN: case PJ_PROJINFO_LOG_LEVEL_ERR: std::cerr << s; break; case PJ_PROJINFO_LOG_LEVEL_INFO: default: std::cout << s; break; } }; int res = projinfo(nullptr, argc - 1, ++argv, dump, nullptr); return res; } //! @endcond proj-9.8.1/src/apps/optargpm.h000664 001750 001750 00000054261 15166171715 016217 0ustar00eveneven000000 000000 /*********************************************************************** OPTARGPM - a header-only library for decoding PROJ.4 style command line options Thomas Knudsen, 2017-09-10 ************************************************************************ For PROJ.4 command line programs, we have a somewhat complex option decoding situation, since we have to navigate in a cocktail of classic single letter style options, prefixed by "-", GNU style long options prefixed by "--", transformation specification elements prefixed by "+", and input file names prefixed by "" (i.e. nothing). Hence, classic getopt.h style decoding does not cut the mustard, so this is an attempt to catch up and chop the ketchup. Since optargpm (for "optarg plus minus") does not belong, in any obvious way, in any systems development library, it is provided as a "header only" library. While this is conventional in C++, it is frowned at in plain C. But frown away - "header only" has its places, and this is one of them. By convention, we expect a command line to consist of the following elements: [short ("-")/long ("--") options} [operator ("+") specs] [operands/input files] or less verbose: [options] [operator specs] [operands] or less abstract: proj -I --output=foo +proj=utm +zone=32 +ellps=GRS80 bar baz... Where Operator is proj Options are -I --output=foo Operator specs are +proj=utm +zone=32 +ellps=GRS80 Operands are bar baz While neither claiming to save the world, nor to hint at the "shape of jazz to come", at least optargpm has shown useful in constructing cs2cs style transformation filters. Supporting a wide range of option syntax, the getoptpm API is somewhat quirky, but also compact, consisting of one data type, 3(+2) functions, and one enumeration: OPTARGS Housekeeping data type. An instance of OPTARGS is conventionally called o or opt opt_parse (opt, argc, argv ...): The work horse: Define supported options; Split (argc, argv) into groups (options, op specs, operands); Parse option arguments. opt_given (o, option): The number of times